Sinon, les blocs augmentent-ils la complexité du code? [fermé]

18

Voici un exemple très simplifié . Ce n'est pas nécessairement une question spécifique à la langue, et je vous demande d'ignorer les nombreuses autres façons dont la fonction peut être écrite et les modifications qui peuvent y être apportées. . La couleur est d'un type unique

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    else
    {
        return "No you can't";
    }
}

Beaucoup de gens que j'ai rencontrés, ReSharper, et ce type (dont le commentaire m'a rappelé que je cherchais à poser cette question depuis un moment) recommanderaient de refactoriser le code pour supprimer le elsebloc en laissant ceci:

(Je ne me souviens pas de ce que la majorité a dit, je n'aurais peut-être pas demandé cela autrement)

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    return "No you can't";
}

Question: Y a - t-il une augmentation de la complexité introduite en n'incluant pas le elsebloc?

J'ai l'impression que les elseÉtats déclarent plus directement l'intention, en déclarant que le code dans les deux blocs est directement lié.

De plus, je trouve que je peux éviter des erreurs subtiles de logique, en particulier après des modifications de code à une date ultérieure.

Prenons cette variante de mon exemple simplifié (en ignorant le fait que l' oropérateur étant donné qu'il s'agit d'un exemple volontairement simplifié):

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

Quelqu'un peut désormais ajouter un nouveau ifbloc basé sur une condition après le premier exemple sans reconnaître immédiatement correctement que la première condition place une contrainte sur sa propre condition.

Si un elsebloc était présent, celui qui a ajouté la nouvelle condition serait obligé d'aller déplacer le contenu du elsebloc (et si d'une manière ou d'une autre ils le masquent, l'heuristique montrera que le code est inaccessible, ce qui n'est pas le cas dans le cas où l'un en ifcontraint un autre) .

Bien sûr, il y a d'autres façons de définir l'exemple spécifique de toute façon, qui empêchent toutes cette situation, mais ce n'est qu'un exemple.

La longueur de l'exemple que j'ai donné peut fausser l'aspect visuel de cela, alors supposez que l'espace occupé par les crochets est relativement insignifiant pour le reste de la méthode.

J'ai oublié de mentionner un cas dans lequel je suis d'accord avec l'omission d'un bloc else, et c'est lorsque j'utilise un ifbloc pour appliquer une contrainte qui doit être logiquement satisfaite pour tout le code suivant, comme un contrôle nul (ou tout autre garde) .

Selali Adobor
la source
En réalité, quand il y a une déclaration "si" avec un "retour", elle peut être considérée comme un "bloc de garde" dans> 50% de tous les cas. Je pense donc que votre idée fausse est ici qu'un "bloc de garde" est le cas exceptionnel, et votre exemple le cas normal.
Doc Brown
2
@AssortedTrailmix: Je suis d'accord qu'un cas comme celui-ci peut être plus lisible lors de l'écriture de la elseclause (à mon goût, il sera encore plus lisible en omettant les parenthèses inutiles. Mais je pense que l'exemple n'est pas bien choisi, car dans le cas ci-dessus, j'écrirais return sky.Color == Color.Blue(sans if / else). Un exemple avec un type de retour différent de bool rendrait probablement cela plus clair.
Doc Brown
1
comme indiqué dans les commentaires ci-dessus, l'exemple suggéré est trop simplifié, invitant à des réponses spéculatives. Quant à la partie de la question qui commence par "Quelqu'un peut maintenant ajouter un nouveau bloc if ..." , elle a été posée et répondue plusieurs fois auparavant, voir par exemple Approches pour vérifier plusieurs conditions? et de multiples questions qui y sont liées.
gnat
1
Je ne vois pas ce que les blocs ont d'autre à voir avec le principe DRY. DRY a plus à voir avec l'abstraction et les références au code couramment utilisé sous la forme de fonctions, de méthodes et d'objets que ce que vous demandez. Votre question est liée à la lisibilité du code et éventuellement aux conventions ou idiomes.
Shashank Gupta
2
Voter pour rouvrir. Les bonnes réponses à cette question ne sont pas particulièrement fondées sur l'opinion. Si l'exemple donné dans une question est inadéquat, l'améliorer en le modifiant serait la chose raisonnable à faire, ne pas voter pour fermer pour une raison qui n'est pas réellement vraie.
Michael Shaw

Réponses:

27

À mon avis, le bloc else explicite est préférable. Quand je vois ça:

if (sky.Color != Blue) {
   ...
}  else {
   ...
}

Je sais que j'ai affaire à des options mutuellement exclusives. Je n'ai pas besoin de lire ce qui se trouve à l'intérieur des blocs if pour pouvoir le dire.

Quand je vois ça:

if (sky.Color != Blue) {
   ...
} 
return false;

Il semble, à première vue, qu'il retourne faux et a un effet secondaire facultatif, le ciel n'est pas bleu.

Selon moi, la structure du deuxième code ne reflète pas ce que fait réellement le code. C'est mauvais. Choisissez plutôt la première option qui reflète la structure logique. Je ne veux pas avoir à vérifier la logique de retour / pause / continuer dans chaque bloc pour connaître le flux logique.

Pourquoi quelqu'un préférerait l'autre est un mystère pour moi, j'espère que quelqu'un prendra la position opposée m'éclairera avec sa réponse.

J'ai oublié de mentionner un cas dans lequel je suis d'accord avec l'omission d'un bloc else, et c'est lorsque j'utilise un bloc if pour appliquer une contrainte qui doit être logiquement satisfaite pour tout le code suivant, comme une vérification nulle (ou tout autre garde ).

Je dirais que les conditions de garde sont bonnes dans le cas où vous avez quitté le chemin heureux. Une erreur ou un échec s'est produit qui empêche l'exécution ordinaire de continuer. Lorsque cela se produit, vous devez lever une exception, renvoyer un code d'erreur ou tout ce qui convient à votre langue.

Peu de temps après la version originale de cette réponse, un code comme celui-ci a été découvert dans mon projet:

Foobar merge(Foobar left, Foobar right) {
   if(!left.isEmpty()) {
      if(!right.isEmpty()) {
          Foobar merged = // construct merged Foobar
          // missing return merged statement
      }
      return left;
   }
   return right;
}

En ne mettant pas le retour à l'intérieur d'elses, le fait qu'une déclaration de retour manquait a été ignoré. S'il avait été utilisé autrement, le code n'aurait même pas été compilé. (Bien sûr, la plus grande préoccupation que j'ai est que les tests écrits sur ce code étaient assez mauvais pour ne pas détecter ce problème.)

Winston Ewert
la source
Cela résume mes réflexions à ce sujet, je suis content de ne pas être le seul à penser comme ça. Je travaille avec des programmeurs, des outils et des IDE préférant la suppression du elsebloc (cela peut être gênant lorsque je suis obligé de le faire juste pour rester en ligne avec les conventions d'une base de code). Moi aussi, je suis intéressé de voir le contre-point ici.
Selali Adobor
1
Je varie sur ce point. Parfois, l'explicite d'autre ne vous gagne pas beaucoup car il est peu probable qu'il déclenche les erreurs logiques subtiles que l'OP mentionne - c'est juste du bruit. Mais la plupart du temps, je suis d'accord, et parfois je ferai quelque chose comme } // elseoù les autres iraient normalement comme compromis entre les deux.
Telastyn
3
@Telastyn, mais qu'est-ce que vous gagnez à sauter le reste? Je suis d'accord que l'avantage est très faible dans de nombreuses situations, mais même pour le léger avantage, je pense que cela vaut la peine de le faire. Certes, il me semble bizarre de mettre quelque chose dans un commentaire alors que ça pourrait être du code.
Winston Ewert
2
Je pense que vous avez la même erreur que l'OP - "si" avec "retour" peut être principalement vu comme un bloc de garde, donc vous discutez ici du cas exceptionnel, tandis que le cas plus fréquent de blocs de garde est à mon humble avis plus lisible sans le "autre".
Doc Brown
... donc ce que vous avez écrit n'est pas faux, mais à mon humble avis ne vaut pas les nombreux votes positifs que vous avez obtenus.
Doc Brown
23

La principale raison de la suppression du elsebloc que j'ai trouvé est une indentation excessive. Le court-circuitage des elseblocs permet une structure de code beaucoup plus plate.

De nombreuses ifdéclarations sont essentiellement des gardes. Ils empêchent le déréférencement de pointeurs nuls et autres "ne faites pas ça!" les erreurs. Et ils conduisent à une résiliation rapide de la fonction actuelle. Par exemple:

if (obj === NULL) {
    return NULL;
}
// further processing that now can assume obj attributes are set

Maintenant, il peut sembler qu'une elseclause serait très raisonnable ici:

if (obj === NULL) {
    return NULL;
}
else if (obj.color != Blue) {
   // handle case that object is not blue
}

Et en effet, si c'est tout ce que vous faites, ce n'est pas beaucoup plus en retrait que l'exemple ci-dessus. Mais, c'est rarement tout ce que vous faites avec des structures de données réelles à plusieurs niveaux (une condition qui se produit tout le temps lors du traitement de formats courants comme JSON, HTML et XML). Donc, si vous souhaitez modifier le texte de tous les enfants d'un nœud HTML donné, dites:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
p = elements[0]
if ((p.children === NULL) or (p.children.length == 0)) {
    return NULL;
}
for (c in p.children) {
    c.text = c.text.toUpperCase();
}
return p;

Les gardes n'augmentent pas l'indentation du code. Avec une elseclause, tout le travail réel commence à se déplacer vers la droite:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
else {
    p = elements[0]
    if ((p.children === NULL) or (p.children.length == 0)) {
        return NULL;
    }
    else {
        for (c in p.children) {
            c.text = c.text.toUpperCase();
        }
        return p;
    }
}

Cela commence à avoir un mouvement vers la droite significatif, et c'est un exemple assez trivial, avec seulement deux conditions de garde. Chaque garde supplémentaire ajoute un autre niveau d'indentation. Le vrai code que j'ai écrit en traitant XML ou en consommant des flux JSON détaillés peut facilement empiler 3, 4 ou plusieurs conditions de garde. Si vous utilisez toujours le else, vous vous retrouvez 4, 5 ou 6 niveaux en retrait. Peut-être plus. Et cela contribue certainement à un sentiment de complexité du code et à plus de temps passé à comprendre ce qui correspond à quoi. Le style de sortie rapide, plat et rapide élimine une partie de cette «structure excessive» et semble plus simple.

Addendum Les commentaires sur cette réponse m'ont fait réaliser qu'il n'était peut-être pas clair que la manipulation NULL / vide n'est pas la seule raison des protections conditionnelles. Pas presque! Bien que cet exemple simple se concentre sur la gestion NULL / vide et ne contienne pas d'autres raisons, la recherche de structures profondes telles que XML et AST pour un contenu "pertinent", par exemple, comporte souvent une longue série de tests spécifiques pour éliminer les nœuds non pertinents et sans intérêt cas. Une série de tests "si ce nœud n'est pas pertinent, retour" provoquera le même type de dérive vers la droite que les gardes NULL / vides. Et dans la pratique, les tests de pertinence ultérieurs sont fréquemment associés à des gardes NULL / vides pour les structures de données correspondantes plus profondes recherchées - un double coup dur pour la dérive vers la droite.

Jonathan Eunice
la source
2
C'est une réponse détaillée et valide, mais je vais l'ajouter à la question (elle m'a échappé au début); Il y a une "exception" où je trouve toujours un elsebloc superflu, lorsque j'utilise un ifbloc pour appliquer une contrainte qui doit être logiquement satisfaite pour tout le code suivant, comme un contrôle nul (ou tout autre garde).
Selali Adobor
@AssortedTrailmix Je crois que si vous excluez les cas où vous pouvez régulièrement faire une sortie anticipée dans la thenclause (comme les déclarations de garde successives), alors il n'y a pas de valeur particulière à éviter la elseclause particulière . En effet, cela réduirait la clarté et pourrait être dangereux (en omettant de simplement énoncer la structure alternative qui est / devrait être là).
Jonathan Eunice
Je pense qu'il est raisonnable d'utiliser des conditions de garde pour éjecter une fois que vous avez quitté le chemin heureux. Cependant, je trouve également pour le cas du traitement XML / JSON que si vous validez d'abord l'entrée par rapport à un schéma, vous obtenez de meilleurs messages d'erreur et un code plus propre.
Winston Ewert
Ceci est une explication parfaite des cas où le reste est évité.
Chris Schiffhauer
2
J'aurais enveloppé l'API pour ne pas produire de NULL idiots à la place.
Winston Ewert
4

Quand je vois ça:

if(something){
    return lamp;
}
return table;

Je vois un idiome commun . Un schéma commun , qui dans ma tête se traduit par:

if(something){
    return lamp;
}
else return table;

Parce que c'est un idiome très courant en programmation, le programmeur qui a l'habitude de voir ce type de code a une «traduction» implicite dans sa tête chaque fois qu'il le voit, qui le «convertit» dans le bon sens.

Je n'ai pas besoin d'explicite else, ma tête «le met là» pour moi parce qu'il a reconnu le motif dans son ensemble . Je comprends la signification de l'ensemble du modèle commun, je n'ai donc pas besoin de petits détails pour le clarifier.

Par exemple, lisez le texte suivant:

entrez la description de l'image ici

Tu as lu ça?

J'aime paris au printemps

Si oui, vous vous trompez. Relisez le texte. Ce qui est réellement écrit est

J'adore Paris dans le le printemps

Il y a deux "les" mots dans le texte. Alors pourquoi avez-vous manqué l'un des deux?

C'est parce que votre cerveau a vu un schéma commun et l'a immédiatement traduit dans sa signification connue. Il n'avait pas besoin de lire la chose entière détail par détail pour comprendre le sens, car il reconnaissait déjà le motif en le voyant. C'est pourquoi il a ignoré le supplément "le ".

Même chose avec l'idiome ci-dessus. Les programmeurs qui ont lu beaucoup de code sont utilisés pour voir ce modèle , de if(something) {return x;} return y;. Ils n'ont pas besoin d'explications elsepour comprendre sa signification, car leur cerveau «le met là pour eux». Ils le reconnaissent comme un modèle avec une signification connue: "ce qui est après l'accolade fermante ne fonctionnera que comme implicite elsede la précédente if".

Donc, à mon avis, ce elsen'est pas nécessaire. Mais utilisez simplement ce qui semble plus lisible.

Aviv Cohn
la source
2

Ils ajoutent un encombrement visuel dans le code, donc oui, ils pourraient ajouter de la complexité.

Cependant, l'inverse est également vrai, un refactoring excessif pour réduire la longueur du code peut également ajouter de la complexité (pas dans ce cas simple bien sûr).

Je suis d'accord avec la déclaration selon laquelle le elsebloc énonce l'intention. Mais ma conclusion est différente, car vous ajoutez de la complexité dans votre code pour y parvenir.

Je ne suis pas d'accord avec votre affirmation selon laquelle cela vous permet d'éviter de subtiles erreurs de logique.

Voyons un exemple, maintenant il ne devrait retourner vrai que si le ciel est bleu et qu'il y a une humidité élevée, que l'humidité n'est pas élevée ou si le ciel est rouge. Mais alors, vous confondez une accolade et la placez dans le mauvais else:

bool CanLeaveWithoutUmbrella() {
    if(sky.Color == Color.Blue)
    {
        if (sky.Humidity == Humidity.High) 
        {
            return true;
        } 
    else if (sky.Humidity != Humidity.High)
    {
        return true;
    } else if (sky.Color == Color.Red) 
    {
            return true;
    }
    } else 
    {
       return false;
    }
}

Nous avons vu toutes ces erreurs stupides dans le code de production et cela peut être très difficile à détecter.

Je suggérerais ce qui suit:

Je trouve cela plus facile à lire, car je peux voir en un coup d'œil que la valeur par défaut est false.

De plus, l'idée de forcer le déplacement du contenu d'un bloc pour insérer une nouvelle clause est sujette à des erreurs. Cela se rapporte en quelque sorte au principe ouvert / fermé (le code doit être ouvert pour l'extension mais fermé pour la modification). Vous forcez à modifier le code existant (par exemple, le codeur peut introduire une erreur dans l'équilibrage des accolades).

Enfin, vous amenez les gens à répondre d'une manière prédéterminée que vous pensez être la bonne. Par exemple, vous présentez un exemple très simple mais après, vous déclarez que les gens ne devraient pas le prendre en considération. Cependant, la simplicité de l'exemple affecte toute la question:

  • Si le cas est aussi simple que celui présenté, alors ma réponse serait d'omettre toutes les if elseclauses.

    return (sky.Color == Color.Blue);

  • Si l'affaire est un peu plus compliquée, avec diverses clauses, je répondrais:

    bool CanLeaveWithoutUmbrella () {

    if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
        return true;
    } else if (sky.Humidity != Humidity.High) {
        return true;
    } else if (sky.Color == Color.Red) {
        return true;
    }
    
    return false;
    

    }

  • Si le cas est compliqué et que toutes les clauses ne retournent pas vrai (peut-être qu'elles renvoient un double), et que je veux introduire une commande de point d'arrêt ou de connexion, je considérerais quelque chose comme:

    double CanLeaveWithoutUmbrella() {
    
        double risk = 0.0;
    
        if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
            risk = 1.0;
        } else if (sky.Humidity != Humidity.High) {
            risk = 0.5;
        } else if (sky.Color == Color.Red) {
            risk = 0.8;
        }
    
        Log("value of risk:" + risk);
        return risk;
    

    }

  • Si nous ne parlons pas d'une méthode qui renvoie quelque chose, mais plutôt d'un bloc if-else qui définit une variable ou appelle une méthode, alors oui, j'inclurais une elseclause finale .

Par conséquent, la simplicité de l'exemple est également un facteur à considérer.

FranMowinckel
la source
J'ai l'impression que vous faites la même erreur que d'autres ont fait et que vous modifiez un peu trop l'exemple , examinant ainsi un nouveau problème, mais je dois prendre une seconde et examiner le processus pour arriver à votre exemple, donc pour l'instant, il semble valable pour moi. Et une remarque, ce n'est pas lié au principe ouvert / fermé . La portée est beaucoup trop étroite pour appliquer cet idiome. Mais il est intéressant de noter que son application à un niveau supérieur supprimerait tout cet aspect du problème (en supprimant la nécessité de toute modification de la fonction).
Selali Adobor
1
Mais si nous ne pouvons pas modifier cet exemple trop simplifié d'ajout de clauses, ni modifier la façon dont il est écrit, ni y apporter de changement, alors vous devriez considérer que cette question ne concerne que ces lignes de code précises et ce n'est plus une question générale. Dans ce cas, vous avez tout à fait raison, cela n'ajoute aucune complexité (ni ne la supprime) à la clause else car il n'y avait pas de complexité au départ. Cependant, vous n'êtes pas juste, car c'est vous qui suggérez que de nouveaux articles pourraient être ajoutés.
FranMowinckel
Et comme je le note, je ne dis pas que cela se rapporte au principe bien connu ouvert / fermé. Je pense que l'idée d'amener les gens à modifier votre code comme vous le suggérez est sujette à des erreurs. Je retire simplement cette idée de ce principe.
FranMowinckel
Attendez les modifications, vous êtes sur quelque chose de bien, je tape un commentaire en ce moment
Selali Adobor
1

Bien que le cas if-else décrit ait la plus grande complexité, dans la plupart des situations pratiques (mais pas toutes), cela n'a pas beaucoup d'importance.

Il y a des années, lorsque je travaillais sur un logiciel utilisé pour l'industrie aéronautique (il devait passer par un processus de certification), la vôtre était une question qui a été soulevée. Il s'est avéré qu'il y avait un coût financier à cette déclaration «else» car elle augmentait la complexité du code.

Voici pourquoi.

La présence de l '«autre» a créé un 3ème cas implicite qui a dû être évalué - celui de ni le «si» ni le «sinon» pris. Vous pensez peut-être (comme je l'étais à l'époque) WTF?

L'explication est allée comme ça ...

D'un point de vue logique, il n'y a que les deux options - le «si» et le «sinon». Cependant, ce que nous certifions était le fichier objet que le compilateur générait. Par conséquent, lorsqu'il y avait un «autre», une analyse devait être effectuée pour confirmer que le code de branchement généré était correct et qu'un mystérieux 3ème chemin n'apparaissait pas.

Comparez cela au cas où il n'y a qu'un «si» et pas un «autre». Dans ce cas, il n'y a que deux options - le chemin «si» et le chemin «non-si». Deux cas à évaluer est moins complexe que trois.

J'espère que cela t'aides.

Sparky
la source
dans un fichier objet, les deux cas ne seraient-ils pas rendus comme des jmps identiques?
Winston Ewert
@WinstonEwert - On pourrait s'attendre à ce qu'ils soient les mêmes. Cependant, ce qui est rendu et ce que l'on attend d'être rendu sont deux choses différentes, indépendamment de leur chevauchement.
Sparky
(Je suppose que SE a mangé mon commentaire ...) C'est une réponse très intéressante. Alors que je dirais que le cas qu'il expose est quelque peu niche, il montre que certains outils trouveraient une différence entre les deux (même la métrique de code la plus courante basée sur le flux que je vois, la complexité cyclomatique, ne mesurerait aucune différence.
Selali Adobor
1

Le but le plus important du code est d'être compréhensible comme engagé (par opposition à facilement refactorisé, ce qui est utile mais moins important). Un exemple Python légèrement plus complexe peut être utilisé pour illustrer ceci:

def value(key):
    if key == FROB:
        return FOO
    elif key == NIK:
        return BAR
    [...]
    else:
        return BAZ

C'est assez clair - c'est l'équivalent d'une recherche de casedéclaration ou de dictionnaire, la version de niveau supérieur de return foo == bar:

KEY_VALUES = {FROB: FOO, NIK: BAR, [...]}
DEFAULT_VALUE = BAZ

def value(key):
    return KEY_VALUES.get(key, default=DEFAULT_VALUE)

C'est plus clair, car même si le nombre de jetons est plus élevé (32 vs 24 pour le code d'origine avec deux paires clé / valeur), la sémantique de la fonction est désormais explicite: ce n'est qu'une recherche.

(Cela a des conséquences pour la refactorisation - si vous vouliez avoir un effet secondaire si key == NIK, vous avez trois choix:

  1. Revenez au style if/ elif/ elseet insérez une seule ligne dans le key == NIKboîtier.
  2. Enregistrez le résultat de la recherche dans une valeur et ajoutez une ifinstruction à valuepour le cas spécial avant de revenir.
  3. Mettez une ifdéclaration dans l' appelant.

Nous voyons maintenant pourquoi la fonction de recherche est une puissante simplification: elle fait de la troisième option la solution la plus simple, car en plus de générer une différence plus petite que la première, c'est la seule qui s'assure que valuecela ne fait qu'une chose. )

Pour en revenir au code OP, je pense que cela peut servir de guide pour la complexité des elseinstructions: le code avec une elseinstruction explicite est objectivement plus complexe, mais le plus important est de savoir s'il rend la sémantique du code claire et simple. Et il faut y répondre au cas par cas, car chaque morceau de code a une signification différente.

l0b0
la source
J'aime votre réécriture de table de consultation du boîtier de commutateur simple. Et je sais que c'est un idiome Python préféré. Dans la pratique, cependant, je trouve souvent que les conditions multi-voies (aka caseou switchdéclarations) sont moins homogènes. Plusieurs options ne sont que des retours de valeur simples, mais un ou deux cas nécessitent un traitement supplémentaire. Et lorsque vous découvrez que la stratégie de recherche ne vous convient pas, vous devez réécrire dans une syntaxe entièrement différente if elif... else
Jonathan Eunice
C'est pourquoi je pense que la recherche devrait généralement être une ligne ou une fonction, de sorte que la logique autour du résultat de la recherche soit séparée de la logique de la recherche en premier lieu.
l0b0
Cette question apporte une vue de plus haut niveau à la question qui doit certainement être mentionnée et gardée à l'esprit, mais je pense que suffisamment de contrainte a été placée sur le cas pour que vous puissiez vous sentir libre de développer votre propre position
Selali Adobor
1

Je pense que cela dépend du type de ifdéclaration que vous utilisez. Certaines ifinstructions peuvent être considérées comme des expressions et d'autres ne peuvent être considérées que comme des instructions de flux de contrôle.

Votre exemple me ressemble à une expression. Dans les langages de type C, l'opérateur ternaire ?:ressemble beaucoup à une ifinstruction, mais c'est une expression, et la partie else ne peut pas être omise. Il est logique qu'une branche else ne puisse pas être omise dans une expression, car l'expression doit toujours avoir une valeur.

En utilisant l'opérateur ternaire, votre exemple ressemblerait à ceci:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue)
               ? true
               : false;
}

Puisqu'il s'agit d'une expression booléenne, elle devrait vraiment être simplifiée jusqu'à

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

L'inclusion d'une clause else explicite rend votre intention plus claire. Les gardes peuvent avoir un sens dans certaines situations, mais en choisissant entre deux valeurs possibles, qui ne sont ni exceptionnelles ni inhabituelles, elles n'aident pas.

Michael Shaw
la source
+1, l'OP est à mon humble avis discuter d'un cas pathologique qui devrait être mieux écrit comme vous l'avez montré ci-dessus. Le cas beaucoup plus fréquent pour "si" avec "retour" est à mon avis le cas "garde".
Doc Brown
Je ne sais pas pourquoi cela continue de se produire, mais les gens continuent d'ignorer le tout premier paragraphe de la question. En fait, vous utilisez tous la ligne de code exacte que je dis explicitement de ne pas utiliser. I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor du
Et en relisant votre dernière question, vous semblez mal comprendre l'intention de la question.
Selali Adobor
Vous avez utilisé un exemple qui implorait pratiquement d'être simplifié. J'ai lu et fait attention à votre premier paragraphe, c'est pourquoi mon premier bloc de code est là, au lieu de passer directement à la meilleure façon de faire cet exemple particulier. Si vous pensez que je (ou quelqu'un d'autre) a mal compris l'intention de votre question, vous devriez peut-être la modifier pour que votre intention soit claire.
Michael Shaw
Je l'éditerais, mais je ne saurais pas comment le rendre plus clair puisque j'utilise la ligne de code exacte que vous faites et que je dis «ne fais pas ça». Il y a d'innombrables façons de réécrire le code et de supprimer la question, je ne peux pas les couvrir tous, pourtant tant de gens ont compris ma déclaration et l'ont respectée ...
Selali Adobor
1

Une autre chose à considérer dans cet argument est les habitudes promues par chaque approche.

  • if / else recadre un échantillon de codage en termes de branches. Comme vous le dites, parfois cette explication est bonne. Il y a vraiment deux chemins d'exécution possibles et nous voulons le souligner.

  • Dans d'autres cas, il n'y a qu'un seul chemin d'exécution et toutes ces choses exceptionnelles dont les programmeurs doivent s'inquiéter. Comme vous l'avez mentionné, les clauses de garde sont parfaites pour cela.

  • Il y a, bien sûr, le cas 3 ou plus (n), où nous encourageons généralement l'abandon de la chaîne if / else et l'utilisation d'une instruction case / switch ou d'un polymorphisme.

Le principe directeur que je garde à l'esprit ici est qu'une méthode ou une fonction ne devrait avoir qu'un seul objectif. Tout code avec une instruction if / else ou switch est uniquement destiné à la branche. Il doit donc être absurdement court (4 lignes) et ne déléguer qu'à la bonne méthode ou produire le résultat évident (comme votre exemple). Lorsque votre code est aussi court, il est difficile de rater ces multiples retours :) Tout comme "goto considéré comme nuisible", toute instruction de branchement peut être utilisée abusivement, donc mettre une pensée critique dans d'autres instructions en vaut généralement la peine. Comme vous l'avez mentionné, le simple fait d'avoir un bloc else permet de regrouper le code. Vous et votre équipe devrez décider de ce que vous en pensez.

Ce que l'outil se plaint vraiment, c'est le fait que vous avez un retour / pause / lancer à l'intérieur du bloc if. Donc, en ce qui le concerne, vous avez écrit une clause de garde mais vous l'avez ratée. Vous pouvez choisir de rendre l'outil heureux ou vous pouvez "divertir" sa suggestion sans l'accepter.

Steve Jackson
la source
Je n'ai jamais pensé au fait que l'outil pourrait envisager la clause if to be dès qu'il s'inscrit dans le modèle de l'un, c'est probablement le cas, et cela explique le raisonnement d'un "groupe de partisans" pour son absence
Selali Adobor
@AssortedTrailmix À droite, vous pensez elsesémantiquement, les outils d'analyse de code recherchent généralement des instructions étrangères, car ils mettent souvent en évidence une mauvaise compréhension du flux de contrôle. L' elseaprès un ifqui revient est des bits perdus. if(1 == 1)déclenche souvent le même type d'avertissement.
Steve Jackson
1

Je pense que la réponse est que cela dépend. Votre exemple de code (comme vous le signalez indirectement) renvoie simplement l'évaluation d'une condition et est mieux représenté, comme vous le savez évidemment, comme une expression unique.

Afin de déterminer si l'ajout d'une condition else clarifie ou masque le code, vous devez déterminer ce que représente IF / ELSE. Est-ce une expression (comme dans votre exemple)? Si c'est le cas, cette expression doit être extraite et utilisée. Est-ce une condition de garde? Si c'est le cas, l'ELSE est inutile et trompeur, car il fait apparaître les deux branches comme équivalentes. Est-ce un exemple de polymorphisme procédural? Extrayez-le. Établit-il des conditions préalables? Ensuite, cela peut être essentiel.

Avant de décider d'inclure ou d'éliminer l'ELSE, décidez ce qu'il représente, cela vous dira s'il doit être présent ou non.

jmoreno
la source
0

La réponse est un peu subjective. Un elsebloc peut faciliter la lisibilité en établissant qu'un bloc de code s'exécute exclusivement vers un autre bloc de code, sans avoir à lire les deux blocs. Cela peut être utile si, disons, les deux blocs sont relativement longs. Il y a des cas, cependant, où avoir ifs sans elses peut être plus lisible. Comparez le code suivant avec un certain nombre de if/elseblocs:

if(a == b) {
    if(c <= d) {
        if(e != f) {
            a.doSomeStuff();
            c.makeSomeThingsWith(b);
            f.undo(d, e);
            return 9;
        } else {
            e++;
            return 3;
        }
    } else {
        return 1;
    }
} else {
    return 0;
}

avec:

if(a != b) {
    return 0;
}

if(c > d) {
    return 1;
}

if(e != f) {
    e++;
    return 3;
}

a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;

Le deuxième bloc de code transforme les ifinstructions imbriquées en clauses de garde. Cela réduit l'indentation et rend certaines des conditions de retour plus claires ("si a != b, alors nous revenons 0!")


Il a été souligné dans les commentaires qu'il peut y avoir quelques trous dans mon exemple, je vais donc l'étoffer un peu plus.

Nous aurions pu écrire le premier bloc de code ici comme ça pour le rendre plus lisible:

if(a != b) {
    return 0;
} else if(c > d) {
    return 1;
} else if(e != f) {
    e++;
    return 3;
} else {
    a.doSomeStuff();
    c.makeSomeThingsWith(b);
    f.undo(d, e);
    return 9;
}

Ce code est équivalent aux deux blocs ci-dessus, mais il présente certains défis. La elseclause ici relie ces instructions if ensemble. Dans la version de clause de garde ci-dessus, les clauses de garde sont indépendantes les unes des autres. Ajouter un nouveau signifie simplement écrire un nouveau ifentre le dernier ifet le reste de la logique. Ici, cela peut aussi être fait, avec seulement une légère charge cognitive supplémentaire. La différence, cependant, est quand une logique supplémentaire doit se produire avant cette nouvelle clause de garde. Lorsque les déclarations étaient indépendantes, nous pouvions faire:

...
if(e != f) {
    e++;
    return 3;
}

g.doSomeLogicWith(a);

if(!g.isGood()) {
    return 4;
}

a.doSomeStuff();
...

Avec la if/elsechaîne connectée , ce n'est pas si facile à faire. En fait, la voie la plus simple à prendre serait de briser la chaîne pour ressembler davantage à la version de la clause de garde.

Mais vraiment, cela a du sens. Une utilisation if/elsefaible implique une relation entre les parties. Nous pourrions supposer que cela a != best en quelque sorte lié àc > d la if/elseversion chaînée. Cette supposition serait trompeuse, car nous pouvons voir dans la version de la clause de garde qu'ils ne sont en fait pas liés. Cela signifie que nous pouvons faire plus avec des clauses indépendantes, comme les réorganiser (peut-être pour optimiser les performances des requêtes ou pour améliorer le flux logique). Nous pouvons également les ignorer cognitivement une fois que nous les avons dépassés. Dans une if/elsechaîne, je suis moins sûr que la relation d'équivalence entre aetb ne reviendra pas plus tard.

La relation impliquée par elseest également apparente dans l'exemple de code profondément imbriqué, mais d'une manière différente. Les elseinstructions sont séparées du conditionnel auquel elles sont attachées et elles sont liées dans l' ordre inverse aux conditionnelles (la première elseest attachée à la dernièreif , la dernière elseest attachée à la première if). Cela crée une dissonance cognitive où nous devons revenir en arrière dans le code, en essayant d'aligner l'indentation dans notre cerveau. C'est un peu comme essayer de faire correspondre un tas de parenthèses. Cela empire encore si l'indentation est incorrecte. Les accolades facultatives n'aident pas non plus.

J'ai oublié de mentionner un cas dans lequel je suis d'accord avec l'omission d'un bloc else, et c'est lorsque j'utilise un bloc if pour appliquer une contrainte qui doit être logiquement satisfaite pour tout le code suivant, comme une vérification nulle (ou tout autre garde ).

C'est une belle concession, mais cela n'invalide vraiment aucune réponse. Je pourrais dire que dans votre exemple de code:

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

une interprétation valable pour écrire un code comme celui-ci pourrait être que «nous ne devons partir avec un parapluie que si le ciel n'est pas bleu». Ici, il y a une contrainte que le ciel doit être bleu pour que le code suivant soit valide pour s'exécuter. J'ai rencontré la définition de votre concession.

Et si je dis que si la couleur du ciel est noire, c'est une nuit claire, donc pas de parapluie nécessaire. (Il existe des moyens plus simples d'écrire ceci, mais il existe des moyens plus simples d'écrire l'exemple entier.)

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color == Color.Blue)
    {
        return true;
    }

    if(sky.Color == Color.Black)
    {
        return true;
    }

    return false;
}

Comme dans l'exemple plus large ci-dessus, je peux simplement ajouter la nouvelle clause sans trop y penser. Dans unif/else , je ne peux pas être sûr de ne pas gâcher quelque chose, surtout si la logique est plus compliquée.

Je soulignerai également que votre exemple simple n'est pas si simple non plus. Un test pour une valeur non binaire n'est pas le même que l'inverse de ce test avec des résultats inversés.

cbojar
la source
1
Ma modification de la question porte sur ce cas. Lorsqu'une contrainte qui doit être logiquement satisfaite pour tout le code suivant, comme un contrôle nul (ou tout autre garde), je conviens que l'omission d'un elsebloc est essentielle pour la lisibilité.
Selali Adobor
1
Votre première version est difficile à saisir, mais je ne pense pas que cela soit nécessaire en utilisant if / else. Voyez comment je l' écrirais : gist.github.com/winstonewert/c9e0955bc22ce44930fb .
Winston Ewert
@WinstonEwert Voir les modifications pour la réponse à vos commentaires.
cbojar
Votre modification rapporte des points valides. Et je pense certainement que les clauses de garde ont leur place. Mais dans le cas général, je suis en faveur des clauses else.
Winston Ewert
0

Vous avez trop simplifié votre exemple. L' une ou l'autre alternative peut être plus lisible, en fonction de la situation plus large: en particulier, cela dépend si l'une des deux branches de la condition est anormale . Permettez-moi de vous montrer: dans le cas où l'une ou l'autre branche est également probable, ...

void DoOneOfTwoThings(bool which)
{
    if (which)
        DoThingOne();
    else
        DoThingTwo();
}

... est plus lisible que ...

void DoOneOfTwoThings(bool which)
{
    if (which) {
        DoThingOne();
        return;
    }
    DoThingTwo();
}

... parce que le premier présente une structure parallèle et le second non. Mais, lorsque la majeure partie de la fonction est consacrée à une tâche et qu'il vous suffit de vérifier d'abord certaines conditions préalables, ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input)) {
        ProcessInputOfTypeTwo(input);
        return;
    }
    ProcessInputDefinitelyOfTypeOne(input);

}

... est plus lisible que ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input))
        ProcessInputOfTypeTwo(input);
    else
        ProcessInputDefinitelyOfTypeOne(input);
}

... car délibérément ne pas mettre en place une structure parallèle fournit un indice à un futur lecteur qu'obtenir ici une entrée "vraiment de type deux" est anormal . (Dans la vraie vie, ProcessInputDefinitelyOfTypeOnece ne serait pas une fonction distincte, donc la différence serait encore plus flagrante.)

zwol
la source
Il serait peut-être préférable de choisir un exemple plus concret pour le deuxième cas. Ma réaction immédiate est: "ça ne peut pas être anormal si vous allez de l'avant et le traitez quand même".
Winston Ewert du
@WinstonEwert anormal est peut-être le mauvais terme. Mais je suis d'accord avec Zack, que si vous avez un défaut (case1) et une exception, cela devrait être écrit comme » if-> Faites l'exceptionnel et laissez« comme une stratégie de retour anticipé . Pensez au code, dans lequel la valeur par défaut comprend plus de contrôles, il serait plus rapide de traiter le cas exceptionnel en premier.
Thomas Junk
Cela semble tomber dans le cas dont je parlais when using an if block to apply a constraint that must be logically satisfied for all following code, such as a null-check (or any other guards). Logiquement, tout travail ProcessInputOfTypeOnedépend du fait que !InputIsReallyTypeTwo(input)nous devons donc le rattraper en dehors de notre traitement normal. Normalement, ce ProcessInputOfTypeTwo(input);serait vraiment throw ICan'tProccessThatExceptionce qui rendrait la similitude plus évidente.
Selali Adobor
@WinstonEwert J'aurais peut-être pu mieux formuler cela, oui. Je pensais à une situation qui revient souvent lors du codage manuel des jetons: en CSS, par exemple, la séquence de caractères " u+" introduit généralement un jeton "plage unicode", mais si le caractère suivant n'est pas un chiffre hexadécimal, il faut alors sauvegarder et traiter le «u» comme identifiant à la place. Ainsi, la boucle du scanner principal est distribuée pour "scanner un jeton de plage unicode" de manière quelque peu imprécise, et cela commence par "... si nous sommes dans ce cas spécial inhabituel, sauvegardez, puis appelez plutôt le sous-programme scan-an-identifier".
zwol
@AssortedTrailmix Même si l'exemple auquel je pensais ne provenait pas d'une base de code héritée dans laquelle des exceptions ne peuvent pas être utilisées, ce n'est pas une condition d' erreur , c'est juste que le code qui appelle ProcessInputOfTypeOneutilise une vérification imprécise mais rapide qui parfois attrape plutôt le type deux.
zwol
0

Oui, la complexité augmente car la clause else est redondante . Il vaut donc mieux laisser de côté pour garder le code maigre et méchant. Cela revient au même que d'omettre les thisqualificatifs redondants (Java, C ++, C #) et d'omettre les parenthèses dans les returninstructions, etc.

Un bon programmeur pense "que puis-je ajouter?" tandis qu'un grand programmeur pense "que puis-je supprimer?".

vidstige
la source
1
Quand je vois l'omission du elsebloc, si cela est expliqué dans une ligne (ce qui n'est pas souvent), c'est souvent avec ce genre de raisonnement, donc +1 pour présenter l'argument "par défaut" que je vois, mais je ne le fais pas trouver que c'est un raisonnement assez fort. A good programmer things "what can I add?" while a great programmer things "what can I remove?".semble être un adage commun selon lequel beaucoup de gens surexploitent sans examen attentif, et je trouve personnellement que c'est un tel cas.
Selali Adobor
Oui, en lisant votre question, j'ai immédiatement senti que vous aviez déjà pris votre décision, mais je voulais une confirmation. Cette réponse n'est évidemment pas ce que vous vouliez, mais il est parfois important de tenir compte des opinions avec lesquelles vous n'êtes pas d'accord. Peut-être y a-t-il aussi quelque chose?
vidstige
-1

J'ai remarqué, changeant d'avis sur cette affaire.

Lorsqu'on m'a posé cette question il y a environ un an, j'aurais répondu quelque chose comme:

»Il ne devrait y avoir qu'un entryet un exitdans une méthode« Et dans cet esprit, j'aurais suggéré de refactoriser le code pour:

bool CanLeaveWithoutUmbrella()
{
    bool result

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    else
    {
        result = false;
    }

    return result;
}

Du point de vue de la communication, il est clair ce qui doit être fait. Ifquelque chose est le cas, manipulez le résultat d'une manière , else manipulez-le d'une autre manière .

Mais cela implique un inutile else . Le elseexprime le cas par défaut . Donc, dans un deuxième temps, je pourrais le refactoriser

bool CanLeaveWithoutUmbrella()
{
    bool result=false

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    return result;
}

La question se pose alors: pourquoi utiliser une variable temporaire? La prochaine étape de la refactorisation mène à:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue)
    {
        return true;
    }
    return false;
}

C'est donc clair dans ce qu'il fait. J'irais même plus loin:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) return true;
    return false;
}

Ou pour les timides :

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) { return true; }
    return false;
}

Vous pouvez le lire comme ça: »CanLeaveWithoutUmbrella retourne un bool . Si rien de spécial ne se produit, il renvoie false «C'est la première lecture, de haut en bas.

Et puis vous "analysez" la condition »Si la condition est remplie, elle retourne vrai « Vous pouvez complètement ignorer le conditionnel et avoir compris ce que fait cette fonction dans le cas par défaut . Votre œil et votre esprit n'ont qu'à parcourir quelques lettres du côté gauche, sans lire la partie de droite.

J'ai l'impression que le reste énonce plus directement l'intention, en déclarant que le code dans les deux blocs est directement lié.

Oui, c'est correcte. Mais cette information est redondante . Vous avez un cas par défaut et une "exception" qui est couverte par la ifdéclaration.

Lorsque j'ai affaire à des structures plus complexes, je choisirais une autre stratégie, en omettant le ifou son frère laid switchdu tout.

Thomas Junk
la source
La redondance +1 montre certainement une augmentation non seulement de la complexité, mais de la confusion. Je sais que j'ai toujours dû revérifier le code écrit comme ça exactement à cause de la redondance.
dietbuddha
1
Pourriez-vous supprimer les cas après le cas en commençant par So this is clear in what it does...et les développer? (Les cas précédents sont également d'une pertinence discutable). Je dis cela parce que c'est ce que la question nous demande d'examiner. Vous avez déclaré votre position, en omettant le bloc else, et c'est en fait la position qui nécessite plus d'explications (et celle qui m'a amené à poser cette question). De la question:I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor
@AssortedTrailmix Bien sûr! Sur quoi dois-je m'expliquer exactement? Puisque votre question initiale était «Est-ce que les blocs augmentent la complexité du code? Dans ce cas, il pourrait être omis.
Thomas Junk
Et non! Je ne comprends pas le downvote.
Thomas Junk
-1

Pour faire court, dans ce cas, se débarrasser du elsemot-clé est une bonne chose. C'est totalement redondant ici, et selon la façon dont vous formatez votre code, cela ajoutera une à trois lignes complètement inutiles à votre code. Considérez ce qui est dans le ifbloc:

return true;

Par conséquent, si le ifbloc devait être saisi, le reste de la fonction n'est, par définition, pas exécuté. Mais si elle n'est pas conclue, puisqu'il n'y a pas d'autres ifinstructions, le reste de la fonction est exécuté. Ce serait totalement différent si une returndéclaration n'était pas dans le ifbloc, auquel cas la présence ou l'absence d'un elsebloc aurait un impact, mais ici, cela n'aurait pas d'impact.

Dans votre question, après avoir inversé votre ifcondition et omis else, vous avez déclaré ce qui suit:

Quelqu'un peut désormais ajouter un nouveau bloc if basé sur une condition après le premier exemple sans reconnaître immédiatement correctement que la première condition place une contrainte sur sa propre condition.

Ils doivent alors lire le code un peu plus attentivement. Nous utilisons des langages de haut niveau et une organisation claire du code pour atténuer ces problèmes, mais l'ajout d'un elsebloc complètement redondant ajoute du «bruit» et du «fouillis» au code, et nous ne devrions pas non plus avoir à inverser ou à inverser nos ifconditions. S'ils ne peuvent pas faire la différence, ils ne savent pas ce qu'ils font. S'ils peuvent faire la différence, ils peuvent toujours inverser ifeux-mêmes les conditions d' origine .

J'ai oublié de mentionner un cas dans lequel je suis d'accord avec l'omission d'un bloc else, et c'est lorsque j'utilise un bloc if pour appliquer une contrainte qui doit être logiquement satisfaite pour tout le code suivant, comme une vérification nulle (ou tout autre garde ).

C'est essentiellement ce qui se passe ici cependant. Vous revenez du ifbloc, de sorte que la ifcondition à évaluer false est une contrainte qui doit être logiquement satisfaite pour tout le code suivant.

Y a-t-il une augmentation de la complexité introduite en n'incluant pas le bloc else?

Non, cela constituera une diminution de la complexité de votre code, et cela peut même constituer une diminution du code compilé, selon que certaines optimisations super triviales auront lieu ou non.

Panzercrisis
la source
2
"Ils ont besoin de lire un peu plus attentivement le code." Le style de codage existe afin que les programmeurs puissent accorder moins d'attention aux détails épineux et plus d'attention à ce qu'ils font réellement. Si votre style vous fait prêter attention à ces détails inutilement, c'est un mauvais style. "... des lignes complètement inutiles ..." Elles ne sont pas inutiles - elles vous permettent de voir d'un coup d'œil ce qu'est la structure, sans perdre de temps à réfléchir à ce qui se passerait si telle ou telle partie était exécutée.
Michael Shaw
@MichaelShaw Je pense que la réponse d'Aviv Cohn entre dans ce dont je parle.
Panzercrisis
-2

Dans l'exemple spécifique que vous avez donné, c'est le cas.

  • Cela oblige un réviseur à relire le code pour s'assurer qu'il ne s'agit pas d'une erreur.
  • Il ajoute une complexité cyclomatique (inutile) car il ajoute un nœud au graphe de flux.

Je pense que cela ne pourrait pas être plus simple que cela:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}
Tulains Córdova
la source
6
J'aborde spécifiquement le fait que cet exemple très simplifié peut être réécrit pour supprimer la ifdéclaration. En fait, je décourage explicitement toute simplification supplémentaire en utilisant le code exact que vous avez utilisé. De plus, la complexité cyclomatique n'est pas augmentée par le elsebloc.
Selali Adobor
-4

J'essaie toujours d'écrire mon code de telle sorte que l'intention soit aussi claire que possible. Votre exemple manque de contexte, je vais donc le modifier un peu:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

Notre intention semble être de pouvoir partir sans parapluie lorsque le ciel est bleu. Cependant, cette simple intention est perdue dans une mer de code. Il existe plusieurs façons de faciliter la compréhension.

Tout d'abord, il y a votre question sur la elsesuccursale. Cela ne me dérange pas de toute façon, mais j'ai tendance à laisser de côté le else. Je comprends l'argument d'être explicite, mais mon avis est que les ifdéclarations devraient encapsuler les branches «facultatives», comme return truecelle. Je n'appellerais pas la return falsebranche «facultative», car elle agit comme notre valeur par défaut. Quoi qu'il en soit, je vois cela comme un problème très mineur et beaucoup moins important que les suivants:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        return false;
    }
}

Un problème beaucoup plus important est que vos succursales en font trop! Les succursales, comme tout, devraient être «aussi simples que possible, mais pas plus». Dans ce cas, vous avez utilisé une ifinstruction, qui se branche entre des blocs de code (collections d'instructions) alors que tout ce dont vous avez vraiment besoin pour créer une branche sont des expressions (valeurs). Cela est clair car a) vos blocs de code ne contiennent que des instructions uniques et b) ce sont les mêmes instructions ( return). Nous pouvons utiliser l'opérateur ternaire ?:pour réduire la branche à la seule partie différente:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue)
            ? true
            : false;
    }
}

Nous atteignons maintenant la redondance évidente à laquelle notre résultat est identique this.color == Color.Blue. Je sais que votre question nous demande d'ignorer cela, mais je pense qu'il est vraiment important de souligner qu'il s'agit d'une façon de penser très procédurale: vous décomposez les valeurs d'entrée et construisez des valeurs de sortie. C'est bien, mais si possible, il est beaucoup plus puissant de penser en termes de relations ou de transformations entre l'entrée et la sortie. Dans ce cas, la relation est évidemment qu'ils sont les mêmes, mais je vois souvent du code procédural alambiqué comme celui-ci, qui masque une relation simple. Allons de l'avant et faisons cette simplification:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue);
    }
}

La prochaine chose que je voudrais souligner est que votre API contient un double négatif: il est possible que ce CanLeaveWithoutUmbrellasoit le cas false. Ceci, bien sûr, à Simplifie NeedUmbrellaêtre true, donc nous allons le faire:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool NeedUmbrella()
    {
        return (this.color != Color.Blue);
    }
}

Nous pouvons maintenant commencer à réfléchir à votre style de codage. Il semble que vous utilisiez un langage orienté objet, mais en utilisant des ifinstructions pour créer des branches entre des blocs de code, vous avez fini par écrire des procédures code .

Dans le code orienté objet, tous les blocs de code sont des méthodes et toutes les branches sont effectuées via une répartition dynamique , par exemple. en utilisant des classes ou des prototypes. On peut se demander si cela simplifie les choses, mais cela devrait rendre votre code plus cohérent avec le reste du langage:

class Sky {
    bool NeedUmbrella()
    {
        return true;
    }
}

class BlueSky extends Sky {
    bool NeedUmbrella()
    {
        return false;
    }
}

Notez que l'utilisation d'opérateurs ternaires pour créer des branches entre les expressions est courante dans la programmation fonctionnelle .

Warbo
la source
Le premier paragraphe de la réponse semble être sur la bonne voie pour répondre à la question, mais il diverge immédiatement vers le sujet exact que je demande explicitement à ignorer pour cet exemple très simple . De la question: I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.). En fait, vous utilisez la ligne de code exacte que je mentionne. De plus, bien que cette partie de la question soit hors sujet, je dirais que vous allez trop loin dans votre inférence. Vous avez radicalement changé le code.
Selali Adobor
@AssortedTrailmix C'est purement une question de style. Il est logique de se soucier du style et il est logique de l'ignorer (en se concentrant uniquement sur les résultats). Je ne pense pas qu'il est logique de se soucier de return false;vs else { return false; }tout en ne soucier de la polarité de la branche, l' envoi dynamique, ternaires, etc. Si vous écrivez code de procédure dans un langage OO, un changement radical est nécessaire;)
Warbo
Vous pouvez dire quelque chose comme ça en général, mais cela ne s'applique pas lorsque vous répondez à une question avec des paramètres bien définis (surtout si, sans respecter ces paramètres, vous pouvez "résumer" la question entière). Je dirais également que la dernière phrase est tout simplement fausse. La POO concerne «l'abstraction procédurale», pas «l'oblitération procédurale». Je dirais que le fait que vous soyez passé d'une doublure à un nombre infini de classes (une pour chaque couleur) montre pourquoi. De plus, je me demande toujours ce que la répartition dynamique devrait avoir à faire avec une if/elseinstruction et quelle est la polarité des branches.
Selali Adobor
Je n'ai jamais dit que la solution OO était meilleure (en fait, je préfère la programmation fonctionnelle), j'ai simplement dit qu'elle était plus cohérente avec le langage. Par "polarité", je voulais dire quelle chose est "vraie" et laquelle est "fausse" (je l'ai changé avec "NeedUmbrella"). Dans la conception OO, tout le flux de contrôle est déterminé par la répartition dynamique. Par exemple, Smalltalk n'autorise rien d'autre en.wikipedia.org/wiki/Smalltalk#Control_structures (voir également c2.com/cgi/wiki?HeInventedTheTerm )
Warbo
2
J'aurais surévalué cela jusqu'à ce que j'arrive au tout dernier paragraphe (sur OO), ce qui lui a valu un downvote à la place! C'est exactement le genre de surutilisation irréfléchie des constructions OO qui produit du code ravioli , qui est tout aussi illisible que les spaghettis.
zwol