Vorwort
Ich suche nicht nach einer Möglichkeit, eine große Spaghetti-Code-Klasse umzugestalten. Dieses Thema wurde in anderen Fragen behandelt.
Frage
Ich suche nach Techniken, um eine Klassendatei zu verstehen, die von einem anderen Mitarbeiter geschrieben wurde. Sie umfasst mehr als 4000 Zeilen und verfügt über eine einzige große Aktualisierungsmethode mit mehr als 2000 Zeilen.
Schließlich hoffe ich, Unit-Tests für diese Klasse zu erstellen und sie in viele kleinere Klassen umzugestalten, die DRY und dem Prinzip der Einzelverantwortung folgen.
Wie kann ich diese Aufgabe verwalten und angehen? Ich möchte in der Lage sein, eventuell ein Diagramm der Ereignisse zu zeichnen, die innerhalb der Klasse auftreten, und dann mit der Zusammenfassung der Funktionen fortzufahren, aber ich habe Schwierigkeiten, eine Top-Down-Ansicht der Verantwortlichkeiten und Abhängigkeiten der Klasse zu erhalten.
Bearbeiten: In den Antworten, die bereits zu Themen im Zusammenhang mit Eingabeparametern behandelt wurden, sind diese Informationen für Neulinge bestimmt:
In diesem Fall hat die Hauptmethode der Klasse keine Eingabeparameter und führt eine while-Schleife aus, bis sie zum Stoppen aufgefordert wird. Der Konstruktor akzeptiert auch keine Parameter.
Dies trägt zur Verwirrung der Klasse, ihrer Anforderungen und Abhängigkeiten bei. Die Klasse erhält Verweise auf andere Klassen durch statische Methoden, Singletons und durch Klassen, auf die sie bereits Bezug hat.
quelle
Antworten:
Interessanterweise ist Refactoring der effizienteste Weg, Code wie diesen zu verstehen. Sie müssen es zuerst nicht sauber machen. Sie können einen schnellen und schmutzigen Analyse-Refactoring-Durchgang durchführen, ihn dann zurücksetzen und mit Unit-Tests sorgfältiger durchführen.
Der Grund dafür ist, dass das ordnungsgemäß durchgeführte Refactoring eine Reihe kleiner, fast mechanischer Änderungen ist, die Sie vornehmen können, ohne den Code wirklich zu verstehen. Zum Beispiel kann ich einen Code-Blob sehen, der sich überall wiederholt, und diesen in eine Methode einbeziehen, ohne wirklich wissen zu müssen, wie er funktioniert. Ich könnte ihm zuerst einen lahmen Namen geben
step1
oder so. Dann bemerke ich dasstep1
undstep7
scheine häufig zusammen aufzutreten und das herauszurechnen. Dann hat sich der Rauch plötzlich so weit verzogen, dass ich tatsächlich das große Ganze sehen und einige aussagekräftige Namen machen kann.Um die Gesamtstruktur zu finden, habe ich festgestellt, dass das Erstellen eines Abhängigkeitsdiagramms häufig hilfreich ist. Ich mache dies manuell mit graphviz, da ich festgestellt habe, dass die manuelle Arbeit mir hilft, es zu lernen, aber es sind wahrscheinlich automatisierte Tools verfügbar. Hier ist ein aktuelles Beispiel aus einer Funktion, an der ich arbeite. Ich habe festgestellt, dass dies auch eine effektive Möglichkeit ist, diese Informationen meinen Kollegen mitzuteilen.
quelle
Zunächst ein Wort der Vorsicht (auch wenn es nicht das ist, was Sie fragen), bevor Sie Ihre Frage beantworten: Eine enorm wichtige Frage ist, warumWollen Sie etwas umgestalten? Die Tatsache, dass Sie "Mitarbeiter" erwähnen, wirft die Frage auf: Gibt es einen geschäftlichen Grund für die Änderung? Insbesondere in einer Unternehmensumgebung wird es immer sowohl alten als auch neu festgeschriebenen Code geben, mit dem Sie nicht zufrieden sind. Tatsache ist, dass keine zwei Ingenieure ein bestimmtes Problem mit derselben Lösung lösen können. Wenn es funktioniert und keine schrecklichen Auswirkungen auf die Leistung hat, warum sollte es dann nicht so bleiben? Feature-Arbeit ist im Allgemeinen viel vorteilhafter als Refactoring, da Sie eine Implementierung nicht besonders mögen. Das heißt, es gibt etwas zu sagen für technische Schulden und die Bereinigung von nicht wartbarem Code. Mein einziger Vorschlag hier ist, dass Sie bereits die Unterscheidung getroffen haben und sich einkaufen, um die Änderungen vorzunehmen, sonst werden Sie wahrscheinlich gefragt "nachdem sie in einer Menge Mühe versunken sind und dann wahrscheinlich viel mehr (und verständlicher) zurückschieben werden.
Bevor Sie ein Refactoring Angreifen, will ich immer sicherstellen , dass ich eine feste Vorstellung davon , was es ist , ich bin zu ändern. Es gibt zwei Methoden, die ich im Allgemeinen verwende, wenn ich solche Dinge in Angriff nehme: Protokollierung und Debugging. Letzteres wird durch die Existenz von Komponententests (und Systemtests, wenn diese sinnvoll sind) erleichtert. Wie Sie keine haben, würde ich stark Sie drängen Tests mit voller Zweigüberdeckung zu schreiben , um sicherzustellen , dass die Änderungen keine unerwarteten Verhaltensänderungen erstellen. Unit-Tests und das Durchlaufen des Codes helfen Ihnen dabei, das Verhalten in einer kontrollierten Umgebung auf niedriger Ebene zu verstehen. Die Protokollierung hilft Ihnen dabei, das Verhalten in der Praxis besser zu verstehen (dies ist besonders in Multithread-Situationen nützlich).
Einmal habe ich ein besseres Verständnis für alles erhalten, die sich über das Debuggen und die Protokollierung los ist, dann werde ich das Lesen Code starten. An diesem Punkt habe ich im Allgemeinen ein viel besseres Verständnis dafür, was los ist, und wenn ich mich hinsetze und 2k LoC lese, sollte dies an diesem Punkt viel sinnvoller sein. Meine Absicht hier ist eher, eine End-to-End-Ansicht zu erhalten und sicherzustellen, dass es keine merkwürdigen Randfälle gibt, die die von mir geschriebenen Komponententests nicht abdecken.
Dann werde ich über das Design nachdenken und ein neues entwerfen. Wenn die Abwärtskompatibilität von Bedeutung ist, stelle ich sicher, dass sich die öffentliche Schnittstelle nicht ändert.
Mit einem festen Verständnis der Vorgänge, Tests und einem neuen Design werde ich es zur Überprüfung vorlegen. Sobald das Design von mindestens zwei Personen überprüft wurde (hoffentlich mit dem Code vertraut), werde ich mit der Implementierung der Änderungen beginnen.
Hoffe das ist nicht zu schmerzhaft offensichtlich und hilft.
quelle
Es gibt kein allgemeines Rezept, aber einige Faustregeln ( vorausgesetzt, eine statisch typisierte Sprache, aber das sollte eigentlich keine Rolle spielen):
1) Schauen Sie sich die Methodensignatur an. Es sagt Ihnen , was geht in und hoffentlich kommt heraus . Aus dem Gesamtzustand dieses Gottes -Klasse, nehme ich dies ein erster seinen Schmerzpunkt . Ich nehme an, dass mehr als ein Parameter verwendet wird.
2) Verwenden Sie die Suchfunktion Ihres Editors / EDI, um Exit-Points zu ermitteln (normalerweise wird eine return- Anweisung_ verwendet).
Von da Sie wissen, was die Funktion muss für die Prüfung und was Sie erwarten in Rückkehr .
Ein einfacher erster Test würde also darin bestehen , die Funktion mit den erforderlichen Parametern aufzurufen und zu erwarten, dass das Ergebnis nicht null ist . Das ist nicht viel, aber ein Ausgangspunkt.
Von hier aus konnte man in einen hermeneutischen Kreis eintreten (ein Begriff, der von HG Gadamer - einem deutschen Philosophen - geprägt wurde. Der Punkt ist: Sie haben jetzt ein rudimentäres Verständnis der Klasse und aktualisieren dieses Verständnis mit neuem Detailwissen und haben ein neues Verständnis der gesamten Klasse.
Dies kombiniert mit der wissenschaftlichen Methode : Machen Sie Annahmen und prüfen Sie, ob sie gelten.
3) Nehmen Sie einen Parameter und schauen Sie, wo in der Klasse er irgendwie transformiert ist:
Wenn Sie beispielsweise Java wie ich verwenden, gibt es normalerweise Getter und Setter, nach denen Sie suchen können. Suchmuster
$objectname
. (oder$objectname\.(get|set)
wenn Sie Java machen)Sie können jetzt weitere Annahmen darüber treffen, was die Methode tut.
Verfolgen Sie jeweils nur die Eingabeparameter ( zuerst ) durch die Methode. Erstellen Sie bei Bedarf einige Diagramme oder Tabellen , in denen Sie jede Änderung an jeder der Variablen notieren.
Daraus können Sie weitere Tests schreiben, die das Verhalten der Methode beschreiben. Wenn Sie ein grobes Verständnis dafür haben, wie jeder Eingabeparameter entlang der Methode transformiert wird, beginnen Sie mit dem Experimentieren : Übergeben Sie null für einen Parameter oder eine seltsame Eingabe . Treffen Sie Annahmen, überprüfen Sie das Ergebnis und variieren Sie Eingaben und Annahmen.
Wenn Sie dies einmal tun, haben Sie eine Menge Tests, die das Verhalten Ihrer Methode beschreiben.
4) In einem nächsten Schritt würde ich nach Abhängigkeiten suchen : Was benötigt die Methode neben ihrer Eingabe , um richtig zu funktionieren ? Gibt es Möglichkeiten, diese zu reduzieren oder umzustrukturieren? Je weniger Abhängigkeiten Sie haben, desto klarer sehen Sie die Punkte, an denen die ersten Teilungen vorgenommen werden müssen.
5) Von dort aus können Sie den gesamten Refactoring-Weg mit Refactoring-Mustern und Refactoring zu Mustern gehen.
Hier ist ein schönes Vid: GoGaRuCo 2014- Die wissenschaftliche Methode der Fehlerbehebung Es geht um Fehlerbehebung aber dennoch nützlich für eine allgemeine Methodik zu verstehen , wie etwas funktioniert .
Sie erwähnen, dass die aufgerufene Funktion keine Eingabeparameter hat : In diesem speziellen Fall würde ich versuchen, zuerst die Abhängigkeiten zu identifizieren und sie in Parameter umzugestalten, damit Sie sie nach Belieben ein- und auswechseln können.
quelle
Sie erwähnen eine 2000-Zeilen-
update
Methode.Ich würde anfangen, diese Methode von oben nach unten zu lesen. Wenn Sie eine Reihe von Anweisungen finden, die zueinander gehören, dh eine Verantwortung teilen (z. B. einen Benutzer erstellen), extrahieren Sie diese Anweisungen in eine Funktion.
Auf diese Weise ändern Sie nicht die Funktionsweise des Codes, sondern Sie
Wiederholen Sie diese Schritte für andere Methoden.
quelle