Startseite / Artikel

Sieben Warnsignale bei Reviews, die teure zukünftige Änderungen vorhersagen

Erlernen Sie, sieben häufige Probleme in Code zu erkennen, die erfahrene Prüfer frühzeitig bemerken – von booleschen Modusflaggen über unterdrückte Fehler bis hin zu anderen Problemen – sowie, wie Sie beurteilen können, wann jedes davon ein echtes Problem darstellt.

Verlag: REACTAPP.TOP13 Min. Lesezeit3031 Wörter

Quellen: 7 Code Smells Senior Engineers Catch Immediately

Der problematischste Code sieht nicht fehlerhaft aus. Er kompiliert, bestehen seine Tests und verhält sich in einer Demo gut, doch erfahrene Prüfer können oft bereits nach kurzer Zeit erkennen, dass die nächste Änderung, das nächste Problem oder die nächste Übergabe Probleme verursachen wird. Dieser Leitfaden zeigt sieben Anzeichen, auf die sie reagieren, erklärt, was jedes davon voraussagt, und zeigt, wie man ein echtes Problem von einem Muster unterscheidet, das nur verdächtig erscheint, damit Sie denselben Urteilsvermögen bei Ihren eigenen Prüfungen anwenden können.

Was ein „Code-Smell“ Ihnen tatsächlich mitteilt

Ein erfahrener Reviewer muss selten einen ganzen Modul durchlesen, um Probleme zu erkennen. Ein boolesches Argument, dessen Zweck dort, wo die Funktion aufgerufen wird, unsichtbar ist, eine Utility mit Dutzenden optionaler Funktionalitäten, ein catch-Block, der jeden Fehler in denselben harmlos aussehenden Wert umwandelt – all das bricht heute noch nichts. Das Problem liegt darin, was diese Elemente morgen erfordern werden.

Dieses Instinkt stammt aus Erfahrung und nicht aus einem strengeren Stilleitfaden. Menschen, die seit Jahren Systeme warten, haben gesehen, wie eine kopierte Regel zu drei widersprüchlichen Versionen wurde, wie eine praktische Abstraktion zum Ort für jeden Sonderfall wurde und wie ein Standard-Fallback einen Produktionsausfall wochenlang verdeckte. Was sie gelernt haben, ist zu erkennen, wo im Code Unsicherheit entsteht.

Es hilft, einen Geruch als Anstoß und nicht als Urteil zu betrachten. Er beweist nicht, dass die Umsetzung falsch ist; er signalisiert lediglich, dass der Code genauer überprüft werden muss:

  • Welche Person oder welches Modul ist für diese Entscheidung verantwortlich?
  • Wie hält das aus, wenn sich die Anforderungen ändern?
  • Wird ein Fehler weiterhin sichtbar sein, wenn er auftritt?
  • Kann der nächste Entwickler verstehen, warum der Code so gestaltet ist, ohne herauszufinden, wer ihn geschrieben hat?

Die sieben unten aufgeführten Anzeichen führen am zuverlässigsten zu Kosten, wenn niemand diese Fragen frühzeitig stellt.

1. Eine Funktion, die mehrere unzusammenhängende Aufgaben erledigt

Lange Funktionen ziehen Kritik auf sich, doch ihre Länge ist selten das Problem für einen erfahrenen Reviewer. Eine Funktion kann aus vielen Zeilen bestehen, weil sie eine klare Aufgabe in einer Reihe lesbarer Schritte ausführt. Eine andere passt in zwanzig Zeilen, ist aber dennoch schwer zu handhaben, weil sie gleichzeitig Eingabenerkennung, Berechtigkeitsprüfungen, Speichern in der Datenbank, Formatierung der Antwort, Auslösen von Benachrichtigungen und Übersetzung von Fehlern bewältigt.

Das eigentliche Problem ist, dass gemischte Verantwortlichkeiten einer Funktion viele unabhängige Gründe zum Ändern geben. Eine neue Zugriffsregel betrifft sie, eine Änderung des Antwortformats betrifft sie, ein Schema-Update betrifft sie, eine neue Benachrichtigungsanforderung betrifft sie. Aspekte, die eigentlich getrennt voneinander entwickelt werden sollten, laufen letztendlich an einem Ort zusammen, und schon eine kleine Änderung zwingt einen dazu, den gesamten Ablauf zu verstehen.

Ein nützliches Indiz ist ein Name, der die Funktion nicht ehrlich beschreiben kann. Etwas wie processOrder könnte den Kunden überprüfen, Preise berechnen, Lagerbestände verwalten, eine Zahlungsoption erstellen, die Bestellung speichern und eine Bestätigung per E-Mail senden. Der Name bleibt vage, weil ein präziserer Name wie ein ganzer Absatz aussehen würde.

Warum mechanische Extraktion keine Lösung ist

Die Aufteilung der Funktion in kleinere Teile hilft nicht automatisch. Das Herausziehen von Abschnitten nach Zeilenanzahl kann dieselbe Verwirrung auf mehrere Dateien verteilen, während jede versteckte Abhängigkeit weiterhin bestehen bleibt. Ein besseres Vorgehen besteht darin, nach Grenzen zu suchen, die eine Bedeutung haben:

  • Wo wird eine geschäftliche Entscheidung getroffen?
  • Welcher Teil hat Auswirkungen auf ein externes System?
  • Welcher Teil ist reine Datenumwandlung?
  • Welcher Teil ist für die Transaktion verantwortlich?

Sobald diese Nahtstellen sichtbar werden, kann der Code so organisiert werden, wie die eigentliche Ausführung abläuft. Das zugrunde liegende Problem war niemals die Menge an Arbeit, die die Funktion ausführt. Es lag vielmehr daran, dass niemand eine einzelne Entscheidung ändern konnte, ohne gleichzeitig mehrere unzusammenhängende Entscheidungen im Hinterkopf zu behalten.

2. Boolesche Parameter, die versteckte Modi aktivieren

Ein boolesches Argument scheint unbedeutend zu sein, da es nur einen Bit enthält. In der Praxis schmuggelt es jedoch häufig einen ganzen alternativen Arbeitsablauf unter dem Namen einer bestehenden Funktion hindurch. Betrachten Sie einen Aufruf wie den unten, der dem Leser fast nichts darüber verrät, was geschehen wird:

await updateAccount(accountId, true, false);

Um das zu verstehen, muss man die Implementierung öffnen und herausfinden, ob der erste true einen Update erzwingt, eine Benachrichtigung auslöst, die Validierung umgeht, die Historie beibehält oder das Konto aktiviert. Derjenige, der die Funktion geschrieben hat, erinnert sich vielleicht daran, aber alle anderen müssen die Bedeutung aus den Argumentpositionen rekonstruieren – und ein vertauschtes Paar von Literalen wird jede Typüberprüfung bestehen.

Kombinationen, die niemand entworfen hat

Das Problem verschärft sich, je mehr Flags angesammelt werden. Drei Boolesche Werte ergeben acht mögliche Kombinationen, von denen einige niemals in Betracht gezogen wurden. Wenn eine Funktion forceUpdate, skipValidation und sendNotification akzeptiert, was soll dann geschehen, wenn ein erzwungenes Update die Validierung überspringt, aber dennoch jemanden benachrichtigt? Das Typsystem erlaubt solche Aufrufe gerne, obwohl es im Geschäftsdomänenszenario nicht üblich sein sollte.

Die Prüfer markieren dies, weil eine Reihe von Flaggen in der Regel bedeutet, dass eine Funktion mehrere Operationen ersetzt. Sie mögen interne Strukturen teilen, doch nicht ihre Absicht. Das Bearbeiten von Kontaktdaten, die Überprüfung eines Kontos sowie das Anwenden einer administrativen Überschreibung können alle in derselben Zeile schreiben, dennoch sollte kein Aufrufer sich daran erinnern müssen, welches Flaggenmuster welchen Workflow auswählt.

Die richtige Refaktorierung wählen

In einigen Fällen reicht es aus, auf ein Objekt mit einem einzigen Argument umzusteigen, dessen Eigenschaften Namen haben, denn updateAccount(id, { notify: true }) erklärt sich zumindest am Aufrufort. In anderen Fällen machen separate Funktionen wie verifyAccount und overrideAccount die Geschäftsabsicht explizit und beseitigen völlig unmögliche Kombinationen. Welcher Ansatz geeignet ist, hängt davon ab, ob es sich um tatsächliche Variationen derselben Operation handelt oder um unterschiedliche Operationen, die zufällig Code teilen.

Boolesche Werte sind an sich nicht schlecht. Eine klar benannte Option wie includeArchived ist völlig vernünftig. Das Warnsignal ist eine Flagge, die die Bedeutung der Funktion verändert, anstatt nur ein gut verstandenes Detail anzupassen.

3. Dasselbe Geschäftsregel in unterschiedlichen Formen ausgedrückt

Texte, die kopiert und eingefügt wurden, sind leicht zu erkennen. Verdoppelte Kenntnisse sind jedoch gefährlicher, denn jede Kopie kann völlig anders aussehen.

Nehmen wir eine Berechtigungsregel. Sie kann als versteckter Button in der Benutzeroberfläche, als Rollenprüfung in einem Controller, als Statusbedingung in einem Service oder als WHERE-Klausel in einer Abfrage auftreten. Jede Schicht scheint das System zu schützen, doch jede kann eine leicht unterschiedliche Vorstellung davon enthalten, wer handeln darf.

Alles funktioniert einwandfrei – bis sich die Richtlinien ändern. Ein Endpunkt übernimmt die neue Regel, ein anderer behält die alte Logik bei, und eine geplante Aufgabe umgeht beide, indem sie direkt auf einen internen Service zugreift. Die Schnittstelle bietet nun eine Aktion an, die der Server ablehnt. Ein Teil der Anwendung betrachtet einen Benutzer aufgrund seiner Rolle als autorisiert, während ein anderer Teil darauf besteht, dass er das jeweilige Datensatz eigen sein muss.

Erfahrene Prüfer bemerken wiederkehrende Kombinationen von Statusen, Rollen, Flags und Datumsangaben, da solche Bedingungen oft denselben geschäftlichen Sinn mehrfach ausdrücken. Sie fragen, wo die endgültige Entscheidung liegt und ob die anderen Schichten diese Entscheidung durchsetzen oder sie heimlich neu definieren.

Schichten mit unterschiedlichen Zwecken sind in Ordnung

Das bedeutet nicht, dass eine Regel im gesamten System nur in einer einzigen Codezeile vorkommen darf. Die Benutzeroberfläche kann zur Verbesserung der Nutzerfreundlichkeit Autorisierungen widerspiegeln, während der Backend diese aus Sicherheitsgründen durchsetzen muss. Eine Datenbankbeschränkung kann auf Speicherebene dieselbe Invarianz schützen. Diese Schichten haben unterschiedliche Aufgaben. Ein Problem entsteht, wenn jede von ihnen die geschäftlichen Fragen auf ihre eigene Weise beantwortet.

Ein gesundes Design weist eine klare, bestimmte Verantwortliche für die Regel zu und ermöglicht es den anderen Komponenten, das Ergebnis zu verarbeiten, durchzusetzen oder anzuzeigen. Wenn die Verantwortlichkeit unklar ist, verwandelt sich jede zukünftige Änderung der Richtlinie in eine Suchaufgabe: Man muss jeden Ort aufspüren, an dem die Regel versteckt kodiert ist, und darauf vertrauen, dass man sie alle gefunden hat. Jeder, der bereits Erfahrungen mit Richtlinienverschiebungen gemacht hat, weiß, dass wiederholte Dokumentation in der Regel teurer ist als wiederholter Code.

4. Ein gemeinsamer Hilfsfunktion mit einem wachsenden Options-Objekt

Viele problematische Abstraktionen entstehen ursprünglich als sinnvolle Hilfsfunktionen. Wenn an zwei Stellen ähnliche Logik vorkommt, wird der gemeinsame Teil extrahiert – das Ergebnis ist dann klein, fokussiert und leicht verständlich.

Dann beginnen die beiden Aufrufer unterschiedliche Anforderungen zu haben. Einer benötigt Wiederholungsversuche; der andere darf seine Anfrage niemals wiederholen. Der eine profitiert vom Caching; der andere darf niemals veraltete Werte sehen. Der eine gibt einen vollständigen Datensatz zurück; ein anderer streamt die Daten. Anstatt diese Arbeitsabläufe zu trennen, erzeugt der Hilfsmechanismus für jede Abweichung eine neue Option.

Bald umfasst das Options-Objekt die Übersetzung von Fehlern, die Umformung der Ergebnisse, Einstellungen für das Caching, Anzahl der Wiederholungsversuche, die Handhabung von Authentifizierungen, Hook-Funktionen sowie einige boolesche Schalter. Die meisten Aufrufer nutzen zwei oder drei davon, doch jede Änderung birgt das Risiko, alle davon zu beeinflussen.

Das ist ein Problem, weil die Abstraktion aufgehört hat, Komplexität zu beseitigen, und stattdessen damit begonnen hat, sie zu konzentrieren. Die Aufrufstellen erscheinen kurz, doch das Verständnis des Systems bedeutet nun, zu wissen, wie jede Option mit jeder anderen interagiert. Eine große Anzahl an Aufrufen ist kein Beweis dafür, dass die Abstraktion gut funktioniert; manchmal zeigt sie lediglich an, dass der Hilfsmechanismus nun unvermeidbar geworden ist.

Ähnliche Syntax versus gemeinsames Verhalten

Die entscheidende Frage ist, ob die Aufrufe ein stabiles Verhalten teilen oder lediglich kodisch ähnlich aussehen. Zwei Operationen können beide HTTP-Anfragen senden, aber Fehler und Wiederholungsversuche auf gegensätzliche Weise behandeln. Ein Paar Formulare kann dieselben Felder haben, aber unterschiedliche Validierungs-, Übermittlungs- und Bearbeitungsmechanismen für Entwürfe. Ihr Zusammenführen beseitigt sichtbare Duplikate, macht aber jede tatsächliche Unterschiedlichkeit schwerer ausdrückbar.

Was besser funktioniert, ist in der Regel eine später eingeführte engere Abstraktion. Anstatt den gesamten Workflow zu teilen, sollte man ein einzelnes stabiles Element teilen – wie beispielsweise eine Transformation, eine Richtlinie oder einen Aufruf auf niedriger Ebene – und die unterschiedlichen Workflows klar hervorheben. Wiederverwendung ist weiterhin wertvoll; besorgniserregend sollte hingegen ein Muster sein, bei dem jede neue Anforderung wieder eine weitere Option hinzufügt, die weder von den bestehenden Nutzern benötigt noch verstanden wird. Für eine auf das Frontend ausgerichtete Sicht auf dasselbe Problem siehe warum Frontend-Abstraktionen still und heimlich zu technischem Schuldenberg werden.

5. Fehlerbehandlung, die Misserfolge als Erfolg tarnet

Ein catch-Block kann eine Anwendung als robust erscheinen lassen, während er heimlich das Vertrauen in sie untergräbt.

Stellen Sie sich eine Datenzugriffsschicht vor, die auf einen Datenbankfehler mit der Rückgabe von [] reagiert. Oder einen Dienst, dessen HTTP-Aufruf fehlschlägt und der einfach null zurückgibt. Die Frontend-Anwendung wandelt jeden abgelehnten Anfrageauftrag in einen leeren Zustand um. Nichts bricht zusammen, doch das System kann nicht mehr zwischen einer tatsächlichen Datenfehlbeschaffung und einem fehlgeschlagenen Vorgang unterscheiden.

Prüfer erkennen dieses Problem schnell, weil sie wissen, wie es während eines Incidents abläuft. Ein Dashboard meldet null Datensätze, während die Datenbank nicht verfügbar ist. Ein Kunde sieht „Keine Bestellungen gefunden“, nachdem seine Sitzung stillschweigend abgelaufen ist. Eine Hintergrundaufgabe markiert einen Eintrag als abgeschlossen, weil eine Abhängigkeit nichts Nutzbares zurückgegeben hat. Die Benutzeroberfläche bleibt ruhig, weil sie die Wahrheit durch eine glaubwürdige Antwort ersetzt hat.

Fragen, die man an jeden Fehlerbehandlungsblock stellen sollte

Bei der Überprüfung der Fehlerbehandlung sollten Sie drei Dinge prüfen:

  • Ist die ursprüngliche Ursache beibehalten, zum Beispiel als cause eines neuen Fehlers oder zumindest in den Protokollen?
  • Kann der Aufrufer ein erwartetes Geschäftsresultat von einem technischen Fehler unterscheiden?
  • Hat die Schicht, die den Fehler auffängt, tatsächlich die Befugnis zur nächsten Entscheidung?

Falls der Code auf dieser Ebene keine Möglichkeit zur Wiederherstellung, zum erneuten Versuch, zur Rückgängigmachung von Teilarbeiten oder zur Bereitstellung eines sinnvollen Ersatzes hat, nützt das Auffangen des Fehlers möglicherweise nichts anderes, als Beweise zu zerstören.

Nichts davon bedeutet, dass rohe Ausnahmen die Benutzer erreichen sollten. Den Menschen angezeigte Nachrichten müssen sicher und verständlich sein, und interne Fehler müssen oft in stabile Domänenkategorien übersetzt werden. Die Anforderung ist, dass die Übersetzung genügend Bedeutung beibehält, damit sowohl die Anwendung als auch die Entwickler richtig reagieren können.

Ein leeres Ergebnis sollte bedeuten, dass die Operation erfolgreich war und nichts gefunden wurde. Eine fehlgeschlagene Operation sollte weiterhin als Fehler gelten, es sei denn, das System hat absichtlich einen sicheren Ersatzweg definiert. Die Vereinigung beider Fälle aus Bequemlichkeit verlangsamt das Debuggen und liefert den Benutzern irreführende Informationen. Gute Fehlerbehandlung zeigt einen Fehler auf; schlechte Fehlerbehandlung versteckt ihn.

6. Eine kleine Anforderung mit einer umfangreichen, unzusammenhängenden Änderungsliste

Erfahrene Prüfer schauen sich oft zunächst die Struktur einer Pull-Request an, bevor sie die einzelnen Zeilen lesen. Wenn eine bescheidene Anforderung sich über eine lange Liste unzusammenhängender Dateien erstreckt, deutet das in der Regel auf versteckte Kopplungen oder auf eine Aufgabe hin, die von ihrem ursprünglichen Ziel abgewichen ist.

Das Hinzufügen eines einzigen Feldes kann zuverlässig zu Persistenzlösungen, Diensten, API-Antworten und der Benutzeroberfläche führen; das ist eine kohärente Änderung über alle Schichten hinweg. Das Problem tritt auf, wenn derselbe Pull Request außerdem gemeinsam genutzte Hilfsfunktionen umschreibt, nicht verwandte Typen umbenennt, die Fehlerbehandlung ändert, Module verschiebt und große Snapshots aktualisiert.

Jede dieser Änderungen könnte eine Verbesserung darstellen, doch zusammen fassen sie mehrere Gründe für Änderungen in einer Einheit zusammen. Prüfer müssen die neue Funktion, den Refactoring-Vorgang, die Vertragsänderung sowie die Aufräumarbeiten gleichzeitig bewerten. Wenn eine Regression unentdeckt bleibt, ist es schwieriger, die dafür verantwortliche Änderung zu finden. Muss die neue Funktion wieder rückgängig gemacht werden, gehen auch die nicht verwandten Verbesserungen verloren.

Warum kI-gestütztes Programmieren dies häufiger macht

Code-Generierungswerkzeuge führen leichter zu diesem Muster, da vollständige Umschreibungen fast keinen Aufwand erfordern. Wenn man nur um die Behebung eines einzigen Fehlers bittet, kann man eine überarbeitete Ersatzfunktion erhalten – mit neuen Namen, unterschiedlichem Ablauf, frischen Abstraktionen und geändertem Fehlerverhalten. Die Erstellung kostete fast nichts; die Überprüfung erfordert von dem Team jedoch weiterhin denselben Prüfaufwand wie immer.

Eine gute Regel ist es, die Größe einer Änderung im Verhältnis zu ihrer Erklärbarkeit zu halten. Eine präzise Diagnose verdient in der Regel eine präzise Lösung, während umfassendere Anpassungen in einen eigenen Pull Request aufgenommen werden können, wo sie nach ihren eigenen Merkmalen überprüft und getestet werden können. Das bedeutet nicht, dass überall winzige Pull Requests erforderlich sind; einige architektonische Arbeiten müssen notwendigerweise umfassender sein. Die Frage ist, ob jede sinnvolle Änderung zu einem kohärenten Ergebnis führt. Wenn der Diff größer ist als das Problem, werden in der Regel mehrere Aspekte ohne Trennung behandelt, wodurch Ursache und Wirkung schwerer nachvollzogen werden können.

7. Code, der funktioniert, aber einer Überprüfung widersteht

Einige Implementierungen werden schnell geschrieben – gerade weil ihr Verhalten schwer zu beobachten oder zu steuern ist. Eine Funktion liest die Systemuhr direkt ab, erstellt eigene Abhängigkeiten, verändert den globalen Zustand, führt mehrere externe Aufrufe durch, schluckt ihre eigenen Fehler und gibt fast nichts zurück. Der Erfolgsfall mag in Ordnung sein, doch die Darstellung der Fehlerfälle wird aufwendig.

Die Testbarkeit spiegelt in der Regel die Qualität der Grenzen wider. Wenn eine Geschäftsregel nur durch das Starten der gesamten Anwendung, das Einfügen von Datensatzzeilen in die Datenbank, den Aufruf echter Dienste sowie das Warten auf echte Timer angewendet werden kann, ist die Regel vermutlich mit zu vielen Nebeneffekten verknüpft.

Asynchrone Workflows offenbaren die Lücke

Das Problem tritt am stärksten in mehrstufigen asynchronen Abläufen auf. Stellen Sie sich eine Funktion vor, die eine Änderung speichert, ein Domänenereignis auslöst, einen Benutzer benachrichtigt und eine Zeile im Prüfprotokoll hinzufügt. Fällt der dritte Schritt fehl, kann ein Test erkennen, welche Schritte bereits abgeschlossen sind? Kann man eine Abhängigkeit zum Abbruch zwingen? Kann man simulieren, dass die Funktion zweimal ausgeführt wird? Kann man einen veralteten Update ohne Rücksicht auf Zufälle bezüglich der Zeitreihenfolge erzeugen?

Code, der leicht zu schreiben, aber schwer zu überprüfen ist, fördert das Vertrauen in manuelle Tests. Jemand klickt sich den erfolgreichen Weg durch, sieht das richtige Ergebnis und kommt zu dem Schluss, dass die Funktion fertig ist. In der Produktion treten dann Konkurrenzen, Wiederholungsversuche, ungewöhnliche Daten sowie halb abgeschlossene Operationen auf, die lokal niemand getestet hat.

Überprüfen auf der Ebene, an der das Risiko besteht

Pureität überall und Spott für alles sind ebenfalls keine Lösung. Tests, die hauptsächlich auf Simulierungen beruhen, neigen dazu, interne Details unverändert zu belassen, während sie die tatsächliche Verhaltensweise der Datenbank, des Netzwerks oder der Transaktionen übersehen. Der bessere Ansatz ist es, zu entscheiden, welche Garantien wichtig sind, und jede davon dort zu überprüfen, wo das tatsächliche Risiko liegt:

  • Pure Transformationen können isoliert unitgetestet werden.
  • Datenbank-Invarianten erfordern oft Integrationstests anhand einer echten Datenbank.
  • Dienstgrenzen können Vertragstests erfordern.
  • Verteiltes Verhalten kann von strukturierten Protokollen und Korrelations IDs abhängen, da nicht jede Produktionsbedingung zuverlässig reproduziert werden kann.

Das Einfügen eines Timers oder einer Abhängigkeit anstelle des Zugriffs auf Globale ist oft die kleinste Änderung, die es ermöglicht, wichtige Entscheidungen steuerbar zu machen. Das Problem liegt nicht unbedingt in einer geringen Abdeckung. Es handelt sich vielmehr um Code, bei dem die entscheidenden Entscheidungen nicht gesteuert, beobachtet oder erklärt werden können, sobald der „glückliche“ Ablauf nicht mehr funktioniert.

Wichtige Erkenntnisse

Niemand dieser Anzeichen stellt automatisch ein Urteil dar. Eine lange Funktion kann eine einzige, klare Operation darstellen, eine boolesche Option mag völlig offensichtlich sein, ein gemeinsamer Hilfsfunktion kann wirklich stabiles Verhalten abbilden, und eine umfangreiche Änderung mag unvermeidlich sein. Betrachten Sie die Liste als Orientierungshilfe, um herauszufinden, wo man tiefer graben sollte, und nicht als Checkliste, die mechanisch abgehakt werden muss.

Bei der Überprüfung sind die grundlegenden Fragen:

  • Sind die Verantwortlichkeiten miteinander vermischt, sodass eine Änderung unverwandte Entscheidungen mit sich zieht?
  • Können die Aufrufer eine Funktion verstehen, ohne Positionalflaggen entschlüsseln zu müssen?
  • Hat jede Geschäftsregel einen eindeutigen Verantwortlichen?
  • Versteckt eine gemeinsame Abstraktion tatsächliche Unterschiede hinter Optionen?
  • Erhält die Fehlerbehandlung die genaue Information darüber, was fehlgeschlagen ist?
  • Ist der Unterschied proportional zur von ihm erfüllten Anforderung?
  • Kann das wichtige Verhalten überprüft werden, wenn etwas schiefgeht?

Was erfahrene Reviewer tatsächlich erkennen, ist eine verzögerte Unsicherheit: die Möglichkeit, dass eine neue Anforderung jemanden dazu zwingt, mit unzusammenhängender Logik umzugehen, dass ein neues Problem zu wenige Hinweise für eine Untersuchung hinterlässt, dass der nächste Entwickler nicht weiß, welches Verhalten beabsichtigt war, oder dass ein weiterer Anrufer noch eine weitere Option zu einer bereits überlasteten Abstraktion hinzufügt. Sie erkennen diese Probleme schnell, weil sie bereits die Folgen davon erlebt haben – überladene Hilfsfunktionen entwirren, verschluckte Ausnahmen nachverfolgen, widersprüchliche Regeln in Einklang bringen und die „kleine“ Funktion überprüfen, die bereits die Hälfte des Codebases betrifft. Das Ziel ist es nicht, Code zu beseitigen, der unordentlich aussieht; vielmehr geht es darum, Code zu erkennen, der zukünftige Arbeiten unnötig erschweren wird.

Verwandte Literatur