Introduction : Pourquoi la refactoration et la SOLIDité vont de pair

Chaque système logiciel en développement actif depuis plus de quelques mois accumule inévitablement des dettes techniques. Des corrections rapides, des exigences changeantes et la pression pour expédier de nouvelles fonctionnalités conduisent souvent à des codes fragiles, difficiles à comprendre et difficiles à étendre. Deux pratiques se distinguent comme les antidotes les plus efficaces à cette décroissance : réfactoring et les principes SOLID[.

Refactoring est la technique disciplinée de restructuration du code existant sans modifier son comportement externe. Il ne corrige pas les bugs ou ajoute des fonctionnalités; au lieu de cela, il améliore la structure interne pour que les changements futurs deviennent plus sûrs et plus rapides. Les principes SOLID, introduits par Robert C. Martin, fournissent un ensemble de lignes directrices de conception qui, lorsqu'ils sont suivis, donnent un code de rendement qui est durable, testable et résistant au changement.

En pratique, de nombreuses équipes de développement peinent à appliquer les principes SOLID rétroactivement. Le code original peut être monolithique, étroitement couplé, ou jonché de logique conditionnelle. Sans approche systématique, l'effort pour -faire en sorte que SOLID--S'en sente accablant. C'est là que les techniques de refactoring brillent. En cassant le travail en petites étapes de préservation du comportement, vous pouvez transformer progressivement une base de code jusqu'à ce qu'il s'harmonise avec chaque principe SOLID. Cet article explore des techniques de refactoring concrètes pour chacun des cinq principes, complété par des conseils pratiques sur ce que vous devez chercher et comment procéder.

Comprendre les principes SOLID

Avant de plonger dans des techniques de refacturation, un bref résumé de l'acronyme SOLID va définir la scène :

  • Principe de responsabilité unique (PRS) :[ Une classe ne devrait avoir qu'une seule raison de changer – c'est-à-dire avoir une seule responsabilité bien définie.
  • Principe ouvert/fermé (OCP):[ Les entités logicielles (classes, modules, fonctions) devraient être ouvertes pour l'extension mais fermées pour la modification.
  • Principe de substitution de Liskov (LSP):[ Les objets d'une superclasse doivent être remplaçables par des objets d'une sous-classe sans affecter la justesse du programme.
  • Principe de séparation des interfaces (PSI) :[ Les clients ne devraient pas être obligés de dépendre des interfaces qu'ils n'utilisent pas.
  • Principe d'inversion de la dépendance (DIP) :[ Les modules de haut niveau ne doivent pas dépendre de modules de bas niveau; les deux devraient dépendre d'abstractions.

Chaque principe s'adresse à une sorte spécifique d'odeur de code. SRP combat les classes qui -sont trop au courant. -OCP combat les chaînes conditionnelles cassantes. LSP empêche les hiérarchies d'héritage fragiles. ISP combat les interfaces graisse qui forcent les implémentations de méthodes inutiles.

Refacteur du principe de responsabilité unique

Identification des violations

Par exemple, une classe nommée qui calcule les totaux, formate la facture pour l'affichage, la sauvegarde dans une base de données et envoie un courriel a au moins quatre responsabilités. Tout changement au calcul de la taxe, au formatage HTML, au schéma de stockage ou au contenu de courriel forcera un changement à la même classe. Au fil du temps, la classe devient grande, étroitement couplée et difficile à tester.

Pour repérer ces violations, recherchez des noms de classes qui incluent des mots comme -Manager, -Processeur, -Aide, -Util. -Ces noms cachent souvent plusieurs responsabilités. Aussi, examinez les signatures de la méthode de classe , si certaines méthodes prennent des paramètres qui sont hors de propos pour d'autres méthodes, c'est un autre indice. Une classe qui importe de nombreux paquets ou modules différents est également suspecte.

Techniques de refactoration

La première refacturation pour les PSR est Classe d'extraction. Vous identifiez un ensemble de champs et de méthodes connexes qui forment un concept cohérent et les déplacent dans une nouvelle classe. Par exemple, à partir de la classe , vous pouvez extraire , et . Chaque nouvelle classe a maintenant une seule raison de changer.

Si la logique est dispersée à travers quelques méthodes plutôt qu'une classe entière, utilisez Méthode d'extraction pour isoler un morceau de comportement spécifique. Cela rend la responsabilité plus visible et prépare le terrain pour une classe d'extraction future. Une technique connexe est Méthode de déplacement lorsqu'une méthode semble appartenir plus logiquement à une autre classe que son hôte actuel.

Une autre technique précieuse est Replacer le code en ligne avec l'appel de fonction lorsque vous remarquez une logique répétée qui appartient à un domaine différent. En déplaçant cette logique vers une fonction ou une classe dédiée, vous réduisez la surface de la classe primaire et faites expliciter les responsabilités. Le but est que chaque classe peut être décrite dans une phrase unique sans utiliser le mot -- et.

Application de la refactoration pour le principe ouvert/fermé

Remplacer les conditionnels par le polymorphisme

Le code qui viole le code OCP contient souvent de grandes déclarations ou qui vérifient un type ou un mode. Par exemple, une méthode qui calcule le coût d'expédition à partir d'une chaîne , ou est fermée à de nouvelles méthodes d'expédition.

La refacturation standard ici est Remplacer Conditionnel avec Polymorphisme.Vous créez une classe de base ou une interface abstraite (p. ex. ) avec une méthode .Chaque méthode d'expédition devient une sous-classe béton. Le code client original utilise alors l'abstraction, et de nouvelles méthodes d'expédition sont ajoutées en créant une nouvelle sous-classe – ouverte pour extension, fermée pour modification.

Utilisation des modèles de stratégie et de décorateur

Le modèle Stratégie est le pilote OCP classique. Dans l'étape de refactoring, vous commencez généralement par définir l'interface de stratégie, puis déplacer les branches conditionnelles dans des classes de stratégie distinctes. Enfin, vous injectez la stratégie appropriée dans le client à l'exécution. Cela va souvent de pair avec Classe d'extraction et Introduire l'objet de paramètre pour garder les méthodes de stratégie propres.

Le modèle Décorateur aide lorsque vous devez ajouter un comportement à un objet sans changer sa classe de base. Par exemple, si vous avez une classe qui génère du texte clair, vous pouvez la décorer avec ou sans modifier . La refactorisation vers le modèle de décorateur implique habituellement Extraction Superclass ou Convertir la classe en interface de sorte que le composant de base et les décorateurs partagent une abstraction commune.

Même sans modèles formels, le principe de favoriser la composition par rapport à l'héritage aide OCP. Lorsque vous devez varier votre comportement, composez la classe à partir de petites pièces interchangeables plutôt que de farcer la logique dans la classe elle-même.

Refactoring to Support the Liskov Substitution Principe

Sous-typage et contrats de comportement

Les violations de LSP se présentent souvent comme des méthodes dans une sous-classe qui lancent des exceptions inattendues, retournent où la classe de base renvoie un objet valide, ou affaiblit les conditions préalables et renforce les conditions post. Un exemple classique est une classe qui hérite de mais viole le contrat / parce qu'un carré doit garder les deux dimensions égales.

La première étape de la refacturation consiste à identifier le contrat. Utiliser Introduire l'assertion ou remplacer l'exception par un contrôle préalable pour rendre explicites les contrats implicites. Ensuite, si une sous-classe ne peut honorer le contrat, vous devez rompre l'héritage. Dans le cas Rectangle/Square, le refactoring droit consiste à remplacer l'héritage par une interface commune (p. ex. ) qui n'inclut pas la paire /.

Utilisation d'interfaces pour appliquer le LSP

Une approche pratique consiste à Extraire l'interface[ de la classe de base chaque fois que vous détectez un comportement de sous-classe qui ne s'aligne pas. Ensuite, le client ne dépend que de l'interface. Si les méthodes d'interface sont si étroites que toute implémentation peut les satisfaire, LSP est automatiquement satisfaite. Par exemple, plutôt que d'avoir une classe de base avec une méthode , définir une interface . Les deux et peuvent implémenter comme classe abstraite, mais seulement ] [. Cela évite de forcer à avoir une méthode vide qui lance une exception.

Une autre refacturation utile est Méthode de descente en pression: si une méthode dans une superclasse n'a de sens que pour certaines sous-classes, la déplacer vers ces sous-classes. Cela élimine le risque qu'une sous-classe hérite d'une méthode inappropriée. De même, Push Down Field déplace un état qui n'est pas universellement nécessaire.

Mise en œuvre de la séparation des interfaces par refactoring

Interfaces de partage des graisses

Par exemple, une interface avec , , et force une imprimante texte simple à mettre en œuvre des méthodes de stub. La solution de refactoring est Interface d'extraction[ (ou Interface de split[) pour créer des interfaces plus petites et plus cohérentes : ], , , etc. Chaque client dépend alors seulement des interfaces dont il a réellement besoin.

Lorsque vous divisez, recherchez des groupes de méthodes qui sont souvent utilisés ensemble par des clients spécifiques. Une erreur courante se divise prématurément en de nombreuses interfaces minuscules. Visez des interfaces de rôle : une interface qui représente une seule capacité que peut vouloir un client. Par exemple, une interface pourrait avoir et ; ces deux méthodes sont logiquement couplées et peu susceptibles d'être séparées.

Refactoration du Code client existant

Une fois l'interface de gras divisée, vous devez refactorer chaque client pour qu'il n'implémente que l'interface pertinente.C'est un mélange de Modifier la méthode Signature[ (pour accepter l'interface plus étroite) et Classe de renom[ (pour refléter le nouveau rôle).Vous pouvez aussi devoir séparer les grandes classes d'implémentation : si une classe implémente à la fois et , mais seulement certains clients utilisent la numérisation, il est parfaitement bon pour la classe d'implémenter les deux, tant qu'aucun client n'est obligé de dépendre des deux.

Refactoring pour le principe d'inversion de la dépendance

Abstraction des dépendances

Une violation typique du DIP est une classe de haut niveau, telle que , qui inactive directement une classe de bas niveau comme . Cela force à dépendre de l'implémentation concrète de la base de données, ce qui rend difficile de tester et d'échanger avec un dépôt in-memory. La première refactoration est Extract Interface de la classe de dépendance : créez interface et faites-la implémenter. Changez pour dépendre de l'interface. À ce stade, vous avez encore une inmotion directe cachée – la prochaine étape est Introducte Parameter (injection de constructeur) pour passer la dépendance de l'extérieur.

Injection de dépendance et inversion de contrôle

La technique de refactoring Replacer le constructeur avec la méthode d'usine peut être utilisée lorsque vous ne pouvez pas facilement changer de constructeur. Sinon, utiliser Replacer la référence globale avec le paramètre[ si la dépendance est obtenue à partir d'un simpleton statique ou d'un localisateur de service.

Une fois que vous avez injecté le constructeur, envisagez d'appliquer Extract Method Object si les dépendances injectées sont utilisées dans de nombreuses méthodes – ce qui peut être un signe que la classe elle-même a encore trop de responsabilités.

Les abstractions doivent appartenir au client, et non à l'implémentation concrète.C'est ce qu'on appelle Inversion de propriété. Lorsque l'on refactorise, on définit l'abstraction (interface) dans le même paquet que le module de haut niveau qui l'utilise, et non dans le module de bas niveau. Cela garantit que le module de haut niveau ne dépend pas de quelque chose que le module de bas niveau contrôle. Si votre interface est dans la bibliothèque de bas niveau, on l'inverse en déplaçant la définition de l'interface dans le projet de haut niveau et on fait mettre en œuvre le module de bas niveau (aussi appelé Inversion de de la dépendance[ au niveau du paquet).

Conclusion : Refaire une habitude

Les techniques décrites ici – Classe d'extraction, Remplacer Conditionnel par Polymorphisme, Interface d'extraction, Paramètre d'introduction, et bien d'autres – sont les éléments de construction qui vous permettent de remodeler progressivement une base de code sans la briser. Chaque petite étape réduit la dette technique, rend le code plus compréhensible et ouvre la porte pour faciliter l'extension et les essais.

Pour approfondir votre pratique, étudiez le catalogue des refactorings dans Martin Fowlers .Pour un traitement détaillé des principes SOLID, Robert C. Martin=2]Clean Code blog[ fournit d'excellentes explications. Enfin, rappelez-vous que la refactoration sans tests est dangereuse. Assurez-vous toujours d'avoir une suite solide de tests avant de commencer; utilisez Extraction de la méthode[ et Renommer Variable comme première étape sécuritaire lorsque les tests sont minimes.

Commencez petit : choisissez une classe qui viole le SRP, appliquez la classe Extraire et voyez comment le reste du système réagit. La confiance que vous gagnez vous motivera à aborder le principe suivant. Avec une pratique cohérente, vous internaliserez ces refactorings et commencerez à concevoir un code qui respecte naturellement SOLID dès le début.