web-dev-qa-db-fra.com

Pourquoi la déclaration «si» est-elle considérée comme mauvaise?

Je viens juste de Simple Design and Testing Conference . Dans l'une des sessions, nous parlions de mauvais mots clés dans les langages de programmation. Corey Haines , qui a proposé le sujet, était convaincu que la déclaration if est un mal absolu. Son alternative était de créer des fonctions avec prédicats . Pouvez-vous m'expliquer pourquoi if est mauvais.

Je comprends que vous pouvez écrire du code très laid en abusant de if. Mais je ne pense pas que ce soit si mauvais.

61
Vadim

Les clauses conditionnelles aboutissent parfois à un code plus difficile à gérer. Cela inclut non seulement l'instruction if, mais encore plus couramment l'instruction switch, qui comprend généralement plus de branches que ne le ferait une if correspondante.

Il y a des cas où il est parfaitement raisonnable d'utiliser un if

Lorsque vous écrivez des méthodes utilitaires, des extensions ou des fonctions de bibliothèque spécifiques, il est probable que vous ne pourrez pas éviter ifs (et vous ne devriez pas). Il n'y a pas de meilleur moyen de coder cette petite fonction, ni de la rendre plus auto-documentée qu'elle ne l'est:

// this is a good "if" use-case
int Min(int a, int b)
{
    if (a < b) 
       return a;
    else
       return b;
}

// or, if you prefer the ternary operator
int Min(int a, int b)
{
    return (a < b) ? a : b;
}

La ramification sur un "code de type" est une odeur de code

D'un autre côté, si vous rencontrez du code qui teste une sorte de code de type, ou teste si une variable est d'un certain type, alors c'est très probablement un bon candidat pour le refactoring, à savoir remplacer le conditionnel par polymorphisme .

La raison en est qu'en permettant à vos appelants de se brancher sur un certain code de type, vous créez la possibilité de vous retrouver avec de nombreux contrôles dispersés dans tout votre code, ce qui rend les extensions et la maintenance beaucoup plus complexes. Le polymorphisme, d'autre part, vous permet de rapprocher le plus possible cette décision de branchement de la racine de votre programme.

Considérer:

// this is called branching on a "type code",
// and screams for refactoring
void RunVehicle(Vehicle vehicle)
{
    // how the hell do I even test this?
    if (vehicle.Type == CAR)
        Drive(vehicle);
    else if (vehicle.Type == PLANE)
        Fly(vehicle);
    else
        Sail(vehicle);
}

En plaçant des fonctionnalités communes mais spécifiques au type (c'est-à-dire spécifiques à la classe) dans des classes distinctes et en les exposant via une méthode virtuelle (ou une interface), vous autorisez les parties internes de votre programme à déléguer cette décision à une personne située plus haut dans la hiérarchie des appels ( potentiellement à un seul endroit dans le code), ce qui permet des tests (mocking), une extensibilité et une maintenance beaucoup plus faciles:

// adding a new vehicle is gonna be a piece of cake
interface IVehicle
{
    void Run();
}

// your method now doesn't care about which vehicle 
// it got as a parameter
void RunVehicle(IVehicle vehicle)
{
    vehicle.Run();
}

Et vous pouvez maintenant facilement tester si votre méthode RunVehicle fonctionne comme il se doit:

// you can now create test (mock) implementations
// since you're passing it as an interface
var mock = new Mock<IVehicle>();

// run the client method
something.RunVehicle(mock.Object);

// check if Run() was invoked
mock.Verify(m => m.Run(), Times.Once());

Les modèles qui ne diffèrent que par leurs conditions if peuvent être réutilisés

En ce qui concerne l'argument concernant le remplacement de if par un "prédicat" dans votre question, Haines a probablement voulu mentionner qu'il existe parfois des modèles similaires sur votre code, qui ne diffèrent que par leurs expressions conditionnelles. Les expressions conditionnelles émergent en conjonction avec ifs, mais l'idée est d'extraire un motif répétitif dans une méthode séparée, en laissant l'expression comme paramètre. C'est ce que fait déjà LINQ, habituellement , ce qui donne un code plus propre par rapport à une alternative foreach:

Considérez ces deux méthodes très similaires:

// average male age
public double AverageMaleAge(List<Person> people)
{
    double sum = 0.0;
    int count = 0;
    foreach (var person in people)
    {
       if (person.Gender == Gender.Male)
       {
           sum += person.Age;
           count++;
       }
    }
    return sum / count; // not checking for zero div. for simplicity
}

// average female age
public double AverageFemaleAge(List<Person> people)
{
    double sum = 0.0;
    int count = 0;
    foreach (var person in people)
    {
       if (person.Gender == Gender.Female) // <-- only the expression
       {                                   //     is different
           sum += person.Age;
           count++;
       }
    }
    return sum / count;
}

Cela indique que vous pouvez extraire la condition dans un prédicat, vous laissant une seule méthode pour ces deux cas (et de nombreux autres cas futurs):

// average age for all people matched by the predicate
public double AverageAge(List<Person> people, Predicate<Person> match)
{
    double sum = 0.0;
    int count = 0;
    foreach (var person in people)
    {
       if (match(person))       // <-- the decision to match
       {                        //     is now delegated to callers
           sum += person.Age;
           count++;
       }
    }
    return sum / count;
}

var males = AverageAge(people, p => p.Gender == Gender.Male);
var females = AverageAge(people, p => p.Gender == Gender.Female);

Et comme LINQ a déjà un tas de méthodes d'extension pratiques comme celle-ci, vous n'avez même pas besoin d'écrire vos propres méthodes:

// replace everything we've written above with these two lines
var males = list.Where(p => p.Gender == Gender.Male).Average(p => p.Age);
var females = list.Where(p => p.Gender == Gender.Female).Average(p => p.Age);

Dans cette dernière version de LINQ, l'instruction if a complètement "disparu", bien que:

  1. pour être honnête, le problème n'était pas dans le if en lui-même, mais dans le modèle de code entier (simplement parce qu'il a été dupliqué), et
  2. la if existe toujours, mais elle est écrite dans la méthode d'extension LINQ Where, qui a été testée et fermée pour modification. Avoir moins de votre propre code est toujours une bonne chose: moins de choses à tester, moins de choses à se tromper, et le code est plus simple à suivre, à analyser et à maintenir.

Refactorisez quand vous sentez que c'est une odeur de code, mais n'ingérez pas trop

Cela dit, vous ne devriez pas passer des nuits blanches sur avoir quelques conditionnels de temps en temps. Bien que ces réponses puissent fournir quelques règles générales, la meilleure façon de détecter les constructions qui nécessitent une refactorisation est l'expérience. Au fil du temps, certains modèles émergent qui entraînent la modification répétée des mêmes clauses.

74
Groo

Il y a un autre sens dans lequel if peut être mauvais: quand il vient au lieu du polymorphisme.

Par exemple.

 if (animal.isFrog()) croak(animal)
 else if (animal.isDog()) bark(animal)
 else if (animal.isLion()) roar(animal)

au lieu de

 animal.emitSound()

Mais fondamentalement, c'est un outil parfaitement acceptable pour ce qu'il fait. Il peut être abusé et mal utilisé bien sûr, mais il est loin du statut de goto.

84
flybywire

Une bonne citation de Code Complete:

Codez comme si quiconque maintient votre programme est un psychopathe violent qui sait où vous vivez.
- Anonyme

IOW, restez simple. Si la lisibilité de votre application sera améliorée en utilisant un prédicat dans une zone particulière, utilisez-le. Sinon, utilisez le "si" et continuez.

32
Jordan Parmer

Je pense que cela dépend de ce que vous faites pour être honnête.

Si vous avez un simple if..else, pourquoi utiliser un predicate?

Si vous le pouvez, utilisez un switch pour des remplacements plus grands if, puis si l'option d'utiliser un prédicat pour les opérations de grande taille (là où cela a du sens, sinon votre code sera un cauchemar à maintenir) , utilise le.

Ce mec semble avoir été un peu pédant à mon goût. Remplacer tous les if par des prédicats est juste une folie.

17
Kyle Rozendo

Il y a la campagne Anti-If qui a commencé plus tôt dans l'année. La prémisse principale étant que de nombreuses instructions imbriquées si souvent peuvent souvent être remplacées par du polymorphisme.

Je serais intéressé de voir un exemple d'utilisation du prédicat à la place. Est-ce plus dans le sens de la programmation fonctionnelle?

14
Keith Bloom

if n'est pas mauvais (je soutiens également que l'attribution de la moralité aux pratiques d'écriture de code est insensée ...).

M. Haines est stupide et il faut en rire.

10
Paul Nathan

Tout comme dans le verset de la Bible sur l'argent, si les déclarations ne sont pas mauvaises - l'AMOUR de si les déclarations sont mauvaises. Un programme sans instructions if est une idée ridicule et il est essentiel de les utiliser si nécessaire. Mais un programme qui a 100 blocs if-else if d'affilée (ce que, malheureusement, j'ai vu) est définitivement mauvais.

9
Kaleb Brasee

Je dois dire que j'ai récemment commencé à voir si les instructions comme une odeur de code: surtout quand vous vous retrouvez à répéter plusieurs fois la même condition. Mais il y a quelque chose que vous devez comprendre à propos des odeurs de code: elles ne signifient pas nécessairement que le code est mauvais. Ils signifient simplement qu'il y a une bonne chance le code est mauvais.

Par exemple, les commentaires sont répertoriés comme une odeur de code par Martin Fowler, mais je ne prendrais personne au sérieux qui dirait que "les commentaires sont mauvais; ne les utilisez pas".

Cependant, en général, je préfère utiliser le polymorphisme plutôt que les instructions if dans la mesure du possible. Cela fait tellement moins de place à l'erreur. J'ai tendance à constater que la plupart du temps, l'utilisation de conditions conduit également à beaucoup d'arguments clochards (car vous devez transmettre les données nécessaires pour former le conditionnel à la méthode appropriée).

8
Jason Baker

Je suis d'accord avec toi; il avait tort. Vous pouvez aller trop loin avec des choses comme ça, trop intelligent pour votre propre bien.

Le code créé avec des prédicats au lieu de ifs serait horrible à maintenir et à tester.

7
Rap

Peut-être qu'avec l'informatique quantique, il sera judicieux de ne pas utiliser d'instructions IF, mais de laisser chaque étape du calcul se poursuivre et de n'avoir que la fonction "s'effondrer" à la fin pour obtenir un résultat utile.

4

Les prédicats proviennent de langages de programmation logiques/déclaratifs, comme PROLOG. Pour certaines classes de problèmes, comme la résolution de contraintes, ils sont sans doute supérieurs à beaucoup de merde étape par étape si-ce-faire-cela-puis-faire-cette-merde. Les problèmes qui seraient longs et complexes à résoudre dans les langages impératifs peuvent être résolus en quelques lignes dans PROLOG.

Il y a aussi le problème de la programmation évolutive (en raison de l'évolution vers le multicœur, le web, etc.). Si les instructions et la programmation impérative en général ont tendance à être étape par étape et non évolutives. Cependant, les déclarations logiques et le calcul lambda décrivent comment un problème peut être résolu et en quelles parties il peut être décomposé. En conséquence, l'interpréteur/processeur exécutant ce code peut efficacement décomposer le code en morceaux et le distribuer sur plusieurs processeurs/cœurs/threads/serveurs.

Certainement pas utile partout; Je détesterais essayer d'écrire un pilote de périphérique avec des prédicats au lieu d'instructions if. Mais oui, je pense que le point principal est probablement solide, et mérite au moins de se familiariser, sinon de l'utiliser tout le temps.

4
Lee B

Le seul problème avec les prédicats (en termes de remplacement des instructions if) est que vous devez toujours les tester:

function void Test(Predicate<int> pr, int num) 
{
    if (pr(num))
    { /* do something */ }
    else
    { /* do something else */ }
}

Vous pouvez bien sûr utiliser l'opérateur terniaire (?:), mais ce n'est qu'une instruction if déguisée ...

3
Matthew Scharley

Cela se résume probablement à un désir de réduire la complexité cyclomatique du code et de réduire le nombre de points de branchement dans une fonction. Si une fonction est simple à décomposer en un certain nombre de fonctions plus petites, dont chacune peut être testée, vous pouvez réduire la complexité et rendre le code plus facilement testable.

2
madlep

OMI: Je soupçonne qu'il essayait de provoquer un débat et de faire réfléchir les gens sur l'utilisation abusive du "si". Personne ne suggérerait sérieusement qu'une construction aussi fondamentale de la syntaxe de programmation devait être complètement évitée, n'est-ce pas?

2

Parfois, il est nécessaire de prendre une position extrême pour faire valoir votre point de vue. Je suis sûr que cette personne utilise if - mais chaque fois que vous utilisez un if, cela vaut la peine de réfléchir si un modèle différent rendrait le code plus clair.

La préférence du polymorphisme à if est au cœur de tout cela. Plutôt que:

if(animaltype = bird) {
     squawk();
} else if(animaltype = dog) {
     bark();
}

... utilisation:

animal.makeSound();

Mais cela suppose que vous ayez une classe/interface animale - donc vraiment ce que le if vous dit, c'est que vous devez créer cette interface.

Donc dans le monde réel, quels types de if voyons-nous qui nous conduisent à une solution de polymorphisme?

if(logging) {
      log.write("Did something");
}

C'est vraiment irritant de voir tout au long de votre code. Que diriez-vous, à la place, d'avoir deux (ou plus) implémentations de Logger?

this.logger = new NullLogger();    // logger.log() does nothing
this.logger = new StdOutLogger();  // logger.log() writes to stdout

Cela nous amène au modèle de stratégie.

Au lieu de:

if(user.getCreditRisk() > 50) {
     decision = thoroughCreditCheck();
} else if(user.getCreditRisk() > 20) {
     decision = mediumCreditCheck();
} else {
     decision = cursoryCreditCheck();
}

... tu aurais pu ...

decision = getCreditCheckStrategy(user.getCreditRisk()).decide();

Bien sûr, getCreditCheckStrategy() peut contenir un if - et cela pourrait bien être approprié. Vous l'avez poussé dans un endroit soigné où il appartient.

2
slim

Je pense que si les déclarations sont mauvaises, mais si les expressions ne le sont pas. Ce que je veux dire par une expression if dans ce cas peut être quelque chose comme l'opérateur ternaire C # (condition? TrueExpression: falseExpression). Ce n'est pas mal parce que c'est une fonction pure (au sens mathématique). Il évalue à une nouvelle valeur, mais il n'a aucun effet sur quoi que ce soit d'autre. Pour cette raison, cela fonctionne dans un modèle de substitution.

Impératif Si les déclarations sont mauvaises parce qu'elles vous forcent à créer des effets secondaires lorsque vous n'en avez pas besoin. Pour qu'une instruction If soit significative, vous devez produire différents "effets" selon l'expression de la condition. Ces effets peuvent être des choses comme les E/S, le rendu graphique ou les transactions de base de données, qui changent des choses en dehors du programme. Ou, il peut s'agir d'instructions d'affectation qui modifient l'état des variables existantes. Il est généralement préférable de minimiser ces effets et de les séparer de la logique réelle. Mais, en raison des instructions If, nous pouvons librement ajouter ces "effets exécutés conditionnellement" partout dans le code. Je pense que c'est mauvais.

1
VB Guy

Bon qu'en Ruby nous avons sauf;)

Mais sérieusement probablement si est le prochain goto, que même si la plupart des gens pensent que c'est mal dans certains cas, cela simplifie/accélère les choses (et dans certains cas comme code hautement optimisé de bas niveau c'est un must).

0
Jakub Troszok