91 votes

Pourquoi l'énoncé "si" est-il considéré comme mauvais ?

Je viens juste de Conférence sur la conception et les tests simples . Dans l'une des sessions, nous avons parlé des mots-clés maléfiques dans les langages de programmation. Corey Haines qui a proposé le sujet, était convaincu que if Cette déclaration est le mal absolu. Son alternative était de créer des fonctions avec prédicats . Pouvez-vous m'expliquer pourquoi if c'est le mal.

Je comprends que l'on puisse écrire du code très laid en abusant de if . Mais je ne crois pas que ce soit si grave.

137 votes

if Les déclarations sont diaboliques comme le sont les marteaux. Certaines personnes peuvent en faire un mauvais usage, mais c'est un outil essentiel.

26 votes

Alors un type balance une affirmation sans la soutenir ou expliquer pourquoi ?

11 votes

Je me méfie beaucoup des déclarations générales comme celles qui précèdent. Je n'ai rien contre les préférences et les suggestions sur les raisons pour lesquelles vous ne souhaitez pas utiliser tel ou tel outil ou fonctionnalité, etc. Mais un sceau dogmatique du "mal" me rebute (je ne sais pas s'il a qualifié cela du tout, d'ailleurs).

112voto

Groo Points 19453

Le site if est rarement considéré comme "mauvais" comme goto ou des variables globales mutables - et même ces dernières ne sont pas universellement et absolument mauvaises. Je suggérerais de considérer cette affirmation comme un peu hyperbolique.

Cela dépend aussi largement de votre langage de programmation et de votre environnement. Dans les langages qui prennent en charge le filtrage de motifs, vous disposerez d'excellents outils pour remplacer if à votre disposition. Mais si vous programmez un microcontrôleur de bas niveau en C, remplacer if avec des pointeurs de fonction sera un pas dans la mauvaise direction. Ainsi, je vais surtout envisager de remplacer if dans la programmation OOP, car dans les langages fonctionnels, if n'est pas idiomatique de toute façon, alors que dans les langages purement procéduraux, vous n'avez pas beaucoup d'autres options pour commencer.

Néanmoins, les clauses conditionnelles donnent parfois lieu à un code plus difficile à gérer. Cela ne concerne pas seulement les if mais, plus souvent encore, l'instruction switch qui comprend généralement plus de branches qu'une déclaration correspondante. if serait.

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

Lorsque vous écrivez des méthodes utilitaires, des extensions ou des fonctions spécifiques de la bibliothèque, il est probable que vous ne pourrez pas éviter if (et vous ne devriez pas). Il n'y a pas de meilleure façon 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;
}

Le branchement 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 qui teste si une variable est d'un certain type, alors il s'agit très probablement d'un bon candidat pour le refactoring, à savoir remplacer le conditionnel par le polymorphisme .

La raison en est qu'en permettant à vos appelants de se brancher sur un certain type de code, vous créez une possibilité de vous retrouver avec de nombreuses vérifications dispersées dans tout votre code, rendant les extensions et la maintenance beaucoup plus complexes. Le polymorphisme, quant à lui, vous permet d'amener cette décision de branchement aussi près que possible de la racine de votre programme.

Pensez-y :

// 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 une fonctionnalité commune mais spécifique à un type (c'est-à-dire spécifique à une classe) dans des classes distinctes et en l'exposant par le biais d'une méthode virtuelle (ou d'une interface), vous permettez aux parties internes de votre programme de déléguer cette décision à une personne située plus haut dans la hiérarchie d'appel (potentiellement à un seul endroit du code), ce qui facilite grandement les tests (mocking), l'extensibilité et la maintenance :

// 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 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());

Des modèles qui ne diffèrent que par leur if les conditions peuvent être réutilisées

En ce qui concerne l'argument du remplacement if avec un "prédicat" dans votre question, Haines voulait probablement mentionner que parfois des modèles similaires existent dans votre code, qui ne diffèrent que par leurs expressions conditionnelles. Les expressions conditionnelles apparaissent en conjonction avec if mais l'idée générale est d'extraire un motif répétitif dans une méthode distincte, en laissant l'expression comme paramètre. C'est ce que fait déjà LINQ, généralement résultant en 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, ce qui vous laisse 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 dispose déjà d'un grand nombre 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, le if a complètement "disparu", bien que :

  1. pour être honnête, le problème n'était pas dans la if par lui-même, mais dans l'ensemble du modèle de code (simplement parce qu'il a été dupliqué), et
  2. le site if existe toujours, mais il est écrit à l'intérieur du LINQ Where méthode d'extension, qui a été testée et fermée pour modification. Avoir moins de code propre est toujours une bonne chose : moins de choses à tester, moins de choses qui peuvent mal tourner, et le code est plus simple à suivre, à analyser et à maintenir.

D'énormes séries d'imbrications if / else déclarations

Lorsque vous voyez une fonction qui s'étend sur 1000 lignes et qui comporte des douzaines d'éléments imbriqués if il y a de grandes chances qu'elle puisse être réécrite pour

  1. utiliser une meilleure structure de données et organiser les données d'entrée d'une manière plus appropriée (par exemple, une table de hachage, qui fera correspondre une valeur d'entrée à une autre en un seul appel),
  2. utiliser une formule, une boucle, ou parfois simplement une fonction existante qui exécute la même logique en 10 lignes ou moins (par ex. cet exemple notoire me vient à l'esprit, mais l'idée générale s'applique à d'autres cas),
  3. utiliser des clauses de garde pour éviter l'imbrication (les clauses de garde donnent plus de confiance dans l'état des variables tout au long de la fonction, car elles se débarrassent des cas exceptionnels dès que possible),
  4. au moins remplacer par un switch le cas échéant.

Refaire le code quand vous le sentez, mais ne pas sur-ingénier.

Ayant dit tout cela, vous ne devrait pas passer des nuits blanches que d'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. Avec le temps, certains schémas émergent et conduisent à modifier les mêmes clauses encore et encore.

2 votes

Bel exemple de correspondance de motifs, avec seulement un petit problème dans le résultat : sum / people.Count est incorrect, car il divise la somme partielle par le nombre total de personnes. Considérons une seule femme dans une équipe de 15 programmeurs. L'âge réel de la femme est de 45 ans, mais en divisant par 15, l'âge moyen des femmes est de 3 ans, ce qui n'est pas un âge moyen correct...

92voto

flybywire Points 36050

Il y a un autre sens dans lequel if peut être maléfique : quand elle vient à la place 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 en fait, 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 d'avoir le statut de goto.

7 votes

Je suis d'accord avec celui-ci. C'est le même problème que j'ai avec la déclaration de l'interrupteur. Dans ce cas, nous pouvons utiliser un modèle de stratégie.

1 votes

J'ai eu un professeur qui disait exactement ça - le polymorphisme doit être utilisé pour cacher les types. if s. Il a même mis en garde contre l'utilisation de `is(obj is Foo), car cette réflexion impliquait un défaut de conception.

1 votes

+1 pour remplacer if par une solution d'héritage plus générique, mais je pense que Corey a parlé de FP plus qu'autre chose.

39voto

Jordan Parmer Points 12286

Une bonne citation de Code Complete :

Code comme si celui qui maintient votre programme était un psychopathe violent qui sait où vous vivez.
- Anonyme

Autrement dit, restez simple. Si la lisibilité de votre application est améliorée par l'utilisation d'un prédicat dans un domaine particulier, utilisez-le. Sinon, utilisez le "si" et passez à autre chose.

28 votes

Ah, mais la citation de Code Complete contient une instruction if et est donc mauvaise :-)

7 votes

@JasonBaker votre commentaire a aussi la déclaration "que" donc votre commentaire aussi est mauvais. Arrêtons la récursion ici :)

1 votes

@JordanParmer Je suis d'accord avec vous. J'ai vu des gens éviter ifs dans des cas qui vont complètement à l'encontre du principe KISS.

18voto

Kyle Rozendo Points 15606

Je pense que ça 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 les plus grands if et ensuite, si l'option d'utiliser un prédicat pour les grandes opérations (lorsque cela a du sens, sinon votre code sera un cauchemar à maintenir), utilisez-la.

Ce type semble avoir été un peu pédant à mon goût. Remplacer tous les if avec des prédicats n'est que pure folie.

9 votes

Je ne suis pas fan des déclarations d'interrupteurs. Je préfère utiliser le pattern strategy à la place quand je le peux.

9 votes

Je suis avec Kyle, c'est juste des paroles en l'air. Vous ne pouvez pas programmer sans conditionnelles de la même manière que vous pouvez programmer sans GOTOs. Les commutateurs, les prédicats, les tableaux de répartition, etc., ne sont que du sucre syntaxique pour IF.

0 votes

Au-delà de cela, les sauts conditionnels sont plus performants que les consultations de tables V et les appels de méthodes.

16voto

Keith Bloom Points 1564

Il y a le Anti-If qui a débuté plus tôt dans l'année. L'hypothèse principale étant que de nombreuses instructions if imbriquées peuvent souvent être remplacées par le polymorphisme.

Je serais intéressé de voir un exemple d'utilisation du prédicat à la place. Est-ce que cela s'apparente davantage à de la programmation fonctionnelle ?

2 votes

Je n'avais même pas entendu parler de la campagne anti-IV. Ça semble un peu extrême, mais je suis d'accord avec les principes de base de ce qu'ils disent.

7 votes

Cela ressemble à une autre sous-religion à but lucratif créée dans l'espoir d'inciter les codeurs à acheter des livres sur un sujet qu'ils apprendront mieux et plus complètement avec la pratique.

3 votes

On dirait qu'ils ne sont pas anti-if, ils sont anti-switch-écrit comme-if.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X