Qual é o objetivo de usar chaves (ou seja, {}) para uma única linha if ou loop?


321

Estou lendo algumas notas de aula do meu professor de C ++ e ele escreveu o seguinte:

  1. Usar recuo // OK
  2. Nunca confie na precedência do operador - Sempre use parênteses // OK
  3. Sempre use um bloco {} - mesmo para uma única linha // não está bem , por que ???
  4. Objeto Const no lado esquerdo da comparação // OK
  5. Use não assinado para variáveis ​​que são> = 0 // truque legal
  6. Defina o Ponteiro como NULL após a exclusão - Proteção contra exclusão dupla // não é ruim

A terceira técnica não está clara para mim: o que eu ganharia colocando uma linha em uma { ... }?

Por exemplo, pegue este código estranho:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

e substitua-o por:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;

Qual é o benefício de usar a 1ª versão?


256
Legibilidade e manutenção. Não é imediatamente óbvio a que bloco de instrução 'j ++' pertence, e que a adição de código após ele não será associada à instrução if.
A floresta e as árvores

27
Sempre me disseram para usar o aparelho {} para essas linhas por algumas razões. Isso torna o código mais claro para leitura. Além disso, outra pessoa dentro de seis meses pode precisar editar seu código, para que a clareza seja importante e, com os aparelhos, um erro é menos provável. Não há nada tecnicamente mais correto nisso, é apenas uma questão de boas práticas. Lembre-se de que um projeto pode ter milhares e milhares de linhas de código para um cara novo pensar!
RossC

30
Não concordo com o 6, pois oculta uma exclusão dupla e potencialmente oculta erros de lógica.
Luchian Grigore 30/08/2012

28
# 5 pode ser complicado - considerar este loop: for (unsigned i = 100; i >= 0; --i).
Archie

36
Aliás, (i % 2 == 0)contradiz (2). Você está confiando na precedência do operador, e é claro ((i % 2) == 0)que o significado é mais do que isso (i % (2 == 0)). Eu classificaria a regra 2 como "um sentimento válido, mas 'sempre' está errado".
30512 Steve Steveop

Respostas:


509

Vamos tentar também modificar iquando incrementamos j:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
        i++;

Ah não! Vindo do Python, isso parece ok, mas na verdade não é, pois é equivalente a:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

Claro, esse é um erro bobo, mas que até um programador experiente poderia cometer.

Outro motivo muito bom é apontado na resposta de ta.speot.is .

Um terceiro que eu consigo pensar é o aninhado if:

if (cond1)
   if (cond2) 
      doSomething();

Agora, suponha que você queira agora doSomethingElse()quando cond1não for atendido (novo recurso). Assim:

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();

o que está obviamente errado, uma vez que os elseassociados ao interior if.


Edit: Uma vez que isso está recebendo alguma atenção, vou esclarecer minha opinião. A pergunta que eu estava respondendo é:

Qual é o benefício de usar a 1ª versão?

Que eu descrevi. Existem alguns benefícios. Mas, IMO, regras "sempre" nem sempre se aplicam. Então eu não apoio totalmente

Sempre use um bloco {} - mesmo para uma única linha // não está bem, por que ???

Não estou dizendo que sempre use um {}bloco. Se é uma condição e comportamento bastante simples, não. Se você suspeitar que alguém possa entrar mais tarde e alterar seu código para adicionar funcionalidade, faça.


5
@ Science_Fiction: True, mas se você adicionar i++ antes j++ , as duas variáveis ​​ainda estarão no escopo quando usadas.
Mike Seymour

26
Isso parece bastante razoável, mas negligencia o fato de o editor fazer o recuo, não você, e recuará o i++;de uma maneira que mostre imediatamente que não faz parte do loop. (No passado, isso pode ter sido um argumento razoável, e eu vi esses problemas cerca de 20 anos atrás desde então...)
James Kanze

43
@ James: isso não é um "fato", porém, é o seu fluxo de trabalho. E o fluxo de trabalho de muitas pessoas, mas não de todos. Eu não acho que seja necessariamente um erro tratar a fonte C ++ como um arquivo de texto sem formatação, em vez da saída de um editor WYSIWYG (vi / emacs / Visual Studio) que impõe regras de formatação. Portanto, essa regra é independente do editor além do que você precisa, mas não além do que as pessoas realmente usam para editar C ++. Daí "defensivo".
21412 Steve Steveop

31
@JamesKanze Você está realmente confiando na suposição de que todo mundo sempre trabalha em IDEs poderosos? O último IC escrito foi em Nano. Mesmo considerando que, uma das primeiras coisas que desligo em um IDE é a indentação automática - porque o IDE tende a atrapalhar meu fluxo de trabalho não linear , tentando corrigir meus 'erros' com base em informações incompletas . Os IDEs não são muito bons em identificar automaticamente o fluxo natural de todos os programadores. Os programadores que usam esses recursos tendem a mesclar seu estilo ao IDE, o que é bom se você usar apenas um IDE, mas não se trabalhar com muitos.
Rushyo 30/08/12

10
"Este é um erro bobo, mas que até um programador experiente poderia cometer." - Como eu disse na minha resposta, não acredito nisso. Eu acho que é um caso totalmente artificial que não representa um problema na realidade.
Konrad Rudolph

324

É muito fácil alterar acidentalmente o fluxo de controle com comentários, se você não usar {e }. Por exemplo:

if (condition)
  do_something();
else
  do_something_else();

must_always_do_this();

Se você comentar do_something_else()com um único comentário de linha, você terminará com o seguinte:

if (condition)
  do_something();
else
  //do_something_else();

must_always_do_this();

Compila, mas must_always_do_this()nem sempre é chamado.

Tivemos esse problema em nossa base de código, onde alguém havia desabilitado algumas funcionalidades muito rapidamente antes do lançamento. Felizmente, nós o pegamos na revisão de código.


3
Ohh garoto !! é um comportamento definido que must_always_do_this();será executado se você comentar // do_something_else ();
Mr.Anubis

1
@Supr, como foi escrito pela primeira vez, ele está dizendo que é difícil interromper o fluxo correto se você usar chaves, e depois dá um exemplo de como é fácil quebrar sem ter o código entre colchetes corretamente
SeanC

20
Eu encontrei isso no outro dia. if(debug) \n //print(info);. Basicamente tirou uma biblioteca inteira.
Kevin

4
Fortunately we caught it in code review.Ai! Isso parece tão errado. Fortunately we caught it in unit tests.seria muito melhor!
BЈовић

4
@ Bћовић Mas e se o código estivesse em um teste de unidade? A mente confunde. (Brincadeirinha, é um aplicativo de legado Não há testes de unidade..)
ta.speot.is

59

Tenho minhas dúvidas quanto à competência do palestrante. Considerando seus pontos:

  1. Está bem
  2. Alguém realmente escreveria (ou gostaria de ler) (b*b) - ((4*a)*c)? Algumas precedências são óbvias (ou deveriam ser), e os parênteses extras apenas aumentam a confusão. (Por outro lado, você deve usar os parênteses em casos menos óbvios, mesmo que saiba que eles não são necessários.)
  3. Tipo de. Existem duas convenções de espalhamento amplo para formatar condicionais e loops:
    if (cond) {
        código;
    }
    
    e:
    se (cond)
    {
        código;
    }
    
    No primeiro, eu concordo com ele. A abertura {não é tão visível, então é melhor assumir que está sempre lá. No segundo, no entanto, eu (e a maioria das pessoas com quem trabalhei) não tenho nenhum problema em omitir os aparelhos para uma única declaração. (Desde que, é claro, que a indentação seja sistemática e que você use esse estilo de forma consistente. (E muitos programadores muito bons, escrevendo código muito legível, omitem os chavetas, mesmo quando formatam a primeira maneira))
  4. NÃO . Coisas como if ( NULL == ptr )são feias o suficiente para dificultar a legibilidade. Escreva as comparações intuitivamente. (O que em muitos casos resulta na constante à direita.) Seus 4 são maus conselhos; qualquer coisa que torne o código não natural o torna menos legível.
  5. NÃO . Tudo, exceto intestá reservado para casos especiais. Para programadores experientes em C e C ++, o uso de unsignedoperadores de bits de sinais. O C ++ não possui um tipo cardinal real (ou qualquer outro tipo de subintervalo eficaz); unsignednão funciona para valores numéricos, devido às regras de promoção. Valores numéricos nos quais nenhuma operação aritmética faria sentido, como números de série, poderiam ser presumivelmente unsigned. Eu argumentaria contra, no entanto, porque envia a mensagem errada: operações bit a bit também não fazem sentido. A regra básica é que tipos integrais são int, a menos que haja um motivo significativo para o uso de outro tipo.
  6. NÃO . Fazer isso sistematicamente é enganoso e, na verdade, não protege contra nada. No código OO estrito, delete this;é frequentemente o caso mais frequente (e você não pode definir thisa NULL), e de outra forma, a maioria deleteestão em destruidores, para que você não pode acessar o ponteiro depois de qualquer maneira. E configurá-lo para NULLnão faz nada com outros ponteiros flutuando. Definir o ponteiro sistematicamente para NULLdá uma falsa sensação de segurança e realmente não compra nada para você.

Veja o código em qualquer uma das referências típicas. Stroustrup viola todas as regras que você deu, exceto a primeira, por exemplo.

Eu sugiro que você encontre outro professor. Alguém que realmente sabe do que está falando.


13
O número 4 pode ser feio, no entanto, existe um objetivo. Ele está tentando impedir se (ptr = NULL). Acho que nunca usei delete this, é mais comum do que já vi? Eu não costumo pensar que definir um ponteiro para NULL após o uso seja algo ruim de fazer, além do YMMV. Talvez seja só eu, mas a maioria das diretrizes dele não parece tão ruim.
Firedragon

16
@Firedragon: A maioria dos compiladores avisará, a if (ptr = NULL)menos que você o escreva como if ((ptr = NULL)). Tenho que concordar com James Kanze que a feiúra de ter NULLprimeiro o torna um NÃO definitivo para mim.
Leo

34
@ JamesKanze: Eu diria que não concordo com a maior parte do que você declarou aqui - embora eu aprecie e respeite seus argumentos para chegar a eles. Para programadores experientes em C e C ++, o uso de operadores de bits de sinais não assinados. - Não concordo nada: o uso de operadores de bits sinaliza o uso de operadores de bits. Para mim, o uso de unsignedindica uma aspiração por parte do programador de que a variável deve representar apenas números positivos. Misturar com números assinados geralmente causará um aviso do compilador, provavelmente o que o professor pretendia.
Component 10

18
Para programadores experientes em C e C ++, o uso de operadores de bits de sinais não assinados Ou não. size_t, qualquer um?
ta.speot.is

16
@ James Kanze, considere o propósito. Você está comparando o código produzido por um programador experiente com exemplos instrucionais. Essas regras são fornecidas pelo professor porque são os tipos de erros que ele vê seus alunos cometerem. Com a experiência, os alunos podem relaxar ou desconsiderar esses absolutos.
Joshua Shane Liberman

46

Todas as outras respostas defendem a regra do seu professor 3.

Deixe-me dizer que concordo com você: a regra é redundante e eu não a aconselharia. É verdade que teoricamente evita erros se você sempre adiciona colchetes. Por outro lado, nunca encontrei esse problema na vida real : ao contrário do que outras respostas sugerem, nunca esqueci de adicionar os colchetes quando eles se tornaram necessários. Se você usar o recuo adequado, torna-se imediatamente óbvio que você precisará adicionar colchetes depois que mais de uma instrução for recuada.

A resposta do “Componente 10”, na verdade, destaca o único caso concebível em que isso realmente pode levar a um erro. Mas, por outro lado, a substituição de código por meio de expressão regular sempre exige muito cuidado.

Agora, vamos olhar para o outro lado da medalha: existe uma desvantagem em usar sempre colchetes? As outras respostas simplesmente ignoram esse ponto. Mas não é uma desvantagem: ele ocupa muito espaço na tela vertical, e este por sua vez, pode fazer o seu código ilegível, porque isso significa que você tem que mover-se mais do que o necessário.

Considere uma função com muitas cláusulas de guarda no início (e sim, o código a seguir é ruim para C ++, mas em outros idiomas isso seria uma situação bastante comum):

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
    {
        throw null_ptr_error("a");
    }
    if (b == nullptr)
    {
        throw null_ptr_error("b");
    }
    if (a == b)
    {
        throw logic_error("Cannot do method on identical objects");
    }
    if (not a->precondition_met())
    {
        throw logic_error("Precondition for a not met");
    }

    a->do_something_with(b);
}

Este é um código horrível, e eu argumento fortemente que o seguinte é muito mais legível:

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
        throw null_ptr_error("a");
    if (b == nullptr)
        throw null_ptr_error("b");
    if (a == b)
        throw logic_error("Cannot do method on identical objects");
    if (not a->precondition_met())
        throw logic_error("Precondition for a not met");

    a->do_something_with(b);
}

Da mesma forma, loops aninhados curtos se beneficiam da omissão dos colchetes:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
        for (auto j = 0; j < a.h(); ++j)
            c(i, j) = a(i, j) + b(i, j);

    return c;
}

Compare com:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
    {
        for (auto j = 0; j < a.h(); ++j)
        {
            c(i, j) = a(i, j) + b(i, j);
        }
    }

    return c;
}

O primeiro código é conciso; o segundo código está inchado.

E sim, isso pode ser atenuado até certo ponto , colocando a chave de abertura na linha anterior. Mas isso ainda seria menos legível que o código sem colchetes.

Resumindo: não escreva códigos desnecessários que ocupam espaço na tela.


26
Se você não acredita em escrever códigos que ocupam espaço desnecessariamente na tela, não é possível colocar a chave de abertura em sua própria linha. Provavelmente agora vou ter que me esquivar e fugir da santa vingança do GNU, mas sério - você quer que seu código seja verticalmente compacto ou não. E se o fizer, não faça coisas projetadas apenas para tornar seu código menos compacto verticalmente. Mas, como você diz, tendo corrigido isso, você também deseja remover chaves redundantes. Ou talvez apenas escreva if (a == nullptr) { throw null_ptr_error("a"); }como uma linha.
21712 Steve Jobs (

6
@ Steve Por uma questão de fato, eu não colocar a chave de abertura na linha anterior, pela simples razão de que você declarou. Usei o outro estilo aqui para tornar mais óbvio o quão extrema pode ser a diferença.
Konrad Rudolph

9
+1 Concordo plenamente que seu primeiro exemplo é muito mais fácil de ler sem chaves. No segundo exemplo, meu estilo de codificação pessoal é usar chaves no loop for externo e não no interno. Eu discordo do @SteveJessop sobre ter que ser um extremo ou outro sobre o código verticalmente compacto. Eu omito chaves extras com uma linha para reduzir o espaço vertical, mas coloco minhas chaves de abertura em uma nova linha porque acho mais fácil ver o escopo quando as chaves estão alinhadas. O objetivo é a legibilidade, e às vezes isso significa usar mais espaço vertical, outras vezes significa usar menos.
precisa saber é o seguinte

22
"Eu nunca encontrei esse problema na vida real": sorte sua. Coisas como essa não apenas o queimam, elas oferecem 90% de queimaduras de terceiro grau (e isso é apenas algumas camadas de gerenciamento que exigem uma correção no final da noite).
Richard

9
@ Richard Eu simplesmente não compro isso. Como expliquei no bate-papo, mesmo que esse erro ocorra (o que acho improvável), é trivial corrigi-lo quando você olha para o rastreamento da pilha, porque é óbvio que o erro é apenas olhando o código. Sua alegação exagerada é completamente sem fundamento.
Konrad Rudolph

40

A base de código em que estou trabalhando é repleta de código por pessoas com aversão patológica a aparelhos, e para as pessoas que aparecem mais tarde, realmente pode fazer a diferença na manutenção.

O exemplo problemático mais frequente que encontrei é o seguinte:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
    this_looks_like_a_then-statement_but_isn't;

Portanto, quando eu aparecer e desejar adicionar uma declaração, eu posso facilmente terminar com isso se não for cuidadoso:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
    this_looks_like_a_then-statement_but_isn't;
    i_want_this_to_be_a_then-statement_but_it's_not;
}

Tendo em conta que é preciso ~ 1 segundo para adicionar chaves e você pode salvar no mínimo alguns minutos confusos depuração, por que você nunca não ir com a opção reduziu-ambigüidade? Parece uma economia falsa para mim.


16
O problema neste exemplo não está na indentação imprópria e nas filas muito longas, e não nas chaves?
Honza Brabec

7
Sim, mas seguir as diretrizes de design / codificação que são apenas 'seguras', pressupondo que as pessoas também estejam seguindo outras orientações (como não ter longas filas) parece estar pedindo problemas. Se os aparelhos estivessem presentes desde o início, seria impossível acabar com um bloco if incorreto nessa situação.
jam

16
Como adicionar chaves ( if(really long...editor){ do_foo;}ajudando a evitar esse caso? Parece que o problema ainda seria o mesmo. Pessoalmente, prefiro evitar chaves quando não for necessário, mas isso não tem nada a ver com o tempo necessário para escrevê-las, mas com a legibilidade reduzida. devido às duas linhas extras no código.
Grizzly

1
Bom ponto - eu estava assumindo que impor o uso de aparelhos também resultaria em um lugar sensato, mas é claro que alguém determinado a dificultar as coisas poderia colocá-los alinhados, como no seu exemplo. Eu imagino que a maioria das pessoas não, no entanto.
jam

2
A primeira e a última coisa que faço ao tocar em um arquivo é clicar no botão de formatação automática. Elimina a maioria desses problemas.
31412 Jonathan Allen

20

Meu 2c:

Usar recuo

Obviamente

Nunca confie na precedência do operador - Sempre use parênteses

Eu não usaria as palavras "nunca e" sempre ", mas em geral vejo essa regra sendo útil. Em alguns idiomas (Lisp, Smalltalk) isso não é problema.

Sempre use um bloco {} - mesmo para uma única linha

Eu nunca faço isso e nunca tive um único problema, mas posso ver como isso pode ser bom para os alunos, especialmente. se eles estudaram Python antes.

Objeto Const no lado esquerdo da comparação

Condições de Yoda? Não por favor. Dói a legibilidade. Basta usar o nível máximo de aviso ao compilar seu código.

Use não assinado para variáveis ​​que são> = 0

ESTÁ BEM. Engraçado o suficiente, ouvi Stroustrup discordar.

Definir ponteiro como NULL após exclusão - Proteção contra exclusão dupla

Mau conselho! Nunca tenha um ponteiro que aponte para um objeto excluído ou inexistente.


5
+1 apenas no último ponto. Um ponteiro bruto não tem nenhuma empresa que possua memória de qualquer maneira.
Konrad Rudolph

2
Com relação ao uso não assinado: não apenas Stroustrup, mas K&R (em C), Herb Sutter e (eu acho) Scott Meyers. Na verdade, nunca ouvi alguém que realmente entendesse as regras do C ++ argumentar sobre o uso não assinado.
precisa saber é o seguinte

2
@JamesKanze De fato, na mesma ocasião em que ouvi a opinião de Stroustrup (uma conferência de Boston em 2008), Herb Sutter estava lá e discordou de Bjarne no local.
Nemanja Trifunovic

3
Apenas para concluir o " unsignedestá quebrado", um dos problemas é que, quando o C ++ compara tipos assinados e não assinados de tamanho semelhante, ele se converte no não assinado antes de fazer a comparação. O que resulta em uma mudança de valor. Converter para o assinado não seria necessariamente muito melhor; a comparação realmente deve ocorrer "como se" os dois valores fossem convertidos em um tipo maior que pudesse representar todos os valores em ambos os tipos.
precisa saber é o seguinte

1
@SteveJessop Acho que você precisa entender isso no contexto de uma função retornando unsigned. Tenho certeza que ele não tem nenhum problema em exp(double)retornar um valor maior que MAX_INT:-). Mas mais uma vez, o verdadeiro problema são as conversões implícitas. int i = exp( 1e6 );é perfeitamente válido em C ++. Stroustrup, na verdade, propôs a depreciação de conversões implícitas com perdas a certa altura, mas o comitê não estava interessado. (Uma questão interessante: seria unsigned-> intser considerado lossy Eu considero ambos. unsigned-> inte int-> unsignedlossy que seria um longo caminho a fazer. unsignedOK
James Kanze

18

é mais intuitivo e facilmente compreensível. Isso torna clara a intenção.

E garante que o código não seja interrompido quando um novo usuário pode, sem saber, sentir falta dele {, }enquanto adiciona uma nova instrução de código.


Makes the intent clear+1, esse é provavelmente o motivo mais conciso e preciso.
Qix - MONICA FOI ERRADA EM 01/04

13

Para adicionar às sugestões mais sensatas acima, um exemplo que encontrei ao refatorar algum código de onde isso se torna crítico foi o seguinte: Eu estava alterando uma base de código muito grande para alternar de uma API para outra. A primeira API teve uma chamada para definir o ID da empresa da seguinte maneira:

setCompIds( const std::string& compId, const std::string& compSubId );

considerando que a substituição precisava de duas chamadas:

setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );

Comecei a mudar isso usando expressões regulares que foram muito bem-sucedidas. Também passamos o código pelo astyle , o que realmente o tornou muito mais legível. Então, no meio do processo de revisão, descobri que, em algumas circunstâncias condicionais, isso estava mudando:

if ( condition )
   setCompIds( compId, compSubId );

Para isso:

if ( condition )
   setCompId( compId );
setCompSubId( compSubId );

o que claramente não é o que era necessário. Eu tive que voltar ao início, tratando a substituição completamente dentro de um bloco e alterando manualmente qualquer coisa que acabasse parecendo pateta (pelo menos não seria incorreta).

Percebo que o estilo agora tem a opção --add-bracketsque permite adicionar colchetes onde não há nenhum e eu recomendo isso se você se encontrar na mesma posição que eu.


5
Uma vez vi uma documentação com a maravilhosa cunhagem "Microsoftligent". Sim, é possível cometer erros significativos com a pesquisa global e a substituição. Isso significa apenas que a pesquisa e a substituição globais devem ser usadas de maneira inteligente, e não microssignificante.
Pete Becker

4
Sei que esse não é o meu desempenho post-mortem, mas se você deseja fazer a substituição de texto no código-fonte, deve fazê-lo de acordo com as mesmas regras que usaria para o tipo de substituição de texto bem estabelecido na linguagem: macros. Você não deve escrever uma macro #define FOO() func1(); \ func2(); (com uma quebra de linha após a barra invertida), o mesmo vale para pesquisa e substituição. Dito isto, vi "sempre use chaves" avançado como regra de estilo, precisamente porque evita que você envolva todas as suas macros de múltiplas instruções do .. while(0). Mas eu discordo.
30512 Steve Steveop

2
Aliás, isso é "bem estabelecido" no sentido de que o knotweed japonês está bem estabelecido: não estou dizendo que devemos nos esforçar para usar macros e substituição de texto, mas estou dizendo que quando fazemos isso coisa, devemos fazê-lo de uma forma que funciona, ao invés de fazer algo que só funciona se uma regra de um estilo particular foi com sucesso imposta a toda a base de código :-)
Steve Jessop

1
@SteveJessop Pode-se argumentar também por aparelho e cinto. Se você precisar usar essas macros (e o fizemos antes de C ++ e inline), provavelmente deve procurar que funcionem o mais parecido possível com uma função, usando o do { ... } while(0)truque, se necessário (e muitos parênteses extras. Mas isso ainda não impedi-lo de usar aparelho em todos os lugares, se esse for o estilo da casa. (FWIW: Trabalhei em locais com estilos variados de casas, cobrindo todos os estilos discutidos aqui. Nunca achei que isso fosse um problema sério).
precisa saber é o seguinte

1
E eu acho que, quanto mais estilos você trabalhou, mais cuidadosamente você lê e edita o código. Portanto, mesmo se você preferir o que é mais fácil de ler, ainda assim lerá com êxito os outros. Eu trabalhei em uma empresa onde diferentes componentes foram escritos em diferentes "estilos de casas" pelas diferentes equipes, e a solução correta é reclamar sobre isso na lanchonete sem grande efeito, sem tentar criar um estilo global :-)
30512 Steve Steveop

8

Estou usando em {}qualquer lugar, exceto em alguns casos em que é óbvio. Linha única é um dos casos:

if(condition) return; // OK

if(condition) // 
   return;    // and this is not a one-liner 

Pode machucá-lo quando você adiciona algum método antes de retornar. O recuo indica que o retorno está sendo executado quando a condição é atendida, mas sempre retornará.

Outro exemplo em C # com o uso de declaração

using (D d = new D())  // OK
using (C c = new C(d))
{
    c.UseLimitedResource();
}

que é equivalente a

using (D d = new D())
{
    using (C c = new C(d))
    {
        c.UseLimitedResource();
    }
}

1
Basta usar vírgulas na usingdeclaração e você não precisa :) :)
Ry-

1
@minitech Isso simplesmente não funciona aqui - você só pode usar vírgula quando os tipos forem iguais, não para tipos desiguais. A maneira de Lukas fazer isso é a maneira canônica, o IDE formata isso de maneira diferente (observe a falta de recuo automático do segundo using).
Konrad Rudolph

8

O exemplo mais pertinente em que consigo pensar:

if(someCondition)
   if(someOtherCondition)
      DoSomething();
else
   DoSomethingElse();

Com o qual ifo elsepar será emparelhado? A indentação implica que o externo ifrecebe o else, mas não é assim que o compilador o verá; o interior if receberá o elsee o exterior ifnão. Você precisaria saber isso (ou vê-lo se comportar dessa maneira no modo de depuração) para descobrir por inspeção por que esse código pode estar falhando em suas expectativas. Fica mais confuso se você conhece Python; nesse caso, você sabe que o recuo define os blocos de código; portanto, você esperaria que ele fosse avaliado de acordo com o recuo. O C #, no entanto, não dá a mínima para o espaço em branco.

Agora, dito isso, não concordo particularmente com esta regra "sempre use colchetes". Torna o código muito verticalmente barulhento, reduzindo a capacidade de lê-lo rapidamente. Se a declaração for:

if(someCondition)
   DoSomething();

... então deve ser escrito assim. A afirmação "sempre use colchetes" soa como "sempre envolva operações matemáticas entre parênteses". Que iria transformar a afirmação muito simples a * b + c / dpara ((a * b) + (c / d)), introduzindo a possibilidade de perder um close-paren (a ruína de um codificador muitos), e para quê? A ordem das operações é bem conhecida e aplicada, portanto os parênteses são redundantes. Você usaria apenas parênteses para impor uma ordem de operações diferente da que seria normalmente aplicada: a * (b+c) / dpor exemplo. Chaves de bloco são semelhantes; use-os para definir o que você deseja fazer nos casos em que difere do padrão e não seja "óbvio" (subjetivo, mas geralmente bastante senso comum).


3
@AlexBrown ... que era exatamente o meu ponto. A regra conforme declarada no OP é "sempre use colchetes, mesmo para linhas únicas", da qual discordo pelo motivo indicado. Suportes iria ajudar com o primeiro exemplo de código, porque o código não vai se comportar da maneira que é recuado; você precisaria usar colchetes para parear elseo primeiro ao ifinvés do segundo. Por favor, remova o voto negativo.
Keiths

5

Ao analisar as respostas, ninguém declarou explicitamente o tipo de prática que eu tenho o hábito de contar a história do seu código:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

Torna-se:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0) j++;
}

Colocar j++na mesma linha que o if deve sinalizar para mais alguém: "Eu só quero que esse bloco seja incrementado j" . De coursethis só vale a pena se a linha é tão simplista quanto possível, porque colocar um ponto de interrupção aqui, como peri menciona, não vai ser muito útil.

Na verdade, eu acabei de percorrer parte da API do Twitter Storm que possui esse 'tipo' de código em java, eis o snippet relvant do código de execução, na página 43 desta apresentação de slides :

...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
...

O bloco de loop for tem duas coisas, então eu não incorporaria esse código. Ou seja, nunca :

int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;

É horrível e nem sei se funciona (como pretendido); não faça isso . Novas linhas e chaves ajudam a distinguir trechos de código separados, mas relacionados, da mesma forma que uma vírgula ou um ponto e vírgula na prosa. O bloco acima é uma sentença muito longa, com algumas cláusulas e outras declarações que nunca quebram ou pausam para distinguir partes separadas.

Se você realmente deseja telegrafar para outra pessoa, é um trabalho de apenas uma linha, use um operador ou ?:formulário ternário :

for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;

Mas isso está se aproximando do code-golf, e acho que não é uma boa prática (não está claro para mim se devo colocar o j ++ em um lado :ou não). NB: Eu nunca executei um operador ternário em C ++ antes, não sei se isso funciona, mas existe .

Em resumo:

Imagine como o seu leitor (ou seja, a pessoa que mantém o código) interpreta sua história (código). Deixe o mais claro possível para eles. Se você sabe que o aluno / programador iniciante está mantendo isso, talvez deixe o maior número {}possível, só para não ficar confuso.


1
(1) Colocar a declaração na mesma linha a torna menos legível, não mais. Pensamentos especialmente simples como um incremento são facilmente ignorados. Do colocá-los em uma nova linha. (2) É claro que você pode colocar seu forloop em uma única linha, por que isso não deveria funcionar? Funciona pelo mesmo motivo que você pode omitir os chavetas; newline simplesmente não é significativo em C ++. (3) Seu exemplo de operador condicional, além de horrível, é C ++ inválido.
Konrad Rudolph

@KonradRudolph obrigado, estou um pouco enferrujado em C ++. Eu nunca disse que (1) era mais legível, mas seria sinal de que esse código deveria estar on-line em uma linha. (2) Meu comentário foi mais do que eu não seria capaz de lê-lo e saber que funcionava, de maneira alguma ou como pretendido; é um exemplo do que não fazer por esse motivo. (3) Obrigado, não escrevo C ++ há muito tempo. Eu vou consertar isso agora.
Pureferret 3/12/12

2
Também colocar mais de uma expressão em uma linha torna mais difícil a depuração do código. Como você coloca o ponto de interrupção na 2ª expressão nessa linha?
Piotr Perak

5

Porque quando você tem duas declarações sem {}, é fácil perder um problema. Vamos assumir que o código se parece com isso.

int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();

if ((err = prepare_hash(hash, &hash_result))) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
    goto fail;
    goto fail;
if ((err = hash_finish(hash)) != 0)
    goto fail;

error = do_important_stuff_with(hash);

fail:
hash_free(hash);
return error;

Parece bem. O problema com isso é realmente fácil de perder, especialmente quando a função que contém o código é muito maior. O problema é que goto failé executado incondicionalmente. Você pode facilmente imaginar o quão frustrante isso é (fazendo você perguntar por que o último hash_updatesempre falha, afinal tudo parece hash_updatefuncionar bem).

No entanto, isso não significa que sou a favor de adicionar em {}todos os lugares (na minha opinião, ver {}todos os lugares é irritante). Embora possa causar problemas, isso nunca aconteceu em meus próprios projetos, pois meu estilo de codificação pessoal proíbe condicionais sem {}que eles não estejam na mesma linha (sim, eu concordo que meu estilo de codificação não é convencional, mas eu gosto e eu use o estilo de código do projeto ao contribuir para outros projetos). Isso torna o código a seguir correto.

if (something) goto fail;

Mas não o seguinte.

if (something)
    goto fail;

Exatamente. Apenas não coloque a nova linha (completamente desnecessária) + recuo, e você evita completamente esse problema que todos são sempre rápidos em abordar.
Alexander - Reinstate Monica

4

Isso torna seu código mais legível, definindo claramente o escopo de seus loops e blocos condicionais. Também evita erros acidentais.


4

wrt 6: É mais seguro, pois excluir um ponteiro nulo não é necessário. Portanto, se você acidentalmente seguir esse caminho duas vezes, não fará com que a corrupção da memória libere a memória livre ou que tenha sido alocada para outra coisa.

Esse é o maior problema com objetos estáticos de escopo de arquivo e singletons que não têm vida útil muito clara e que são conhecidos por serem recriados após serem destruídos.

Na maioria dos casos, você pode evitar a necessidade disso usando auto_ptrs


6
Se você passar por esse caminho duas vezes, terá um erro de programação. Definir um ponteiro como null para tornar esse erro menos prejudicial não resolve o problema subjacente.
Pete Becker

1
Concordo, mas já vi isso recomendado antes e acredito que esteja em alguns padrões de programação profissional. Eu estava comentando mais sobre o motivo pelo qual o professor do pôster o inventou, e não quando foi bom
Tom Tanner

2
Seguindo o que Pete Becker disse: não resolve o problema subjacente, mas pode mascará-lo. (Há casos em que você definiria um ponteiro NULLapós excluí-lo. Se NULLfor um valor correto para o ponteiro nessas circunstâncias; por exemplo, o ponteiro aponta para um valor em cache e NULLindica um cache inválido. Mas quando você vê alguém definindo um ponteiro para NULLcomo a última linha de um destruidor, você quer saber se ele sabe C ++).
James Kanze

4

Gosto da resposta aceita de Luchian; na verdade, aprendi da maneira mais difícil que ele está certo, então sempre uso aparelho, mesmo para blocos de linha única. No entanto, pessoalmente, faço uma exceção ao escrever um filtro, como você está no seu exemplo. Este:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

parece confuso para mim. Ele separa o loop for e a instrução if em ações separadas, quando realmente sua intenção é uma ação única: contar todos os números inteiros divisíveis por 2. Em uma linguagem mais expressiva, isso pode ser escrito como:

j = [1..100].filter(_%2 == 0).Count

Em idiomas que não têm fechamento, o filtro não pode ser expresso em uma única instrução, mas deve ser um loop for seguido por uma instrução if. No entanto, ainda é uma ação na mente do programador, e acredito que isso deve se refletir no código, assim:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
  if (i % 2 == 0)
{
    j++;
}

7
Eu gosto de como todo mundo consegue ignorar for (int i = 0; i < 100; i += 2);, por uma questão de continuar a discussão sobre indentação ;-) Provavelmente há uma briga de coelhos separada que poderíamos ter, como "melhor" para expressar a lógica "para cada ium em um determinado intervalo com uma certa propriedade" em C ++ sem loop, usando alguma combinação de pesadelo de algoritmos padrão filter_iteratore / ou counting_iterator.
30512 Steve Steveop

1
Além disso, se tivéssemos isso, poderíamos discordar sobre como recuar a declaração única maciça resultante.
Steve Jessop

2
@ Steve, é apenas um exemplo. Há muitos usos legítimos do padrão. Obviamente, se você quiser contar os números de 1 a 100, que são divisíveis por 2, tudo o que você precisa fazer é 100/2.
precisa saber é o seguinte

2
Claro, eu sei, é por isso que abstraí para "para cada ium em um determinado intervalo com uma certa propriedade". Geralmente, no SO, as pessoas são muito rápidas em ignorar a pergunta real em favor de uma abordagem completamente diferente do exemplo dado. Mas o recuo é importante , por isso não faz ;-)
Steve Jessop

4

Uma opção para ajudar a evitar os erros descritos acima é alinhar o que você deseja que aconteça quando não usa aparelho. Torna muito mais difícil não perceber os erros quando você tenta modificar o código.

if (condition) doSomething();
else doSomethingElse();

if (condition) doSomething();
    doSomething2(); // Looks pretty obviously wrong
else // doSomethingElse(); also looks pretty obviously wrong

5
A segunda opção geraria um erro de compilação, porque o elsenão está associado a um if.
Luchian Grigore 30/08/2012

Um problema não tão visível com o inline é que a maioria dos IDEs por padrão o altera para o estilo recuado ao usar seu utilitário de autoformação.
Honza Brabec

@Honza: essa é uma questão política altamente carregada. Se estivermos cooperando em uma base de código, precisamos usar o mesmo estilo de indentação até todos os detalhes, ou temos que concordar em não autoformar o código existente "apenas porque". Se o primeiro, o estilo acordado ainda pode incluir isso, mas você precisa configurar seu IDE para respeitá-lo ou não usar o formato automático. Concordar que o formato comum é "seja qual for o formato automático do meu IDE" será muito bom se todos usarmos o mesmo IDE para sempre, e não tão bom assim.
30512 Steve Steveop

3

Se você é um compilador, não faz diferença. Ambos são iguais.

Mas para os programadores, o primeiro é mais claro, fácil de ler e menos propenso a erros.


2
Além de abrir {em sua própria linha, de qualquer maneira.
Rob

3

Outro exemplo de adição de chaves. Uma vez eu estava procurando por um bug e encontrei esse código:

void SomeSimpleEventHandler()
{
    SomeStatementAtTheBeginningNumber1;
    if (conditionX) SomeRegularStatement;
    SomeStatementAtTheBeginningNumber2;
    SomeStatementAtTheBeginningNumber3;
    if (!SomeConditionIsMet()) return;
    OtherwiseSomeAdditionalStatement1;
    OtherwiseSomeAdditionalStatement2;
    OtherwiseSomeAdditionalStatement3;
}

Se você ler o método linha por linha, notará que existe uma condição no método que retorna se não for verdadeira. Mas, na verdade, parece com outros 100 manipuladores de eventos simples que definem algumas variáveis ​​com base em algumas condições. E um dia o Fast Coder entra e adiciona uma declaração de configuração de variável adicional ao final do método:

{
    ...
    OtherwiseSomeAdditionalStatement3;
    SetAnotherVariableUnconditionnaly;
}

Como resultado, o SetAnotherVariableUnconditionnaly é executado quando o SomeConditionIsMet (), mas o cara rápido não percebeu, porque todas as linhas são quase de tamanho semelhante e mesmo quando a condição de retorno é recuada verticalmente, não é tão perceptível.

Se o retorno condicional for formatado assim:

if (!SomeConditionIsMet())
{
    return;
}

é muito perceptível e o Fast Coder o encontrará rapidamente.


Se o seu codificador rápido não puder se incomodar em localizar uma returninstrução destacada pela sintaxe no corpo de uma função antes de adicionar algo a ela, não deixe o codificador rápido se aproximar do seu código. Você não impedirá que esse tipo de pessoa passeie pelo seu código incluindo chaves.
cmaster - restabelece monica

@ master Ele não trabalha mais conosco. De qualquer forma, o destaque da sintaxe é bom, mas lembre-se de que existem pessoas que não enxergam claramente (vi até um post de um programador cego no ano passado).
Artemix 26/05

2

Considero o primeiro claro, depois o segundo. Dá a sensação de instruções de fechamento, com pouco código é bom quando o código fica complexo {...}ajuda muito, mesmo que seja endifoubegin...end

//first
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}


//second
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

1

É melhor definir o ponteiro como NULL quando terminar.

Aqui está um exemplo do porquê:

A classe A faz o seguinte:

  1. Aloca um bloco de memória
  2. Algum tempo depois, ele exclui esse bloco de memória, mas não define o ponteiro para NULL

A classe B faz o seguinte

  1. Aloca memória (e, nesse caso, recebe o mesmo bloco de memória que foi excluído pela classe A.)

Nesse ponto, tanto a Classe A quanto a Classe B têm ponteiros apontando para o mesmo bloco de memória. No que diz respeito à Classe A, esse bloco de memória não existe porque foi finalizado.

Considere o seguinte problema:

E se houvesse um erro lógico na Classe A que resultasse na gravação na memória que agora pertence à Classe B?

Nesse caso específico, você não receberá um erro de exceção de acesso incorreto porque o endereço de memória é legal, enquanto a classe A está corrompendo efetivamente os dados da classe B.

A classe B pode eventualmente travar se encontrar valores inesperados e, quando travar, as chances são de que você passará muito tempo caçando esse bug na classe B quando o problema estiver na classe A.

Se você tivesse definido o ponteiro de memória excluído como NULL, teria recebido um erro de exceção assim que qualquer erro lógico da Classe A tentasse gravar no ponteiro NULL.

Se você estiver preocupado com o erro lógico com exclusão dupla quando os ponteiros forem NULL pela segunda vez, adicione assert para isso.

Além disso: Se você estiver indo para votar, por favor, explique.


2
Se houve um erro lógico, ele deve ser corrigido, em vez de mascará-lo.
uınbɐɥs

@Barmar, OP diz ... 6. Defina Pointer como NULL após a exclusão - Proteção contra exclusão dupla // não é ruim. Algumas pessoas responderam por não defini-lo como Nulo e estou dizendo por que ele deve ser definido como NULL. Qual parte de 6. minha resposta para definir NULL não se encaixa em 6?
John

1
@ Shaquin, e como você propõe encontrar esses erros de lógica em primeiro lugar? Depois de definir a variável do ponteiro como NULL após a memória ser excluída. Qualquer tentativa de referenciar o ponteiro NULL irá falhar no depurador na linha em que sua tentativa ilegal foi feita. Você pode rastrear e ver onde estava o erro lógico e corrigir o problema. Se você não definir a variável de ponteiro como NULL após excluir a memória, sua tentativa ilegal de gravar essa memória excluída devido a erros de lógica do UNAWARE poderá ter êxito e, portanto, não será interrompida nesse momento. Não está mascarando.
John

1

Ter sempre chaves é uma regra muito simples e robusta. No entanto, o código pode parecer deselegante quando há muitas chaves. Se as regras permitirem omitir chaves, deve haver regras de estilo mais detalhadas e ferramentas mais sofisticadas. Caso contrário, pode resultar facilmente em código caótico e confuso (não elegante). Portanto, procurar regras de estilo único separadas do restante dos guias e ferramentas de estilo usadas provavelmente é infrutífero. Trarei apenas alguns detalhes importantes sobre essa regra nº 3 que nem foram mencionados em outras respostas.

O primeiro detalhe interessante é que a maioria dos defensores dessa regra concorda em violá-la no caso de else. Em outras palavras, eles não exigem na revisão esse código:

// pedantic rule #3
if ( command == Eat )
{
    eat();
}
else
{
    if ( command == Sleep )
    {
        sleep();
    }
    else
    {
        if ( command == Drink )
        {
            drink();
        }
        else
        {
            complain_about_unknown_command();
        }
    }
}

Em vez disso, se o virem, podem até sugerir que seja escrito assim:

// not fully conforming to rule #3
if ( command == Eat )
{
    eat();
}
else if ( command == Sleep )
{
    sleep();
}
else if ( command == Drink )
{
    drink();
}
else
{
   complain_about_unknown_command();
}

Isso é tecnicamente uma violação dessa regra, pois não há colchetes entre elsee if. Essa dualidade da regra aparece quando tentar aplicá-la automaticamente à base de código com uma ferramenta irracional. Na verdade, por que argumentar, basta deixar uma ferramenta aplicar estilo automaticamente.

O segundo detalhe (que também costuma ser esquecido pelos defensores dessa regra) é que os erros que podem ocorrer nunca são apenas por causa de violações à regra # 3. Na verdade, esses quase sempre envolvem violações da regra 1 também (com a qual ninguém discute). Novamente do ponto de vista das ferramentas automáticas, não é difícil criar uma ferramenta que reclama imediatamente quando a regra 1 é violada e, portanto, a maioria dos erros pode ser detectada em tempo hábil.

O terceiro detalhe (que muitas vezes é esquecido pelos oponentes dessa regra) é a natureza confusa da declaração vazia que é representada por ponto e vírgula único. A maioria dos desenvolvedores com alguma experiência ficou confusa, mais cedo ou mais tarde, com o ponto e vírgula no lugar errado ou com a declaração vazia que foi escrita usando o ponto e vírgula único. Duas chaves em vez de ponto e vírgula são visualmente mais fáceis de detectar.


1

Eu tenho que admitir que nem sempre use {}para linha única, mas é uma boa prática.

  • Digamos que você escreva um código sem colchetes parecido com este:

    for (int i = 0; i <100; ++ i) for (int j = 0; j <100; ++ j) DoSingleStuff ();

E depois de algum tempo, você deseja adicionar joutras informações ao loop, basta fazer isso por alinhamento e esquecer de adicionar colchetes.

  • A distribuição de memória é mais rápida. Vamos dizer que você tem um grande escopo e cria grandes matrizes dentro (sem newelas estão na pilha). Essas matrizes são removidas da memória logo após você sair do escopo. Mas é possível que você use essa matriz em um único local, que ficará na pilha por um tempo e será algum tipo de lixo. Como uma pilha possui tamanho limitado e muito pequeno, é possível exceder o tamanho da pilha. Portanto, em alguns casos, é melhor escrever {}para evitar isso. OBSERVAÇÃO: não é para uma linha, mas para tais situações:

    if (...) {// SomeStuff ... {// não temos if, while, etc. // SomeOtherStuff} // SomeMoreStuff}

  • A terceira maneira de usá-lo é semelhante à segunda. Apenas não para tornar a pilha mais limpa, mas para abrir algumas funções. Se você usa mutexfunções longas, geralmente é melhor bloquear e desbloquear imediatamente antes de acessar os dados e logo após terminar de ler / escrever isso. OBSERVAÇÃO desta maneira está sendo usado se você possuir um pouco classou structcom constructore destructorpara bloquear a memória.

  • O que é mais:

    se (...) se (...) SomeStuff (); else SomeOtherStuff (); // vai para o segundo if, mas o alligment mostra que está no primeiro ...

No geral, não posso dizer, qual é a melhor maneira de sempre usar {}para uma única linha, mas não é nada ruim fazer isso.

EDIÇÃO IMPORTANTE Se você escrever colchetes de código de compilação para uma única linha, não fará nada, mas se o seu código for interpretado, ele diminui a velocidade do código muito, muito levemente. Muito levemente.


1

Existem várias maneiras possíveis de escrever instruções de controle; certas combinações delas podem coexistir sem prejudicar a legibilidade, mas outras combinações causarão problemas. O estilo

if (condition)
  statement;

coexistirá confortavelmente com algumas das outras maneiras de escrever declarações de controle, mas não tão bem com outras. Se instruções controladas por várias linhas forem escritas como:

if (condition)
{
  statement;
  statement;
}

será visualmente óbvio quais ifinstruções controlam uma única linha e quais controlam várias linhas. Se, no entanto, ifinstruções de várias linhas forem escritas como:

if (condition) {
  statement;
  statement;
}

então a probabilidade de alguém tentar estender uma ifconstrução de instrução única sem adicionar os aparelhos necessários pode ser muito maior.

A instrução de declaração única na próxima linha iftambém pode ser problemática se a base de código fizer uso significativo do formulário

if (condition) statement;

Minha preferência é que ter a declaração em sua própria linha geralmente melhore a legibilidade, exceto nos casos em que houver muitas ifdeclarações com blocos de controle semelhantes, por exemplo

if (x1 > xmax) x1 = xmax;
if (x1 < xmin) x1 = xmin;
if (x2 > xmax) x2 = xmax;
if (x2 < xmin) x2 = xmin;
etc.

nesse caso, geralmente precederei e seguirei esses grupos de ifinstruções com uma linha em branco para separá-los visualmente de outro código. Ter uma série de instruções que começam com ifo mesmo recuo fornecerá uma indicação visual clara de que há algo incomum.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.