Startseite / Artikel / Neun koderebene Gewohnheiten, die die Arbeit von Senior-Engineern zuverlässiger machen

Neun koderebene Gewohnheiten, die die Arbeit von Senior-Engineern zuverlässiger machen

Es werden neun konkrete Programmierpraktiken untersucht – von Schutzklauseln bis hin zu einem strengen Datenmodellieren –, die den Code widerstandsfähiger, lesbarer und unter Druck leichter zu debuggen machen.

1471 Wörter

Über einen langen Zeitraum schien die Kluft zwischen den erfahrensten Ingenieuren in einem Team und allen anderen auf reines Wissen zurückzuführen zu sein – irgendein selten bekannter Framework-Trick, eine versteckte API oder ein Shortcut, den noch niemand sonst entdeckt hatte.

Durch die Beobachtung erfahrener Ingenieure aus der Nähe, sei es bei Pairing-Sessions, Code-Reviews oder Incident-Calls, wird etwas weniger Glanzvolles sichtbar. Sie sind nicht unbedingt intelligenter. Sie schreiben einfach Code, der gegen versehentliche Fehler widerstandsfähig ist – und der viel leichter zu reparieren ist, wenn doch etwas schiefgeht.

Nine spezifische Gewohnheiten treten so häufig auf, dass es sich lohnt, sie bewusst anzunehmen.

1. Guard Clauses statt Pyramiden des Untergangs

Ein häufiger früher Fehler besteht darin, Validierungslogik so verschachtelt zu verwenden, dass daraus eine Treppe aus Einrückungen entsteht:

function processWithdrawal(account, amount) {
  if (account.isActive) {
    if (amount > 0) {
      if (account.balance >= amount) {
        return debit(account, amount);
      }
    }
  }
  throw new Error("Withdrawal failed");
}

Dies funktioniert zwar, wird aber nach der dritten Bedingung unleserlich, und die eigentliche Abwicklungslogik liegt drei Ebenen tief versteckt. Der erfahrene Ansatz kehrt die Reihenfolge um: Ungültige Fälle werden sofort abgelehnt, anschließend läuft die eigentliche Logik auf der obersten Ebene ohne Einrückung:

function processWithdrawal(account, amount) {
  if (!account.isActive) throw new AccountInactiveError();
  if (amount <= 0) throw new InvalidAmountError();
  if (account.balance < amount) throw new InsufficientFundsError();

  return debit(account, amount);
}

Es geschieht hier nichts Besonderes. Sie ist einfach unter Druck gut lesbar – was gerade dann wichtig ist, wenn man sie wirklich braucht: während eines Incidents, um ein Uhr morgens, halb wach und dabei zu versuchen herauszufinden, welche der fünf verschachtelten Bedingungen einen in die Irre führt.

2. Namen, die das Geschäft beschreiben, nicht den Datentyp

Die Benennung von Variablen nach ihrer Form statt nach ihrem Sinn – data, res, obj, list – ist in einer einzigen Datei noch verständlich. Sobald es jedoch vierzig Dateien gibt, in denen jeweils data eine andere Bedeutung hat, wird das völlig unübersichtlich.

data = fetch(order_id)
if data["status"] == "done":
    process(data)

Verglichen mit:

order = fetch_order(order_id)
if order.is_fulfilled:
    archive_order(order)

Die zweite Version macht klar, was das Objekt darstellt und welche Bedingung tatsächlich relevant ist, ohne dass man die Funktion zurückverfolgen muss, aus der sie stammt. Das kostet zwar ein paar zusätzliche Zeichen, spart dem nächsten Programmierer jedoch die Mühe, durch drei unzusammenhängende Dateien zu suchen.

3. Eine einzige Schnittstelle zwischen Ihrem Code und der Außenwelt

Drittanbieter-APIs sind unzuverlässige Quellen. Feldnamen werden umbenannt, verschachtelte Strukturen ändern ihre Form, optionale Felder werden zu obligatorischen und wieder umgekehrt. Wenn man die rohen API-Antworten direkt in die Geschäftslogik übernimmt, verwandeln sich all diese Änderungen in eine Art Suchspiel durch den gesamten Codebase.

// scattered everywhere
const price = apiResponse.line_items[0].unit_price_cents / 100;

Der bessere Ansatz ist es, die Antwort genau einmal, direkt an der Grenze, zu übersetzen:

function toLineItem(raw) {
  return {
    label: raw.description,
    priceInDollars: raw.unit_price_cents / 100,
  };
}

Falls der Anbieter später unit_price_cents in price umbenennt, muss sich genau eine Funktion ändern. Alles danach bleibt unberührt und bemerkt den Unterschied nicht.

4. Datenmodelle, die nicht lügen können

Ein Typ, der aus einem Dutzend optionaler Felder besteht, ist ein Typ, der aufgehört hat, die Realität genau darzustellen:

type Ticket = {
  id?: string;
  assignee?: string;
  resolvedAt?: Date;
  resolution?: string;
};

Diese Form ermöglicht es, ein „gelöstes“ Ticket ohne zugehörige Lösung oder ein „zugeteiltes“ Ticket ohne Zuweiser zu erstellen – Zustände, die eigentlich unmöglich sein sollten, werden jedoch ohne Fehler kompiliert. Die Einteilung nach dem tatsächlichen Zustand beseitigt diese ganze Kategorie von Fehlern:

type OpenTicket = { id: string; assignee?: string };
type ResolvedTicket = { id: string; assignee: string; resolvedAt: Date; resolution: string };

Eine Funktion, die eine E-Mail mit einem Zusammenfassung der Lösung sendet, kann nun ausdrücklich ein Argument vom Typ ResolvedTicket verlangen, und der Compiler garantiert, dass nichts Unvollendetes jemals bei ihr ankommt.

5. Frage vom Befehl trennen

Wenn Geschäftsregeln und Nebeneffekte miteinander verwickelt werden, wird das Testen schwierig – und sobald das Testen schwierig ist, werden die Regeln überhaupt nicht mehr getestet:

def promote_employee(employee_id):
    emp = get_employee(employee_id)
    if emp.tenure_months < 12:
        raise Error("Not eligible yet")
    if emp.current_rating < 3:
        raise Error("Rating too low")
    give_raise(emp)
    notify_hr(emp)
    log_promotion(emp)

Durch die Extraktion der Eignungsprüfung in eine eigene, eigenständige Funktion kann man die Regel mit einem einfachen Objekt testen, ohne dass eine Datenbank oder ein E-Mail-Service involviert ist:

def promotion_eligibility(emp):
    if emp.tenure_months < 12:
        return Ineligible("Not enough tenure")
    if emp.current_rating < 3:
        return Ineligible("Rating too low")
    return Eligible()

Sobald die Überprüfung der Regel kostengünstig ist, wird sie tatsächlich durchgeführt, und Randfälle bleiben nicht monatelang unbemerkt, wenn jemand die Anforderungen bezüglich der Dauer ändert.

6. Kommentare erklären warum, niemals was

Ein Kommentar, der lediglich wiederholt, was der Code bereits sagt, ist überflüssiger Ballast:

// increment the counter
counter++;

Ein Kommentar, der die Begründung für eine Entscheidung darlegt, ist einer, den man beibehalten sollte:

// Retry once — the vendor's webhook occasionally arrives before
// the payment record finishes committing on their end.
retryOnce(processWebhook, payload);

Erfahrene Ingenieure verfassen in der Regel deutlich weniger Kommentare als jüngere Kollegen – nicht aus Faulheit, sondern weil sie erkannt haben, dass die meisten Kommentare dazu dienen, Code auszugleichen, der sich nicht von selbst erklärt. Die Kommentare, die bleiben, enthalten Informationen, die der Code allein nicht vermitteln kann: eine Begründung, ein Kompromiss oder eine Hinweis auf etwas, das auf den ersten Blick nicht offensichtlich ist.

7. Fehler, die auf etwas Nützliches hinweisen

Eine Fehlermeldung wie "Ungültige Eingabe" liefert dem nächsten Entwickler keine Anhaltspunkte. Inwiefern ungültig? Welche Eingabe? Eine nützliche Fehlermeldung enthält genügend Details, damit jemand darauf reagieren kann:

{
  "code": "INVALID_DATE_RANGE",
  "message": "End date must be after start date.",
  "field": "endDate"
}

Es ist nicht ungewöhnlich, Frontend-Code zu finden, der Fehlermeldungen prüft, um zu entscheiden, welche Nachricht angezeigt werden soll – etwa if (err.message.includes("date")). Dieses Muster ist von vornherein anfällig. Sobald jemand die Backend-Nachricht umformuliert, funktioniert die UI-Logik stillschweigend nicht mehr. Code existiert dafür, dass Maschinen darauf basierend Entscheidungen treffen können; Nachrichten existieren dafür, dass Menschen sie lesen können. Wenn man die beiden getrennt hält, muss keiner von ihnen unangenehm den anderen ersetzen.

8. Einer Pull Request, eine Idee

Ein Pull Request mit dem Titel wie „fix billing stuff“, der Dutzende von Dateien umfasst und sechs unzusammenhängende Änderungen bündelt, ist nahezu nicht überprüfbar. Wer ihn prüft, genehmigt ihn entweder ohne wirklich nachzusehen oder verbringt eine Stunde damit, herauszufinden, welche Änderung welchen Effekt hatte.

Der disziplinierte Ansatz kann fast übermäßig vorsichtig erscheinen: Den Feldnamen in einem PR ändern, die neue Validierung in einem zweiten und deren Einbindung in den Ablauf in einem dritten. Auf diese Weise wirkt die Arbeit im Moment langsamer. Doch sie ist viel schneller zu überprüfen, und wenn später etwas in der Produktion ausfällt, liefert git log eine echte Antwort, anstatt dass man sich durch einen 400 Zeilen langen Diff kämpfen muss.

9. Behandeln Sie den ersten Entwurf als solchen

Diese letzte Gewohnheit hat weniger mit dem Code selbst zu tun und eher mit Ego. Entwickler in den Anfängen ihrer Karriere betrachten oft die erste funktionierende Version bereits als fertiges Produkt – sie funktioniert, also wird sie veröffentlicht. Erfahrene Ingenieure schreiben diese erste Version bereits unter der Annahme, dass sie sie vor dem Einsatz kritisch überarbeiten werden.

Diese zweite Überprüfung ist der Moment, in dem verschachtelte bedingte Anweisungen zu Schutzklauseln vereinfacht werden, in denen unklare Namen umbenannt werden und in dem ein scheinbar „unmögliches“ Zustand bereits vorher erkannt wird, bevor ein Kunde darauf stößt. Es handelt sich um eine einfache Gewohnheit – anzuhalten, nochmals zu lesen und zu prüfen, ob dies jemanden, der keinerlei Kontext hat, verwirren könnte – doch gerade sie sorgt dafür, dass die anderen acht Gewohnheiten in der Praxis umgesetzt werden.

Der gemeinsame Nenner

Hinter all dem steckt eigentlich nur eine wiederholte Handlung: Komplexität, die sonst später im Kopf eines anderen stecken bleiben würde, jetzt an einem sichtbaren Ort festzuhalten – in einem Namen, einer Grenze, einer Typenbezeichnung oder einer kleinen, gezielten Änderung. Dafür sind keine außergewöhnlichen Fähigkeiten nötig. Es genügt, konsequent zu entscheiden, dass jeder, der diesen Code später liest, eine echte Chance verdient, ihn zu verstehen.

Zusätzliche Lektüre

  • Softwarearchäologie: Eine praktische Methode zum Lesen von Legacy-Code — Lernen Sie einen schrittweisen Ansatz, um sicher dokumentationslose Legacy-Codebasen zu untersuchen – von der Analyse der Commit-Geschichte bis zum Refactoring ohne Störung der Produktion.
  • Sieben Warnsignale bei Code-Reviews, die teure zukünftige Änderungen vorhersagen — Lernen Sie, sieben von Reviewern frühzeitig erkannte Code-Probleme zu erkennen – von booleschen Modusflaggen über ignorierte Fehler bis hin zur Beurteilung, wann jedes davon ein echtes Problem darstellt.