Nehmen wir an, Sie haben ein Projekt, das wirklich schlecht geschrieben ist, viele Code-Gerüche, wtfs usw. enthält. Außerdem ist seine Code-Struktur so kompliziert, dass es extrem schwierig ist, neue Funktionen hinzuzufügen. Auf der anderen Seite funktioniert das Projekt so, wie es soll.
Sie möchten das Projekt umgestalten, es vielleicht in ein neues Framework verschieben, wie würden Sie dieses Problem angehen? Versuchen Sie, ein neues von Grund auf neu zu erstellen oder verwenden Sie einige Techniken (specify), um das Arbeitsprojekt in ein neues zu konvertieren?
Ich möchte die Frage ein wenig klären, da es eine Verwirrung gibt, was ich meine, wenn ich "Refactoring" sage.
Ich werde ein Beispiel über ein Auto geben und es als Software-Projekt betrachten. Nehmen wir an, Sie haben Ihr eigenes Auto gebaut. Seine Konstruktion ist ziemlich komisch: der Motor steht auf dem Kopf, so sind alle Rohre anders verlegt, die Stromkabel sind miteinander verflochten und niemand hat eine Vorstellung, wo sie anfangen oder enden, usw.
Es funktioniert jedoch alles gut: Sie können es leicht zum Einkaufen, Arbeiten usw. fahren. Allerdings ist der Kraftstoffverbrauch etwas zu hoch. Auch wenn Sie jemals neue Scheinwerfer installieren wollten, wäre es eine Katastrophe mit all dem Chaos in den Leitungen.
Sie können es sich nicht leisten, ein neues Fahrzeug zu kaufen, also müssen Sie das Auto irgendwie umgestalten: Stellen Sie die Motorenstellung auf normal, machen Sie die Leitungen sauber usw. Sie müssen dies tun, denn früher oder später müssen Sie den Motor wechseln, Scheinwerfer, installiere neue Stereoanlage etc .. Auf der anderen Seite brauchst du immer noch etwas, um dich jeden Morgen zur Arbeit zu fahren, also musst du sicherstellen, dass du nicht alles vermasselst.
Kommen wir nun zurück zum Projekt. Wie würden Sie das Projekt so umgestalten wie das Auto oben, ohne seine primäre Funktion und seinen Zweck zu stören?
Ich möchte dies auch zu einem Community-Wiki machen. Bitte bearbeiten.
Bisher sind die wichtigsten Tendenzen:
Links:
Arbeiten ist die einzige Art von Projekt, die Sie refactoring machen sollten. Wenn Sie Fehler beheben, ändern Sie das Verhalten, und das Refactoring bezieht sich explizit auf das nicht Verhalten. Aber funktioniert hat andere Definitionen. Der Gute - der Nützliche zum Refactoring - ist gut getestet. Wenn Sie über eine gute Testabdeckung verfügen ( automatisierte Tests!), Sind Sie bereit für eine Umgestaltung. Wenn Sie nicht ...
Lesen Sie Michael Feathers effektiv mit Legacy-Code arbeiten. Den Code knabbern. Wählen Sie einen WTF, der Sie besonders beleidigt, und testen Sie ihn mit automatisierten Komponententests. Dann ersetzen Sie die WTF durch etwas Vernünftiges und stellen Sie sicher, dass die Tests bestehen bleiben. Aufschäumen, abspülen, wiederholen.
Erstellen Sie zunächst eine Reihe automatisierter Komponententests und bestätigen Sie, dass Sie eine hohe Codeabdeckung (70% oder mehr) haben. Während des Refactorings werden Sie diese Tests sehr häufig durchführen, um sich selbst (und das Management) davon zu überzeugen, dass Sie nichts kaputt gemacht haben.
Keine Komponententests = kein Refactoring.
Lass mich meine Meinung ein wenig ändern. Sie benötigen keine Komponententests - Sie benötigen eine hohe Codeabdeckung von Tests, die häufig ausgeführt werden, wenn Sie den Code ändern. Dies können automatisierte Komponententests oder automatisierte Funktionstests sein.
Ich glaube nicht, dass manuelle Tests ein adäquater Ersatz sind. Sie werden viel seltener während des Refactoring-Prozesses ausgeführt. Refactoring ist viel weniger wahrscheinlich ohne die ständige Gewissheit, dass der Code nicht gebrochen wurde, um es zu reparieren.
Es gibt eine Menge Text als Antwort auf diese Frage, die der Literatur sehr genau folgt, und (ohne die Poster dieser Antworten zu beleidigen) wirft mich die Frage auf, ob die Leute einfach ein System von bedeutendem Umfang nicht überarbeitet haben ist wirklich und wirklich gefickt.
Ich habe drei wichtige Anwendungen in meiner Karriere umgeschrieben (Signifikant: 50k LOC oder größer, Datenebene, Logikstufe, Integrationsanforderungen). Jedes System hat von mir verlangt, dass ich irgendwann einmal zur guten Praxis sagen muss. Weißt du, was ich daraus gelernt habe? Ab einem gewissen Punkt kann es wirklich, wirklich sicher sein. Absolut, Sie müssen darüber nachdenken, was es bedeutet, Vorsicht in den Wind zu werfen, aber es ist wesentlich wichtiger, dass Sie versenden, als dass Sie der Idee einer guten Praxis anderer folgen.
Lassen Sie mich das anhand eines Beispiels illustrieren:
Ich arbeite heute an einem System, das über sechs Jahre geschrieben wurde, das ursprünglich von einer damals vielleicht zehn Jahre alten Anwendung in eine .NET-Sprache konvertiert wurde und mit einem DOS-Client geschrieben wurde, der alles enthielt die Logik. Der ursprüngliche Code und die meisten nachfolgenden Aktualisierungen und Konvertierungen wurden von unerfahrenen Programmierern ausgeführt. Dies ist eine Dokumentenmanagement-Engine für alle Absichten und Zwecke, und es gibt keine einzige Abstraktion für "Dokument" irgendwo im Code.
Ich wurde gebeten, ein Mittel zu implementieren, um Dateien über ein WAN zu übertragen, so dass es mit den Kernroutinen des Systems funktionieren würde. Ich begann mit der Erstellung eines netten kleinen Testclients und Servers mit anständigen Praktiken, die sich um sie herum erstreckten, Tests usw. Ich ging durch die Kernsystemarchitektur und suchte nach dem richtigen Ort, um meinen Code erneut zu stubben. Alles, was ich gefunden habe, war ein großer, böser Brocken nach einem großen, fiesen Brocken von kopiertem und eingefügtem Code mit winzigen Modifikationen, die Einheiten bildeten.
Ich begann, kleine Code-Teile zu ändern, die Dinge begannen zu brechen, ich setzte meine Änderungen zurück. Ich extrahierte Methoden und fand heraus, dass Variablen im Code verstreut waren und sich auf Änderungen während des gesamten Verfahrens stützten. Ich habe versucht, Klassen zu extrahieren, nur um festzustellen, dass ich Methoden im Großhandel entzifferte und dass die Zustandsdaten der alten Klasse wieder einmal im gesamten Code verstreut waren und nicht für den Übergang geeignet waren.
Also sagte ich THWI. Zwei Wochen später hatten wir einen netten kleinen Zipper-Client und Server und unser Kerncode wurde viel besser für unsere Probleme berücksichtigt.
Wenn Sie ein System kennen, wenn Sie darauf achten, wenn Sie Ihren Code testen, und wenn Sie AUFMERKSAMKEIT ACHTEN, dann ist es oft falsch, große Änderungen in einer unsicheren Art vorzunehmen.
Ich werde dafür Downvotes bekommen, aber agile Praktiken haben die Vision der Leute zu sehr getrübt, und es ist wirklich an der Zeit, dass Zitate aus der Literatur aufhören, diese Diskussionen zu dominieren. Wenn Sie jeden Tag mit einem System arbeiten, kennen Sie das System, und keine Menge an Komponententests entspricht dem, was Sie erreichen können, wenn Sie die Verantwortung übernehmen und das verdammte System reparieren.
Aus diesem Grund müssen Sie natürlich mit einem System arbeiten und das System lernen, anstatt nur von Technologie zu Technologie zu gehen. Das Umschreiben in eine neue Sprache ist nicht annähernd so gut wie das Umbauen mit dem Ziel, das Design zu verbessern. Wenn Sie dann feststellen, dass es im System Einschränkungen gibt, die Sie mit einer neuen Technologie überwinden könnten, wird es wesentlich einfacher, diese Änderung an diesem Punkt vorzunehmen.
Unit Tests sind eine gute Sache. Der Code muss jedoch in einem bestimmten Zustand sein, bevor Sie ihn testen können. Ich wette, Sie haben eine Datenvalidierung, die mit der Persistenzschicht innerhalb der Nachrichtenverarbeitungsfunktionen kommuniziert. Ich bin sicher, dass Sie Tausende von Zeilen Geschäftslogik haben, die den Benutzer mit Bestätigungsnachrichtenfeldern unterbricht.
Sie müssen die Geschäftslogik von der Benutzeroberflächenschicht trennen, und das kann sehr viel Arbeit sein.
Nun haben Sie eine klassische Catch-22-Situation - Sie sollten sie nicht ohne Testabdeckung umgestalten, und Sie können keine Unit-Tests schreiben, bis sie refaktoriert ist.
Die einzige Lösung ist also, sehr geduldig zu sein. Verstehen Sie genau, wie die Anwendung funktionieren soll. Versuchen Sie und verstehen Sie, was jeder unordentliche Code tut. Akzeptieren Sie die Tatsache, dass Sie manchmal keinen Code verstehen können, und der Grund dafür ist, dass der Code eigentlich sinnlos ist. Aber der sinnlose Code wird genau so aussehen wie der grundlegend nützliche Code, und nur extrem harte Arbeit wird Ihnen die Einsicht geben, den Unterschied zu erkennen.
Arbeiten Sie auf das Ziel hin, eine Unit-Testabdeckung zu haben - aber das Fehlen von Unit-Tests sollte Sie nicht davon abhalten zu starten. Akzeptieren Sie die Tatsache, dass Sie nie die gewünschte Testabdeckung erhalten, aber behalten Sie das Ideal im Auge.
Und jeden Tag wirst du zu dir selbst sagen: "Das wäre so viel schneller, wenn ich nur das Ganze neu schreiben würde". Handeln Sie nicht gegen dieses Gefühl. (Siehe Joels Artikel, bereits erwähnt).
Ich würde mich eher für ein Refactoring entscheiden als für ein Neuschreiben, wo immer es möglich ist, weil es weniger riskant ist. Wenn Sie es richtig gemacht haben, haben Sie immer ein funktionierendes Programm, und es wird allmählich besser, wenn Sie inkrementelle Änderungen daran vornehmen. Wenn Sie früher aufhören müssen (aufgrund von Zeit- oder Budgeteinschränkungen), erhalten Sie immer noch den Vorteil der bisherigen Arbeit, denn das Programm ist noch brauchbar und es ist besser als vorher, auch wenn es noch nicht perfekt ist.
Wenn Sie sich andererseits für ein Neuschreiben entscheiden und auf halbem Wege stehen bleiben müssen, bleiben Sie mit dem ursprünglichen Programm "Working but but bad" und dem neuen Programm "clean - but-incomplete". Sie müssen entscheiden, ob die neue Version verschrottet werden soll und ob die Mühe vergeudet werden soll oder ob die Funktionalität geopfert werden soll und Fehler auftreten sollen, indem Sie zu ihr wechseln, bevor sie fertig ist.
Zuerst stimme ich den anderen Plakaten zu - geben Sie nicht der Versuchung nach, von Grund auf neu zu schreiben. Der Code funktioniert schließlich. Zweitens, und es mag offensichtlich klingen, aber konzentrieren Sie Ihr Refactoring auf diejenigen Bereiche, die geändert werden müssen (für neue Funktionen usw.). Es mag Code geben, der umstrukturiert werden möchte, aber wenn es funktioniert und in einem stabilen Teil der App, lass es in Ruhe.
Effektives Arbeiten mit Legacy-Code eignet sich hervorragend, um die Catch-22 zu adressieren, die Sie nicht ohne Komponententests umgestalten möchten, aber Sie können Komponententests nicht ohne Refactoring durchführen. Er schlägt unter anderem vor, sich auf automatisierte Refactoring-Tools zu verlassen, um Fehler beim Refactoring ohne Tests zu vermeiden.
Andere mögen mir nicht zustimmen, aber IMO haben Sie manchmal keine andere Wahl, als das anfängliche Refactoring ohne Komponententests durchzuführen und sich auf automatisierte oder (sorgfältige) manuelle Integrationstests zu verlassen, um sicherzustellen, dass Sie alles richtig gemacht haben. Das anfängliche Refactoring sollte ermöglichen, dass der Code in einem Unit-Test-Harness ausgeführt wird, als Sie unterwegs sind.
Tags und Links refactoring