Chaque entrée dans les quatre volumes — techniques de refactoring, patrons de conception, odeurs de code et patrons d'architecture — regroupés par famille.
Restructuration contrôlée du code existant — préserve le comportement, améliore la conception.
Problème : une méthode ne dispose pas de suffisamment de données pour effectuer certaines actions. Solution : créez un nouveau paramètre afin de transmettre les données nécessaires.
Problème : Une méthode n’est pas utilisée par d’autres classes ou n’est utilisée qu’au sein de sa propre hiérarchie de classes. Solution : Rendez la méthode privée ou protégée.
Problème : vos méthodes contiennent un groupe de paramètres qui se répète. Solution : remplacez ces paramètres par un objet.
Problème : Plusieurs méthodes effectuent des actions similaires qui ne diffèrent que par leurs valeurs, nombres ou opérations internes. Solution : Combinez ces méthodes en utilisant un paramètre qui transmettra la valeur particulière nécessaire.
Problème : vous récupérez plusieurs valeurs d'un objet, puis vous les passez comme paramètres à une méthode. Solution : essayez plutôt de passer l'objet entier.
Problème : un paramètre n’est pas utilisé dans le corps d’une méthode. Solution : supprimez le paramètre inutilisé.
Problème : la valeur d'un champ ne doit être définie qu'au moment de sa création et ne doit jamais changer par la suite. Solution : supprimez donc les méthodes qui modifient la valeur du champ.
Problème : Le nom d’une méthode n’explique pas ce que fait la méthode. Solution : Renommez la méthode.
Problème : vous avez un constructeur complexe qui fait autre chose que simplement affecter les valeurs des paramètres aux champs de l'objet. Solution : créez une méthode de fabrique et utilisez-la pour remplacer les appels au constructeur.
Problème : une méthode renvoie une valeur spéciale qui indique une erreur ? Solution : levez plutôt une exception.
Problème : Vous levez une exception là où un simple test ferait l'affaire ? Solution : Remplacez l'exception par un test conditionnel.
Problème : Une méthode est divisée en parties, chacune étant exécutée en fonction de la valeur d'un paramètre. Solution : Extrayez les différentes parties de la méthode dans leurs propres méthodes et appelez-les à la place de la méthode d'origine.
Problème : appeler une méthode de requête et passer son résultat comme paramètre d'une autre méthode, alors que cette méthode pourrait appeler directement la requête. Solution : au lieu de passer la valeur via un paramètre, essayez de placer un appel de requête à l'intérieur du corps de la méthode.
Problème : avez-vous une méthode qui renvoie une valeur mais modifie aussi quelque chose à l'intérieur d'un objet ? Solution : scindez la méthode en deux méthodes distinctes. Comme on peut s'y attendre, l'une doit renvoyer la valeur et l'autre modifier l'objet.
Problème : vous avez une association bidirectionnelle entre des classes, mais l'une des classes n'utilise pas les fonctionnalités de l'autre. Solution : supprimez l'association inutilisée.
Problème : vous avez un objet référence trop petit et trop rarement modifié pour justifier la gestion de son cycle de vie. Solution : transformez-le en objet valeur.
Problème : Vous avez deux classes qui ont chacune besoin d'utiliser les fonctionnalités de l'autre, mais l'association entre elles n'est qu'unidirectionnelle. Solution : Ajoutez l'association manquante à la classe qui en a besoin.
Problème : vous avez de nombreuses instances identiques d'une même classe que vous devez remplacer par un seul objet. Solution : convertissez les objets identiques en un unique objet de référence.
Problème : des données métier sont-elles stockées dans des classes responsables de l’interface graphique ? Solution : il est alors judicieux de séparer les données dans des classes distinctes, en assurant la liaison et la synchronisation entre la classe métier et l’interface graphique.
Problème : Une classe contient un champ de type collection ainsi qu'un simple getter et un simple setter pour manipuler la collection. Solution : Rendez la valeur renvoyée par le getter en lecture seule et créez des méthodes pour ajouter/supprimer des éléments de la collection.
Problème : vous avez un champ public. Solution : rendez le champ privé et créez des méthodes d'accès pour celui-ci.
Problème : Vous avez un tableau qui contient différents types de données. Solution : Remplacez le tableau par un objet qui aura des champs distincts pour chaque élément.
Problème : une classe (ou un groupe de classes) contient un champ de données. Ce champ possède son propre comportement et ses propres données associées. Solution : créez une nouvelle classe, placez-y l'ancien champ et son comportement, et stockez l'objet de cette classe dans la classe d'origine.
Problème : votre code utilise un nombre qui a une certaine signification. Solution : remplacez ce nombre par une constante portant un nom lisible qui explique la signification du nombre.
Problème : vous avez des sous-classes qui ne diffèrent que par leurs méthodes (renvoyant des constantes). Solution : remplacez ces méthodes par des champs dans la classe parente et supprimez les sous-classes.
Problème : Une classe possède un champ qui contient un code de type. Les valeurs de ce type ne sont pas utilisées dans des conditions d'opérateur et n'affectent pas le comportement du programme. Solution : Créez une nouvelle classe et utilisez ses objets à la place des valeurs du code de type.
Problème : vous avez un type codé qui influe sur le comportement mais vous ne pouvez pas recourir à des sous-classes pour vous en débarrasser. Solution : remplacez le code de type par un objet d'état. S'il faut remplacer la valeur d'un champ contenant le code de type, un autre objet d'état est « branché » à la place.
Problème : Vous avez un type codé qui affecte directement le comportement du programme (les valeurs de ce champ déclenchent différents codes dans des conditions). Solution : Créez des sous-classes pour chaque valeur du type codé. Puis extrayez les comportements pertinents de la classe d'origine vers ces sous-classes. Remplacez le code de contrôle de flux par du polymorphisme.
Problème : vous accédez directement aux champs privés à l'intérieur d'une classe. Solution : créez un getter et un setter pour le champ, et utilisez-les exclusivement pour accéder au champ.
Problème : vous avez une hiérarchie de classes dans laquelle une sous-classe est pratiquement identique à sa superclasse. Solution : fusionnez la sous-classe et la superclasse.
Problème : plusieurs clients utilisent la même partie de l'interface d'une classe. Autre cas : une partie de l'interface est identique dans deux classes. Solution : déplacez cette portion identique dans sa propre interface.
Problème : une classe possède des fonctionnalités qui ne sont utilisées que dans certains cas. Solution : créez une sous-classe et utilisez-la dans ces cas.
Problème : Vous avez deux classes qui partagent des champs et des méthodes communs. Solution : Créez une superclasse commune pour elles et déplacez-y tous les champs et méthodes identiques.
Problème : Vos sous-classes implémentent des algorithmes qui contiennent des étapes similaires dans le même ordre. Solution : Déplacez la structure de l'algorithme et les étapes identiques vers une superclasse, et laissez l'implémentation des étapes différentes dans les sous-classes.
Problème : vos sous-classes ont des constructeurs dont le code est en grande partie identique. Solution : créez un constructeur dans la superclasse et déplacez-y le code commun aux sous-classes. Appelez le constructeur de la superclasse dans les constructeurs des sous-classes.
Problème : deux classes possèdent le même champ. Solution : supprimez le champ des sous-classes et déplacez-le vers la super-classe.
Problème : Vos sous-classes ont des méthodes qui effectuent un travail similaire. Solution : Rendez les méthodes identiques, puis déplacez-les vers la superclasse appropriée.
Problème : un champ n'est-il utilisé que dans quelques sous-classes ? Solution : déplacez le champ vers ces sous-classes.
Problème : Un comportement implémenté dans une superclasse n’est-il utilisé que par une seule (ou quelques) sous-classe(s) ? Solution : Déplacez ce comportement vers les sous-classes.
Problème : une classe contient de nombreuses méthodes simples qui délèguent à toutes les méthodes d’une autre classe. Solution : faites de la classe un héritier du délégué, ce qui rend les méthodes de délégation inutiles.
Problème : vous avez une sous-classe qui n'utilise qu'une partie des méthodes de sa superclasse (ou il est impossible d'hériter des données de la superclasse). Solution : créez un champ, placez-y un objet de la superclasse, déléguez les méthodes à cet objet et supprimez l'héritage.
Problème : vous avez plusieurs conditions qui mènent au même résultat ou à la même action. Solution : regroupez toutes ces conditions en une seule expression.
Problème : un code identique se retrouve dans toutes les branches d’une condition. Solution : déplacez le code en dehors de la condition.
Problème : Vous avez une condition complexe (if-then/else ou switch). Solution : Décomposez les parties compliquées de la condition en méthodes distinctes : la condition, le then et le else.
Problème : pour qu'une portion de code fonctionne correctement, certaines conditions ou valeurs doivent être vraies. Solution : remplacez ces hypothèses par des vérifications d'assertion explicites.
Problème : comme certaines méthodes renvoient null au lieu d’objets réels, votre code comporte de nombreuses vérifications de null. Solution : au lieu de null, renvoyez un objet Null qui présente le comportement par défaut.
Problème : vous avez une variable booléenne qui sert de drapeau de contrôle pour plusieurs expressions booléennes. Solution : au lieu de la variable, utilisez break, continue et return.
Problème : vous avez une instruction conditionnelle qui exécute diverses actions selon le type ou les propriétés d’un objet. Solution : créez des sous-classes correspondant aux branches de la conditionnelle. Dans chacune, créez une méthode commune et déplacez-y le code de la branche correspondante de la conditionnelle. Remplacez ensuite la conditionnelle par l’appel de la méthode appropriée. Le résultat est que la bonne implémentation sera obtenue par polymorphisme en fonction de la classe de l’objet.
Problème : Vous avez un groupe de conditions imbriquées et il est difficile de déterminer le flux normal d'exécution du code. Solution : Isolez toutes les vérifications spéciales et les cas limites dans des clauses distinctes et placez-les avant les vérifications principales. Idéalement, vous devriez obtenir une liste « plate » de conditions, les unes après les autres.
Problème : lorsqu’une seule classe fait le travail de deux, cela devient malcommode. Solution : créez plutôt une nouvelle classe et placez-y les champs et les méthodes responsables de la fonctionnalité concernée.
Problème : le client obtient l'objet B à partir d'un champ ou d'une méthode de l'objet A. Le client appelle ensuite une méthode de l'objet B. Solution : créez une nouvelle méthode dans la classe A qui délègue l'appel à l'objet B. Désormais, le client ne connaît plus la classe B et n'en dépend plus.
Problème : Une classe ne fait presque rien et n’est responsable de rien, et aucune responsabilité supplémentaire n’est prévue pour elle. Solution : Déplacez toutes les fonctionnalités de la classe vers une autre.
Problème : une classe utilitaire ne contient pas la méthode dont vous avez besoin et vous ne pouvez pas l'y ajouter. Solution : ajoutez la méthode à une classe cliente et passez-lui un objet de la classe utilitaire en argument.
Problème : une classe utilitaire ne contient pas certaines méthodes dont vous avez besoin. Mais vous ne pouvez pas ajouter ces méthodes à la classe. Solution : créez une nouvelle classe contenant ces méthodes et faites-en soit une classe fille, soit un wrapper de la classe utilitaire.
Problème : un champ est davantage utilisé dans une autre classe que dans sa propre classe. Solution : créez un champ dans une nouvelle classe et redirigez vers lui tous les utilisateurs de l’ancien champ.
Problème : une méthode est davantage utilisée dans une autre classe que dans la sienne. Solution : créez une nouvelle méthode dans la classe qui utilise le plus la méthode, puis déplacez-y le code de l'ancienne méthode. Transformez le code de la méthode d'origine en une référence à la nouvelle méthode dans l'autre classe, ou bien supprimez-la entièrement.
Problème : Une classe possède trop de méthodes qui se contentent de déléguer à d’autres objets. Solution : Supprimez ces méthodes et obligez le client à appeler directement les méthodes finales.
Problème : vous avez un fragment de code qui peut être regroupé. Solution : déplacez ce code dans une nouvelle méthode (ou fonction) distincte et remplacez l'ancien code par un appel à la méthode.
Problème : vous avez une expression difficile à comprendre. Solution : placez le résultat de l'expression ou de ses parties dans des variables distinctes qui se passent d'explication.
Problème : lorsque le corps d'une méthode est plus parlant que la méthode elle-même, employez cette technique. Solution : remplacez les appels à la méthode par son contenu, puis supprimez la méthode.
Problème : Vous avez une variable temporaire à laquelle est affecté le résultat d’une simple expression, et rien de plus. Solution : Remplacez les références à cette variable par l’expression elle-même.
Problème : une valeur est affectée à un paramètre à l'intérieur du corps d'une méthode. Solution : utilisez une variable locale au lieu d'un paramètre.
Problème : Vous avez une longue méthode dans laquelle les variables locales sont si entremêlées que vous ne pouvez pas appliquer Extraire une méthode. Solution : Transformez la méthode en une classe distincte afin que les variables locales deviennent des champs de la classe. Vous pourrez alors scinder la méthode en plusieurs méthodes au sein de la même classe.
Problème : vous placez le résultat d'une expression dans une variable locale pour l'utiliser plus tard dans votre code. Solution : déplacez l'expression entière dans une méthode distincte et renvoyez-en le résultat. Interrogez la méthode au lieu d'utiliser une variable. Intégrez la nouvelle méthode dans d'autres méthodes si nécessaire.
Problème : vous avez une variable locale utilisée pour stocker différentes valeurs intermédiaires à l'intérieur d'une méthode (à l'exception des variables de boucle). Solution : utilisez des variables différentes pour des valeurs différentes. Chaque variable ne doit être responsable que d'une seule chose en particulier.
Problème : vous souhaitez remplacer un algorithme existant par un nouveau ? Solution : remplacez le corps de la méthode qui implémente l’algorithme par un nouvel algorithme.
Solutions typiques aux problèmes courants de conception logicielle, organisées par intention.
Fabrique abstraite est un patron de conception qui permet de créer des familles d’objets apparentés sans préciser leur classe concrète.
Monteur est un patron de conception de création qui permet de construire des objets complexes étape par étape. Il permet de produire différentes variations ou représentations d’un objet en utilisant le même code de construction.
Fabrique est un patron de conception de création qui définit une interface pour créer des objets dans une classe mère, mais délègue le choix des types d’objets à créer aux sous-classes.
Prototype est un patron de conception qui crée de nouveaux objets à partir d’objets existants sans rendre le code dépendant de leur classe.
Singleton est un patron de conception de création qui garantit que l’instance d’une classe n’existe qu’en un seul exemplaire, tout en fournissant un point d’accès global à cette instance.
L’Adaptateur est un patron de conception structurel qui permet de faire collaborer des objets ayant des interfaces normalement incompatibles.
Le Pont est un patron de conception structurel qui permet de séparer une grosse classe ou un ensemble de classes connexes en deux hiérarchies — abstraction et implémentation — qui peuvent évoluer indépendamment l’une de l’autre.
Composite est un patron de conception structurel qui permet d'agencer les objets dans des arborescences afin de pouvoir traiter celles-ci comme des objets individuels.
Décorateur est un patron de conception structurel qui permet d’affecter dynamiquement de nouveaux comportements à des objets en les plaçant dans des emballeurs qui implémentent ces comportements.
Façade est un patron de conception structurel qui procure une interface offrant un accès simplifié à une librairie, un framework ou à n’importe quel ensemble complexe de classes.
Poids mouche est un patron de conception structurel qui permet de stocker plus d’objets dans la RAM en partageant les états similaires entre de multiples objets, plutôt que de stocker les données dans chaque objet.
La Procuration est un patron de conception structurel qui vous permet d’utiliser un substitut pour un objet. Elle donne le contrôle sur l’objet original, vous permettant d’effectuer des manipulations avant ou après que la demande ne lui parvienne.
Chaîne de responsabilité est un patron de conception comportemental qui permet de faire circuler des demandes dans une chaîne de handlers. Lorsqu'un handler reçoit une demande, il décide de la traiter ou de l’envoyer au handler suivant de la chaîne.
Commande est un patron de conception comportemental qui prend une action à effectuer et la transforme en un objet autonome qui contient tous les détails de cette action. Cette transformation permet de paramétrer des méthodes avec différentes actions, planifier leur exécution, les mettre dans une file d’attente ou d’annuler des opérations effectuées.
Itérateur est un patron de conception comportemental qui permet de parcourir les éléments d’une collection sans révéler sa représentation interne (liste, pile, arbre, etc.).
Médiateur est un patron de conception comportemental qui diminue les dépendances chaotiques entre les objets. Il restreint les communications directes entre les objets et les force à collaborer uniquement via un objet médiateur.
Mémento est un patron de conception comportemental qui permet de sauvegarder et de rétablir l'état précédent d’un objet sans révéler les détails de son implémentation.
L’Observateur est un patron de conception comportemental qui permet de mettre en place un mécanisme de souscription pour envoyer des notifications à plusieurs objets, au sujet d’événements concernant les objets qu’ils observent.
État est un patron de conception comportemental qui permet de modifier le comportement d’un objet lorsque son état interne change. L’objet donne l’impression qu’il change de classe.
Stratégie est un patron de conception comportemental qui permet de définir une famille d’algorithmes, de les mettre dans des classes séparées et de rendre leurs objets interchangeables.
Patron de Méthode est un patron de conception comportemental qui permet de mettre le squelette d’un algorithme dans la classe mère, mais laisse les sous-classes redéfinir certaines étapes de l’algorithme sans changer sa structure.
Visiteur est un patron de conception comportemental qui vous permet de séparer les algorithmes et les objets sur lesquels ils opèrent.
Indicateurs de problèmes plus profonds — et les refactorings qui les traitent.
Deux classes remplissent des fonctions identiques mais ont des noms de méthodes différents.
Si une sous-classe n'utilise qu'une partie des méthodes et propriétés héritées de ses parents, la hiérarchie est bancale. Les méthodes inutiles peuvent simplement rester inutilisées ou être redéfinies pour lever des exceptions.
Vous avez un opérateur switch complexe ou une suite d'instructions if.
Les champs temporaires reçoivent leurs valeurs (et sont donc nécessaires aux objets) uniquement dans certaines circonstances. En dehors de ces circonstances, ils sont vides.
Une méthode est truffée de commentaires explicatifs.
Une classe de données désigne une classe qui ne contient que des champs et des méthodes rudimentaires pour y accéder (getters et setters). Ce ne sont que de simples conteneurs de données utilisés par d'autres classes. Ces classes ne contiennent aucune fonctionnalité supplémentaire et ne peuvent pas opérer de manière autonome sur les données qu'elles possèdent.
Une variable, un paramètre, un champ, une méthode ou une classe n'est plus utilisé (généralement parce qu'il est obsolète).
Deux fragments de code se ressemblent presque à l'identique.
Comprendre et maintenir des classes coûte toujours du temps et de l'argent. Donc si une classe n'en fait pas assez pour mériter votre attention, elle doit être supprimée.
Il existe une classe, une méthode, un champ ou un paramètre inutilisé.
Il arrive que différentes parties du code contiennent des groupes de variables identiques (comme les paramètres de connexion à une base de données). Ces amas devraient être transformés en classes à part entière.
Une classe contient de nombreux champs, méthodes ou lignes de code.
Une méthode contient trop de lignes de code. En règle générale, toute méthode de plus de dix lignes devrait vous amener à vous poser des questions.
Plus de trois ou quatre paramètres pour une méthode.
Utilisation de primitifs au lieu de petits objets pour des tâches simples (comme les montants monétaires, les plages de valeurs, les chaînes spéciales pour les numéros de téléphone, etc.) Utilisation de constantes pour encoder des informations (comme une constante USER_ADMIN_ROLE = 1 pour désigner les utilisateurs disposant de droits d'administrateur.) Utilisation de constantes de type chaîne comme noms de champs pour les tableaux de données.
Vous vous retrouvez à devoir modifier de nombreuses méthodes sans rapport entre elles lorsque vous apportez des changements à une classe. Par exemple, lors de l'ajout d'un nouveau type de produit, vous devez modifier les méthodes de recherche, d'affichage et de commande des produits.
Chaque fois que vous créez une sous-classe pour une classe, vous vous retrouvez à devoir créer une sous-classe pour une autre classe.
La moindre modification vous oblige à apporter de nombreux petits changements à de nombreuses classes différentes.
Une méthode accède aux données d'un autre objet plus qu'à ses propres données.
Une classe utilise les champs et méthodes internes d'une autre classe.
Dans le code, vous voyez une série d'appels ressemblant à $a->b()->c()->d()
Si une classe n'effectue qu'une seule action, déléguant le travail à une autre classe, pourquoi existe-t-elle ?
Patrons et méthodologies au niveau système — DDD, CQRS, TDD et Spec-Driven Development.
Une approche du développement logiciel qui centre la conception sur un modèle riche du domaine métier, exprimé dans un langage partagé par les ingénieurs et les experts du domaine.
Séparer le modèle qui modifie l'état (commandes) du modèle qui lit l'état (requêtes), afin que chaque côté puisse être modélisé, mis à l'échelle et optimisé indépendamment.
Une discipline de développement dans laquelle on écrit un test qui échoue avant le code qui le fait passer, puis on refactorise — en laissant les tests guider la conception au fil de cycles courts et resserrés.
Rédigez d'abord une spécification explicite et faisant autorité, puis pilotez l'implémentation, les tests et le code généré à partir d'elle — en conservant la spec comme unique source de vérité.