Selon Martin Fowler , le refactoring de code est (c'est moi qui souligne):
Le refactoring est une technique disciplinée pour restructurer un corps de code existant, en modifiant sa structure interne sans changer son comportement externe . Son cœur est une série de petits comportements préservant les transformations. Chaque transformation (appelée «refactoring») ne fait pas grand-chose, mais une séquence de transformations peut produire une restructuration importante. Étant donné que chaque refactoring est petit, il est moins susceptible de mal tourner. Le système est également maintenu en parfait état de fonctionnement après chaque petite refactorisation, ce qui réduit les risques qu'un système puisse être sérieusement endommagé pendant la restructuration.
Qu'est-ce que le «comportement extérieur» dans ce contexte? Par exemple, si j'applique le refactoring de la méthode move et que je déplace une méthode vers une autre classe, il semble que je change de comportement externe, n'est-ce pas?
Donc, je veux savoir à quel moment un changement cesse d'être un refactor et devient quelque chose de plus. Le terme «refactoring» peut être utilisé à mauvais escient pour des changements plus importants: y a-t-il un mot différent pour cela?
Mise à jour. Beaucoup de réponses intéressantes sur l'interface, mais le fait de ne pas déplacer la refactorisation de méthode ne changerait-il pas l'interface?
la source
Réponses:
"Externe" dans ce contexte signifie "observable par les utilisateurs". Les utilisateurs peuvent être humains dans le cas d'une application, ou d'autres programmes dans le cas d'une API publique.
Donc, si vous déplacez la méthode M de la classe A à la classe B, et que les deux classes sont au cœur d'une application, et qu'aucun utilisateur ne peut observer de changement dans le comportement de l'application en raison du changement, vous pouvez à juste titre l'appeler refactoring.
Si, OTOH, un autre sous-système / composant de niveau supérieur change de comportement ou se casse en raison du changement, cela est en effet (généralement) observable par les utilisateurs (ou au moins par les administrateurs système qui vérifient les journaux). Ou si vos classes faisaient partie d'une API publique, il peut y avoir du code tiers qui dépend du fait que M fasse partie de la classe A, pas B. Donc, aucun de ces cas n'est refactoring au sens strict.
En effet, c'est une conséquence triste mais attendue du refactoring devenu à la mode. Les développeurs ont fait un remaniement de code de manière ad hoc depuis des lustres, et il est certainement plus facile d'apprendre un nouveau mot à la mode que d'analyser et de changer des habitudes enracinées.
J'appellerais cela une refonte .
Mise à jour
De quoi? Les classes spécifiques, oui. Mais ces classes sont-elles directement visibles du monde extérieur d'une manière ou d'une autre? Sinon - car ils sont à l'intérieur de votre programme, et ne font pas partie de l'interface externe (API / GUI) du programme - aucune modification apportée n'est observable par des parties externes (à moins que la modification ne casse quelque chose, bien sûr).
Je pense qu'il y a une question plus profonde au-delà de cela: une classe spécifique existe-t-elle en tant qu'entité indépendante par elle-même? Dans la plupart des cas, la réponse est non : la classe n'existe que dans le cadre d'un composant plus large, un écosystème de classes et d'objets, sans lequel elle ne peut pas être instanciée et / ou est inutilisable. Cet écosystème n'inclut pas seulement ses dépendances (directes / indirectes), mais aussi d'autres classes / objets qui en dépendent. En effet, sans ces classes de niveau supérieur, la responsabilité associée à notre classe peut être vide de sens / inutile pour les utilisateurs du système.
Par exemple, dans notre projet qui traite de la location de voitures, il y a un
Charge
classe. Cette classe n'a aucune utilité pour les utilisateurs du système en elle-même, car les agents de location et les clients ne peuvent pas faire grand-chose avec une charge individuelle: ils traitent les contrats de location dans leur ensemble (qui incluent un tas de différents types de charges) . Les utilisateurs sont surtout intéressés par la somme totale de ces frais, qu'ils doivent payer au final; l'agent est intéressé par les différentes options du contrat, la durée de la location, le groupe de véhicules, le package d'assurance, les articles supplémentaires, etc. etc. sélectionnés, qui (via des règles commerciales sophistiquées) régissent les frais présents et la manière dont le paiement final est calculé hors de ceux-ci. Et les représentants des pays / analystes commerciaux se soucient des règles commerciales spécifiques, de leur synergie et de leurs effets (sur les revenus de l'entreprise, etc.).Récemment, j'ai refactorisé cette classe, renommant la plupart de ses champs et méthodes (pour suivre la convention de nommage Java standard, qui était totalement négligée par nos prédécesseurs). Je prévois aussi refactoring plus pour remplacer
String
etchar
champs avec plus appropriésenum
etboolean
types. Tout cela va certainement changer l'interface de la classe, mais (si je fais correctement mon travail), rien ne sera visible aux utilisateurs de notre application. Aucun d'entre eux ne se soucie de la façon dont les charges individuelles sont représentées, même s'ils connaissent sûrement le concept de charge. J'aurais pu sélectionner comme exemple une centaine d'autres classes ne représentant aucun concept de domaine, donc étant même conceptuellement invisible pour les utilisateurs finaux, mais j'ai pensé qu'il était plus intéressant de choisir un exemple où il y a au moins une certaine visibilité au niveau du concept. Cela montre bien que les interfaces de classe ne sont que des représentations de concepts de domaine (au mieux), pas la vraie chose *. La représentation peut être modifiée sans affecter le concept. Et les utilisateurs ont et comprennent seulement le concept; c'est notre tâche de faire la correspondance entre concept et représentation.* Et on peut facilement ajouter que le modèle de domaine, que notre classe représente, n'est lui-même qu'une représentation approximative de quelque "chose réelle" ...
la source
Externe signifie simplement interface dans son vrai sens lingual. Considérez une vache pour cet exemple. Tant que vous nourrissez des légumes et obtenez du lait comme valeur de retour, vous ne vous souciez pas du fonctionnement de ses organes internes. Maintenant, si Dieu change les organes internes des vaches, de sorte que son sang devienne bleu, tant que le point d'entrée et le point de sortie (bouche et lait) ne changent pas, cela peut être considéré comme une refactorisation.
la source
Pour moi, le refactoring a été plus productif / confortable lorsque les limites ont été fixées par des tests et / ou par des spécifications formelles.
Ces limites sont suffisamment rigides pour que je me sente en sécurité sachant que si je traverse de temps en temps, il sera détecté assez tôt pour que je n'aie pas à annuler beaucoup de changements pour récupérer. D'un autre côté, ceux-ci donnent une marge de manœuvre suffisante pour améliorer le code sans se soucier de changer les comportements non pertinents.
Ce que j'aime particulièrement, c'est que ces types de frontières sont pour ainsi dire adaptatifs . Je veux dire, 1) je fais le changement et vérifie qu'il est conforme aux spécifications / tests. Ensuite, 2) il est passé au QA ou aux tests utilisateurs - notez ici, il peut toujours échouer car quelque chose manque dans les spécifications / tests. OK, si 3a) les tests réussissent, j'ai terminé, très bien. Sinon, si 3b) les tests échouent, je 4) annule la modification et 5) ajoute des tests ou clarifie les spécifications afin que la prochaine fois, cette erreur ne se répète pas. Notez que peu importe si les tests réussissent ou échouent, je gagne quelque chose - que ce soit du code / des tests / des spécifications s'améliore - mes efforts ne se transforment pas en gaspillage total.
Quant aux autres types de frontières - jusqu'à présent, je n'ai pas eu beaucoup de chance.
"Observable pour les utilisateurs" est une valeur sûre si l' on a une discipline pour le suivre - ce qui pour moi a toujours impliqué beaucoup d'efforts dans l'analyse des tests existants / création de nouveaux tests - peut-être trop d'efforts. Une autre chose que je n'aime pas dans cette approche est que la suivre aveuglément peut s'avérer trop restrictive. - Ce changement est interdit car avec lui le chargement des données prendra 3 sec au lieu de 2. - Uhm bien que diriez-vous de vérifier avec les utilisateurs / UX expert si cela est pertinent ou non? - Pas question, tout changement de comportement observable est interdit, point final. Sûr? tu paries! productif? pas vraiment.
Un autre que j'ai essayé est de garder la logique du code (la façon dont je le comprends lors de la lecture). Sauf pour les changements les plus élémentaires (et généralement pas très fructueux), celui-ci a toujours été une boîte de vers ... ou devrais-je dire une boîte de bugs? Je veux dire des bugs de régression. Il est tout simplement trop facile de casser quelque chose d'important lorsque vous travaillez avec du code spaghetti.
la source
Le livre Refactoring est assez fort dans son message que vous ne pouvez effectuer de refactoring que lorsque vous avez une couverture de test unitaire .
Ainsi, vous pourriez dire que tant que vous ne cassez aucun de vos tests unitaires, vous refactorisez . Lorsque vous passez des tests, vous ne refactorisez plus.
Mais: que diriez-vous de ces refactorings simples tels que le changement des noms de classes ou de membres? Ne cassent-ils pas les tests?
Oui, et dans chaque cas, vous devrez déterminer si cette rupture est importante. Si votre SUT est une API / SDK publique, un changement de nom est en effet un changement de rupture. Sinon, c'est probablement OK.
Cependant, considérez que souvent, les tests échouent non pas parce que vous avez changé le comportement qui vous tient vraiment à cœur, mais parce que les tests sont des tests fragiles .
la source
La meilleure façon de définir un «comportement externe», dans ce contexte, peut être des «cas de test».
Si vous refactorisez le code et qu'il continue de passer les cas de test (définis avant le refactoring), le refactoring n'a pas modifié le comportement externe. Si un ou plusieurs cas de test échouent, vous avez modifié le comportement externe.
C'est du moins ce que je comprends des divers livres publiés sur le sujet (par exemple, Fowler's).
la source
La frontière serait la ligne qui indique entre ceux qui développent, maintiennent, soutiennent le projet et ceux qui en sont les utilisateurs autres que les supporters, les mainteneurs, les développeurs. Donc, pour le monde extérieur, le comportement est le même alors que les structures internes derrière le comportement ont changé.
Il devrait donc être OK de déplacer des fonctions entre les classes tant qu'elles ne sont pas celles que les utilisateurs voient.
Tant que le code retravaillé ne change pas les comportements externes, n'ajoute pas de nouvelles fonctions ou ne supprime pas les fonctions existantes, je suppose qu'il est OK d'appeler le retravail un refactoring.
la source
Avec tout le respect que je vous dois, nous devons nous rappeler que les utilisateurs d'une classe ne sont pas les utilisateurs finaux des applications qui sont construites avec la classe, mais plutôt les classes qui sont implémentées en utilisant - soit en appelant soit en héritant de - la classe en cours de refactorisation. .
Lorsque vous dites que "le comportement externe ne doit pas changer", vous voulez dire qu'en ce qui concerne les utilisateurs, la classe se comporte exactement comme avant. Il se peut que l'implémentation d'origine (non refactorisée) soit une seule classe et que la nouvelle implémentation (refactorisée) ait une ou plusieurs super-classes sur lesquelles la classe est construite, mais les utilisateurs ne voient jamais l'intérieur (l'implémentation) qu'ils voir uniquement l'interface.
Donc, si une classe a une méthode appelée "doSomethingAmazing", cela n'a pas d'importance pour l'utilisateur si cela est implémenté par la classe à laquelle ils se réfèrent, ou par une superclasse sur laquelle cette classe est construite. Tout ce qui compte pour l'utilisateur, c'est que le nouveau (refactorisé) "doSomethingAmazing" a le même résultat que l'ancien (non refactoré) "doSomethingAmazing.
Cependant, ce que l'on appelle le refactoring dans de nombreux cas n'est pas un vrai refactoring, mais peut-être une réimplémentation qui est effectuée pour rendre le code plus facile à modifier pour ajouter une nouvelle fonctionnalité. Donc, dans ce dernier cas de (pseudo) refactoring, le nouveau code (refactoré) fait en fait quelque chose de différent, ou peut-être quelque chose de plus que l'ancien.
la source
Par «comportement externe», il parle principalement de l'interface publique, mais cela englobe également les sorties / artefacts du système. (c'est-à-dire que vous avez une méthode qui génère un fichier, changer le format du fichier changerait le comportement externe)
e: Je considérerais la "méthode de déplacement" comme un changement de comportement externe. Gardez à l'esprit ici que Fowler parle de bases de code existantes qui ont été publiées dans la nature. Selon votre situation, vous pourrez peut-être vérifier que votre changement ne casse aucun client externe et poursuivre votre joyeux chemin.
e2: "Alors, quel est le bon mot pour des retouches qui changent le comportement externe?" - Refactoring API, Breaking Change, etc ... son refactoring toujours, il ne suit tout simplement pas les meilleures pratiques pour refactoring une interface publique qui est déjà dans la nature avec les clients.
la source
La «méthode de déplacement» est une technique de refactorisation, pas une refactorisation en soi. Un refactoring est le processus d'application de plusieurs techniques de refactoring aux classes. Là, quand vous dites, j'ai appliqué la "méthode de déplacement" à une classe, vous ne voulez pas dire "j'ai refactorisé (la classe)", vous voulez dire "j'ai appliqué une technique de refactoring sur cette classe". La refactorisation, dans son sens le plus pur, est appliquée à la conception, ou plus spécifiquement, à une partie de la conception d'une application qui peut être considérée comme une boîte noire.
On pourrait dire que "refactoring" utilisé dans le contexte des classes, signifie "technique de refactoring", donc "méthode de déplacement" ne rompt pas la définition de refactoring-le-processus. Sur la même page, le «refactoring» dans le contexte du design, ne casse pas les fonctionnalités existantes dans le code, il «casse» seulement le design (ce qui est de toute façon son but).
En conclusion, "la frontière", mentionnée dans la question, est franchie si vous confondez (mix: D) le refactoring-la-technique avec le refactoring-du-processus.
PS: Bonne question, au fait.
la source
si vous parlez de la factorisation des nombres, vous décrivez le groupe d'entiers qui, multipliés ensemble, sont égaux au nombre de départ. Si nous prenons cette définition pour l'affacturage et l'appliquons au terme de programmation refactoriser, alors le refactoring serait de décomposer un programme en plus petites unités logiques, de sorte que lorsqu'elles sont exécutées en tant que programme, produire la même sortie (avec la même entrée ) comme programme d'origine.
la source
Les limites d'un refactoring sont spécifiques à un refactoring donné. Il ne peut tout simplement pas y avoir une seule réponse et englobe tout. L'une des raisons est que le terme refactoring est lui-même non spécifique.
Vous pouvez refactoriser:
et je suis sûr que plusieurs autres choses.
Le refactoriseur pourrait peut-être être défini comme
la source
Il y a certainement des limites à la mesure dans laquelle vous pouvez refactoriser. Voir: Quand deux algorithmes sont-ils identiques?
Alors n'allez pas trop loin, sinon vous n'aurez aucune confiance dans le résultat. D'un autre côté, l'expérience dicte que vous pouvez souvent remplacer un algorithme par un autre et obtenir les mêmes réponses, parfois plus rapidement. C'est ça la beauté, hein?
la source
Vous pouvez penser à un sens «externe»
Ainsi, toute modification du système qui n'affecte aucun porc peut être considérée comme une refactorisation.
Changer une interface de classe n'est pas un problème, si la classe n'est utilisée que par un seul système qui est construit et maintenu par votre équipe. Cependant, si la classe est une classe publique dans le framework .net utilisé par tous les programmeurs .net, c'est une question très différente.
la source