Alguém pode desafiar o tio Bob por seu amor por remover "aparelhos inúteis"?

94

Odeio fazer referência a conteúdo pago, mas este vídeo mostra exatamente do que estou falando. Precisamente 12 minutos em Robert Martin olha para isso:

Algum código

E diz: "Uma das minhas coisas favoritas a fazer é se livrar de aparelhos inúteis", como ele transforma isso:

Mais código

Há muito tempo, em uma educação distante, fui ensinado a não fazer isso porque facilita a introdução de um bug adicionando outra linha recuada, pensando que é controlada pelo ifquando não é.

Para ser justo com o tio Bob, ele está refatorando um longo método Java para minúsculas funções que, eu concordo, são muito mais legíveis. Quando ele termina de mudar, (22.18) fica assim:

Ainda mais código

Gostaria de saber se isso deve validar a remoção dos aparelhos. Eu já estou familiarizado com as melhores práticas . O tio Bob pode ser desafiado nesse ponto? O tio Bob defendeu a ideia?

candied_orange
fonte
6
Estou votando para encerrar esta questão como fora de tópico, porque qualquer resposta para isso será "não" ou "sim, e aqui está uma citação".
Blrfl
35
Dê alguns anos, talvez ele também remova a quebra de linha inútil e "se (veja (algo)) diga (algo);" vai realmente ser uma linha de código ;-)
Steve Jessop
8
@SteveJessop É bom saber que estou anos à frente. : D Sem a nova linha, não há risco do bug infame. Adicionar / remover chaves conforme necessário é uma sobrecarga adicional, mas muito mais é economizado ao ler.
Maaartinus
9
O tio Bob faz alguns pontos positivos por fazê-lo em Clean Code, página 35. Se um ifbloco é apenas uma linha, isso não importa. Se você precisar adicionar mais linhas, deve ser outra função que reduz o ifbloco de volta a uma linha (a chamada de função). Se você aderir a isso, os aparelhos simplesmente não importam - de maneira alguma .
13
ele deveria fazer isso return (page==null) ? "" : includePage(mode,page);se quiser ser mais conciso ... Pensei que o estilo sem chaves é legal até começar a desenvolver aplicativos profissionalmente. Ruído difuso, possíveis erros de digitação, etc. Os aparelhos, estando lá o tempo todo, economizam o tempo e as despesas gerais que você precisará para apresentá-los mais tarde.
precisa saber é o seguinte

Respostas:

47

Legibilidade não é pouca coisa.

Eu tenho uma mente confusa quando se trata de aparelhos que envolvem um único método. Eu pessoalmente os removo para coisas como declarações de retorno de linha única, mas deixar esses aparelhos de fora realmente nos incomodou muito no último lugar em que trabalhei. Alguém adicionou uma linha de código a uma ifinstrução sem também adicionar as chaves necessárias e, por ser C, travou o sistema sem aviso.

Eu nunca desafio alguém religioso a usar sempre aparelho depois desse pequeno fiasco.

Portanto, vejo o benefício da legibilidade, mas tenho plena consciência dos problemas que podem surgir quando você deixa esses aparelhos de lado.

Eu não me incomodaria em tentar encontrar um estudo ou a opinião publicada de alguém. Todo mundo tem uma (uma opinião), e por ser uma questão estilística, uma opinião é tão boa quanto qualquer outra. Pense sobre o assunto você mesmo, avalie os prós e os contras e faça sua própria mente. Se a loja em que você trabalha tiver um padrão de codificação que cubra esse problema, basta seguir isso.

Robert Harvey
fonte
7
Eu fui mordido por isso muito recentemente, quando o recuo foi enganoso.
Andy
12
Porque era C sem o GCC 6-Wmisleading-indentation .
wchargin
2
@CandiedOrange: É o tio Bob que está desafiando o dogma estabelecido.
Robert Harvey
1
@RobertHarvey Não deixei isso claro? Sim, ele é um dogma desafiador. Ele está desafiando meu dogma. Só estou tentando ser uma boa aluna e pelo menos considerar isso. Mas o tio Bob é tão carismático que me faz pensar se estou perdendo a objetividade. Então, estou implorando por ajuda para encontrar um antídoto antes de tomar o koolaid.
Candied_orange 4/16
1
@CandiedOrange: É absolutamente uma coisa de contexto. Quem apenas aceita cegamente o dogma não considera o contexto; eles ainda usam a regra "somente um retorno" em idiomas gerenciados, muito depois que a regra perde a utilidade e se torna um impedimento.
Robert Harvey
133

Você pode encontrar várias promoções ou rejeições publicadas de estilos sem suporte aqui ou aqui ou onde quer que sejam pintados os galpões de bicicletas.

Afastando-se dos galpões das bicicletas, lembra-se do grande bug SSL do OS X / iOS de 2014?

if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
    goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
    goto fail;
    goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
    goto fail;

Sim, "causado" por blocos sem colchetes https://www.imperialviolet.org/2014/02/22/applebug.html

As preferências podem depender do estilo da chave. Se eu tivesse que escrever

if (suiteSetup)
{
    includePage(mode, suiteSetup);
}

Eu posso estar inclinado a economizar espaço também. Mas

if (suiteSetup) {
    includePage(mode, suiteSetup);
}

usa apenas uma linha "extra".


Sei que você não perguntou, mas se estou trabalhando sozinho, sou herege. Se eu remover aparelho, prefiro

if (suiteSetup != null) includePage(mode, suiteSetup);

Ele não tem o mesmo problema visual que o bug no estilo SSL do iOS, mas salva ainda mais linhas "desnecessárias" do que o tio Bob;) Lê bem se você está acostumado a isso e tem uso convencional em Ruby ( includePage(mode, suiteSetup) unless suiteSetup.nil?). Oh, bem, apenas saiba que existem muitas opiniões.

Paul Draper
fonte
13
Além disso, se você usar } else[if(...)] {, isso não adiciona nenhuma linha extra adicional, adicionando apenas uma linha extra ao longo da coisa toda.
usar o seguinte comando
12
Estou feliz que você trouxe o bug SSL do iOS. Desde então, eu me vejo usando aparelhos em tais situações cada vez mais.
Zach Lipton
4
Com relação ao bug "goto fail", vale a pena notar que outros aspectos do estilo de codificação do tio Bob também o teriam impedido, principalmente sua forte preferência por métodos extremamente curtos. Ele nunca teria tolerado um método de 79 linhas em primeiro lugar, e se o método tivesse sido dividido em métodos menores (a) o conflito de mesclagem que causou a duplicação provavelmente nunca teria acontecido e (b) o controle de fluxo mais simples no método provavelmente significaria que seriam gerados avisos do compilador para código inacessível que deveriam ter evitado o problema.
Jules
16
"ir ao fracasso" não foi causado por blocos de linha única, foi causado por programação desleixada, revisão de código ainda mais deselegante e nem mesmo uma vez percorrendo o código manualmente.
gnasher729
35
Meh, a falta de aparelho não causou esse bug. Programadores apavorantes (e a falta de qualquer revisão significativa de código, teste) o fizeram. Corrija o problema demitindo os responsáveis, não amarrando as mãos do resto de nós!
Lightness Races in Orbit
62

O tio Bob tem muitas camadas de defesa contra esse erro que não eram tão comuns quando "sempre usar aparelho" era a sabedoria predominante:

  1. Uma forte preferência pessoal por condicionais de linha única, para que os de várias linhas se destaquem e recebam um exame extra.
  2. Um editor que supera automaticamente quando você insere uma segunda linha.
  3. Um conjunto completo de testes de unidade que ele executa constantemente.
  4. Um conjunto completo de testes de integração.
  5. Uma revisão por pares feita antes da incorporação do código.
  6. Uma reexecução de todos os testes por um servidor de integração contínua.
  7. Testes realizados por um departamento de qualidade do produto.

Acho que se alguém publicasse um desafio para o tio Bob, ele teria uma boa refutação com os pontos acima. Este é um dos erros mais fáceis de detectar cedo.

Karl Bielefeldt
fonte
16
Eu nunca tive medo de acidentes da mesma forma que temo coisas que falham silenciosamente. Pelo menos você sabe quando ocorre uma falha. A parte de trás do meu cérebro formiga quando vejo essas coisas. Formiga da mesma maneira que quando vejo while(true);{break;}. Se realmente existe um contexto válido para isso, levarei um tempo para superar isso.
Candied_orange 4/06/16
9
Temos uma maneira automatizada de verificar que includePage()não é uma macro mal escrita com mais de uma instrução?
Martin York
51
@LokiAstari Sim, é chamado de "Java não possui macros".
svick
11
Todas as alternativas acima são mais "maneiras de evitar que as quedas relacionadas à política de aparelhos inúteis" me machuquem "do que realmente" razões para usar a política de "aparelhos inúteis". Os fatos de que você precisa (especialmente o número 1) devem ser considerados mais uma desvantagem do que uma vantagem.
precisa saber é o seguinte
16
@Jules Oh sim! TODOS os produtos que recebi ou liberei no trabalho têm 100% de cobertura de teste (no mínimo), são revisados ​​por pares (várias vezes) e todos os negócios ao redor têm departamentos de controle de qualidade de software. Sim. Eu acho que você encontrou o mesmo em sua carreira profissional, porque toda empresa possui equipes de programadores em vez de programadores únicos, e eles oferecem tempo mais que suficiente para realizar todos os testes que gostariam de fazer. Sim, que descreve todos os locais de trabalho perfeitamente. Por que se preocupar em adicionar alguns erros adicionais quando temos certeza de que nosso software SEMPRE envia 100% de erros?
SJuan76
31

Na maioria das vezes, essa é uma preferência pessoal, mas há algumas coisas a considerar.

Possíveis erros

Embora se possa argumentar que os erros causados por esquecer de adicionar-in chaves são raros, pelo que tenho visto que eles não acontecem de vez em quando (não esquecer a famosa Goto IOS falhar bug). Portanto, acho que isso deve ser um fator ao considerar seu estilo de código (algumas ferramentas alertam sobre indentação enganosa , portanto depende da sua cadeia de ferramentas) .

Código válido (que pode parecer um bug)

Mesmo assumindo que o seu projeto não sofrem de tais erros, ao ler o código que você pode ver alguns blocos de código que se parecem com eles poderia ser erros - mas não são, tendo alguns dos seus ciclos mentais.

Começamos com:

if (foo)
    bar();

Um desenvolvedor adiciona um comentário útil.

if (foo)
    // At this point we know foo is valid.
    bar();

Mais tarde, um desenvolvedor se expande.

if (foo)
    // At this point we know foo is valid.
    // This never fails but is too slow even for debug, so keep disabled.
    // assert(is_valid(foo));
    bar();

Ou adiciona um bloco aninhado:

if (foo)
    while (i--) {
        bar(i);
        baz(i);
    }

Ou usa uma macro:

if (foo)
    SOME_MACRO();

"... Como as macros podem definir várias linhas de código, a macro usa do {...} while (0)para várias linhas? Deve ser porque está no nosso guia de estilo, mas é melhor verificar apenas por precaução!"


Os exemplos acima são todos códigos válidos; no entanto, quanto mais conteúdo no bloco de códigos, mais você precisa ler para garantir que não haja erros.

Talvez o seu estilo de código defina que os blocos de várias linhas exigem uma chave (não importa o que aconteça, mesmo que não sejam códigos) , mas vi esses tipos de comentários sendo adicionados ao código de produção. Quando você o lê, há uma pequena dúvida de que quem quer que tenha editado essas linhas pela última vez se esqueceu de adicionar uma chave, às vezes sinto que a necessidade de verificar duas vezes está funcionando conforme o esperado (especialmente ao investigar um bug nesta área do código) .

Diff Noise

Uma razão prática para usar chaves para linhas únicas é reduzir o ruído diferencial .

Ou seja, mudando:

if (foo)
    bar();

Para:

if (foo) {
    bar();
    baz();
}

... faz com que a linha condicional apareça em um diff como sendo alterada, isso adiciona uma sobrecarga pequena, mas desnecessária.

  • as linhas aparecem como sendo alteradas nas revisões de código; se suas ferramentas diferentes são baseadas em palavras, é fácil ver que apenas a chave mudou, mas leva mais tempo para verificar se a linha não foi alterada.
    Dito isto, nem todas as ferramentas suportam diffing baseado em palavras, diff (svn, git, hg ... etc) será exibido como se toda a linha tivesse mudado, mesmo com ferramentas sofisticadas, às vezes você pode precisar olhar rapidamente sobre uma linha simples diff baseado em para ver o que mudou.
  • As ferramentas de anotação (como git blame) mostrarão a linha como sendo alterada, tornando o rastreamento da origem de uma linha mais uma etapa para encontrar a mudança real .

Ambos são pequenos e dependem de quanto tempo você gasta na revisão ou rastreamento de código que confirma as linhas de código alteradas.

Um inconveniente mais tangível de ter alterações de linhas extras em um diff, é mais provável que alterações no código causem conflitos que se fundem e precisam ser resolvidos manualmente .


Há uma exceção a isso, para bases de código que possuem {sua própria linha - não é um problema.

O argumento diff noise não se aplica se você escrever neste estilo:

if (foo)
{
    bar();
    baz();
}

No entanto, essa não é uma convenção tão comum, portanto contribuindo principalmente para a resposta de completude (não sugerindo que os projetos usem esse estilo) .

ideasman42
fonte
8
Eu gosto do ruído diff reduz comentou que é um define plus.
Martin York
2
Se apenas todo mundo que é louco por JSON tivesse alguma consideração pelo ruído de diferença! Estou olhando para você, regra de última vírgula.
Roman Starkov
1
Huh, eu não tinha considerado o benefício do ruído diferenciado do estilo on-the {-own-line. Eu realmente odeio esse estilo e não gosto de trabalhar em projetos nos quais esse estilo é necessário. Talvez com isso em mente, eu ache um pouco menos frustrante ter que codificar dessa maneira. A densidade do código é importante. Se você puder ver todas as funções no monitor de uma só vez, poderá entender mais facilmente tudo. Eu gosto de deixar linhas em branco entre blocos de código separados dentro de uma função para facilitar a leitura (para agrupar instruções relacionadas), e ser forçado a perder espaço em outros lugares torna as quebras pretendidas mais fracas.
Peter Cordes
1
O @ peter-cordes, também não é fã do estilo ' {on-your-own-line', apenas o incluiu para completar as respostas. Quanto à confiança na utilização de macros do{}while(0), acho que o problema é que você pode confiar nela quase o tempo todo. O problema é que acidentes acontecem, erros podem passar por revisão de código ... e erros podem ser introduzidos que não seriam de outra forma.
ideasman42
2
Se estamos listando possíveis bugs, a capacidade de comentar linhas individuais é outra coisa boa. Eu estava rastreando um bug causado por alguém comentando cegamente o corpo de uma declaração if de uma linha. A instrução a seguir foi executada condicionalmente, em vez de executada incondicionalmente.
Eldritch Cheese
15

Anos atrás, fui trazido para depurar algum código C. O bug era muito difícil de encontrar, mas acabou se resumindo a uma declaração como:

if (debug)
   foo (4);

E aconteceu que a pessoa que o havia escrito definiu foocomo macro. Uma macro com duas linhas de código. E, claro, apenas a primeira dessas duas linhas estava sujeita ao if. (Portanto, a segunda linha foi executada incondicionalmente.)

Isso pode ser absolutamente exclusivo para C e seu pré-processador - que faz substituições antes da compilação - mas nunca esqueci. Esse tipo de coisa deixa uma marca em você e por que não jogar pelo seguro - especialmente se você usa uma variedade de idiomas e cadeias de ferramentas e não pode ter certeza de que essas travessuras não sejam possíveis em outros lugares?

Agora eu recuo e uso aparelhos de maneira diferente de todos os outros, aparentemente. Para uma única linha if, eu faria:

if (debug) { foo (4); }

portanto, não são necessárias linhas adicionais para incluir os chavetas.

Wayne
fonte
6
Nesse caso, quem escreveu essa macro é o culpado.
precisa saber é o seguinte
6
Escrever macros do pré-processador C corretamente pode não ser intuitivo, mas pode ser feito. E isso deve ser feito, como gnasher729 aponta. E seu exemplo é o motivo pelo qual qualquer macro que contém mais de uma instrução deve incluir seu corpo em um do { ... } while(0)loop. Isso não apenas garantirá que sempre seja tratado corretamente por if()declarações anexadas , mas também que o ponto-e-vírgula no final da declaração seja engolido corretamente. Quem não conhece essas regras simples, simplesmente não deve escrever macros de pré-processador. A defesa no local de chamada é o lugar errado para se defender.
cmaster
18
@ master: Verdade, mas como as macros (ou outros recursos do idioma) são escritas por outras pessoas está além do meu controle. Como eu uso aparelho está sob meu controle. Definitivamente, não estou dizendo que este é um motivo de ferro para incluir aparelho, mas é algo que eu posso fazer para me defender dos erros de outras pessoas.
Wayne
@ gnasher729, quem se importa se a culpa é de outra pessoa? Se você estiver revisando códigos e seguindo alguns padrões razoáveis ​​de código, pode ser uma falha de sua equipe. E, se chegar aos usuários, eles culparão a organização pelo lançamento de software de buggy.
ideasman42
1
Quem criou macros é o culpado.
Neme
14

"Tio Bob" pode ter sua opinião, você pode ter sua opinião. Não há necessidade de desafiá-lo.

Se você deseja apelar à autoridade, use Chris Lattner. Em Swift, as declarações se perderam entre parênteses, mas sempre vêm com chaves. Sem discussão, faz parte do idioma. Portanto, se "Tio Bob" começar a remover chaves, o código para de compilar.

Examinar o código de outra pessoa e "se livrar de aparelhos inúteis" é um mau hábito. Só causa trabalho extra quando o código precisa ser revisado e quando conflitos são desnecessariamente criados. Talvez "Tio Bob" seja um programador tão bom que ele não precise de revisões de código? Perdi uma semana da minha vida porque um dos principais programadores mudou "if (p! = NULL)" para "if (! P)" sem uma revisão de código, oculta no pior lugar possível.

Este é principalmente um debate de estilo inofensivo. Chaves tem a vantagem de poder inserir outra linha de código sem adicionar chaves. Como uma declaração de log ou um comentário (se seguido de um comentário seguido de uma declaração é horrível). declaração na mesma linha como se tivesse a desvantagem prática de que você tem problemas com muitos depuradores. Mas faça o que você preferir.

gnasher729
fonte
1
Eu acho que é "desafiador", porque UB (sic!) Apresenta suas opiniões como fatos - pelo menos parece-me assim quando o ouço.
precisa saber é o seguinte
3
Dizer "faça o que você preferir" é realmente concordar com o tio Bob, porque é isso que ele argumenta "não importa". Em muitos idiomas, isso não é problema. Eu já tinha pensado em todos os casos em que é um problema que você SEMPRE deve usar aparelho e nunca viu alguém contestando isso. Agora, aqui está o tio Bob, desafiando-o e eu estou me perguntando se ele está se safando porque ele trabalha em métodos minúsculos altamente refatorados ou porque está cheio disso. Eu não tinha pensado nisso inofensivo exatamente por causa dos tipos de bugs que @PaulDraper menciona em sua resposta.
candied_orange
Re sempre : o ruído sintática é perturbador e a linha extra "em branco" para o próximo cinta acrescenta um separador visual no parágrafo onde as linhas não deve ser se separaram, dificultando ainda mais a legibilidade. Isso é especialmente importante em pequenos blocos concisos que você menciona.
JDługosz 06/06/19
9

Minhas razões para não remover chaves são:

  1. reduzir a fadiga da decisão. Se você sempre usa aparelho, nunca precisa decidir se vai precisar de aparelho ou não.

  2. reduzir o arrasto desenvolvimento: mesmo se você se esforçar para , eventualmente, extrair todas as várias linhas de lógica para métodos, tendo que converter um braceless se a um preparou se para adicionar lógica é um pouco chato de arrasto desenvolvimento. Portanto, há o esforço de remover os chavetas e o esforço de adicioná-los novamente quando você precisar de mais código. Minúsculo, mas irritante.

Michael Johnston
fonte
0

Um jovem colega de trabalho disse que os aparelhos que consideramos redundantes e supérfluos são de fato úteis para ele. Não me lembro exatamente o porquê, mas se isso permite que ele escreva um código melhor mais rapidamente, esse é o único motivo para mantê-lo.

Fwiw, chegámos a acordo sobre um compromisso que colocá-los em uma linha não faz tais pré-condições curtas un legível / distraindo-me. Por exemplo

if (!param) { throw invalid_argument (blahblah); }
if (n < 2) { return 0; }
// real code for function starts here...

Também aponto que essas pré-condições introdutórias são frequentemente declarações de fluxo de controle para o corpo (por exemplo, a return); portanto, o medo de adicionar uma segunda declaração que deveria estar sob a mesma condição e o esquecimento de escrever chaves são discutíveis. Uma segunda declaração sob a condição seria um código morto de qualquer maneira e não faria sentido.

Suspeito que a fluência na questão da leitura seja causada pelo fato de o analisador de cérebros da pessoa ter que usar o aparelho como parte da declaração condicional. Essa não é uma representação precisa da gramática do C ++, mas pode ser um efeito colateral do aprendizado de outras línguas primeiro, onde é esse o caso.

JDługosz
fonte
1
O tio Bob provavelmente pensa que escreve um código melhor mais rapidamente sem eles.
djechlin
2
Assim como eu, e outros fluentes em C ++.
JDługosz 06/06/19
1
"se lhe permite escrever um código melhor mais rapidamente, isso é motivo para mantê-los" ++ Eu tenho um Jr. que disse o mesmo, portanto, nós os mantemos.
precisa
... e, portanto, é o único motivo para fazê-lo da maneira que o tio Bob deseja.
djechlin
-1

Tendo assistido a esse vídeo agora, notei que a eliminação de chaves facilita a visualização de onde o código ainda precisa ser refatorado. Se você precisar de aparelhos, seu código provavelmente poderá ser um pouco mais limpo.

Dito isto, quando você está em uma equipe, a eliminação de aparelhos provavelmente levará a um comportamento inesperado algum dia. Eu digo, mantenha-os, e você economizará muitas dores de cabeça quando as coisas derem errado.

Gijs Overvliet
fonte
este não parece oferecer nada substancial sobre os pontos feitos e explicado em antes 10 respostas
mosquito
-3

Fui ensinado a não fazer isso porque facilita a introdução de um bug adicionando outra linha recuada, pensando que ela é controlada pelo if quando não é.

Se o seu código for bem testado por unidade , esse tipo de "bug" explodirá na cara.

Limpar o código, evitando colchetes inúteis e feios, é uma prática que sigo.

Mik378
fonte
9
Obviamente, o inverso também é verdadeiro: se seu código está livre de erros, você não precisa de nenhum teste de unidade. A realidade é que vivemos em um mundo imperfeito, onde às vezes há erros no código e outras vezes nos testes. (Na minha experiência, os últimos são realmente mais comuns.) Portanto, embora os testes certamente reduzam o risco de erros, isso não é desculpa para encontrar outras maneiras de aumentar o risco novamente.
ruakh 6/06/16
3
Para adicionar ao comentário de ruakh: não é possível testar 100% um código de unidade.
06616
Por favor, venha ver o meu código;) Enquanto pratica TDD, é sempre possível mostrar esse tipo de bug rapidamente.
Mik378
@ Mik378 que provavelmente não é verdade. O ponto é que, se você usava aparelho, nem precisaria se preocupar com isso.
GoatInTheMachine