Alter Code. Hässlicher Code. Komplizierter Code Spaghetti-Code. Jibberish Blödsinn. In zwei Worten, Legacy Code. Diese Serie hilft Ihnen bei der Arbeit und beim Umgang damit.
In unserer vorherigen Lektion haben wir unseren alten Quellcode zum ersten Mal gefunden. Dann ließen wir uns mit dem Code ein Urteil über die Grundfunktionalität bilden und erstellten eine Golden Master-Testsuite, um ein rohes Sicherheitsnetz für zukünftige Änderungen zu haben. Wir werden diesen Code weiter bearbeiten und Sie finden ihn unter Trivia / php_start
Mappe. Der andere Ordner Trivia / php_start
ist mit dieser Lektion fertig Code.
Die Zeit für die ersten Änderungen ist gekommen und wie kann man eine schwierige Codebasis besser verstehen, als magische Konstanten und Zeichenfolgen in Variablen zu extrahieren? Diese scheinbar einfachen Aufgaben geben uns größere und manchmal unerwartete Einblicke in die inneren Abläufe von altem Code. Wir müssen die Absichten des ursprünglichen Code-Autors herausfinden und die richtigen Namen für die Codeteile finden, die wir noch nie zuvor gesehen haben.
Magische Zeichenfolgen sind Zeichenfolgen, die direkt in verschiedenen Ausdrücken verwendet werden, ohne einer Variablen zugewiesen zu werden. Diese Art von Zeichenfolge hatte eine besondere Bedeutung für den ursprünglichen Autor des Codes, aber anstatt sie einer gut benannten Variablen zuzuweisen, hielt der Autor die Bedeutung der Zeichenfolge für offensichtlich.
Beginnen wir mit einem Blick auf unsere Game.php
und versuchen, Strings zu identifizieren. Wenn Sie eine IDE verwenden (und Sie sollten) oder einen intelligenten Texteditor, der Quellcode hervorheben kann, ist das Erkennen der Zeichenfolgen ein Kinderspiel. Hier ist ein Bild, wie der Code auf meinem Display aussieht.
Alles mit Orange ist eine Schnur. Die Suche nach jedem String in unserem Quellcode ist auf diese Weise sehr einfach. Stellen Sie daher sicher, dass die Hervorhebung in Ihrem Editor unterstützt und aktiviert ist, unabhängig von Ihrer Anwendung.
Der erste orangefarbene Teil in unserem Code befindet sich sofort in Zeile drei. Die Zeichenfolge enthält jedoch nur ein Zeilenvorschubzeichen. Dies sollte meiner Meinung nach offensichtlich genug sein, damit wir weitermachen können.
Wenn es darum geht zu entscheiden, was extrahiert werden soll und was unverändert bleiben soll, gibt es wenige Daumen hoch, aber am Ende muss sich Ihre professionelle Meinung durchsetzen. Auf dieser Grundlage müssen Sie entscheiden, was mit jedem von Ihnen analysierten Code zu tun ist.
für ($ i = 0; $ i < 50; $i++) array_push($this->popQuestions, "Pop-Frage". $ i); array_push ($ this-> scienceQuestions, ("Science Question". $ i)); array_push ($ this-> sportsQuestions, ("Sports Question". $ i)); array_push ($ this-> rockQuestions, $ this-> createRockQuestion ($ i)); Funktion createRockQuestion ($ index) gibt "Rock-Frage" zurück. $ index;
Lassen Sie uns also die Zeilen 32 bis 42 analysieren, den Ausschnitt, den Sie oben sehen können. Für Pop-, Wissenschafts- und Sportfragen gibt es nur eine einfache Verkettung. Die Aktion zum Erstellen der Zeichenfolge für eine Gesteinsfrage wird jedoch in eine Methode extrahiert. Sind diese Verkettungen und Strings Ihrer Meinung nach klar genug, um alle in unserer for-Schleife zu halten? Glauben Sie, dass das Extrahieren aller Strings in ihre Methoden die Existenz dieser Methoden rechtfertigen würde? Wenn ja, wie würden Sie diese Methoden nennen??
Unabhängig von der Antwort müssen wir den Code ändern. Es ist an der Zeit, unseren Goldenen Meister zum Laufen zu bringen und unseren Test zu schreiben, der tatsächlich ausgeführt wird und unseren Code mit dem vorhandenen Inhalt vergleicht.
Die Klasse GoldenMasterTest erweitert PHPUnit_Framework_TestCase private $ gmPath; function setUp () $ this-> gmPath = __DIR__. '/gm.txt'; function testGenerateOutput () $ times = 20000; $ this-> generateMany ($ times, $ this-> gmPath); Funktion testOutputMatchesGoldenMaster () $ times = 20000; $ actualPath = '/tmp/actual.txt'; $ this-> generateMany ($ times, $ actualPath); $ file_content_gm = file_get_contents ($ this-> gmPath); $ file_content_actual = file_get_contents ($ actualPath); $ this-> assertTrue ($ file_content_gm == $ file_content_actual); private Funktion generateMany ($ times, $ fileName) … private Funktion generateOutput ($ seed) …
Wir haben einen weiteren Test erstellt, um die Ergebnisse zu vergleichen testGenerateOutput ()
generiert die Ausgabe nur einmal und führt nichts anderes aus. Wir haben auch die goldene Master-Ausgabedatei verschoben "gm.txt"
in das aktuelle Verzeichnis da "/ tmp"
kann gelöscht werden, wenn das System neu startet. Für unsere tatsächlichen Ergebnisse können wir es noch verwenden. Auf den meisten UNIX-ähnlichen Systemen "/ tmp"
ist im Arbeitsspeicher eingebunden und ist somit viel schneller als das Dateisystem. Wenn Sie gut abschneiden, sollten die Tests ohne Probleme bestehen.
Denken Sie daran, unseren Generatortest für zukünftige Änderungen als "übersprungen" zu kennzeichnen. Wenn Sie sich beim Kommentieren oder Löschen ganzer fühlen, tun Sie dies bitte. Es ist wichtig, dass sich unser Goldener Meister nicht ändert, wenn wir unseren Code ändern. Es wurde einmal generiert, und wir möchten es niemals ändern, so dass wir sicher sein können, dass unser neu generierter Code immer mit dem Original übereinstimmt. Wenn Sie sich bei der Datensicherung wohler fühlen, fahren Sie bitte fort.
function testGenerateOutput () $ this-> markTestSkipped (); $ times = 20000; $ this-> generateMany ($ times, $ this-> gmPath);
Ich werde den Test einfach als übersprungen markieren. Dies wird unser Testergebnis auf "Gelb"
, Das bedeutet, dass alle Tests erfolgreich sind, einige werden jedoch als unvollständig markiert.
Mit Tests können wir anfangen, den Code zu ändern. Meiner Meinung nach können alle Saiten im Inneren gehalten werden zum
Schleife. Wir werden den Code aus der createRockQuestion ()
Methode, verschieben Sie es in die zum
Schleife und löschen Sie die Methode insgesamt. Dieses Refactoring wird aufgerufen Inline-Methode.
"Setzen Sie den Körper der Methode in den Körper der Aufrufer und entfernen Sie die Methode." ~ Martin Fowler
Es gibt bestimmte Schritte, um diese Art von Refactoring durchzuführen, wie von Marting Fowler in Refactoring definiert: Verbesserung des Designs von vorhandenem Code:
Wir haben keine erweiterten Unterklassen Spiel
, so gilt der erste Schritt.
Es gibt nur eine einzige Verwendung unserer Methode im zum
Schleife.
function __construct () $ this-> players = array (); $ this-> places = Array (0); $ this-> Geldbörsen = Array (0); $ this-> inPenaltyBox = Array (0); $ this-> popQuestions = array (); $ this-> scienceQuestions = array (); $ this-> sportsQuestions = array (); $ this-> rockQuestions = array (); für ($ i = 0; $ i < 50; $i++) array_push($this->popQuestions, "Pop-Frage". $ i); array_push ($ this-> scienceQuestions, ("Science Question". $ i)); array_push ($ this-> sportsQuestions, ("Sports Question". $ i)); array_push ($ this-> rockQuestions, "Rock Question". $ i); Funktion createRockQuestion ($ index) gibt "Rock-Frage" zurück. $ index;
Wir geben den Code aus createRockQuestion ()
in die zum
Schleife im Konstruktor. Wir haben immer noch unseren alten Code. Jetzt ist es an der Zeit, unseren Test durchzuführen.
Unsere Tests sind bestanden. Wir können unsere löschen createRockQuestion ()
Methode.
Funktion __construct () //… // für ($ i = 0; $ i < 50; $i++) array_push($this->popQuestions, "Pop-Frage". $ i); array_push ($ this-> scienceQuestions, ("Science Question". $ i)); array_push ($ this-> sportsQuestions, ("Sports Question". $ i)); array_push ($ this-> rockQuestions, "Rock Question". $ i); function isPlayable () return ($ this-> howManyPlayers ()> = 2);
Schließlich sollten wir unsere Tests erneut durchführen. Wenn wir einen Aufruf einer Methode verpasst haben, schlagen sie fehl.
Sie sollten wieder passieren. Glückwunsch! Wir sind mit unserem ersten Refactoring fertig.
Strings in den Methoden hinzufügen()
und roll () werden nur verwendet, um sie mit der Echoln ()
Methode. Fragen stellen()
vergleicht Zeichenfolgen mit Kategorien. Dies scheint auch akzeptabel zu sein. currentCategory ()
gibt dagegen Zeichenfolgen zurück, die auf einer Zahl basieren. In dieser Methode gibt es viele doppelte Zeichenfolgen. Das Ändern einer Kategorie, außer Rock würde den Namen an drei Stellen ändern, nur in dieser Methode.
function currentCategory () if ($ this-> places [$ this-> currentPlayer] == 0) return "Pop"; if ($ this-> places [$ this-> currentPlayer] == 4) return "Pop"; if ($ this-> places [$ this-> currentPlayer] == 8) return "Pop"; if ($ this-> places [$ this-> currentPlayer] == 1) return "Wissenschaft"; if ($ this-> places [$ this-> currentPlayer] == 5) return "Wissenschaft"; if ($ this-> places [$ this-> currentPlayer] == 9) return "Wissenschaft"; if ($ this-> places [$ this-> currentPlayer] == 2) return "Sports"; if ($ this-> places [$ this-> currentPlayer] == 6) return "Sports"; if ($ this-> places [$ this-> currentPlayer] == 10) return "Sports"; Gib "Rock" zurück.
Ich denke wir können es besser machen. Wir können eine Refactoring-Methode verwenden Lokale Variable einführen und beseitigen Sie die Vervielfältigung. Befolgen Sie diese Richtlinien:
function currentCategory () $ popCategory = "Pop"; $ scienceCategory = "Wissenschaft"; $ sportCategory = "Sport"; $ rockCategory = "Rock"; if ($ this-> places [$ this-> currentPlayer] == 0) return $ popCategory; if ($ this-> places [$ this-> currentPlayer] == 4) return $ popCategory; if ($ this-> places [$ this-> currentPlayer] == 8) return $ popCategory; if ($ this-> places [$ this-> currentPlayer] == 1) return $ scienceCategory; if ($ this-> places [$ this-> currentPlayer] == 5) return $ scienceCategory; if ($ this-> places [$ this-> currentPlayer] == 9) return $ scienceCategory; if ($ this-> places [$ this-> currentPlayer] == 2) return $ sportCategory; if ($ this-> places [$ this-> currentPlayer] == 6) return $ sportCategory; if ($ this-> places [$ this-> currentPlayer] == 10) return $ sportCategory; return $ rockCategory;
Das ist viel besser. Sie haben wahrscheinlich bereits Verbesserungen in der Zukunft beobachtet, die wir an unserem Code vornehmen könnten, aber wir stehen erst am Anfang unserer Arbeit. Es ist verlockend, alles sofort zu reparieren, aber bitte nicht. Oft, besonders bevor der Code gut verstanden wird, können verführerische Änderungen zu Sackgassen oder sogar zu mehr Code-Bruch führen. Wenn Sie glauben, dass Sie die Möglichkeit haben, dass Sie Ihre Idee vergessen, notieren Sie sie einfach auf einer Notiz oder erstellen Sie eine Aufgabe in Ihrer Projektverwaltungssoftware. Jetzt müssen wir mit unseren Problemen in Bezug auf die Zeichenfolge fortfahren.
Im Rest der Datei werden alle Zeichenfolgen in Bezug auf die Ausgabe ausgegeben und an gesendet Echoln ()
. Vorläufig lassen wir sie unberührt. Wenn Sie diese ändern, wirkt sich dies auf die Druck- und Bereitstellungslogik unserer Anwendung aus. Sie sind Teil der Präsentationsschicht, gemischt mit der Geschäftslogik. Wir werden uns in einer zukünftigen Lektion mit der Trennung verschiedener Anliegen befassen.
Magiekonstanten sind sehr ähnlich wie magische Zeichenketten, aber mit Werten. Diese Werte können boolesche Werte oder Zahlen sein. Wir werden uns hauptsächlich auf die Zahlen konzentrieren, die in verwendet werden ob
Aussagen oder Rückkehr
Aussagen oder andere Ausdrücke. Wenn diese Zahlen eine unklare Bedeutung haben, müssen wir sie in Variablen oder Methoden extrahieren.
include_once __DIR__. '/Game.php'; $ notAWinner; $ aGame = neues Spiel (); $ aGame-> add ("Chet"); $ aGame-> add ("Pat"); $ aGame-> add ("Sue"); do $ aGame-> roll (rand (0, 5) + 1); if (rand (0, 9) == 7) $ notAWinner = $ aGame-> wrongAnswer (); else $ notAWinner = $ aGame-> wasCorrectlyAnswered (); while ($ notAWinner);
Wir fangen mit an GameRunner.php
diesmal und unser erster Fokus ist der rollen()
Methode, die einige Zufallszahlen erhält. Der vorherige Autor wollte diesen Zahlen keine Bedeutung geben. Können wir? Wenn wir den Code analysieren:
rand (0, 5) + 1
Es wird eine Zahl zwischen eins und sechs zurückgegeben. Der zufällige Teil gibt eine Zahl zwischen null und fünf zurück, zu der wir immer eins hinzufügen. Es ist also sicher zwischen eins und sechs. Nun müssen wir den Kontext unserer Bewerbung berücksichtigen. Wir entwickeln ein Trivia-Spiel. Wir wissen, dass es eine Art Brett gibt, auf dem sich unsere Spieler bewegen müssen. Und dazu müssen wir würfeln. Ein Würfel hat sechs Gesichter und kann Zahlen zwischen eins und sechs erzeugen. Das scheint ein vernünftiger Abzug zu sein.
$ würfel = rand (0, 5) + 1; $ aGame-> würfeln ($ würfeln);
Ist das nicht nett? Wir haben das Refactoring-Konzept "Lokale Variablen einführen" erneut verwendet. Wir haben unsere neue Variable benannt $ würfeln
und es stellt die Zufallszahl dar, die zwischen eins und sechs erzeugt wird. Dies machte auch unsere nächste Aussage sehr natürlich: Spiel, würfeln.
Hast du deine Tests gemacht? Ich habe es nicht erwähnt, aber wir müssen sie so oft wie möglich ausführen. Wenn Sie dies nicht getan haben, wäre dies ein guter Zeitpunkt, um sie auszuführen. Und sie sollten passieren.
Es war also nichts anderes als eine Zahl mit einer Variablen auszutauschen. Wir haben einen ganzen Ausdruck genommen, der eine Zahl darstellt, und diese in eine Variable extrahiert. Dies kann technisch gesehen als magischer Konstantfall betrachtet werden, nicht jedoch als reiner Fall. Was ist mit unserem nächsten zufälligen Ausdruck??
if (rand (0, 9) == 7)
Das ist schwieriger. Was sind null, neun und sieben in diesem Ausdruck? Vielleicht können wir sie nennen. Auf den ersten Blick habe ich keine guten Ideen für null und neun, also versuchen wir mal sieben. Wenn die von unserer Zufallsfunktion zurückgegebene Zahl gleich sieben ist, betreten wir den ersten Zweig von ob
Aussage, die eine falsche Antwort erzeugt. Vielleicht könnten wir unsere sieben nennen $ wrongAnswerId
.
$ wrongAnswerId = 7; if (rand (0, 9) == $ wrongAnswerId) $ notAWinner = $ aGame-> wrongAnswer (); else $ notAWinner = $ aGame-> wasCorrectlyAnswered ();
Unsere Tests sind immer noch erfolgreich und der Code ist etwas ausdrucksvoller. Nun, da wir es geschafft haben, unsere Nummer sieben zu nennen, wird die Bedingung in einen anderen Kontext gestellt. Wir können uns auch einige anständige Namen für Null und Neun vorstellen. Sie sind nur Parameter zu rand ()
, Daher werden die Variablen wahrscheinlich min-etwas und max-etwas genannt.
$ minAnswerId = 0; $ maxAnswerId = 9; $ wrongAnswerId = 7; if (rand ($ minAnswerId, $ maxAnswerId) == $ wrongAnswerId) $ notAWinner = $ aGame-> wrongAnswer (); else $ notAWinner = $ aGame-> wasCorrectlyAnswered ();
Nun, das ist ausdrucksstark. Wir haben eine minimale Antwort-ID, eine maximale und eine andere für die falsche Antwort. Geheimnis gelüftet.
do $ dice = rand (0, 5) + 1; $ aGame-> würfeln ($ würfeln); $ minAnswerId = 0; $ maxAnswerId = 9; $ wrongAnswerId = 7; if (rand ($ minAnswerId, $ maxAnswerId) == $ wrongAnswerId) $ notAWinner = $ aGame-> wrongAnswer (); else $ notAWinner = $ aGame-> wasCorrectlyAnswered (); while ($ notAWinner);
Beachten Sie jedoch, dass der gesamte Code in einem mache es
Schleife. Müssen wir die Antwort-ID-Variablen jedes Mal neu zuweisen? Ich denke nicht. Versuchen wir, sie aus der Schleife zu entfernen und zu sehen, ob unsere Tests erfolgreich sind.
$ minAnswerId = 0; $ maxAnswerId = 9; $ wrongAnswerId = 7; do $ dice = rand (0, 5) + 1; $ aGame-> würfeln ($ würfeln); if (rand ($ minAnswerId, $ maxAnswerId) == $ wrongAnswerId) $ notAWinner = $ aGame-> wrongAnswer (); else $ notAWinner = $ aGame-> wasCorrectlyAnswered (); while ($ notAWinner);
Ja. Die Tests bestehen auch so.
Es ist Zeit zu wechseln Game.php
und suche dort auch magische Konstanten. Wenn Sie Code hervorheben, werden Konstanten in hellen Farben hervorgehoben. Meine sind blau und sie sind ziemlich leicht zu erkennen.
Die magische Konstante 50 darin finden zum
Die Schleife war ziemlich einfach. Und wenn wir uns ansehen, was der Code tut, können wir das in der zum
Schleife werden Elemente auf mehrere Arrays verschoben. Wir haben also eine Art Liste mit jeweils 50 Elementen. Jede Liste stellt eine Fragekategorie dar und die Variablen sind eigentlich Klassenfelder, die oben als Arrays definiert wurden.
$ this-> popQuestions = array (); $ this-> scienceQuestions = array (); $ this-> sportsQuestions = array (); $ this-> rockQuestions = array ();
Was kann also 50 darstellen? Ich wette, du hast schon ein paar Ideen. Das Benennen ist eine der schwierigsten Aufgaben beim Programmieren. Wenn Sie mehr als eine Idee haben und sich unsicher sind, welche Sie wählen sollen, schämen Sie sich nicht. Ich habe auch verschiedene Namen in meinem Kopf und prüfe die Möglichkeiten, die beste zu wählen, selbst wenn ich diesen Absatz schreibe. Ich denke, wir können für 50 einen konservativen Namen wählen. Etwas in der Richtung von$ questionsInEachCategory
oder $ categorySize
oder etwas ähnliches.
$ categorySize = 50; für ($ i = 0; $ i < $categorySize; $i++) array_push($this->popQuestions, "Pop-Frage". $ i); array_push ($ this-> scienceQuestions, ("Science Question". $ i)); array_push ($ this-> sportsQuestions, ("Sports Question". $ i)); array_push ($ this-> rockQuestions, "Rock Question". $ i);
Das sieht anständig aus. Wir können es behalten. Und die Tests sind natürlich bestanden.
function isPlayable () return ($ this-> howManyPlayers ()> = 2);
Was ist zwei Ich bin mir sicher, zu diesem Zeitpunkt ist Ihnen die Antwort klar. Das ist einfach:
function isPlayable () $ minimumNumberOfPlayers = 2; return ($ this-> howManyPlayers ()> = $ minimumNumberOfPlayers);
Sind Sie einverstanden? Wenn Sie eine bessere Idee haben, können Sie unten einen Kommentar abgeben. Und deine Tests? Sind sie noch vorbei??
Jetzt in der rollen()
Methode haben wir auch einige Zahlen: zwei, null, 11 und 12.
if ($ roll% 2! = 0)
Das ist ziemlich klar. Wir werden diesen Ausdruck in eine Methode extrahieren, aber nicht in diesem Tutorial. Wir sind immer noch in der Phase des Verstehens und der Jagd nach magischen Konstanten und Zeichenketten. Was ist mit 11 und 12? Sie sind in der dritten Ebene von begraben ob
Aussagen. Es ist ziemlich schwer zu verstehen, wofür sie stehen. Vielleicht, wenn wir die Linien um sie herum betrachten.
if ($ this-> places [$ this-> currentPlayer]> 11) $ this-> places [$ this-> currentPlayer] = $ this-> Plätze [$ this-> currentPlayer] - 12;
Wenn der Platz oder die Position des aktuellen Spielers größer als 11 ist, wird seine Position auf den aktuellen Minus 12 reduziert. Dies klingt wie der Fall, wenn ein Spieler das Ende des Bretts oder des Spielfelds erreicht und sich in seiner ursprünglichen Position befindet Position. Wahrscheinlich Position Null. Wenn unser Spielplan rund ist, wird der Spieler, wenn er die zuletzt markierte Position überschreitet, in die relative erste Position gebracht. 11 könnte also die Boardgröße sein.
$ boardSize = 11; if ($ this-> inPenaltyBox [$ this-> currentPlayer]) if ($ roll% 2! = 0) //… // if ($ this-> platziert [$ this-> currentPlayer]> $ boardSize) $ this-> places [$ this-> currentPlayer] = $ this-> places [$ this-> currentPlayer] - 12; //… // else //… // else //… // if ($ this-> setzt [$ this-> currentPlayer]> $ boardSize) $ this-> setzt [$ this] -> currentPlayer] = $ this-> platziert [$ this-> currentPlayer] - 12; //… //
Vergessen Sie nicht, 11 an beiden Stellen innerhalb der Methode zu ersetzen. Dies wird uns zwingen, die Zuweisung der Variablen außerhalb der ob
Anweisungen, direkt auf der ersten Einrückungsebene.
Aber wenn 11 die Größe des Boards hat, was ist dann 12? Wir subtrahieren 12 von der aktuellen Position des Spielers, nicht 11. Und warum setzen wir die Position nicht einfach auf null anstatt subtrahieren? Denn das würde unsere Tests zum Scheitern bringen. Unsere vorherige Vermutung, dass der Spieler nach dem Code in der Position auf Position 0 landet ob
Anweisung wird ausgeführt, war falsch. Nehmen wir an, ein Spieler ist auf Position zehn und würfelt eine Vier. 14 ist größer als 11, daher wird die Subtraktion erfolgen. Der Spieler landet in Position 10 + 4-12 = 2
.
Dies führt uns zu einer anderen möglichen Benennung für 11 und 12. Ich denke, es ist angemessener, 12 anzurufen $ boardSize
. Aber was lässt uns das für 11? Könnte sein $ lastPositionOnTheBoard
? Ein bisschen lang, aber zumindest sagt es uns die Wahrheit über die magische Konstante aus.
$ lastPositionOnTheBoard = 11; $ boardSize = 12; if ($ this-> inPenaltyBox [$ this-> currentPlayer]) if ($ roll% 2! = 0) //… // if ($ this-> platziert [$ this-> currentPlayer]> $ lastPositionOnTheBoard) $ this-> places [$ this-> currentPlayer] = $ this-> places [$ this-> currentPlayer] - $ boardSize; //… // else //… // else //… // if ($ this-> platziert [$ this-> currentPlayer]> $ lastPositionOnTheBoard) $ this-> platziert [$ this -> currentPlayer] = $ this-> platziert [$ this-> currentPlayer] - $ boardSize; //… //
Ich weiß, ich weiß! Dort gibt es einige Code-Duplikate. Es ist ziemlich offensichtlich, besonders wenn der Rest des Codes verborgen ist. Denken Sie jedoch daran, dass wir nach magischen Konstanten gesucht haben. Es wird auch eine Zeit für doppelten Code geben, aber nicht jetzt.
Ich habe eine letzte magische Konstante im Code gelassen. Kannst du es erkennen? Wenn Sie sich den endgültigen Code ansehen, wird er ersetzt, aber das wäre natürlich Betrug. Viel Glück beim Finden und Danke fürs Lesen.