Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Web Development

Refactoring Legacy Code: Teil 1 - Der goldene Meister

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Refactoring Legacy Code.
Refactoring Legacy Code: Part 2 - Magic Strings & Constants

German (Deutsch) translation by Alex Grigorovich (you can also view the original English article)

Alter Code. Hässlicher Code. Komplizierter Code. Spaghetti-Code. Quatsch Unsinn. So, Legacy Code. Dies ist eine Serie, die Ihnen hilft, damit zu arbeiten und umzugehen.

In einer idealen Welt würden Sie nur neuen Code schreiben. Sie würden es schön und perfekt schreiben. Sie müssten Ihren Code niemals erneut besuchen und niemals zehn Jahre alte Projekte warten. In einer idealen Welt...

Leider leben wir in einer Realität, die nicht ideal ist. Wir müssen uralten Code verstehen, modifizieren und verbessern. Wir müssen mit Legacy-Code arbeiten. Also, worauf warten Sie? Lassen Sie uns in dieses erste Tutorial eintauchen, den Code abrufen, ihn ein wenig verstehen und ein Sicherheitsnetz für unsere zukünftigen Änderungen erstellen.

Die Definition des Legacy-Codes

Legacy-Code wurde auf so viele Arten definiert, dass es unmöglich ist, eine einzige, allgemein akzeptierte Definition dafür zu finden. Die wenigen Beispiele zu Beginn dieses Tutorials sind nur die Spitze des Eisbergs. Ich werde Ihnen also keine offizielle Definition geben. Stattdessen zitiere ich Ihnen meinen Favoriten.

Für mich ist Legacy-Code einfach Code ohne Tests. ~ Michael Feathers

Nun, das ist die erste formale Definition des Ausdrucks Legacy Code, die von Michael Feathers in seinem Buch Working Effectively with Legacy Code veröffentlicht wurde. Natürlich verwendete die Industrie den Ausdruck seit Ewigkeiten, im Grunde genommen für jeden Code, der schwer zu ändern ist. Diese Definition hat jedoch etwas anderes zu erzählen. Es erklärt das Problem sehr deutlich, so dass die Lösung offensichtlich wird. "Schwer zu ändern" ist so vage. Was sollen wir tun, um das Ändern zu vereinfachen? Wir haben keine Ahnung! "Code ohne Tests" ist dagegen sehr konkret. Und die Antwort auf unsere vorherige Frage ist einfach: Machen Sie Code testbar und testen Sie ihn. Also lasst uns anfangen.

Erhalten Sie unseren Legacy-Code

Diese Serie basiert auf dem außergewöhnlichen Trivia-Spiel von J.B. Rainsberger, das für Legacy Code Retreat-Events entwickelt wurde. Es ist wie echter Legacy-Code gestaltet und bietet auch Möglichkeiten für eine Vielzahl von Refactorings auf einem angemessenen Schwierigkeitsgrad.

Überprüfen Sie den Quellcode

Das Trivia-Spiel wird auf GitHub gehostet und ist GPLv3-lizenziert, sodass Sie frei damit herumspielen können. Wir werden diese Serie beginnen, indem wir uns das offizielle Repository ansehen. Der Code ist auch diesem Tutorial mit allen Änderungen beigefügt, die wir vornehmen werden. Wenn Sie also irgendwann verwirrt sind, können Sie einen kurzen Blick auf das Endergebnis werfen.

Wenn Sie das trivia-Verzeichnis öffnen, sehen Sie unseren Code in mehreren Programmiersprachen. Wir werden in PHP arbeiten, aber Sie können Ihren Favoriten auswählen und die hier vorgestellten Techniken anwenden.

Den Code verstehen

Per Definition ist Legacy-Code schwer zu verstehen, insbesondere wenn wir nicht einmal wissen, was er tun soll. Der erste Schritt besteht also darin, den Code auszuführen und eine Art Argumentation zu machen, worum es geht.

Wir haben zwei Dateien in unserem Verzeichnis.

GameRunner.php scheint ein guter Kandidat für unseren Versuch zu sein, den Code auszuführen.

OK. Unsere Vermutung war richtig. Unser Code lief und erzeugte einige Ausgaben. Durch die Analyse dieser Ausgabe können wir eine grundlegende Vorstellung davon gewinnen, was der Code tut.

  1. Wir wissen, dass es ein Trivia-Spiel ist. Wir wussten es, als wir den Quellcode auscheckten.
  2. Unser Beispiel hat drei Spieler: Chet, Pat und Sue.
  3. Es gibt eine Art Würfeln oder ein ähnliches Konzept.
  4. Es gibt einen aktuellen Standort für einen Spieler. Möglicherweise auf einer Art Brett?
  5. Es gibt verschiedene Kategorien, aus denen Fragen gestellt werden.
  6. Die Benutzer beantworten die Fragen.
  7. Richtige Antworten geben den Spielern Gold.
  8. Falsche Antworten schicken die Spieler in den Strafraum.
  9. Die Spieler können aus dem Strafraum herauskommen, basierend auf einer nicht ganz klaren Logik.
  10. Es scheint, als würde der Benutzer, der zuerst sechs Goldmünzen erreicht, gewinnen.

Das ist viel Wissen. Wir könnten den größten Teil des grundlegenden Verhaltens der Anwendung herausfinden, indem wir nur die Ausgabe betrachten. In realen Anwendungen ist die Ausgabe möglicherweise kein Text auf dem Bildschirm, sondern eine Webseite, ein Fehlerprotokoll, eine Datenbank, eine Netzwerkkommunikation, eine Speicherauszugsdatei usw. In anderen Fällen kann das zu ändernde Modul nicht isoliert ausgeführt werden. In diesem Fall müssen Sie es über andere Module der größeren Anwendung ausführen. Versuchen Sie einfach, das Minimum hinzuzufügen, um eine angemessene Ausgabe Ihres Legacy-Codes zu erhalten.

Scannen von Code

Nachdem wir eine Vorstellung davon haben, was der Code ausgibt, können wir uns das ansehen. Wir werden mit dem Läufer beginnen.

Der Game Runner

Ich beginne gerne damit, den gesamten Code über den Formatierer meiner IDE auszuführen. Dies verbessert die Lesbarkeit erheblich, indem das Formular des Codes mit dem vertraut gemacht wird, was ich gewohnt bin. Also das:

... wird dies:

... was etwas besser ist. Es mag kein großer Unterschied zu dieser kleinen Menge Code sein, aber es wird in unserer nächsten Datei sein.

Wenn wir uns unsere Datei GameRunner.php ansehen, können wir leicht einige wichtige Aspekte identifizieren, die wir in der Ausgabe beobachtet haben. Wir können die Zeilen sehen, die die Benutzer hinzufügen (9-11), dass eine roll() -Methode aufgerufen und ein Gewinner ausgewählt wird. Natürlich sind diese weit entfernt von den inneren Geheimnissen der Logik des Spiels, aber zumindest könnten wir damit beginnen, Schlüsselmethoden zu identifizieren, die uns helfen, den Rest des Codes zu entdecken.

Die Spieledatei

Wir sollten die gleiche Formatierung auch für die Game.php-Datei vornehmen.

Diese Datei ist viel größer; Über 200 Codezeilen. Die meisten Methoden haben eine angemessene Größe, aber einige sind ziemlich groß, und nach der Formatierung können wir feststellen, dass der Codeeinzug an zwei Stellen über vier Ebenen hinausgeht. Ein hohes Maß an Einrückung bedeutet normalerweise viele komplexe Entscheidungen. Daher können wir vorerst davon ausgehen, dass diese Punkte in unserem Code komplexer und sinnvoller zu ändern sind.

Der goldene Meister

Und der Gedanke an Veränderung führt uns zu unserem Mangel an Tests. Die Methoden, die wir in Game.php gesehen haben, sind ziemlich komplex. Mach dir keine Sorgen, wenn du sie nicht verstehst. An diesem Punkt sind sie auch für mich ein Rätsel. Legacy-Code ist ein Rätsel, das wir lösen und verstehen müssen. Wir haben unseren ersten Schritt gemacht, um es zu verstehen, und jetzt ist es Zeit für unseren zweiten.

Was ist dieser goldene Meister?

Wenn Sie mit Legacy-Code arbeiten, ist es fast unmöglich, ihn zu verstehen und Code zu schreiben, der mit Sicherheit alle logischen Pfade durch den Code ausführt. Für diese Art von Tests müssten wir den Code verstehen, aber wir tun es noch nicht. Wir müssen also einen anderen Ansatz wählen.

Anstatt herauszufinden, was zu testen ist, können wir alles oft testen, so dass wir eine enorme Menge an Output erhalten, von der wir mit ziemlicher Sicherheit annehmen können, dass sie durch Ausübung aller Teile unseres Erbes erzeugt wurde Code. Es wird empfohlen, den Code mindestens 10.000 (zehntausend) Mal auszuführen. Wir werden einen Test schreiben, um ihn doppelt so oft auszuführen und die Ausgabe zu speichern.

Schreiben von Golden Master Generator

Wir können vorausdenken und zunächst einen Generator und einen Test als separate Dateien für zukünftige Tests erstellen. Aber ist das wirklich notwendig? Das wissen wir noch nicht genau. Beginnen Sie also einfach mit einer einfachen Testdatei, die unseren Code einmal ausführt und von dort aus unsere Logik aufbaut.

Sie finden im angehängten Codearchiv im source ordner, aber außerhalb des trivia-Ordners unseren Testordner. In diesem Ordner erstellen wir eine Datei: GoldenMasterTest.php.

Wir könnten dies auf viele Arten tun. Wir könnten beispielsweise unseren Code von der Konsole aus ausführen und seine Ausgabe in eine Datei umleiten. Es ist jedoch ein Vorteil, den Test nicht zu ignorieren, wenn er in einem Test ausgeführt wird, der problemlos in unserer IDE ausgeführt werden kann.

Der Code ist recht einfach, puffert die Ausgabe und fügt sie in die $output variable ein. Mit require_once() wird auch der gesamte Code in der enthaltenen Datei ausgeführt. In unserem Var-Dump sehen wir einige bereits bekannte Ausgaben.

Bei einem zweiten Lauf können wir jedoch etwas Seltsames beobachten:

... die Ausgänge unterscheiden sich. Obwohl wir denselben Code ausgeführt haben, ist die Ausgabe unterschiedlich. Die gewürfelten Zahlen sind unterschiedlich, die Positionen der Spieler sind unterschiedlich.

Den Zufallsgenerator säen

Durch die Analyse des wesentlichen Codes vom Läufer können wir sehen, dass er die Funktion rand() verwendet, um Zufallszahlen zu generieren. Unser nächster Stopp ist die offizielle PHP-Dokumentation zur Erforschung dieser rand() - Funktion.

Der Zufallszahlengenerator wird automatisch gesetzt.

Die Dokumentation sagt uns, dass das Seeding automatisch erfolgt. Jetzt haben wir eine andere Aufgabe. Wir müssen einen Weg finden, den Samen zu kontrollieren. Die Funktion srand() kann dabei helfen. Hier ist seine Definition aus der Dokumentation.

Setzt den Zufallszahlengenerator mit Startwert oder mit einem Zufallswert, wenn kein Startwert angegeben ist.

Es sagt uns, dass wir immer die gleichen Ergebnisse erzielen sollten, wenn wir dies vor einem Aufruf von rand() ausführen.

Wir setzen srand(1) vor require_once(). Jetzt ist die Ausgabe immer gleich.

Fügen Sie die Ausgabe in eine Datei ein

Diese Änderung sieht vernünftig aus. Richtig? Wir haben die Codegenerierung in eine Methode extrahiert, zweimal ausgeführt und erwartet, dass die Ausgabe gleich ist. Sie werden es jedoch nicht sein.

Der Grund dafür ist, dass require_once() nicht zweimal dieselbe Datei benötigt. Der zweite Aufruf der Methode generateOutput() erzeugt eine leere Zeichenfolge. Was könnten wir also tun? Was ist, wenn wir require() benötigen? Das sollte jedes Mal ausgeführt werden.

Nun, das führt zu einem anderen Problem: "Cannot redeclare echoln()". Aber woher kommt das? Es befindet sich direkt am Anfang der Game.php-Datei. Der Grund, warum dieser Fehler auftritt, ist, dass wir in GameRunner.php enthalten haben include __DIR__ . '/Game.php';, das versucht, die Spieledatei zweimal einzuschließen, jedes Mal, wenn wir die generateOutput() -Methode aufrufen.

Die Verwendung von include_once in GameRunner.php löst unser Problem. Ja, wir mussten GameRunner.php noch ändern, ohne Tests dafür zu haben! Wir können jedoch zu 99% sicher sein, dass unsere Änderung den Code selbst nicht beschädigt. Es ist eine kleine und einfache Änderung, die uns nicht sehr erschreckt. Und am wichtigsten ist, dass die Tests bestanden werden.

Führen Sie es mehrmals aus

Jetzt, da wir Code haben, den wir viele Male ausführen können, ist es Zeit, eine Ausgabe zu generieren.

Wir haben hier eine andere Methode extrahiert: generateMany(). Es hat zwei Parameter. Eine für die Häufigkeit, mit der wir unseren Generator ausführen möchten, die andere ist eine Zieldatei. Die generierte Ausgabe wird in die Dateien eingefügt. Beim ersten Durchlauf werden die Dateien geleert und für die restlichen Iterationen werden die Daten angehängt. Sie können in die Datei schauen, um die generierte Ausgabe 20 Mal zu sehen.

Aber warte! Der gleiche Spieler gewinnt jedes Mal? Ist das möglich?

Ja! Es ist möglich! Es ist mehr als möglich. Es ist eine sichere Sache. Wir haben den gleichen Samen für unsere Zufallsfunktion. Wir spielen immer wieder das gleiche Spiel.

Führen Sie es jedes Mal anders aus

Wir müssen verschiedene Spiele spielen, sonst ist es fast sicher, dass nur ein kleiner Teil unseres Legacy-Codes tatsächlich immer wieder ausgeführt wird. Der Umfang des goldenen Meisters besteht darin, so viel wie möglich zu trainieren. Wir müssen den Zufallsgenerator jedes Mal neu setzen, aber auf kontrollierte Weise. Eine Möglichkeit besteht darin, unseren Zähler als Startwert zu verwenden.

Dadurch bleibt unser Test weiterhin bestehen, sodass wir sicher sind, dass wir jedes Mal dieselbe vollständige Ausgabe generieren, während die Ausgabe für jede Iteration ein anderes Spiel spielt.

Es gibt verschiedene Gewinner für das Spiel in zufälliger Weise. Das sieht gut aus.

20.000 erreichen

Das erste, was Sie versuchen können, ist, unseren Code für 20.000 Spieliterationen auszuführen.

Das wird fast funktionieren. Es werden zwei 55-MB-Dateien generiert.

Andererseits schlägt der Test mit einem unzureichenden Speicherfehler fehl. Es spielt keine Rolle, wie viel RAM Sie haben, dies wird fehlschlagen. Ich habe 8 GB plus 4 GB Swap und es schlägt fehl. Die beiden Saiten sind einfach zu groß, um in unserer Behauptung verglichen zu werden.

Mit anderen Worten, wir generieren gute Dateien, aber PHPUnit kann sie nicht vergleichen. Wir brauchen eine Umgehung.

Das scheint ein guter Kandidat zu sein, aber es scheitert immer noch. Schade. Wir müssen die Situation weiter untersuchen.

Dies funktioniert jedoch.

Es kann die beiden Zeichenfolgen vergleichen und fehlschlagen, wenn sie unterschiedlich sind. Es hat jedoch einen kleinen Preis. Es kann nicht genau sagen, was falsch ist, wenn sich die Zeichenfolgen unterscheiden. Es wird einfach gesagt: "Failed asserting that false is true." Aber wir werden uns in einem kommenden Tutorial damit befassen.

Schlussfolgerung

Wir sind für dieses Tutorial fertig. Wir haben in unserer ersten Lektion viel gelernt und sind auf einem guten Start für unsere zukünftige Arbeit. Wir haben den Code kennengelernt, ihn auf unterschiedliche Weise analysiert und seine wesentliche Logik größtenteils verstanden. Dann haben wir eine Reihe von Tests erstellt, um sicherzustellen, dass sie so oft wie möglich ausgeführt werden. Ja. Die Tests sind sehr langsam. Auf meiner Core i7-CPU benötigen sie 24 Sekunden, um die Ausgabe zweimal zu generieren. Glücklicherweise werden wir in unserer zukünftigen Entwicklung die Datei gm.txt unberührt lassen und nur einmal pro Lauf eine weitere generieren. Aber 12 Sekunden sind immer noch eine enorme Zeit für eine so kleine Codebasis.

Bis wir diese Serie beendet haben, sollten unsere Tests in weniger als einer Sekunde ausgeführt werden und den gesamten Code ordnungsgemäß testen. Seien Sie also gespannt auf unser nächstes Tutorial, wenn wir Probleme wie magische Konstanten, magische Zeichenfolgen und komplexe Bedingungen angehen. Danke fürs Lesen.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.