Utilisez un else after exception (ou non)

9

Considérez ce morceau de code:

if (x == 1)
{
  throw "no good; aborting" ;
}

[... more code ...]

Considérez maintenant ce code:

if (x == 1)
{
  throw "no good; aborting" ;
}
else
{
  [... more code ...]
}

Les deux cas fonctionnent exactement de la même manière. Le premier cas présente l'avantage de ne pas avoir à "encapsuler" le reste du code dans un else. La seconde a l'avantage de suivre la pratique d'avoir explicitement un elsepour tous if.

Quelqu'un peut-il fournir des arguments solides en faveur de l'un plutôt que de l'autre?

rlandster
la source
5
La pratique que vous citez en faveur de l'explicite elsesemble bidon. Assez souvent, il n'y a tout simplement rien à mettre dans le elsebloc, sauf si vous vous penchez en arrière.
Cohérence? Permettre la robustesse face au changement de code? Lisibilité?
Thomas Eding
1
Ehhhh, je ne suis pas tellement fan de l'idée que tout le monde en a ifbesoin else. Le dernier programmeur qui a travaillé sur notre base de code a suivi cela de manière rigide ( enfin, parfois ... c'est un peu schizophrène ). En conséquence, nous avons beaucoup de else { /* do nothing */ }blocs entièrement dénués de sens qui jonchent le code ...
KChaloux
4
"Un autre pour chaque si" ressemble à une proclamation bizarre émise par un architecte cloud au nom d'une cohérence (stupide). Je ne vois aucun avantage à suivre cette pratique et je n'en ai jamais entendu parler nulle part.
Erik Dietrich
C'est redondant. Si vous travaillez avec la pile .NET, installez ReSharper et cela vous rappellera de supprimer toutes les instructions else redondantes.
CodeART

Réponses:

16

Vous ne devez pas ajouter elseaprès les ifbranches qui interrompent inconditionnellement le flux de contrôle , telles que celles contenant un throwou un return. Cela améliorerait la lisibilité de votre programme en supprimant le niveau d'imbrication inutile introduit par la elsebranche.

Ce qui semble plus ou moins correct avec un seul throwdevient vraiment moche lorsque plusieurs lancers consécutifs sont inversés:

void myMethod(int arg1, int arg2, int arg3) {
    // This is demonstrably ugly - do not code like that!
    if (!isValid(arg1)) {
        throw new ArgumentException("arg1 is invalid");
    } else {
        if (!isValid(arg2)) {
            throw new ArgumentException("arg2 is invalid");
        } else {
            if (!isValid(arg3)) {
                throw new ArgumentException("arg3 is invalid");
            } else {
                // The useful code starts here
            }
        }
    }
}

Cet extrait fait la même chose, mais il semble beaucoup mieux:

void myMethod(int arg1, int arg2, int arg3) {
    if (!isValid(arg1)) {
        throw new ArgumentException("arg1 is invalid");
    }
    if (!isValid(arg2)) {
        throw new ArgumentException("arg2 is invalid");
    }
    if (!isValid(arg3)) {
        throw new ArgumentException("arg3 is invalid");
    }
    // The useful code starts here
}
dasblinkenlight
la source
+1 Vrai. Le deuxième cas OP vous oblige à lire attentivement, puis vous laisse avec un WTF. Mais ... essayez toujours de raccourcir les méthodes. Un retour au milieu d'une méthode de 200 lignes est également mauvais.
Tulains Córdova
1
Pour être juste, si vous utilisez simplement des répétitions si vous pouvez le faire else if.
Guvante
2
@Guvante: Chacun ifteste une seule condition et la traite si la condition est vraie, et à moins qu'il ne se passe quelque chose d'autre si la condition est fausse, les else ifs ne sont pas nécessaires. Nous avons un terme dans mon bureau pour le code comme le premier extrait de dasblinkenlight: " machines pachinko ."
Blrfl
@Blrfl pachinko machines hah, analogie parfaite +1
Jimmy Hoffa
@Blrfl: Je faisais référence à des ifs répétés qui sont un mauvais exemple de trop d'imbrication. Vous ne devriez pas imbriquer des ifs répétés de toute façon. Je suis d'accord qu'en général, à moins que vous ne parliez d'une petite quantité de code, il n'y a aucune raison de l'inclure else.
Guvante
5

J'appellerais la pratique «explicite autrement» que vous appelez un anti-modèle, car elle obscurcit le fait qu'il n'y a pas de code spécial dans le cas contraire de votre if.

La lisibilité / maintenabilité est généralement améliorée lorsque vous n'avez pour la plupart que des constructions de flux de code nécessaires et que vous les minimisez. Cela signifie des elses redondants et si ceux qui ajouteront une portée à une fonction entière rendent le suivi et la maintenance plus difficiles.

Dites par exemple que vous avez cette fonction:

public void ConfigureOblogon(Oblogon oblogonToConfigure)
{
    if (_validColors.Contains(oblogonToConfigure.Color))
    {
        oblogonToConfigure.ColorIndex = _validColors.IndexOf(oblogonToConfigure.Color);
    }
    else
    {
        oblogonToConfigure.Color = _validColors[0];
        oblogonToConfigure.ColorIndex = 0;
    }
}

Maintenant, l'exigence vient que pendant la configuration, vous devez également spécifier le type / index de type de l'oblogon, il existe plusieurs étendues que quelqu'un pourrait placer ce code et se retrouver avec un code invalide, c'est-à-dire

public void ConfigureOblogon(Oblogon oblogonToConfigure)
{
    if (!_validOblogons.Contains(oblogonToConfigure.Type))
    {
        oblogonToConfigure.Type = _validOblogons[0];
        oblogonToConfigure.TypeIndex = 0;
        if (_validColors.Contains(oblogonToConfigure.Color))
        {
            oblogonToConfigure.ColorIndex = _validColors.IndexOf(oblogonToConfigure.Color);
        }
        else
        {
            oblogonToConfigure.Color = _validColors[0];
            oblogonToConfigure.ColorIndex = 0;
        }
    }
    else
    {
        oblogonToConfigure.TypeIndex = _validOblogons.IndexOf(oblogonToConfigure.Type);
    }
}

Comparez cela à si le code d'origine a été écrit avec des constructions de flux de contrôle minimales nécessaires et minimisées à cela.

public void ConfigureOblogon(Oblogon oblogonToConfigure)
{
    if (!_validColors.Contains(oblogonToConfigure.Color))
    {
        oblogonToConfigure.Color = _validColors[0];
    }

    oblogonToConfigure.ColorIndex = _validColors.IndexOf(oblogonToConfigure.Color);
}

Il serait désormais beaucoup plus difficile de mettre accidentellement quelque chose dans la mauvaise portée ou de finir par des ballonnements gonflés, ce qui entraînerait une duplication à long terme de la croissance et du maintien de cette fonction. De plus, il est évident quels sont les flux possibles via cette fonction, de sorte que la lisibilité est améliorée.

Je sais, l'exemple est un peu artificiel, mais j'ai vu plusieurs fois

SomeFunction()
{
    if (isvalid)
    {
        /* ENTIRE FUNCTION */
    }
    /* Nothing should go here but something does on accident, and an invalid scenario is created. */
}

Donc, je pense que formaliser ces règles sur les constructions de flux de contrôle peut aider les gens à développer l'intuition nécessaire pour sentir quelque chose lorsqu'ils commencent à écrire du code comme ça. Ensuite, ils commenceront à écrire ..

SomeFunction()
{
    if (!isvalid)
    {
        /* Nothing should go here, and it's so small no one will likely accidentally put something here */
        return;
    }

    /* ENTIRE FUNCTION */
}
Jimmy Hoffa
la source