J'ai un projet. Dans ce projet, je souhaitais le refactoriser pour ajouter une fonctionnalité et le refactorer pour ajouter la fonctionnalité.
Le problème est que quand j'ai eu fini, il s'est avéré que je devais faire un changement d'interface mineur pour l'adapter. Alors j'ai fait le changement. Et puis, la classe consommatrice ne peut pas être implémentée avec son interface actuelle par rapport à la nouvelle, elle a donc également besoin d'une nouvelle interface. Maintenant, trois mois plus tard, j'ai dû résoudre d'innombrables problèmes pratiquement sans rapport. Je cherche à résoudre les problèmes qui étaient planifiés depuis un an ou simplement répertoriés comme ne seront pas réglés en raison de difficultés avant que la chose ne soit compilée encore.
Comment puis-je éviter ce genre de refactorisation en cascade à l'avenir? Est-ce juste un symptôme de mes classes précédentes dépendantes trop étroitement les unes des autres?
Brève édition: Dans ce cas, le refactor était la fonctionnalité, car le refactor augmentait l'extensibilité d'un morceau de code particulier et diminuait le couplage. Cela signifiait que les développeurs externes pouvaient faire plus, ce qui était la fonctionnalité que je souhaitais proposer. Ainsi, le refactor d'origine lui-même n'aurait pas dû être un changement fonctionnel.
Plus gros montage que j'ai promis il y a cinq jours:
Avant de commencer ce refactor, j'avais un système avec une interface, mais lors de l'implémentation, j'ai simplement passé en revue dynamic_cast
toutes les implémentations possibles fournies. Cela signifiait évidemment que vous ne pouviez pas simplement hériter de l'interface, d'une part, et qu'il serait impossible à quiconque sans accès à la mise en oeuvre d'implémenter cette interface. J'ai donc décidé que je voulais résoudre ce problème et ouvrir l'interface à la consommation publique afin que tout le monde puisse l'implémenter et que l'implémentation de l'interface nécessitait la totalité du contrat - évidemment une amélioration.
Quand j'ai trouvé et tué avec le feu tous les endroits où j'avais fait cela, j'ai trouvé un endroit qui s'est avéré être un problème particulier. Cela dépendait des détails d'implémentation de toutes les classes dérivées et des fonctionnalités dupliquées déjà implémentées, mais améliorées ailleurs. Il aurait pu être implémenté en termes d’interface publique et réutiliser l’implémentation existante de cette fonctionnalité. J'ai découvert qu'il fallait un élément de contexte particulier pour fonctionner correctement. Grosso modo, l'implémentation précédente de l'appelant ressemblait un peu à
for(auto&& a : as) {
f(a);
}
Cependant, pour obtenir ce contexte, je devais le changer en quelque chose de plus semblable à
std::vector<Context> contexts;
for(auto&& a : as)
contexts.push_back(g(a));
do_thing_now_we_have_contexts();
for(auto&& con : contexts)
f(con);
Cela signifie que pour toutes les opérations qui faisaient auparavant partie f
, certaines d’entre elles doivent faire partie de la nouvelle fonction g
qui fonctionne sans contexte, et certaines d’entre elles doivent faire partie d’une partie de la fonction actuellement différée f
. Mais toutes les méthodes f
n’appellent pas nécessairement le besoin ou le désir de ce contexte - certaines d’entre elles ont besoin d’un contexte distinct qu’elles obtiennent par des moyens distincts. Donc, pour tout ce qui f
finit par s’appeler (c’est-à-dire grosso modo tout ), je devais déterminer le contexte, le cas échéant, dont ils avaient besoin, de quelle manière, et comment les scinder de l’ancien f
au nouveau f
. g
.
Et c'est comme ça que j'ai fini où je suis maintenant. La seule raison pour laquelle j'ai persévéré, c'est que j'avais besoin de cette refactorisation pour d'autres raisons.