Der beste Weg, eine große Klasse zu analysieren, bevor sie in kleinere Klassen umgestaltet wird?

8

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.

Sydney
quelle
1
Wie unterscheidet sich dies von den meisten Refactoring-Aufgaben? Sie haben nicht vor, die Benutzeroberfläche zu beschädigen, oder?
JeffO
@ JeffO Ich denke, er möchte eine 30000-Fuß-Ansicht, wie man methodisch festhält, was die Klasse tut und was getan werden muss. nicht "Methode extrahieren", "Variable extrahieren" usw.
Thomas Junk
1
Erstellen Sie eventuell keine Komponententests. Beginnen Sie sofort mit dem Erstellen von Komponententests und codieren Sie in den Tests alle Informationen, die Sie im Laufe der Zeit über die Klasse erfahren. (Leider können die Tests Fehler in der Klasse entdecken, die das ganze Bild verwirren, aber es ist immer noch besser, als zu versuchen, zu verstehen, was ohne Tests vor sich geht.)
Mike Nakis
2
Könnte ein Duplikat sein: programmers.stackexchange.com/questions/6395/…
JeffO
1
Mögliches Duplikat der Entschlüsselung von
Mücke

Antworten:

10

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 step1oder so. Dann bemerke ich das step1und step7scheine 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.

Abhängigkeitsgraph

Karl Bielefeldt
quelle
2
Verdammt, ich wünschte, ich hätte das heute vor meinem Interview gelesen ... Ich hatte die Aufgabe, eine Klasse umzugestalten, das hätte mir sehr geholfen.
Grim
8

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.

Demian Brecht
quelle
1
Ihre Antwort ist wirklich nützlich für mich. Ich hatte eigentlich nicht daran gedacht, zuerst Protokollergebnisse aus der aktuellen Klasse zu erstellen, aber es gibt einige Probleme: Ich glaube nicht, dass ich diese Klasse einem Unit-Test unterziehen kann. Alle Hauptfunktionen werden in einer Methode ohne Eingabeparameter ausgeführt. Diese Methode übernimmt den größten Teil ihres Status von anderen Klassen, die nicht übergeben werden. Ihr Konstruktor akzeptiert ebenfalls keine Argumente. Auf ihre Abhängigkeiten wird über Singletons zugegriffen. Ihr Vorschlag ist, zuerst einen Komponententest durchzuführen und dann mit dem Redesign zu beginnen. Wie kann ich mich anpassen, wenn sich herausstellt, dass für den Unit-Test wahrscheinlich ein Redesign erforderlich ist?
Sydney
Ein zusätzlicher Hinweis zu Ihrem Wort der Vorsicht. Ich verstehe das, ich habe Leute darüber reden sehen, wie oft es am besten ist, diese Dinge in Ruhe zu lassen, aber die Klasse wird derzeit noch entwickelt und neue Entwickler beginnen damit zu arbeiten. Mit neuen Modifikationen und keinen Komponententests kann nicht verstanden werden, wie sich eine Änderung auf die Klasse auswirkt. Ich wurde bereits damit beauftragt, ein Merkmal der Klasse zu abstrahieren, und ich habe diese Aufgabe abgeschlossen, aber dies hat wenig Unterschied zur Mammutgröße des Problems gemacht, und ich glaube, dass noch viel mehr getan werden muss.
Sydney
1
@sydan: Kannst du die Interaktion mit anderen Klassen verspotten? Ich weiß, dass dies in einigen Sprachen einfacher ist als in anderen. Wenn das ein No-Go ist, vielleicht Systemtests, um loszulegen?
Demian Brecht
Interessant, warum das Downvote?
Demian Brecht
Ich bin auch interessiert.
Sydney
4

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.

Thomas Junk
quelle
Hallo Thomas, danke für deine Antwort, wie Demians ist es sehr nützlich und ein guter methodischer Ansatz. Ich glaube, ich werde anfangen, diesen Weg zu beschreiten, indem ich die von Demian vorgeschlagene Protokollierung mit den von Ihnen vorgeschlagenen unterschiedlichen Eingaben kombiniere. Leider hat die Methode, die diese Klasse ausführt, keine Eingabeparameter. Es ist eine Clock-Aufgabe, die eine ewige while-Schleife ausführt. Es sieht so aus, als würden die Abhängigkeiten über Singletons, statische Klassen und dann durch Klassen erfasst, um andere zu erfassen.
Sydney
Die erste Phase der Einrichtung der Methodeneingaben wird für mich wahrscheinlich die schwierigste sein, da nicht bekannt ist, welche Variablen die Klasse verwendet, sind Eingaben, Konstanten, Ausgaben, temporäre Variablen oder zufällige Debugging-Variablen.
Sydney
Oh, das klingt nach Spaß! Vielleicht springen Sie direkt hinein und folgen dem ersten Objekt, das Sie finden. Der Kreis beginnt dann wie beschrieben;)
Thomas Junk
Das ist vielleicht die beste Route ... würde ein 'Strg-F' für die am häufigsten verwendete Variable als starker Ausgangspunkt erscheinen? Sollte ich meine ursprüngliche Frage bearbeiten, um das Fehlen von Eingabeparametern und die Methodenstruktur zu erwähnen?
Sydney
1
Mir gefällt, was Sie in Ihrer Bearbeitung erwähnt haben.
Sydney
4

Sie erwähnen eine 2000-Zeilen- updateMethode.

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

  • verkürzen Sie diese riesige Methode
  • ein besseres Verständnis dieser Methode auf höherer Ebene erlangen
  • Bereiten Sie die Methode für weitere Refactorings vor

Wiederholen Sie diese Schritte für andere Methoden.

Oliver Weiler
quelle
1
Dieser Prozess wird, wenn er zerstörungsfrei ausgeführt wird (nur lesen, beachten, planen), manchmal als Codeüberprüfung bezeichnet . Steven C. McConnells Buch - Code Complete - hat einige schöne
Checklisten-