Ich habe ein Projekt. In diesem Projekt wollte ich es umgestalten, um ein Feature hinzuzufügen, und ich habe das Projekt umgestaltet, um das Feature hinzuzufügen.
Das Problem ist, dass es sich herausstellte, als ich fertig war, dass ich eine geringfügige Änderung an der Benutzeroberfläche vornehmen musste, um sie aufzunehmen. Also habe ich die Änderung vorgenommen. Und dann kann die konsumierende Klasse nicht mit ihrer aktuellen Schnittstelle in Bezug auf die neue implementiert werden, weshalb sie auch eine neue Schnittstelle benötigt. Jetzt ist es drei Monate später, und ich musste unzählige Probleme beheben, die praktisch nichts miteinander zu tun haben, und ich bin auf der Suche nach Lösungen für Probleme, die in einem Jahr geplant wurden oder die aufgrund von Schwierigkeiten nicht behoben werden können, bevor die Sache kompiliert werden kann nochmal.
Wie kann ich diese Art von kaskadierenden Refactorings in Zukunft vermeiden? Ist es nur ein Symptom für meine früheren Klassen, die zu eng voneinander abhängen?
Kurze edit: In diesem Fall ist das Refactoring war das Merkmal, da das Umgestalten der Dehnbarkeit eines bestimmten Stückes Code erhöht und eine Kopplung verringert. Dies bedeutete, dass externe Entwickler mehr tun konnten, was die Funktion war, die ich liefern wollte. Der ursprüngliche Refactor selbst sollte also keine funktionale Änderung sein.
Größere Änderung, die ich vor fünf Tagen versprochen habe:
Bevor ich mit diesem Refactor anfing, hatte ich ein System mit einer Schnittstelle, aber bei der Implementierung habe ich einfach dynamic_cast
alle möglichen Implementierungen durchgearbeitet, die ich ausgeliefert habe. Dies bedeutete offensichtlich, dass Sie zum einen nicht einfach von der Schnittstelle erben konnten und zum anderen, dass es für niemanden ohne Implementierungszugriff möglich wäre, diese Schnittstelle zu implementieren. Deshalb habe ich beschlossen, dieses Problem zu beheben und die Schnittstelle für den öffentlichen Verbrauch zu öffnen, damit jeder sie implementieren kann, und die Implementierung der Schnittstelle war der gesamte erforderliche Vertrag - offensichtlich eine Verbesserung.
Als ich alle Orte gefunden und mit Feuer getötet hatte, an denen ich dies getan hatte, fand ich einen Ort, der sich als besonderes Problem herausstellte. Es hing von den Implementierungsdetails aller verschiedenen Ableitungsklassen und der duplizierten Funktionalität ab, die bereits implementiert waren, aber an einer anderen Stelle besser. Es hätte stattdessen in Form der öffentlichen Schnittstelle implementiert und die vorhandene Implementierung dieser Funktionalität wiederverwendet werden können. Ich entdeckte, dass es eines bestimmten Kontextes bedurfte, um richtig zu funktionieren. Grob gesagt sah die aufrufende vorherige Implementierung irgendwie so aus
for(auto&& a : as) {
f(a);
}
Um diesen Kontext zu erhalten, musste ich ihn jedoch in etwas Ähnliches ändern
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);
Dies bedeutet, dass für alle Vorgänge, zu denen früher ein Teil gehörte f
, einige von ihnen zu einem Teil der neuen Funktion gemacht werden müssen g
, die ohne Kontext ausgeführt wird, und einige von ihnen zu einem Teil der jetzt zurückgestellten f
. Aber nicht alle Methoden f
benötigen oder wollen diesen Kontext - einige von ihnen benötigen einen bestimmten Kontext, den sie auf unterschiedliche Weise erhalten. Also musste ich für alles, was f
letztendlich aufruft (was grob gesagt so ziemlich alles ist ), bestimmen, welchen Kontext sie brauchten, woher sie ihn nehmen sollten und wie sie von alt f
nach neu f
und von neu zu trennen waren g
.
Und so bin ich dort gelandet, wo ich jetzt bin. Der einzige Grund, warum ich so weitermachte, war, dass ich dieses Refactoring sowieso aus anderen Gründen brauchte.