Was ist das Ziel von Refactoring in Ihrem speziellen Fall?
Um meine Antwort zu ertragen, nehmen wir an, dass wir alle (bis zu einem gewissen Grad) an TDD (Test-Driven Development) glauben.
Wenn der Zweck Ihres Refactorings darin besteht, vorhandenen Code zu bereinigen, ohne das vorhandene Verhalten zu ändern, stellen Sie durch Schreiben von Tests vor dem Refactoring sicher, dass Sie das Verhalten des Codes nicht geändert haben. Wenn Sie erfolgreich sind, sind die Tests sowohl vorher als auch nachher erfolgreich Sie refactor.
Mithilfe der Tests können Sie sicherstellen, dass Ihre neue Arbeit tatsächlich funktioniert.
Die Tests werden wahrscheinlich auch Fälle aufdecken, in denen das Originalwerk nicht funktioniert.
Aber wie können Sie wirklich signifikante Umgestaltungen vornehmen, ohne das Verhalten in gewissem Maße zu beeinträchtigen ?
Hier ist eine kurze Liste einiger Dinge, die beim Refactoring passieren können:
- Variable umbenennen
- Funktion umbenennen
- Funktion hinzufügen
- Löschfunktion
- Split-Funktion in zwei oder mehr Funktionen
- kombinieren Sie zwei oder mehr Funktionen zu einer Funktion
- Split-Klasse
- Klassen kombinieren
- Klasse umbenennen
Ich werde argumentieren, dass jede einzelne dieser aufgeführten Aktivitäten das Verhalten in irgendeiner Weise verändert.
Und ich werde argumentieren, dass, wenn sich Ihr Refactoring-Verhalten ändert, Ihre Tests immer noch die Art und Weise sein werden, wie Sie sicherstellen, dass Sie nichts kaputt gemacht haben.
Vielleicht ändert sich das Verhalten auf Makroebene nicht, aber der Punkt des Unit- Tests besteht nicht darin, das Makroverhalten sicherzustellen. Das ist Integrationstest . Der Zweck des Komponententests besteht darin, sicherzustellen, dass die einzelnen Teile, aus denen Sie Ihr Produkt bauen, nicht beschädigt werden. Kette, schwächstes Glied usw.
Wie wäre es mit diesem Szenario:
Angenommen, Sie haben function bar()
function foo()
ruft an bar()
function flee()
ruft auch zum Funktionieren auf bar()
Nur für Abwechslung, flam()
ruft anfoo()
Alles funktioniert hervorragend (anscheinend zumindest).
Sie refactor ...
bar()
wird umbenannt in barista()
flee()
wird geändert, um anzurufen barista()
foo()
wird nicht geändert, um anzurufenbarista()
Offensichtlich scheitern Ihre Tests für beide foo()
und flam()
jetzt.
Vielleicht haben Sie gar nicht bemerkt, dass Sie überhaupt foo()
angerufen bar()
haben. Sie wusste schon gar nicht , dass flam()
auf abhing bar()
haft foo()
.
Was auch immer. Der Punkt ist , dass Ihre Tests werden das neu aufgebrochen Verhalten beide aufzudecken foo()
und flam()
, in einer inkrementellen Art und Weise während der Refactoring Arbeit.
Die Tests helfen Ihnen letztendlich dabei, das Produkt gut umzugestalten.
Es sei denn, Sie haben keine Tests.
Das ist ein bisschen ein ausgedachtes Beispiel. Es gibt diejenigen , die argumentieren , dass , wenn Wechsel bar()
bricht foo()
, dann foo()
zu komplex waren , mit zu beginnen und abgebaut werden soll. Aber Prozeduren können andere Prozeduren aus einem bestimmten Grund aufrufen, und es ist unmöglich, die gesamte Komplexität zu beseitigen , oder? Unsere Aufgabe ist es, die Komplexität einigermaßen gut zu managen .
Stellen Sie sich ein anderes Szenario vor.
Sie bauen ein Gebäude.
Sie bauen ein Gerüst, um sicherzustellen, dass das Gebäude ordnungsgemäß gebaut wird.
Das Gerüst hilft Ihnen unter anderem beim Bau eines Aufzugsschachts. Anschließend reißen Sie das Gerüst ab, der Aufzugsschacht bleibt jedoch erhalten. Sie haben "Originalarbeit" zerstört, indem Sie das Gerüst zerstört haben.
Die Analogie ist dürftig, aber der Punkt ist, dass es nicht ungewöhnlich ist, Tools zu erstellen, die Ihnen beim Erstellen von Produkten helfen. Auch wenn die Werkzeuge nicht permanent sind, sind sie nützlich (sogar notwendig). Tischler stellen ständig Vorrichtungen her, manchmal nur für einen Job. Dann reißen sie die Vorrichtungen auseinander, manchmal verwenden sie die Teile, um andere Vorrichtungen für andere Arbeiten zu bauen, manchmal nicht. Aber das macht die Vorrichtungen nicht nutzlos oder unnötig.