Quel est l'avantage de terminer si… else if construit avec une clause else?


136

Notre organisation a une règle de codage obligatoire (sans aucune explication) qui:

if… else if les constructions doivent être terminées par une clause else

Exemple 1:

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

Exemple 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 */
}

Quel cas de bord est-ce conçu pour gérer?

Ce qui m'inquiète également à propos de la raison, c'est que l' exemple 1 n'a pas besoin d'un, elsemais que l' exemple 2 en a besoin . Si la raison est la réutilisation et l'extensibilité, je pense qu'il elsedevrait être utilisé dans les deux cas.


30
Demandez peut-être à votre entreprise la raison et les avantages. À première vue, cela oblige le programmeur à y réfléchir et à ajouter un commentaire "aucune action requise". Même raisonnement derrière (et au moins aussi controversé que) les exceptions vérifiées de Java.
Thilo

32
L'exemple 2 est en fait un bon exemple où un assert(false, "should never go here")pourrait avoir du sens
Thilo

2
Notre organisation a des règles similaires mais pas tout à fait aussi granulaires. Le but dans notre cas est double. Tout d'abord, la cohérence du code. Deuxièmement, pas de chaînes lâches / lisibilité. Exiger l'autre, même si cela n'est pas nécessaire, ajoute de la clarté au code même lorsqu'il n'est pas documenté. Exiger un autre est quelque chose que j'ai fait dans le passé, même lorsque cela n'était pas nécessaire, juste pour m'assurer d'avoir pris en compte tous les résultats possibles dans l'application.
LuvnJesus

4
Vous pouvez toujours vaincre si avec if (x < 0) { x = 0; } else { if (y < 0) { x = 3; }}. Ou vous pouvez simplement suivre de telles règles, dont beaucoup sont stupides, simplement parce que vous y êtes obligé.
Jim Balter

3
@Thilo Je suis un peu en retard, mais personne n'a encore attrapé l'erreur: rien n'indique que l'autre ne devrait jamais arriver, seulement qu'il ne devrait pas avoir d'effets secondaires (ce qui semble normal lors des < 0vérifications), donc cette affirmation va pour planter le programme dans ce qui est probablement le cas le plus courant où les valeurs sont dans les limites attendues.
Loduwijk

Réponses:


148

Comme mentionné dans une autre réponse, cela provient des directives de codage MISRA-C. Le but est la programmation défensive, un concept souvent utilisé dans la programmation critique.

Autrement dit, tout if - else ifdoit se terminer par un else, et tout switchdoit se terminer par un default.

Il y a deux raisons à cela:

  • Code auto-documenté. Si vous écrivez un elsemais que vous le laissez vide, cela signifie: "J'ai définitivement considéré le scénario quand ni ifni ne else ifsont vrais".

    Ne pas écrire un elselà signifie: "soit j'ai considéré le scénario où ni ifni else ifn'est vrai, soit j'ai complètement oublié de le considérer et il y a potentiellement un gros bug ici dans mon code".

  • Arrêtez le code emballé. Dans les logiciels critiques, vous devez écrire des programmes robustes qui tiennent compte même des improbables. Ainsi, vous pouvez voir du code comme

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

    Ce code sera complètement étranger aux programmeurs de PC et aux informaticiens, mais il est parfaitement logique dans les logiciels critiques, car il détecte le cas où le "mybool" a été corrompu, pour une raison quelconque.

    Historiquement, vous craindriez la corruption de la mémoire RAM à cause des EMI / bruit. Ce n'est pas vraiment un problème aujourd'hui. Beaucoup plus probable, la corruption de la mémoire se produit en raison de bogues ailleurs dans le code: pointeurs vers de mauvais emplacements, bogues de tableau hors limites, débordement de pile, code emballé, etc.

    Donc la plupart du temps, un code comme celui-ci revient pour vous gifler lorsque vous avez écrit des bogues lors de la phase d'implémentation. Cela signifie qu'il pourrait également être utilisé comme technique de débogage: le programme que vous écrivez vous avertit lorsque vous avez écrit des bogues.


ÉDITER

En ce qui concerne pourquoi elsen'est pas nécessaire après chaque simple if:

Un if-elseou if-else if-elsecouvre complètement toutes les valeurs possibles qu'une variable peut avoir. Mais une ifdéclaration simple n'est pas nécessairement là pour couvrir toutes les valeurs possibles, elle a un usage beaucoup plus large. Le plus souvent, vous souhaitez simplement vérifier une certaine condition et si elle n'est pas remplie, ne faites rien. Ensuite, il n'est tout simplement pas significatif d'écrire une programmation défensive pour couvrir le elsecas.

De plus, cela encombrerait complètement le code si vous écriviez un vide elseaprès chaque if.

MISRA-C: 2012 15.7 ne donne aucune raison pour laquelle ce elsen'est pas nécessaire, il déclare simplement:

Remarque: une elsedéclaration finale n'est pas requise pour une if déclaration simple .


6
Si la mémoire est corrompue, je m'attends à ce qu'elle détruit beaucoup plus que mybool, y compris peut-être le code de vérification lui-même. Pourquoi n'ajoutez-vous pas un autre bloc qui vérifie que vous avez if/else if/elsecompilé ce que vous attendez? Et puis un autre pour vérifier le vérificateur précédent également?
Oleg V. Volkov

49
Soupir. Écoutez les gars, si vous n'avez absolument aucune autre expérience que la programmation de bureau, il n'est pas nécessaire de faire des commentaires tout savoir sur quelque chose dont vous n'avez évidemment aucune expérience. Cela se produit toujours lorsque la programmation défensive est discutée et qu'un programmeur PC s'arrête. J'ai ajouté le commentaire "Ce code sera complètement étranger aux programmeurs PC" pour une raison. Vous ne programmez pas de logiciel critique pour la sécurité pour qu'il s'exécute sur des ordinateurs de bureau basés sur la RAM . Période. Le code lui-même résidera dans une mémoire flash ROM avec des sommes de contrôle ECC et / ou CRC.
Lundin

6
@Deduplicator En effet (à moins qu'il mybooln'ait un type non booléen, comme c'était le cas avant que C obtienne le sien bool; alors le compilateur ne ferait pas l'hypothèse sans une analyse statique supplémentaire). Et au sujet de 'Si vous écrivez un autre mais laissez-le vide, cela signifie: "J'ai définitivement considéré le scénario quand ni si ni else si sont vrais".': Ma première réaction est de supposer que le programmeur a oublié de mettre du code dans le bloc else, sinon pourquoi avoir un bloc else vide juste assis là? Un // unusedcommentaire serait approprié, pas seulement un bloc vide.
JAB

5
@JAB Oui, le elsebloc doit contenir une sorte de commentaire s'il n'y a pas de code. La pratique courante pour vide elseest un point - virgule , plus un commentaire: else { ; // doesn't matter }. Comme il n'y a aucune raison pour que quelqu'un d'autre taperait simplement un point-virgule unique en retrait sur une ligne qui lui est propre. Pratique similaire est parfois utilisé dans les boucles vides: while(something) { ; // do nothing }. (code avec sauts de ligne, évidemment. SO les commentaires ne les autorisent pas)
Lundin

5
Je voudrais noter que cet exemple de code avec deux IF et else peut aller à autre, même dans les logiciels de bureau habituels dans les environnements multi-threads, donc la valeur de mybool pourrait être modifiée juste entre les deux
Iłya Bursov

61

Votre entreprise a suivi les directives de codage MISRA. Il existe quelques versions de ces directives qui contiennent cette règle, mais d'après MISRA-C: 2004 :

Règle 14.10 (obligatoire): Toutes les constructions if… else if doivent être terminées par une clause else.

Cette règle s'applique chaque fois qu'une instruction if est suivie d'une ou plusieurs instructions if; le dernier autre ifsera suivi d'une else déclaration. Dans le cas d'une simple ifdéclaration, la else déclaration n'a pas besoin d'être incluse. L'exigence d'une else déclaration finale est une programmation défensive. La elsedéclaration doit soit prendre les mesures appropriées, soit contenir un commentaire approprié expliquant pourquoi aucune mesure n'est prise. Cela est conforme à l'exigence d'avoir une defaultclause finale dans une switchdéclaration. Par exemple, ce code est une simple instruction if:

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

alors que le code suivant illustre une if, else ifconstruction

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 */
}

Dans MISRA-C: 2012 , qui remplace la version 2004 et est la recommandation actuelle pour les nouveaux projets, la même règle existe mais porte le numéro 15.7 .

Exemple 1: dans une seule instruction if, le programmeur peut avoir besoin de vérifier n nombre de conditions et d'effectuer une seule opération.

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

Dans une utilisation régulière, l'exécution d'une opération n'est pas nécessaire tout le temps lorsqu'elle ifest utilisée.

Exemple 2: Ici, le programmeur vérifie n nombre de conditions et effectue plusieurs opérations. Dans l' usage régulier if..else ifest comme switchvous devrez peut - être effectuer une opération comme par défaut. Donc, l'utilisation elseest nécessaire selon la norme misra

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

Les versions actuelles et passées de ces publications sont disponibles à l'achat via la boutique en ligne MISRA ( via ).


1
Merci, votre réponse est tout le contenu de la règle Misra, mais j'attends une réponse pour ma confusion en éditant une partie de la question.
Trevor

2
Bonne réponse. Par curiosité, ces guides vous disent-ils quoi faire si vous vous attendez à ce que la elseclause ne soit pas accessible? (Oubliez la condition finale à la place, lancez une erreur, peut-être?)
jpmc26

17
Je vais voter pour le plagiat et les liens qui violent le droit d'auteur. Je vais éditer le message pour qu'il devienne plus clair quels sont vos mots et quels sont les mots de MISRA. Au départ, cette réponse n'était rien d'autre qu'un simple copier / coller.
Lundin le

8
@TrieuTheVan: Les questions ne doivent pas être des cibles mobiles . Assurez-vous que votre question est complète avant de la publier.
TJ Crowder

1
@ jpmc26 Non, mais le but de MISRA n'est pas de vous donner un code serré, c'est de vous donner un code sûr. Tout compilateur de nos jours optimisera de toute façon le code inaccessible, il n'y a donc pas d'inconvénient.
Graham

19

C'est l'équivalent d'exiger un cas par défaut dans chaque commutateur.

Ce supplément réduira la couverture du code de votre programme.


Dans mon expérience avec le portage du noyau Linux ou du code Android sur différentes plates-formes, nous faisons souvent quelque chose de mal et dans logcat, nous voyons une erreur comme

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.
        */
}

2
C'est là __FILE__que les __LINE__macros sont utiles pour faciliter la recherche de l'emplacement source si le message est imprimé.
Peter Cordes

9

Juste une brève explication, puisque j'ai fait tout cela il y a environ 5 ans.

Il n'y a (avec la plupart des langages) aucune exigence syntaxique d'inclure une elsedéclaration «nulle» (et inutile {..}), et dans les «petits programmes simples», ce n'est pas nécessaire. Mais les vrais programmeurs n'écrivent pas de "petits programmes simples" et, tout aussi important, ils n'écrivent pas de programmes qui seront utilisés une fois et ensuite rejetés.

Quand on écrit un if / else:

if(something)
  doSomething;
else
  doSomethingElse;

tout semble simple et on ne voit même pas l'intérêt d'ajouter {..}.

Mais un jour, dans quelques mois, un autre programmeur (vous ne feriez jamais une telle erreur!) Devra "améliorer" le programme et ajoutera une déclaration.

if(something)
  doSomething;
else
  doSomethingIForgot;
  doSomethingElse;

Soudain, doSomethingElseoublie un peu que c'est censé être dans la elsejambe.

Vous êtes donc un bon petit programmeur et vous utilisez toujours {..}. Mais vous écrivez:

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

Tout va bien jusqu'à ce que ce nouvel enfant fasse une modification à minuit:

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

Oui, il est mal formaté, mais la moitié du code du projet l'est aussi, et le "formateur automatique" est bollixé par toutes les #ifdefinstructions. Et, bien sûr, le vrai code est beaucoup plus compliqué que cet exemple de jouet.

Malheureusement (ou pas), je suis hors de ce genre de choses depuis quelques années maintenant, donc je n'ai pas un nouvel exemple "réel" en tête - ce qui précède est (évidemment) artificiel et un peu hokey.


7

Ceci est fait pour rendre le code plus lisible, pour les références ultérieures et à préciser, à un avis plus tard, que les autres cas traités par le dernier else, sont ne font rien des cas, de sorte qu'ils ne soient pas négligés en quelque sorte à première vue.

Il s'agit d'une bonne pratique de programmation, qui rend le code réutilisable et extensible .


6

Je voudrais ajouter - et contredire en partie - les réponses précédentes. Bien qu'il soit certainement courant d'utiliser if-else if d'une manière de type interrupteur qui devrait couvrir l'ensemble des valeurs imaginables d'une expression, il n'est en aucun cas garanti que toute plage de conditions possibles soit entièrement couverte. La même chose peut être dite à propos de la construction du commutateur elle-même, d'où l'exigence d'utiliser une clause par défaut, qui capture toutes les valeurs restantes et peut, si ce n'est pas nécessaire de toute façon, être utilisée comme sauvegarde d'assertion.

La question elle-même présente un bon contre-exemple: la deuxième condition ne concerne pas du tout x (c'est la raison pour laquelle je préfère souvent la variante basée sur if plus flexible à la variante basée sur le commutateur). D'après l'exemple, il est évident que si la condition A est remplie, x doit être mis à une certaine valeur. Si A n'est pas satisfait, la condition B est testée. S'il est satisfait, alors x devrait recevoir une autre valeur. Si ni A ni B ne sont satisfaits, alors x doit rester inchangé.

Ici, nous pouvons voir qu'une branche else vide doit être utilisée pour commenter l'intention du programmeur pour le lecteur.

D'un autre côté, je ne vois pas pourquoi il doit y avoir une clause else, en particulier pour la dernière et la plus profonde déclaration if. En C, il n'y a pas de «sinon si». Il n'y a que si et autre. Au lieu de cela, selon MISRA, la construction devrait formellement être indentée de cette façon (et j'aurais dû mettre les accolades d'ouverture sur leurs propres lignes, mais je n'aime pas ça):

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

Quand MISRA demande de mettre des accolades autour de chaque branche, alors il se contredit en mentionnant "if ... else if constructs".

N'importe qui peut imaginer la laideur des arbres if else profondément imbriqués, voir ici sur une note latérale . Imaginez maintenant que cette construction puisse être étendue arbitrairement n'importe où. Ensuite, demander une clause else à la fin, mais pas ailleurs, devient absurde.

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.
}

Je suis donc sûr que les personnes qui ont développé les lignes directrices MISRA avaient l'intention de passer si-sinon si à l'esprit.

En fin de compte, il leur revient de définir précisément ce que l'on entend par "if ... else if construct"


5

La raison fondamentale est probablement la couverture du code et le else implicite: comment le code se comportera-t-il si la condition n'est pas vraie? Pour des tests authentiques, vous avez besoin d'un moyen de voir que vous avez testé avec la condition false. Si chaque scénario de test que vous avez passe par la clause if, votre code peut avoir des problèmes dans le monde réel en raison d'une condition que vous n'avez pas testée.

Cependant, certaines conditions peuvent ressembler à l'exemple 1, comme sur une déclaration de revenus: «Si le résultat est inférieur à 0, entrez 0». Vous devez toujours avoir un test où la condition est fausse.


5

Logiquement, tout test implique deux branches. Que faites-vous si c'est vrai et que faites-vous si c'est faux.

Pour les cas où l'une ou l'autre des branches n'a aucune fonctionnalité, il est raisonnable d'ajouter un commentaire expliquant pourquoi elle n'a pas besoin de fonctionnalités.

Cela peut être avantageux pour le prochain programmeur de maintenance à venir. Ils ne devraient pas avoir à chercher trop loin pour décider si le code est correct. Vous pouvez en quelque sorte Prehunt the Elephant .

Personnellement, cela m'aide car cela m'oblige à regarder l'autre cas et à l'évaluer. Cela peut être une condition impossible, auquel cas je peux lever une exception car le contrat est violé. Cela peut être bénin, auquel cas un commentaire peut suffire.

Votre kilométrage peut varier.


4

La plupart du temps, lorsque vous n'avez qu'une seule ifdéclaration, c'est probablement l'une des raisons telles que:

  • Vérifications des fonctions de garde
  • Option d'initialisation
  • Branche de traitement facultative

Exemple

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

    printf(text);
}

Mais quand vous le faites if .. else if, c'est probablement l'une des raisons telles que:

  • Boîtier de commutation dynamique
  • Fourche de traitement
  • Gestion d'un paramètre de traitement

Et si votre if .. else ifcouverture couvre toutes les possibilités, dans ce cas votre dernière if (...)n'est pas nécessaire, vous pouvez simplement la supprimer, car à ce stade, les seules valeurs possibles sont celles couvertes par cette condition.

Exemple

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));
    }
}

Et dans la plupart de ces raisons, il est possible que quelque chose ne rentre dans aucune des catégories de votre if .. else if, donc la nécessité de les gérer dans une elseclause finale , le traitement peut être effectué via une procédure au niveau de l'entreprise, une notification utilisateur, un mécanisme d'erreur interne, ..etc.

Exemple

#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
}

Cette dernière elseclause est assez similaire à peu d'autres choses dans des langages tels que Javaet C++, tels que:

  • default case dans une instruction switch
  • catch(...)qui vient après tous les catchblocs spécifiques
  • finally dans une clause try-catch

2

Notre logiciel n'était pas essentiel à la mission, mais nous avons également décidé d'utiliser cette règle en raison d'une programmation défensive. Nous avons ajouté une exception throw au code théoriquement inaccessible (switch + if-else). Et cela nous a sauvé de nombreuses fois car le logiciel a échoué rapidement, par exemple lorsqu'un nouveau type a été ajouté et que nous avons oublié de changer un ou deux if-else ou de changer. En prime, il a été très facile de trouver le problème.


2

Eh bien, mon exemple implique un comportement non défini, mais parfois certaines personnes essaient d'être fantaisistes et échouent dur, jetez un coup d'œil:

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);
}

Vous ne vous attendriez probablement jamais à avoir boolce qui n'est pas non trueplus false, mais cela peut arriver. Personnellement, je pense que c'est un problème causé par une personne qui décide de faire quelque chose de sophistiqué, mais une elsedéclaration supplémentaire peut empêcher d'autres problèmes.


1

Je travaille actuellement avec PHP. Création d'un formulaire d'inscription et d'un formulaire de connexion. Je n'utilise que si et autrement. Pas d'autre si ou quoi que ce soit qui est inutile.

Si l'utilisateur clique sur le bouton Soumettre -> il passe à l'instruction if suivante ... si le nom d'utilisateur est inférieur au nombre de caractères «X», alerte. En cas de succès, vérifiez la longueur du mot de passe et ainsi de suite.

Pas besoin de code supplémentaire tel qu'un autre si cela pourrait compromettre la fiabilité du temps de chargement du serveur pour vérifier tout le code supplémentaire.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.