web-dev-qa-db-fra.com

Dois-je refactoriser le code marqué comme «ne pas changer»?

Je suis confronté à une base de code assez importante et on m'a donné quelques mois pour refactoriser le code existant. Le processus de refactorisation est nécessaire parce que bientôt nous devrons ajouter de nombreuses nouvelles fonctionnalités à notre produit et pour l'instant nous ne pouvons plus ajouter aucune fonctionnalité sans casser autre chose. En bref: code désordonné, énorme et bogué, que beaucoup d'entre nous ont vu dans leur carrière.

Pendant la refactorisation, je rencontre de temps en temps la classe, la méthode ou les lignes de code qui ont des commentaires comme

Time out défini pour donner au Module A un peu de temps pour faire des choses. Si ce n'est pas chronométré comme ça, il se cassera.

ou

Ne changez pas cela. Croyez-moi, vous allez casser des choses.

ou

Je sais que l'utilisation de setTimeout n'est pas une bonne pratique, mais dans ce cas, je devais l'utiliser

Ma question est: dois-je refactoriser le code lorsque je rencontre de tels avertissements de la part des auteurs (non, je ne peux pas entrer en contact avec les auteurs)?

150
kukis

Il semble que vous refactorisez "juste au cas où", sans savoir exactement lequel les parties de la base de code seront modifiées lorsque le développement de la nouvelle fonctionnalité aura lieu. Sinon, vous saurez s'il y a un réel besoin de refactoriser les modules fragiles, ou si vous pouvez les laisser tels quels.

Pour le dire clairement: je pense que c'est une stratégie de refactorisation vouée à l'échec . Vous investissez du temps et de l'argent de votre entreprise pour quelque chose où personne ne sait si cela va vraiment rapporter un avantage, et vous êtes sur le point d'aggraver les choses en introduisant des bogues dans le code de travail.

Voici une meilleure stratégie: utilisez votre temps pour

  • ajouter des tests automatiques (probablement pas des tests unitaires, mais des tests d'intégration) aux modules à risque. En particulier, les modules fragiles que vous avez mentionnés auront besoin d'une suite de tests complète avant de changer quoi que ce soit.

  • ne refactorisez que les bits dont vous avez besoin pour mettre les tests en place. Essayez de minimiser les modifications nécessaires. La seule exception est lorsque vos tests révèlent des bogues - puis corrigez-les immédiatement (et refactorisez dans la mesure où vous devez le faire en toute sécurité).

  • enseignez à vos collègues le "principe du boy-scout" (AKA "refactoring opportuniste" ), donc quand l'équipe commence à ajouter de nouvelles fonctionnalités (ou à corriger des bugs), ils devraient améliorer et refactoriser exactement les parties du base de code dont ils ont besoin pour changer, pas moins, pas plus.

  • obtenir une copie du livre de Feather "Travailler efficacement avec le code hérité" pour l'équipe.

Donc, quand le moment vient où vous savez pour sûr vous devez changer et refactoriser les modules fragiles (soit à cause du développement de nouvelles fonctionnalités, soit parce que les tests vous avez ajouté à l'étape 1 révéler quelques bugs), puis vous et votre équipe êtes prêts à refactoriser les modules, et à ignorer plus ou moins en toute sécurité ces commentaires d'avertissement.

En réponse à certains commentaires : pour être honnête, si l'on soupçonne qu'un module d'un produit existant est régulièrement à l'origine de problèmes, notamment un module qui est marqué comme "ne touchez pas", je suis d'accord avec vous tous. Il devrait être revu, débogué et probablement refactorisé dans ce processus (avec le soutien des tests que j'ai mentionnés, et pas nécessairement dans cet ordre). Les bogues sont une justification solide du changement, souvent plus fort que les nouvelles fonctionnalités. Cependant, il s'agit d'une décision au cas par cas. Il faut vérifier très attentivement si cela vaut vraiment la peine de changer quelque chose dans un module qui a été marqué comme "ne pas toucher".

224
Doc Brown

Oui, vous devez refactoriser le code avant d'ajouter les autres fonctionnalités.

Le problème avec des commentaires comme ceux-ci est qu'ils dépendent des circonstances particulières de l'environnement dans lequel la base de code s'exécute. La temporisation programmée à un point spécifique peut avoir été vraiment nécessaire lors de sa programmation.

Mais il y a un certain nombre de choses qui pourraient changer cette équation: changements matériels, changements de système d'exploitation, changements non liés dans un autre composant du système, changements dans les volumes de données typiques, vous l'appelez. Il n'y a aucune garantie qu'aujourd'hui, il soit toujours nécessaire ou qu'il soit encore suffisant (l'intégrité de la chose qu'il était censé protéger pourrait avoir été brisée pendant longtemps - sans tests de régression appropriés, vous ne le remarquerez peut-être jamais). C'est pourquoi la programmation d'un délai fixe afin de permettre à un autre composant de se terminer est presque toujours incorrecte et ne fonctionne qu'accidentellement.

Dans votre cas, vous ne connaissez pas les circonstances d'origine et vous ne pouvez pas non plus demander aux auteurs originaux. (Vraisemblablement, vous n'avez pas non plus de test de régression/intégration approprié, ou vous pouvez simplement continuer et laisser vos tests vous dire si vous avez cassé quelque chose.)

Cela pourrait ressembler à un argument pour ne rien changer par prudence; mais vous dites qu'il faudra de toute façon des changements majeurs, de sorte que le danger de bouleverser l'équilibre délicat qui était auparavant atteint à cet endroit est déjà là. Il est préférable de bouleverser le Apple cart maintenant , lorsque la seule chose que vous faites est la refactorisation, et assurez-vous que si les choses se cassent, c'est la refactorisation qui l'a causé, que d'attendre que vous apportiez des modifications supplémentaires simultanément et ne soyez jamais sûr.

140
Kilian Foth

Ma question est: dois-je refactoriser le code lorsque je rencontre de tels avertissements des auteurs

Non, ou du moins pas encore. Vous indiquez que le niveau des tests automatisés est très faible. Vous avez besoin de tests avant de pouvoir refaçonner en toute confiance.

pour l'instant nous ne pouvons plus ajouter aucune fonctionnalité sans casser autre chose

En ce moment, vous devez vous concentrer sur l'augmentation de la stabilité, pas sur la refactorisation. Vous pouvez faire du refactoring dans le cadre de l'augmentation de la stabilité, mais c'est un outil pour atteindre votre objectif réel - une base de code stable.

Il semble que cela soit devenu une base de code héritée, vous devez donc le traiter un peu différemment.

Commencez par ajouter des tests de caractérisation. Ne vous inquiétez pas de toute spécification, ajoutez simplement un test qui affirme le comportement actuel. Cela aidera à empêcher un nouveau travail de casser les fonctionnalités existantes.

Chaque fois que vous corrigez un bogue, ajoutez un cas de test prouvant que le bogue est corrigé. Cela empêche les régressions directes.

Lorsque vous ajoutez une nouvelle fonctionnalité, ajoutez au moins quelques tests de base pour vérifier que la nouvelle fonctionnalité fonctionne comme prévu.

Peut-être obtenir une copie de "Travailler efficacement avec le code hérité"?

On m'a donné quelques mois pour refactoriser le code existant.

Commencez par augmenter la couverture des tests. Commencez par les zones qui cassent le plus. Commencez par les domaines qui changent le plus. Ensuite, une fois que vous avez identifié les mauvais dessins, remplacez-les un par un.

Vous n'avez presque jamais un pour refaire un énorme refactor, mais un flux constant de petits refactors chaque semaine. Un grand refactoriste a un énorme risque de casser des choses et il est difficile de bien le tester.

60
Daenyth

Rappelez-vous la clôture de G. K. Chesterton : ne démontez pas une clôture obstruant une route avant d'avoir compris pourquoi elle a été construite.

Vous pouvez trouver le ou les auteurs du code et les commentaires en question, et les consulter pour obtenir la compréhension. Vous pouvez consulter les messages de validation, les fils de discussion ou les documents s'ils existent. Ensuite, vous pourrez soit refactoriser le fragment marqué, soit noter vos connaissances dans les commentaires afin que la prochaine personne à gérer ce code puisse prendre une décision plus éclairée.

Votre objectif est de comprendre ce que fait le code, pourquoi il a été marqué avec un avertissement à l'époque, et ce qui se passerait si l'avertissement était ignoré.

Avant cela, je ne toucherais pas le code qui est marqué "ne pas toucher".

23
9000

Le code avec des commentaires comme ceux que vous avez montrés serait le meilleur sur ma liste de choses à refactoriser si et seulement si j'ai une raison de le faire. Le fait est que le code pue si mal que vous le sentez même dans les commentaires. Il est inutile d'essayer de briller toute nouvelle fonctionnalité dans ce code, ce code doit mourir dès qu'il doit changer.

Notez également que les commentaires ne sont pas du tout utiles: ils ne donnent qu'un avertissement et aucune raison. Oui, ce sont les signes avant-coureurs autour d'un réservoir de requins. Mais si vous voulez faire quoi que ce soit à proximité, il y a peu de points à essayer de nager avec les requins. À mon humble avis, vous devez d'abord vous débarrasser de ces requins.

Cela dit, vous avez absolument besoin de bons cas de test avant d'oser travailler sur un tel code. Une fois que vous avez ces cas de test, assurez-vous de comprendre chaque petit morceau que vous changez, pour vous assurer que vous ne changez vraiment pas de comportement. Faites-en votre priorité absolue de conserver toutes les particularités comportementales du code jusqu'à ce que vous puissiez prouver qu'elles sont sans effet. N'oubliez pas que vous avez affaire à des requins - vous devez être très prudent.

Donc, dans le cas du délai d'attente: laissez-le jusqu'à ce que vous compreniez précisément ce que le code attend, puis corrigez la raison pour laquelle le délai d'attente a été introduit avant de le supprimer.

Assurez-vous également que votre patron comprend à quel effort vous vous embarquez et pourquoi cela est nécessaire. Et s'ils disent non, ne le faites pas. Vous avez absolument besoin de leur soutien à cet égard.

Je dis allez-y et changez-le. Croyez-le ou non, tous les codeurs ne sont pas un génie et un avertissement comme celui-ci signifie qu'il pourrait être un point à améliorer. Si vous trouvez que l'auteur avait raison, vous pouvez (haleter) DOCUMENT ou EXPLIQUER la raison de l'avertissement.

5
JES

Ce n'est probablement pas une bonne idée de refactoriser le code avec de tels avertissements, si les choses fonctionnent bien comme elles sont.

Mais si vous devez refactoriser ...

Tout d'abord, écrivez quelques tests unitaires et tests d'intégration pour tester les conditions dont les avertissements vous avertissent. Essayez d'imiter les conditions de production autant que possible. Cela signifie mettre en miroir la base de données de production sur un serveur de test, exécuter les mêmes autres services sur la machine, etc ... tout ce que vous pouvez faire. Essayez ensuite votre refactoring (sur une branche isolée, bien sûr). Ensuite, exécutez vos tests sur votre nouveau code pour voir si vous pouvez obtenir les mêmes résultats qu'avec l'original. Si vous le pouvez, alors il est probablement OK pour implémenter votre refactoring en production. Mais soyez prêt à annuler les changements si les choses tournent mal.

J'élargis mes commentaires en réponse parce que je pense que certains aspects du problème spécifique sont soit ignorés, soit utilisés pour tirer des conclusions erronées.

À ce stade, la question de savoir s'il faut refactoriser est prématurée (même si elle sera probablement répondu par une forme spécifique de "oui").

Le problème central ici est que (comme indiqué dans certaines réponses) les commentaires que vous citez indiquent fortement que le code a des conditions de concurrence ou d'autres problèmes de concurrence/synchronisation, tels que discutés ici . Ce sont des problèmes particulièrement difficiles, pour plusieurs raisons. Premièrement, comme vous l'avez constaté, des changements apparemment non liés peuvent déclencher le problème (d'autres bogues peuvent également avoir cet effet, mais les erreurs de concurrence le font presque toujours.) Deuxièmement, ils sont très difficiles à diagnostiquer: le bogue se manifeste souvent à un endroit qui est éloigné dans le temps ou le code de la cause, et tout ce que vous faites pour le diagnostiquer peut le faire disparaître ( Heisenbugs ). Troisièmement, les bogues de concurrence sont très difficiles à trouver dans les tests. Cela est dû en partie à l'explosion combinatoire: c'est déjà assez mauvais pour le code séquentiel, mais l'ajout des entrelacs possibles de l'exécution simultanée le fait exploser au point où le problème séquentiel devient insignifiant en comparaison. De plus, même un bon cas de test ne peut déclencher le problème que de temps en temps - Nancy Leveson a calculé que l'un des bogues mortels dans le Therac 25 s'est produit dans 1 des 350 exécutions, mais si vous ne savez pas quoi le bogue est, ou même qu'il y en ait un, vous ne savez pas combien de répétitions font un test efficace. De plus, seuls les tests automatisés sont réalisables à cette échelle, et il est possible que le pilote de test impose des contraintes temporelles subtiles de sorte qu'il ne déclenchera jamais réellement le bogue (Heisenbugs again).

Il existe des outils pour les tests de concurrence dans certains environnements, tels que Helgrind pour le code utilisant les pthreads POSIX, mais nous ne connaissons pas les détails ici. Les tests doivent être complétés par une analyse statique (ou est-ce l'inverse?), S'il existe des outils adaptés à votre environnement.

Pour ajouter à la difficulté, les compilateurs (et même les processeurs, au moment de l'exécution) sont souvent libres de réorganiser le code de manière à rendre parfois le raisonnement sur sa sécurité des threads très contre-intuitif (peut-être le cas le plus connu est le verrou revérifié idiome, bien que certains environnements (Java, C++ ...) aient été modifiés pour l'améliorer.)

Ce code peut avoir un problème simple qui cause tous les symptômes, mais il est plus probable que vous ayez un problème systémique qui pourrait mettre un terme à vos plans pour ajouter de nouvelles fonctionnalités. J'espère que je vous ai convaincu que vous pourriez avoir un grave problème sur les mains, peut-être même une menace existentielle pour votre produit, et la première chose à faire est de découvrir ce qui se passe. Si cela révèle des problèmes de concurrence, je vous conseille vivement de les résoudre d'abord, avant même de vous demander si vous devriez faire un refactoring plus général et avant d'essayer d'ajouter plus de fonctionnalités.

4
sdenham

Je suis confronté à une base de code assez importante et on m'a donné quelques mois pour refactoriser le code existant. Le processus de refactorisation est nécessaire car bientôt nous devrons ajouter de nombreuses nouvelles fonctionnalités à notre produit [...]

Pendant la refactorisation, de temps en temps je rencontre la classe, la méthode ou les lignes de code qui ont des commentaires comme ["ne touchez pas à ça!"]

Oui, vous devez refactoriser ces pièces en particulier. Ces avertissements ont été placés là par les auteurs précédents pour signifier "ne pas falsifier ce genre de choses, c'est très complexe et il y a beaucoup d'interactions inattendues". Comme votre entreprise prévoit de développer davantage le logiciel et d'ajouter de nombreuses fonctionnalités, elle vous a spécifiquement chargé de nettoyer ce genre de choses. Donc vous n'êtes pas paresseusement altéré, vous êtes délibérément chargé de le nettoyer.

Découvrez ce que font ces modules délicats et décomposez-le en petits problèmes (ce que les auteurs originaux auraient dû faire). Pour sortir du code maintenable d'un désordre, les bonnes parties doivent être refactorisées et les mauvaises parties doivent être réécrites.

3
DepressedDaniel

Cette question est une autre variation sur le débat de quand/comment refactoriser et/ou nettoyer le code avec un tiret de "comment hériter du code". Nous avons tous des expériences différentes et travaillons dans différentes organisations avec des équipes et des cultures différentes, donc il n'y a pas de bonne ou de mauvaise réponse sauf "faites ce que vous pensez que vous devez faire, et faites-le d'une manière qui ne vous fasse pas virer" .

Je ne pense pas qu'il existe de nombreuses organisations qui accepteraient avec bonté d'avoir un processus métier de son côté car l'application de support nécessitait un nettoyage ou une refactorisation du code.

Dans ce cas spécifique, les commentaires de code lèvent le drapeau que la modification de ces sections de code ne doit pas être effectuée. Donc, si vous continuez et que l'entreprise tombe de son côté, non seulement vous n'avez rien à montrer pour soutenir vos actions, mais il y a en fait un artefact qui va à l'encontre de votre décision.

Donc, comme c'est toujours le cas, vous devez procéder avec prudence et apporter des modifications uniquement après avoir compris tous les aspects de ce que vous êtes sur le point de changer, et trouver des moyens d'en tester tout en accordant une attention particulière à la capacité, aux performances et au calendrier en raison de les commentaires dans le code.

Mais même quand même, votre direction doit comprendre le risque inhérent à ce que vous faites et convenir que ce que vous faites a une valeur commerciale qui l'emporte sur le risque et que vous avez fait ce qui peut être fait pour atténuer ce risque.

Maintenant, revenons tous à nos propres TODO et les choses que nous savons peuvent être améliorées dans nos propres créations de code si seulement il y avait plus de temps.

2
Thomas Carlisle

Oui absolument. Ce sont des indications claires que la personne qui a écrit ce code n'était pas satisfaite du code et l'a probablement poussé jusqu'à ce qu'il réussisse à fonctionner. Il est possible qu'ils n'aient pas compris quels étaient les vrais problèmes ou, pire, les aient compris et étaient trop paresseux pour les résoudre.

Cependant, c'est un avertissement que beaucoup d'efforts seront nécessaires pour les corriger et que ces correctifs comporteront des risques.

Idéalement, vous pourrez déterminer quel était le problème et le résoudre correctement. Par exemple:

Délai d'expiration défini pour donner au module A le temps de faire des choses. Si ce n'est pas chronométré comme ça, il se cassera.

Cela suggère fortement que le module A n'indique pas correctement quand il est prêt à être utilisé ou quand il a terminé le traitement. Peut-être que la personne qui a écrit cela ne voulait pas déranger la fixation du module A ou ne pouvait pas pour une raison quelconque. Cela ressemble à un désastre qui attend de se produire car cela suggère une dépendance temporelle traitée par la chance plutôt que par un séquençage approprié. Si je voyais cela, je voudrais vraiment y remédier.

Ne changez pas cela. Croyez-moi, vous allez casser des choses.

Cela ne vous dit pas grand-chose. Cela dépendrait de ce que le code faisait. Cela pourrait signifier qu'il a ce qui semble être des optimisations évidentes qui, pour une raison ou une autre, vont réellement casser le code. Par exemple, une boucle peut arriver à laisser une variable à une valeur particulière dont dépend un autre morceau de code. Ou une variable peut être testée dans un autre thread et changer l'ordre des mises à jour des variables peut casser cet autre code.

Je sais que l'utilisation de setTimeout n'est pas une bonne pratique, mais dans ce cas, je devais l'utiliser.

Cela semble facile. Vous devriez pouvoir voir ce que setTimeout fait et peut-être trouver une meilleure façon de le faire.

Cela dit, si ces types de correctifs sont en dehors de la portée de votre refactor, ce sont des indications que tenter de refactoriser dans ce code peut augmenter considérablement la portée de vos efforts.

Au minimum, examinez attentivement le code affecté et voyez si vous pouvez au moins améliorer le commentaire au point qu'il explique plus clairement le problème. Cela pourrait empêcher la prochaine personne de faire face au même mystère que vous.

1
David Schwartz

L'auteur des commentaires le plus probable n'a pas bien compris le code lui-même. Si ils savaient réellement ce qu'ils faisaient, ils auraient écrit des commentaires réellement utiles (ou n'auraient pas présenté les conditions de course en premier lieu). Un commentaire comme " Croyez-moi, vous allez casser des choses. " pour moi indique que l'auteur essaie de changer quelque chose qui a causé des erreurs inattendues qu'ils n'ont pas bien comprises .

Le code est probablement développé par devinettes et essais et erreurs sans une compréhension complète de ce qui se passe réellement.

Ça signifie:

  1. Il est risqué de changer le code. Cela prendra du temps à comprendre, évidemment, et il ne suit probablement pas de bons principes de conception et peut avoir des effets et des dépendances obscurs. Il est très probablement insuffisamment testé, et si personne ne comprend parfaitement ce que fait le code, il sera difficile d'écrire des tests pour s'assurer qu'aucune erreur ou modification de comportement n'est introduite. Les conditions de course (comme les commentaires y font allusion) sont particulièrement pénibles - c'est un endroit où les tests unitaires ne vous sauveront pas.

  2. Il est risqué pas de changer le code. Le code a une forte probabilité de contenir des bugs obscurs et des conditions de course. Si un bogue critique est découvert dans le code, ou qu'un changement des exigences métier de haute priorité vous oblige à changer ce code dans un court délai, vous êtes en profond difficulté. Vous avez maintenant tous les problèmes décrits en 1, mais sous la pression du temps! De plus, ces "zones sombres" du code ont tendance à se propager et à infecter d'autres parties du code qu'il touche.

Une complication supplémentaire: Les tests unitaires ne vous sauveront pas. Habituellement, l'approche recommandée pour ce code hérité de correctif consiste à ajouter d'abord des tests unitaires ou d'intégration, puis à isoler et à refactoriser. Mais les conditions de course ne peuvent pas être capturées par des tests automatisés. La seule solution est de s'asseoir et de réfléchir au code jusqu'à ce que vous le compreniez, puis de le réécrire pour éviter les conditions de course.

Cela signifie que la tâche est beaucoup plus exigeante que la simple refactorisation de routine. Vous devrez le planifier comme une véritable tâche de développement.

Vous pourrez peut-être encapsuler le code affecté dans le cadre d'une refactorisation régulière, de sorte qu'au moins le code dangereux est isolé.

1
JacquesB