Qual é o benefício de terminar se ... else se for construído com uma cláusula else?

136

Nossa organização possui uma regra de codificação necessária (sem qualquer explicação) que:

if… else se construções devem ser finalizadas com uma cláusula else

Exemplo 1:

if ( x < 0 )
{
   x = 0;
} /* else not needed */

Exemplo 2:

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
}

Que case de borda é projetado para lidar com isso?

O que também me preocupa sobre o motivo é que o Exemplo 1 não precisa de um, elsemas o Exemplo 2 precisa. Se o motivo é a reutilização e a extensibilidade, acho que elsedeve ser usado nos dois casos.

Trevor
fonte
30
Talvez pergunte à sua empresa o motivo e o benefício. À primeira vista, força o programador a pensar sobre isso e adicionar um comentário "nenhuma ação é necessária". O mesmo raciocínio por trás (e pelo menos tão controverso quanto) das exceções verificadas do Java.
Thilo
32
Exemplo 2 é realmente um bom exemplo para onde um assert(false, "should never go here")pode fazer sentido
Thilo
2
Nossa organização tem regras semelhantes, mas não tão granulares. O objetivo em nosso caso é duplo. Primeiro, consistência do código. Segundo, sem cordas soltas / legibilidade. Exigir o else, mesmo quando desnecessário, adiciona clareza ao código, mesmo quando não está documentado. Exigir uma outra coisa é algo que eu fiz no passado, mesmo quando não é necessário, apenas para garantir que sou responsável por todos os resultados possíveis no aplicativo.
LuvnJesus 28/01
4
Você sempre pode derrotar se com if (x < 0) { x = 0; } else { if (y < 0) { x = 3; }}. Ou você pode simplesmente seguir essas regras, muitas das quais são tolas, simplesmente porque você é obrigado.
Jim Balter
3
@Thilo Estou um pouco atrasado, mas ainda assim ninguém pegou o erro: não há indicação de que o outro nunca deva acontecer, apenas que ele não deve ter efeitos colaterais (o que parece normal ao fazer < 0verificações), para que a afirmação continue travar o programa no que provavelmente é o caso mais comum em que os valores estão dentro dos limites esperados.
Loduwijk

Respostas:

148

Como mencionado em outra resposta, isso é das diretrizes de codificação MISRA-C. O objetivo é a programação defensiva, um conceito que é frequentemente usado na programação de missão crítica.

Ou seja, todo mundo if - else ifdeve terminar com um else, e todo mundo switchdeve terminar com um default.

Há duas razões para isso:

  • Código de auto-documentação. Se você escreve um, elsemas o deixa vazio, significa: "Definitivamente, considerei o cenário em que nem ifnem else ifsão verdadeiros".

    Não escrever um elselá significa: "ou considerei o cenário em que nem ifnem else ifsomos verdadeiros, ou esqueci completamente de considerá-lo e há potencialmente um bug gordo aqui no meu código".

  • Pare o código descontrolado. No software de missão crítica, você precisa escrever programas robustos que respondem até pelo altamente improvável. Então você pode ver código como

    if (mybool == TRUE) 
    {
    } 
    else if (mybool == FALSE) 
    {
    }
    else
    {
      // handle error
    }
    

    Esse código será completamente estranho para programadores de PC e cientistas da computação, mas faz todo o sentido em software de missão crítica, porque identifica o caso em que o "mybool" foi corrompido, por qualquer motivo.

    Historicamente, você temeria a corrupção da memória RAM devido a EMI / ruído. Hoje isso não é grande coisa. Muito provavelmente, a corrupção de memória ocorre devido a erros em outras partes do código: ponteiros para locais errados, erros de array fora dos limites, excesso de pilha, código descontrolado etc.

    Portanto, na maioria das vezes, um código como esse volta para dar um tapa na cara quando você escreve bugs durante o estágio de implementação. Isso significa que também pode ser usado como uma técnica de depuração: o programa que você está escrevendo informa quando você escreve bugs.


EDITAR

Quanto ao porquê elsenão é necessário depois de cada um if:

Um if-elseou if-else if-elseabrange completamente todos os valores possíveis que uma variável pode ter. Mas uma ifdeclaração simples não está necessariamente lá para cobrir todos os valores possíveis, ela tem um uso muito mais amplo. Na maioria das vezes você só deseja verificar uma determinada condição e, se não for atendida, não faça nada. Simplesmente não faz sentido escrever uma programação defensiva para cobrir o elsecaso.

Além disso, ele iria desordenar completamente o código se você escrevesse um vazio elsedepois de cada um if.

MISRA-C: 2012 15.7 não explica por que elsenão é necessário, apenas afirma:

Nota: uma elsedeclaração final não é necessária para uma if declaração simples .

Lundin
fonte
6
Se a memória estiver corrompida, espero que destrua muito mais do que apenas mybool, incluindo talvez o próprio código de verificação. Por que você não adiciona outro bloco que verifica se você está if/else if/elsecompilado com o que você espera? E mais uma para verificar o verificador anterior também?
Oleg V. Volkov 28/01
49
Suspiro. Olha pessoal, se você não tem absolutamente nenhuma outra experiência além da programação de desktop, não há necessidade de fazer comentários sobre tudo o que obviamente você não tem experiência. Isso sempre acontece quando a programação defensiva é discutida e um programador de PC para. Eu adicionei o comentário "Este código será completamente estranho aos programadores de PC" por um motivo. Você não programa software crítico de segurança para execução em computadores desktop baseados em RAM . Período. O código em si residirá na ROM flash com somas de verificação ECC e / ou CRC.
precisa saber é
6
@Duplicador De fato (a menos que mybooltenha um tipo não-booleano, como era o caso antes de C ter o seu próprio bool; então o compilador não faria a suposição sem análise estática adicional). E sobre o assunto 'Se você escreve um outro, mas o deixa vazio, significa: "Definitivamente, considerei o cenário em que nem se nem mais se são verdadeiros".': Minha primeira reação é presumir que o programador esqueceu de inserir código. o bloco else, caso contrário, por que um bloco else vazio está parado lá? Um // unusedcomentário seria apropriado, não apenas um bloco vazio.
JAB
5
@JAB Sim, o elsebloco precisa conter algum tipo de comentário se não houver código. Prática comum para vazio elseé um único ponto e vírgula, mais um comentário: else { ; // doesn't matter }. Como não há razão para que alguém simplesmente digite um ponto e vírgula único recuado em uma linha própria. Prática semelhante às vezes é usado em loops vazias: while(something) { ; // do nothing }. (código com quebras de linha, obviamente tão comentários não permitem-los.)
Lundin
5
Eu gostaria de nota, que este código de exemplo com dois fundos de investimento e outra pessoa pode ir para outro lugar, mesmo em software de desktop normal em ambientes multi-threaded, pelo que o valor de mybool poderia ser mudado apenas entre
Ilya Bursov
61

Sua empresa seguiu as orientações de codificação MISRA. Existem algumas versões dessas diretrizes que contêm essa regra, mas de MISRA-C: 2004 :

Regra 14.10 (exigida): Todas as construções if / else devem terminar com uma cláusula else.

Esta regra se aplica sempre que uma instrução if for seguida por uma ou mais instruções if; o final mais ifdeve ser seguido por uma else declaração. No caso de uma ifdeclaração simples , a else declaração não precisa ser incluída. O requisito para uma else declaração final é uma programação defensiva. A elsedeclaração deve tomar as medidas apropriadas ou conter um comentário adequado sobre o motivo pelo qual nenhuma ação foi tomada. Isso é consistente com o requisito de ter uma defaultcláusula final em uma switchdeclaração. Por exemplo, este código é uma instrução if simples:

if ( x < 0 )
{
 log_error(3);
 x = 0;
} /* else not needed */

ao passo que o código seguinte demonstra um if, else ifconstruo

if ( x < 0 )
{
 log_error(3);
 x = 0;
}
else if ( y < 0 )
{
 x = 3;
}
else /* this else clause is required, even if the */
{ /* programmer expects this will never be reached */
 /* no change in value of x */
}

No MISRA-C: 2012 , que substitui a versão de 2004 e é a recomendação atual para novos projetos, a mesma regra existe, mas é numerada em 15.7 .

Exemplo 1: em um programador de instrução if if pode precisar verificar n número de condições e executar uma única operação.

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}

Em um uso regular, executar uma operação não é necessário o tempo todo quando ifé usado.

Exemplo 2: Aqui o programador verifica n número de condições e executa várias operações. No uso regular, if..else ifé como se switchvocê precisasse executar uma operação como padrão. Portanto, o uso elseé necessário conforme o padrão misra

if(condition_1 || condition_2 || ... condition_n)
{
   //operation_1
}
else if(condition_1 || condition_2 || ... condition_n)
{
  //operation_2
}
....
else
{
   //default cause
}

As versões atuais e anteriores dessas publicações estão disponíveis para compra na loja virtual da MISRA ( via ).

C incorporado
fonte
1
Obrigado, sua resposta é todo o conteúdo da regra de Misra, mas espero uma resposta para minha confusão em editar parte da pergunta.
Trevor
2
Boa resposta. Por curiosidade, esses guias dizem algo sobre o que fazer se você espera que a elsecláusula seja inacessível? (Deixar fora da condição final em vez disso, lançar um erro, talvez?)
jpmc26
17
Vou votar por plágio e links que violam direitos autorais. Vou editar o post para que fique mais claro quais são as suas palavras e as de MISRA. Inicialmente, essa resposta não passava de uma cópia / pasta bruta.
Lundin 28/01
8
@TrieuTheVan: As perguntas não devem mover alvos . Verifique se a sua pergunta está completa antes de publicá-la.
TJ Crowder
1
@ jpmc26 Não, mas o objetivo do MISRA não é fornecer um código rígido, é um código seguro. Hoje em dia, qualquer compilador otimizará o código inacessível, então não há desvantagem.
Graham
19

Isso equivale a exigir um caso padrão em todos os comutadores.

Esse item adicional diminuirá a cobertura do código do seu programa.


Na minha experiência com a portabilidade do kernel Linux, ou código Android para plataformas diferentes, muitas vezes fazemos algo errado e, no logcat, vemos algum erro como

if ( x < 0 )
{
    x = 0;
}
else if ( y < 0 )
{
    x = 3;
}
else    /* this else clause is required, even if the */
{       /* programmer expects this will never be reached */
        /* no change in value of x */
        printk(" \n [function or module name]: this should never happen \n");

        /* It is always good to mention function/module name with the 
           logs. If you end up with "this should never happen" message
           and the same message is used in many places in the software
           it will be hard to track/debug.
        */
}
Jeegar Patel
fonte
2
Isto é onde __FILE__e __LINE__macros são um útil para fazer o local de origem fácil de encontrar se a mensagem é sempre impresso.
Peter Cordes
9

Apenas uma breve explicação, desde que fiz isso há cerca de 5 anos.

Não existe (na maioria dos idiomas) nenhum requisito sintático para incluir a elsedeclaração "nula" (e desnecessária {..}), e em "pequenos programas simples" não há necessidade. Porém, programadores reais não escrevem "pequenos programas simples" e, igualmente importante, não escrevem programas que serão usados ​​uma vez e depois descartados.

Quando alguém escreve um if / else:

if(something)
  doSomething;
else
  doSomethingElse;

tudo parece simples e dificilmente se vê o ponto de acrescentar {..}.

Mas, algum dia, daqui a alguns meses, algum outro programador (você nunca cometeria esse erro!) Precisará "aprimorar" o programa e incluirá uma declaração.

if(something)
  doSomething;
else
  doSomethingIForgot;
  doSomethingElse;

De repente, doSomethingElsemeio que esquece que deveria estar na elseperna.

Então você é um bom programador e sempre usa {..}. Mas você escreve:

if(something) {
  if(anotherThing) {
    doSomething;
  }
}

Está tudo bem até que o novo garoto faça uma modificação à meia-noite:

if(something) {
  if(!notMyThing) {
  if(anotherThing) {
    doSomething;
  }
  else {
    dontDoAnything;  // Because it's not my thing.
  }}
}

Sim, está formatado incorretamente, mas metade do código do projeto e o "formatador automático" ficam embolados em todas as #ifdefinstruções. E, é claro, o código real é muito mais complicado que este exemplo de brinquedo.

Infelizmente (ou não), estou sem esse tipo de coisa há alguns anos, então não tenho um exemplo "real" novo em mente - o acima é (obviamente) artificial e um pouco obsceno.

Hot Licks
fonte
7

Isso é feito para tornar o código mais legível, para referências posteriores e para esclarecer, para um revisor posterior, que os casos restantes tratados pelo último elsesão casos de nada fazer , para que não sejam negligenciados à primeira vista.

Essa é uma boa prática de programação, que torna o código reutilizável e extensível .

Ahmed Akhtar
fonte
6

Gostaria de acrescentar - e parcialmente contradizer - as respostas anteriores. Embora seja certamente comum usar if-else se de uma maneira semelhante a uma opção que cubra toda a gama de valores pensáveis ​​para uma expressão, não é de forma alguma garantido que qualquer faixa de condições possíveis seja totalmente coberta. O mesmo pode ser dito sobre a própria construção do comutador, daí o requisito de usar uma cláusula padrão, que captura todos os valores restantes e pode, se não for necessário de outra forma, ser usado como uma salvaguarda de asserção.

A pergunta em si apresenta um bom contra-exemplo: a segunda condição não se relaciona com x (de modo que é a razão pela qual eu prefiro a variante baseada em if mais flexível do que a variante baseada em switch). A partir do exemplo, é óbvio que, se a condição A for atendida, x deve ser definido com um determinado valor. Se A não for atendido, a condição B será testada. Se for atendido, x deve receber outro valor. Se nem A nem B forem atendidos, x deve permanecer inalterado.

Aqui podemos ver que um ramo else vazio deve ser usado para comentar a intenção do programador para o leitor.

Por outro lado, não vejo por que deve haver uma cláusula else especialmente para a declaração if mais recente e mais interna. Em C, não existe um 'else if'. Existe apenas se e mais. Em vez disso, de acordo com MISRA, o construto deve ser formalmente recuado dessa maneira (e eu deveria ter colocado as chaves de abertura em suas próprias linhas, mas não gosto disso):

if (A) {
    // do something
}
else {
    if (B) {
        // do something else (no pun intended)
    }
    else {
        // don't do anything here
    }
}

Quando MISRA pede para colocar chaves em todos os ramos, ele se contradiz mencionando "se ... senão se constrói".

Qualquer um pode imaginar a feiúra das árvores profundamente aninhadas, veja aqui em uma nota lateral . Agora imagine que essa construção possa ser arbitrariamente estendida em qualquer lugar. Então, pedir uma cláusula else no final, mas não em nenhum outro lugar, torna-se absurdo.

if (A) {
    if (B) {
        // do something
    }
    // you could to something here
}
else {
    // or here
    if (B) { // or C?
        // do something else (no pun intended)
    }
    else {
        // don't do anything here, if you don't want to
    }
    // what if I wanted to do something here? I need brackets for that.
}

Portanto, tenho certeza de que as pessoas que desenvolveram as diretrizes da MISRA tinham em mente a intenção de alternar entre outros fatores.

No final, cabe a eles definir com precisão o que se entende por um "se ... outro se constrói"

Ingo Schalk-Schupp
fonte
5

O motivo básico é provavelmente a cobertura do código e o mais implícito: como o código se comportará se a condição não for verdadeira? Para testes genuínos, você precisa de alguma maneira de verificar se testou com a condição false. Se todos os casos de teste que você passar pela cláusula if, seu código poderá ter problemas no mundo real devido a uma condição que você não testou.

No entanto, algumas condições podem ser apropriadamente como o Exemplo 1, como em uma declaração de imposto: "Se o resultado for menor que 0, insira 0." Você ainda precisa fazer um teste em que a condição é falsa.

TheBick
fonte
5

Logicamente, qualquer teste implica duas ramificações. O que você faz se for verdade e o que você faz se for falso.

Nos casos em que um dos ramos não tem funcionalidade, é razoável adicionar um comentário sobre o motivo pelo qual não precisa ter funcionalidade.

Isso pode ser benéfico para o próximo programador de manutenção. Eles não devem ter que procurar muito longe para decidir se o código está correto. Você pode meio que caçar o elefante .

Pessoalmente, isso me ajuda, pois me força a olhar para o caso else e avaliá-lo. Pode ser uma condição impossível, caso em que posso lançar uma exceção quando o contrato for violado. Pode ser benigno, caso em que um comentário pode ser suficiente.

Sua milhagem pode variar.

EvilTeach
fonte
4

Na maioria das vezes, quando você só tem uma única ifdeclaração, é provavelmente um dos motivos, como:

  • Verificações de proteção de função
  • Opção de inicialização
  • Filial de processamento opcional

Exemplo

void print (char * text)
{
    if (text == null) return; // guard check

    printf(text);
}

Mas quando você faz if .. else ifisso, é provavelmente um dos motivos, como:

  • Caixa de comutação dinâmica
  • Forquilha de processamento
  • Manipulando um Parâmetro de Processamento

E se o seu if .. else ifabrange todas as possibilidades, nesse caso o seu último if (...)não é necessário, você pode simplesmente removê-lo, porque nesse momento os únicos valores possíveis são os cobertos por essa condição.

Exemplo

int absolute_value (int n)
{
    if (n == 0)
    {
        return 0;
    }
    else if (n > 0)
    {
        return n;
    }
    else /* if (n < 0) */ // redundant check
    {
        return (n * (-1));
    }
}

E, na maioria desses motivos, é possível que algo não se encaixe em nenhuma das categorias da sua empresa if .. else if; portanto, é necessário tratá-las em uma elsecláusula final . ..etc.

Exemplo

#DEFINE SQRT_TWO   1.41421356237309504880
#DEFINE SQRT_THREE 1.73205080756887729352
#DEFINE SQRT_FIVE  2.23606797749978969641

double square_root (int n)
{
         if (n  > 5)   return sqrt((double)n);
    else if (n == 5)   return SQRT_FIVE;
    else if (n == 4)   return 2.0;
    else if (n == 3)   return SQRT_THREE;
    else if (n == 2)   return SQRT_TWO;
    else if (n == 1)   return 1.0;
    else if (n == 0)   return 0.0;
    else               return sqrt(-1); // error handling
}

Esta elsecláusula final é bastante semelhante a poucas outras coisas em idiomas como Javae C++, como:

  • default caso em uma instrução switch
  • catch(...)que vem depois de todos os catchblocos específicos
  • finally em uma cláusula try-catch
Khaled.K
fonte
2

Nosso software não era de missão crítica, mas também decidimos usar essa regra por causa da programação defensiva. Adicionamos uma exceção de lançamento ao código teoricamente inacessível (switch + if-else). E isso nos salvou muitas vezes, pois o software falhou rapidamente, por exemplo, quando um novo tipo foi adicionado e esquecemos de mudar um ou dois se-outro ou mudar. Como bônus, ficou super fácil encontrar o problema.

holdfenytolvaj
fonte
2

Bem, meu exemplo envolve comportamento indefinido, mas às vezes algumas pessoas tentam ser extravagantes e falham bastante, dê uma olhada:

int a = 0;
bool b = true;
uint8_t* bPtr = (uint8_t*)&b;
*bPtr = 0xCC;
if(b == true)
{
    a += 3;
}
else if(b == false)
{
    a += 5;
}
else
{
    exit(3);
}

Você provavelmente nunca esperaria ter o boolque não é truenem false, no entanto, pode acontecer. Pessoalmente, acredito que esse seja um problema causado por uma pessoa que decide fazer algo sofisticado, mas elsedeclarações adicionais podem impedir outros problemas.

ST3
fonte
1

Atualmente, estou trabalhando com PHP. Criando um formulário de registro e um formulário de login. Estou apenas usando puramente se e mais. Não há mais se ou qualquer coisa que seja desnecessária.

Se o usuário clicar no botão enviar -> ele passará para a próxima instrução if ... se o nome de usuário for menor que a quantidade de caracteres 'X' e alertá-lo. Se for bem-sucedido, verifique o tamanho da senha e assim por diante.

Não há necessidade de código extra, como outro, se isso puder dispensar a confiabilidade do tempo de carregamento do servidor para verificar todo o código extra.

Porixify
fonte