Examiner les caractéristiques des conditions complexes

Terminée

Les développeurs n’ont pas l’intention d’écrire un enchevêtrement compliqué d’instructions if à partir de zéro. Au lieu de cela, les conditions complexes s’accumulent progressivement à mesure qu’une base de code évolue. Comprendre ce processus évolutif peut vous aider à identifier ces modèles dans vos propres projets (et idéalement à les empêcher au début).

Émergence des conditionnels complexes

Les conditions complexes proviennent souvent d’une combinaison de facteurs :

  • Ajouts de fonctionnalités incrémentielles : le code commence souvent par une structure de décision simple pour un scénario simple. Au fil du temps, de nouvelles fonctionnalités ou exigences sont ajoutées. Chaque fois, au lieu de redéfinir la logique, un développeur peut choisir le chemin de résistance minimale : ajoutez simplement une autre if ou étendez la condition existante. Par exemple, une routine de traitement des commandes peut commencer par vérifier if (order.Total > 100) { applyDiscount(); }. Plus tard, une remise spéciale pour les clients VIP est ajoutée comme autre condition, puis une promotion de vacances est ajoutée comme condition imbriquée à l’intérieur de la remise VIP, et ainsi de suite. Chaque changement individuel semble petit, mais ils se composent d’un filet complexe de conditions.

  • Correctifs de bogues de cas de périphérie : les correctifs rapides pour les bogues constituent une autre source courante. Supposons qu’une révision d’assurance qualité génère un ticket qui décrit le problème suivant : « si le compte de l’utilisateur est verrouillé et qu’il essaie une réinitialisation de mot de passe, le système se comporte de manière incorrecte ». Un développeur peut résoudre ce problème en insérant un contrôle conditionnel ciblé dans le code. Par exemple, le code qui implémente la logique suivante : « si le compte est verrouillé et le mot de passe réinitialisé, alors faire X ». Cette correction fonctionne et le code réussit les tests associés. Toutefois, il ajoute une autre branche à la logique. Pendant la durée du projet, il est possible de nombreux correctifs de cas de périphérie s’accumulent, chacun ajoutant un peu plus d’imbrication ou de complexité. Ce qui a commencé en tant que simple if/else peut se transformer en arborescence à plusieurs branches, car des cas spéciaux sont ajoutés sans refactorisation.

  • Exigences en évolution : les exigences dépassent souvent la conception d’origine du code. Considérez un système d’approbation de prêt qui prend une décision initiale en vérifiant quelques métriques de base (score de crédit et revenu). Au fur et à mesure que l’entreprise s’étend, elle introduit davantage de règles : gestion spéciale pour les emprunteurs pour la première fois, approbation conditionnelle avec des garanties, règles différentes pour différents types de prêts, vérifications réglementaires, etc. Si le code d’origine n’est pas restructuré, l’inclination naturelle est d'imbriquer davantage les instructions if. Par exemple, à l’intérieur du if (creditOK) bloc, ajoutez if (hasCollateral) ... else ...et dans ce bloc, peut-être un autre if pour un indicateur réglementaire. Chaque nouvelle règle augmente l’imbrication ou ajoute de nouvelles branches au même niveau. Au fil des mois ou des années, la fonction d’approbation de prêt se transforme en une méthode géante comptant des centaines de lignes, avec une multitude de conditions couvrant chaque scénario rencontré par l'activité.

  • Manque de refactorisation périodique : les problèmes de complexité du code se développent lorsque des ajouts incrémentiels se produisent sans révisions qui nettoient la structure de code. Il est courant dans le développement rapide de déprioritiser la refactorisation (« il fonctionne, ne le touchons pas »). Par conséquent, la logique conditionnelle qui devrait être repensée (répartie en fonctions plus petites, ou transformée en table de configuration, etc.) reste sous une forme de plus en plus maladroite. Au moment où quelqu’un remarque à quel point le code est difficile, la fonction est si fragile que les gens hésitent à le refactoriser – une accumulation classique de dettes techniques.

Signes de conditions trop complexes

En tant que développeur, vous devez être à l’affût des indicateurs rouges dans le code qui suggèrent que la complexité conditionnelle est hors de main :

  • Niveaux d’imbrication profonde : les fonctions avec plus de 2 à 3 niveaux d’instructions imbriquées if (en particulier avec des blocs entrelacés else ) sont des candidats forts. Visuellement, le code forme une flèche en diagonale vers la droite, ce qui rend difficile d’aligner la logique dans votre esprit. Si vous vous retrouvez à compter des accolades ou des mises en retrait pour déterminer les paires else qui sont associées à if, il s’agit d’un mauvais signe.

  • Longues chaînes de else-if ou de cas de switch : une série de else if (...) { ... } else if (...) { ... } ... qui s'étend sur des dizaines de lignes peut indiquer que le code gère de nombreuses variantes à un seul endroit. Parfois, une longue switch instruction avec de nombreux cas peut être équivalente. Ces structures peuvent souvent être simplifiées ou divisées en morceaux plus petits (ou mappages pilotés par les données) s’ils représentent de nombreuses conditions statiques.

  • Expressions booléennes complexes : conditions qui combinent de nombreux termes, par exemple : if ((A && B && !C) || (D && (E || !F))) { ... } ces expressions sont difficiles à lire et encore plus difficiles à obtenir correctement. Si vous voyez une logique conditionnelle avec plusieurs opérateurs && et || mélangés à des négations !, elle peut bénéficier de la simplification (par exemple, en fractionnant en sous-conditions plus claires ou en utilisant une variable explicative).

  • Expressions booléennes complexes : conditions qui combinent de nombreux termes, par exemple : if ((A && B && !C) || (D && (E || !F))) { ... } ces expressions sont difficiles à lire et encore plus difficiles à obtenir correctement. Si vous voyez une logique conditionnelle avec plusieurs opérateurs && et || mélangés à des négations !, elle peut bénéficier de la simplification (par exemple, en fractionnant en sous-conditions plus claires ou en utilisant une variable explicative).

  • Vérifications répétées et duplication de code : recherchez les cas où la même condition ou le même code similaire apparaît dans plusieurs branches. Par exemple, si vous voyez if (user.IsAdmin) dans deux parties différentes d’une if/else échelle, vous pouvez évaluer la condition dans un emplacement unique si vous restructurez la logique. Vous pouvez également trouver deux branches d’une condition qui contiennent une logique de code similaire avec de légères différences. Si vous remarquez ce modèle, le conditionnel peut être refactorisé pour éviter la duplication.

  • Utilisation de variables « flag » pour contrôler le flux : parfois, les développeurs introduisent des indicateurs temporaires comme solution de contournement pour une logique complexe (par exemple). bool isValid = false; ... if (condition) { isValid = true; } ... if (isValid) { ... } Même si l’utilisation d’indicateurs n’est pas identique aux conditions d’imbrication, il s’agit souvent d’une réponse à la complexité : le code n’a pas pu facilement effectuer ce dont il a besoin en une seule fois, de sorte qu’il définit un indicateur à vérifier plus tard. Vous pourriez être en mesure d’éliminer ce modèle en restructurant des conditions ou en utilisant des retours anticipés.

Résumé

Les conditions complexes proviennent souvent des modifications incrémentielles, des correctifs de bogues et des exigences en constante évolution sans accompagner la refactorisation. Les signes de conditions trop complexes incluent l’imbrication approfondie, les longues chaînes de conditions, les expressions booléennes complexes, les vérifications répétées et l’utilisation de variables d’indicateur. La reconnaissance de ces modèles est la première étape de l’amélioration de la qualité du code grâce à la refactorisation.