web-dev-qa-db-fra.com

Le développeur insiste si les instructions ne doivent pas avoir de conditions négatives et doivent toujours avoir un bloc else

J'ai une connaissance, un développeur plus aguerri que moi. Nous parlions de pratiques de programmation et j'ai été surpris par son approche sur les déclarations "si". Il insiste sur certaines pratiques concernant les déclarations que je trouve plutôt étranges.

Premièrement, une instruction if doit être suivie d'une instruction else, qu'il y ait quelque chose à y mettre ou non. Ce qui conduit à un code ressemblant à ceci:

if(condition) 
{
    doStuff();
    return whatever;
}
else
{
}

Deuxièmement, il est préférable de tester les valeurs vraies plutôt que fausses. Cela signifie qu'il est préférable de tester une variable "doorClosed" au lieu d'une variable "! DoorOpened"

Son argument est qu'il rend plus clair ce que fait le code.

Ce qui m'embrouille un peu, car une combinaison de ces deux règles peut l'amener à écrire ce type de code s'il veut faire quelque chose lorsque la condition n'est pas remplie.

if(condition)
{
}
else
{
    doStuff();
    return whatever;
}

Mon sentiment à ce sujet est qu'il est en effet très moche et/ou que l'amélioration de la qualité, s'il y en a, est négligeable. Mais en tant que junior, je suis enclin à douter de mon instinct.

Mes questions sont donc les suivantes: Est-ce une bonne/mauvaise/pratique "peu importe"? Est-ce une pratique courante?

175
Patsuan

Bloc else explicite

La première règle ne fait que polluer le code et le rend ni plus lisible, ni moins sujet aux erreurs. Le but de votre collègue - je suppose - est d'être explicite, en montrant que le développeur était parfaitement conscient que la condition peut être évaluée à false. Bien que ce soit une bonne chose d'être explicite, une telle explicitation ne devrait pas coûter trois lignes de code supplémentaires .

Je ne mentionne même pas le fait qu'une instruction if n'est pas nécessairement suivie par un else ou rien: elle pourrait être suivie par un ou plusieurs Elifs aussi.

La présence de l'instruction return aggrave les choses. Même si vous aviez réellement du code à exécuter dans le bloc else, il serait plus lisible de le faire comme ceci:

if (something)
{
    doStuff();
    return whatever;
}

doOtherThings();
return somethingElse;

Cela rend le code prendre deux lignes de moins et désindente le bloc else. clauses de garde sont tout à ce sujet.

Notez cependant que la technique de votre collègue pourrait résoudre partiellement un modèle très désagréable de blocs conditionnels empilés sans espaces:

if (something)
{
}
if (other)
{
}
else
{
}

Dans le code précédent, l'absence d'un saut de ligne sain après le premier bloc if rend très facile une mauvaise interprétation du code. Cependant, alors que la règle de votre collègue rendrait plus difficile la lecture erronée du code, une solution plus simple serait simplement d'ajouter une nouvelle ligne.

Testez pour true, pas pour false

La deuxième règle pourrait avoir un certain sens, mais pas dans sa forme actuelle.

Il n'est pas faux que le test d'une porte fermée soit plus intuitif que le test d'une porte non ouverte . Les négations, et en particulier les négations imbriquées, sont généralement difficiles à comprendre:

if (!this.IsMaster || (!this.Ready && !this.CanWrite))

Pour résoudre cela, au lieu d'ajouter des blocs vides, créez des propriétés supplémentaires, le cas échéant, ou des variables locales .

La condition ci-dessus pourrait être rendue lisible assez facilement:

if (this.IsSlave || (this.Synchronizing && this.ReadOnly))
189
Arseni Mourzenko

Concernant la première règle, c'est un exemple de frappe inutile. Non seulement il faut plus de temps pour taper, mais cela causera une énorme confusion à quiconque lira le code. Si le code n'est pas nécessaire, ne l'écrivez pas. Cela irait même jusqu'à ne pas avoir un else rempli dans votre cas, car le code revient du bloc if:

if(condition) 
{
    doStuff();
    return whatever;
}

doSomethingElse(); // no else needed
return somethingelse;

Concernant le deuxième point, il est bon d'éviter les noms booléens qui contiennent un négatif:

bool doorNotOpen = false; // a double negative is hard to reason
bool doorClosed = false; // this is much clearer

Cependant, étendre cela pour ne pas tester à nouveau un négatif, comme vous le faites remarquer, conduit à une saisie plus inutile. Ce qui suit est beaucoup plus clair que d'avoir un bloc if vide:

if(!condition)
{
    doStuff();
    return whatever;
}
62
David Arno

1. Un argument en faveur des instructions else vides.

J'utilise souvent (et plaide pour) quelque chose qui ressemble à cette première construction, un autre vide. Il signale aux lecteurs du code (à la fois des outils d'analyse humains et automatisés) que le programmeur a réfléchi à la situation. else déclarations manquantes qui auraient dû être présentes ont tué des personnes, accidenté des véhicules et coûté des millions de dollars. MISRA-C, par exemple, exige au moins un commentaire indiquant que le reste manquant est intentionnel dans une séquence if (condition_1) {do_this;} else if (condition_2) {do_that;} ... else if (condition_n) {do_something_else;}. D'autres normes de fiabilité élevées vont encore plus loin: à quelques exceptions près, les instructions manquantes else sont interdites.

Une exception est un simple commentaire, quelque chose comme /* Else not required */. Cela signale la même intention que les trois lignes vides ailleurs. Une autre exception où ce vide n'est pas nécessaire est celui où il est évident pour les lecteurs du code et pour les outils d'analyse automatisés que ce vide est superflu. Par exemple, if (condition) { do_stuff; return; } De même, un autre élément vide n'est pas nécessaire dans le cas de throw something Ou goto some_label1 au lieu de return.

2. Un argument pour préférer if (condition) à if (!condition).

Il s'agit d'un élément lié aux facteurs humains. La logique booléenne complexe déclenche de nombreuses personnes. Même un programmeur chevronné devra penser à if (!(complex || (boolean && condition))) do_that; else do_this;. Au minimum, réécrivez ceci en tant que if (complex || (boolean && condition)) do_this; else do_that;.

3. Cela ne signifie pas que l'on devrait préférer les instructions then vides.

La deuxième section dit "préfère" plutôt que "tu dois". C'est une ligne directrice plutôt qu'une règle. La raison pour laquelle cette directive préfère des conditions if positives est que le code doit être clair et évident. Une clause then vide (par exemple, if (condition) ; else do_something;) viole cela. Il s'agit d'une programmation obscurcie, obligeant même les programmeurs les plus aguerris à sauvegarder et relire la condition if dans sa forme annulée. Donc, écrivez-le sous la forme négative en premier lieu et omettez l'instruction else (ou ayez un autre vide ou un commentaire à cet effet si vous êtes mandaté pour le faire).



1J'ai écrit que les clauses se terminant par return, throw ou goto ne nécessitent pas d'autre vide. Il est évident que la clause else n'est pas nécessaire. Mais qu'en est-il de goto? Soit dit en passant, les règles de programmation critiques pour la sécurité interdisent parfois le retour anticipé et interdisent presque toujours de lever des exceptions. Ils autorisent cependant goto sous une forme restreinte (par exemple, goto cleanup1;). Cette utilisation restreinte de goto est la pratique préférée à certains endroits. Le noyau Linux, par exemple, regorge de telles instructions goto.

45
David Hammen

J'utilise une branche else vide (et parfois une branche if vide) dans de très rares cas: lorsqu'il est évident que la partie if et else doivent être gérées d'une manière ou d'une autre, mais pour une raison non triviale, le cas peut être traité par Ne rien faire. Et par conséquent, quiconque lit le code avec l'action else nécessaire soupçonne immédiatement qu'il manque quelque chose et perd son temps.

if (condition) {
    // No action needed because ...
} else {
    do_else_action()
}

if (condition) {
    do_if_action()
} else {
    // No action needed because ...
}

Mais non:

if (condition) {
    do_if_action()
} else {
    // I was told that an if always should have an else ...
}
31
gnasher729

Toutes choses étant égales par ailleurs, préférez la brièveté.

Ce que vous n'écrivez pas, personne n'a à lire et à comprendre.

Bien qu'être explicite puisse être utile, ce n'est le cas que si cela rend évident sans verbosité excessive que ce que vous avez écrit est vraiment ce que vous vouliez écrire.


Ainsi, évitez les branches vides, elles sont non seulement inutiles mais aussi peu communes et donc source de confusion.

Évitez également d'écrire une branche else si vous quittez directement la branche if.

Une application utile d'explicitness serait de mettre un commentaire chaque fois que vous tombez dans les commutateurs // FALLTHRU, Et d'utiliser un commentaire ou un bloc vide où vous avez besoin d'une instruction vide for(a;b;c) /**/;.

22
Deduplicator

Il n'y a pas de règle stricte concernant les conditions positives ou négatives pour une déclaration IF, pas à ma connaissance. Personnellement, je préfère coder pour un cas positif plutôt que négatif, le cas échéant. Je ne le ferai certainement pas si cela me conduit à faire un bloc IF vide, suivi d'un ELSE plein de logique. Si une telle situation se produisait, il faudrait de toute façon 3 secondes pour le refactoriser de nouveau pour tester un cas positif.

Ce que je n'aime vraiment pas dans vos exemples, c'est l'espace vertical complètement inutile occupé par le ELSE vierge. Il n'y a tout simplement aucune raison de le faire. Cela n'ajoute rien à la logique, cela n'aide pas à documenter ce que fait le code et n'augmente pas du tout la lisibilité. En fait, je dirais que l'espace vertical ajouté pourrait éventuellement diminution lisibilité.

6
Langecrew

Bloc else explicite

Je ne suis pas d'accord avec cela en tant qu'instruction générale couvrant toutes les instructions if mais il arrive parfois que l'ajout d'un bloc else par habitude soit une bonne chose.

À mon avis, une instruction if couvre en fait deux fonctions distinctes.

Si nous sommes censés faire quelque chose, faites-le ici.

Des trucs comme ça ont évidemment pas besoin d'une partie else.

    if (customer.hasCataracts()) {
        appointmentSuggestions.add(new CataractAppointment(customer));
    }
    if (customer.isDiabetic()) {
        customer.assignNurse(DiabeticNurses.pickBestFor(customer));
    }

et dans certains cas, insister sur l'ajout d'un else peut induire en erreur.

    if (k > n) {
        return BigInteger.ZERO;
    }
    if (k <= 0 || k == n) {
        return BigInteger.ONE;
    }

est pas identique à

    if (k > n) {
        return BigInteger.ZERO;
    } else {
        if (k <= 0 || k == n) {
            return BigInteger.ONE;
        }
    }

même s'il est fonctionnellement le même. L'écriture du premier if avec un else vide peut vous conduire au deuxième résultat qui est inutilement laid.

Si nous recherchons un état spécifique, c'est souvent une bonne idée d'ajouter un else vide juste pour vous rappeler de couvrir cette éventualité

        // Count wins/losses.
        if (doors[firstChoice] == Prize.Car) {
            // We would have won without switching!
            winWhenNotSwitched += 1;
        } else {
            // We win if we switched to the car!
            if (doors[secondChoice] == Prize.Car) {
                // We picked right!
                winWhenSwitched += 1;
            } else {
                // Bad choice.
                lost += 1;
            }
        }

N'oubliez pas que ces règles s'appliquent niquement lorsque vous écrivez un nouveau code. IMHO Les clauses else vides doivent être supprimées avant l'archivage.


Testez pour true, pas pour false

Encore une fois, ce sont de bons conseils à un niveau général, mais dans de nombreux cas, cela rend le code inutilement complexe et moins lisible.

Même si du code comme

    if(!customer.canBuyAlcohol()) {
        // ...
    }

est choquant pour le lecteur, mais

    if(customer.canBuyAlcohol()) {
        // Do nothing.
    } else {
        // ...
    }

est au moins aussi mauvais, sinon pire.

J'ai codé dans BCPL il y a de nombreuses années et dans ce langage il y a une clause IFet une clause UNLESS afin que vous puissiez coder beaucoup plus lisiblement comme:

    unless(customer.canBuyAlcohol()) {
        // ...
    }

ce qui est nettement mieux, mais toujours pas parfait.


Mon processus personnel

Généralement, lorsque j'écris du code nouvea, j'ajoute souvent un bloc else vide à une instruction if juste pour me rappeler que je n'ai pas encore couvert cette éventualité. Cela m'aide à éviter le piège DFS et garantit que lorsque je passe en revue le code, je remarque qu'il y a plus à faire. Cependant, j'ajoute généralement un commentaire TODO pour garder une trace.

  if (returnVal == JFileChooser.APPROVE_OPTION) {
    handleFileChosen();
  } else {
    // TODO: Handle case where they pressed Cancel.
  }

Je trouve que généralement j'utilise else rarement dans mon code car cela peut souvent indiquer une odeur de code.

5
OldCurmudgeon

Pour le premier point, j'ai utilisé un langage qui obligeait les instructions IF à être utilisées de cette façon (dans Opal, le langage derrière un grattoir d'écran mainframe pour mettre une interface graphique GUI sur les systèmes mainframe), et avec une seule ligne pour l'IF et le reste. Ce n'était pas une expérience agréable!

Je m'attendrais à ce que n'importe quel compilateur optimise ces clauses ELSE supplémentaires. Mais pour le code en direct, il n'ajoute rien (en développement, il peut être un marqueur utile pour un code supplémentaire).

Une fois que j'utilise quelque chose comme ces clauses supplémentaires, c'est lorsque j'utilise le traitement de type CASE/WHEN. J'ajoute toujours une clause par défaut même si elle est vide. C'est une habitude à long terme des langues qui commet une erreur si une telle clause n'est pas utilisée, et oblige à se demander si les choses devraient vraiment passer.

Il y a longtemps, le mainframe (par exemple, PL/1 et COBOL) pratiquait que les contrôles négatifs étaient moins efficaces. Cela pourrait expliquer le 2e point, bien que de nos jours il y ait des économies d'efficacité massivement plus importantes qui sont ignorées en tant que micro optimisations.

La logique négative a tendance à être moins lisible, mais pas tellement sur une instruction IF aussi simple.

1
Kickstart

J'appuie la plupart des réponses selon lesquelles les blocs else vides sont pratiquement toujours un gaspillage nocif d'encre électronique. N'ajoutez pas ceux-ci sauf si vous avez une très bonne raison de le faire, auquel cas le bloc vide ne devrait pas être vide du tout, il devrait contenir un commentaire expliquant pourquoi il est là.

Le problème d'éviter les négatifs mérite cependant plus d'attention: en général, chaque fois que vous devez utiliser une valeur booléenne, vous avez besoin de certains blocs de code pour fonctionner lorsqu'il est défini, et de certains autres blocs pour fonctionner lorsqu'il n'est pas défini. Ainsi, si vous appliquez une règle non négative, vous appliquez soit des instructions if() {} else {...} (avec un bloc if vide!), Soit vous créez un deuxième booléen pour chaque booléen qui contient son valeur négative. Les deux options sont mauvaises, car elles déroutent vos lecteurs.

Une politique utile est la suivante: n'utilisez jamais une forme niée dans le nom d'un booléen et exprimez la négation comme un seul !. Une déclaration comme if(!doorLocked) est parfaitement claire, une déclaration comme if(!doorUnlocked) noeud cerveaux. Le dernier type d'expression est la chose que vous devez éviter à tout prix, pas la présence d'un seul ! Dans une condition if().

... une instruction if doit être suivie d'une instruction else, qu'il y ait quelque chose à y mettre ou non.

Je ne suis pas d'accord que if (a) { } else { b(); } devrait être réécrit en if (!a) { b(); } et if (a) { b(); } else { } devrait être réécrit en if (a) { b(); }.

Cependant, il convient de souligner que j'ai rarement une branche vide. C'est parce que normalement je me connecte que je suis allé dans la branche autrement vide. De cette façon, je peux développer uniquement des messages de journal. J'utilise rarement un débogueur. Dans les environnements de production, vous n'avez pas de débogueur; c'est agréable de pouvoir résoudre les problèmes de production avec les mêmes outils que ceux que vous utilisez pour développer.

Deuxièmement, il est préférable de tester les valeurs vraies plutôt que fausses. Cela signifie qu'il est préférable de tester une variable "doorClosed" au lieu d'une variable "! DoorOpened"

J'ai des sentiments mitigés à ce sujet. Un inconvénient d'avoir doorClosed et doorOpened est qu'il double potentiellement le nombre de mots/termes dont vous devez être conscient. Un autre inconvénient est qu'avec le temps, la signification de doorClosed et doorOpened peut changer (d'autres développeurs viendront après vous) et vous pourriez vous retrouver avec deux propriétés qui ne sont plus des négations précises l'une de l'autre. Au lieu d'éviter les négations, j'apprécie d'adapter le langage du code (noms de classe, noms de variables, etc.) au vocabulaire des utilisateurs métier et aux exigences qui me sont données. Je ne voudrais pas inventer un tout nouveau terme pour éviter un ! si ce terme n'a de sens que pour un développeur que les utilisateurs professionnels ne comprendront pas. Je veux que les développeurs parlent le même langage que les utilisateurs et les personnes écrivant les exigences. Maintenant, si de nouveaux termes simplifient le modèle, c'est important, mais cela doit être traité avant la finalisation des exigences, pas après. Le développement doit gérer ce problème avant de commencer à coder.

J'ai tendance à douter de mon instinct.

C'est bon de continuer à se remettre en question.

Est-ce une bonne/mauvaise/pratique "sans importance"?

Dans une certaine mesure, cela n'a pas d'importance. Faites réviser votre code et adaptez-vous à votre équipe. C'est généralement la bonne chose à faire. Si tout le monde dans votre équipe veut coder d'une certaine manière, il est probablement préférable de le faire de cette façon (changer 1 personne - vous-même - prend moins de points d'histoire que changer un groupe de personnes).

Est-ce une pratique courante?

Je ne me souviens pas avoir vu une branche vide. Cependant, je vois des gens ajouter des propriétés pour éviter les négations.

0
Words Like Jared

Je dirais que c'est définitivement une mauvaise pratique. L'ajout d'instructions else va ajouter tout un tas de lignes inutiles à votre code qui ne font rien et le rendent encore plus difficile à lire.

0
ayrton clark

Il y a un autre modèle qui n'a pas encore été mentionné sur la gestion du deuxième cas qui a un bloc if vide. Une façon de le gérer serait de revenir dans le bloc if. Comme ça:

void foo()
{
    if (condition) return;

    doStuff();
    ...
}

Il s'agit d'un modèle courant de vérification des conditions d'erreur. Le cas affirmatif est toujours utilisé, et l'intérieur n'est plus vide, laissant les futurs programmeurs se demander si un no-op était intentionnel ou non. Il peut également améliorer la lisibilité, car vous pourriez être amené à créer une nouvelle fonction (divisant ainsi les grandes fonctions en fonctions individuelles plus petites).

0
Shaz

Je vais seulement aborder la deuxième partie de la question et cela vient de mon point de vue de travailler dans des systèmes industriels et de manipuler des appareils dans le monde réel.

Cependant, il s'agit en quelque sorte d'un commentaire étendu plutôt que d'une réponse.

Quand il est indiqué

Deuxièmement, il est préférable de tester les valeurs vraies plutôt que fausses. Cela signifie qu'il est préférable de tester une variable "doorClosed" au lieu d'une variable "! DoorOpened"

Son argument est qu'il rend plus clair ce que fait le code.

De mon point de vue, on suppose ici que l'état de la porte est un état binaire alors qu'en fait c'est au moins un état ternaire:

  1. La porte est ouverte.
  2. La porte n'est ni complètement ouverte ni complètement fermée.
  3. La porte est fermée.

Ainsi, selon l'endroit où se trouve un seul capteur numérique binaire, vous ne pouvez que supposer l'un des deux concepts suivants:

  1. Si le capteur est du côté ouvert de la porte: la porte est ouverte ou non ouverte
  2. Si le capteur est du côté fermé de la porte: La porte est fermée ou non fermée.

Et vous ne pouvez pas échanger ces concepts grâce à l'utilisation d'une négation binaire. Ainsi doorClosed et !doorOpened ne sont probablement pas synonymes et toute tentative de prétendre qu'ils sont synonymes est une pensée erronée qui suppose une meilleure connaissance de l'état du système qu'il n'en existe réellement.

En revenant à votre question, je soutiendrais un verbiage qui correspond à l'origine des informations que la variable représente. Ainsi, si les informations sont dérivées du côté fermé de la porte, allez avec doorClosed etc. Cela peut impliquer l'utilisation de doorClosed ou !doorClosed selon les besoins dans diverses déclarations selon les besoins, entraînant une confusion potentielle avec l'utilisation de la négation. Mais ce qu'il ne fait pas, c'est de propager implicitement des hypothèses sur l'état du système.


À des fins de discussion pour le travail que je fais, la quantité d'informations dont je dispose sur l'état du système dépend des fonctionnalités requises par l'ensemble du système lui-même.

Parfois, j'ai seulement besoin de savoir si la porte est fermée ou non. Dans ce cas, un seul capteur binaire suffira. Mais à d'autres moments, j'ai besoin de savoir que la porte est ouverte, fermée ou en transition. Dans de tels cas, il y aurait soit deux capteurs binaires (à chaque extrême du mouvement de la porte - avec vérification des erreurs par rapport à l'ouverture et à la fermeture simultanée de la porte) ou un capteur analogique mesurant le degré d'ouverture de la porte.

Un exemple du premier serait la porte d'un four à micro-ondes. Vous activez le fonctionnement du four en fonction de la fermeture ou non de la porte. Vous ne vous souciez pas de l'ouverture de la porte.

Un exemple du second serait un simple actionneur motorisé. Vous empêchez de faire avancer le moteur lorsque l'actionneur est complètement sorti. Et vous empêchez d'entraîner le moteur en marche arrière lorsque l'actionneur est à fond.

Fondamentalement, le nombre et le type de capteurs se résument à une analyse des coûts des capteurs par rapport à une analyse des exigences de ce qui est nécessaire pour atteindre la fonctionnalité requise.

0
Peter M

Il y a un point lorsque l'on considère l'argument "toujours avoir une clause else" que je n'ai vu dans aucune autre réponse: cela peut avoir du sens dans un style de programmation fonctionnel. Sorte de.

Dans un style de programmation fonctionnel, vous traitez des expressions plutôt que des instructions. Ainsi, chaque bloc de code a une valeur de retour - y compris un if-then-else expression. Cela empêcherait cependant un bloc else vide. Permettez-moi de vous donner un exemple:

var even = if (n % 2 == 0) {
  return "even";
} else {
  return "odd";
}

Maintenant, dans les langages avec un style C ou une syntaxe inspirée du style C (comme Java, C # et JavaScript, pour n'en nommer que quelques-uns), cela semble bizarre. Cependant, il semble beaucoup plus familier lorsqu'il est écrit comme tel:

var even = (n % 2 == 0) ? "even" : "odd";

Laisser la branche else vide ici entraînerait une valeur indéfinie - dans la plupart des cas, pas ce que nous voulons être un cas valide lors de la programmation des fonctionnalités. Même chose avec le laisser complètement. Cependant, lorsque vous programmez de manière itérative, je fixe très peu de raisons d'avoir toujours un bloc else.

0
blalasaadri