4 Tipps, die Entwickler befolgen sollten, um sauberen Code zu schreiben

4 Tipps, die Entwickler befolgen sollten, um sauberen Code zu schreiben

vg

Haben Sie sich jemals alten Code angesehen, den Sie geschrieben haben, und gedacht: „Was habe ich mir dabei gedacht?“  Oder haben Sie sich gefragt, ob Ihre Teamkollegen ihn leicht verstehen können?

Das Schreiben von klarem, lesbarem Code ist eine Schlüsselkompetenz für jeden Programmierer. Es ist wie Ihre Visitenkarte – zeigen Sie Ihre Fähigkeiten.

Guter Code spricht für sich selbst und sagt anderen, ob Sie Anfänger oder Profi sind.

Die wahre Wahrheit liegt im Code selbst, nicht in den Absichten oder Dokumentationsversprechen des Entwicklers .

Der Java-Compiler nimmt Ihren Code und wandelt ihn in maschinenlesbaren Bytecode um.

Wenn Ihr Code alle Regeln befolgt, funktioniert er. Wenn nicht, werden Sie mit Fehlern konfrontiert. Aber während Compiler mit jedem gültigen Java-Code umgehen können, können Menschen nicht immer dasselbe tun.

Das Schreiben von Code, der für andere Benutzer leicht zu verstehen ist, ist viel schwieriger, als ihn nur zu kompilieren.

Aus diesem Grund konzentrieren wir uns in diesem Kapitel darauf, Code zu schreiben, der sowohl auf Expertenniveau als auch einfach zu lesen ist.

Wenn du das liest, denkst du vielleicht: „Hey, ich mache das die ganze Zeit!“

Aber dieser Blog schlüsselt auf, warum Sie es aus Gewohnheit tun, und erklärt die Risiken, wenn Sie es nicht besser machen.

Lassen Sie uns eintauchen und Ihre Programmierfähigkeiten verbessern! 

1. Stellen Sie die Code-Symmetrie sicher

Beim Schreiben von Bedingungen spielt die Symmetrie eine Schlüsselrolle, um Ihren Code sauber und leicht verständlich zu machen.

Symmetrische Codestrukturen sind nicht nur leichter zu lesen, sondern machen Ihre Absichten auch für andere klarer (und für sich selbst, wenn Sie den Code später noch einmal durchgehen!).

Stellen Sie sich vor, wir bauen ein E-Commerce-Checkout-System auf. Wir müssen je nach Mitgliedschaftsstatus des Benutzers unterschiedliche Rabatte anwenden.

Hier ist der asymmetrische Code:

class Checkout {
DiscountService discountService;
double applyDiscount(User user, double totalAmount) {
if (user.isGuest()) {
return totalAmount;
} else if (user.isRegularMember()) {
return discountService.applyRegularDiscount(totalAmount);
} else if (user.isPremiumMember()) {
return discountService.applyPremiumDiscount(totalAmount);
// Premium members get free shipping
} else if (user.isLifetimeMember()) {
double discountedAmount = discountService.applyPremiumDiscount(totalAmount);
return discountService.applyLifetimeBonusDiscount(discountedAmount);
}
return totalAmount;
}
}
  • Asymmetrie: Die erste Bedingung user.isGuest() gibt einfach den Gesamtbetrag zurück, während andere Bedingungen abgezinste Beträge zurückgeben. Die Struktur ist inkonsistent.
  • Code-Duplizierung: Bei beiden isPremiumMember und isLifetimeMember wenden Sie den Prämienrabatt an, aber das ist nicht offensichtlich, da die Logik getrennt ist.

Diese Art von Struktur mag in einem kleinen System funktionieren, wird aber schwieriger zu befolgen und zu pflegen sein, wenn mehr Arten von Mitgliedern oder Rabatten hinzugefügt werden. Lassen Sie uns das beheben!

Die symmetrische Version

Wir können dies verbessern, indem wir die Struktur symmetrischer gestalten und die Logik in klar definierte Blöcke aufteilen.

class Checkout {
 RabattService rabattService;
  double applyDiscount(Benutzer Benutzer, double totalAmount) {
 Objekte.requireNonNull(Benutzer);  Stellen Sie sicher, dass user nicht null
   ist, wenn (user.isGuest()) {
 totalAmount zurückgeben;  Gäste zahlen den vollen Preis
 }
  Behandeln Sie alle Mitgliedertypen
 double discountedAmount = totalAmount;
  
 if (Benutzer.isRegularMember()) {
 discountedAmount = discountService.applyRegularDiscount(totalAmount);
 } else if (Benutzer.isPremiumMember()) {
 discountedAmount = discountService.applyPremiumDiscount(totalAmount);
 } else if (Benutzer.isLifetimeMember()) {
 discountedAmount = discountService.applyPremiumDiscount(totalAmount);
 discountedAmount = discountService.applyLifetimeBonusDiscount(discountedAmount);
 }
 zurückgegeben discountedAmount;
    }
}

Jetzt behandeln wir alle Elementtypen auf ähnliche Weise. Die Aussagen sind aufeinander abgestimmt und befassen sich jeweils mit einem bestimmten Rabatt in einem übersichtlichen Block.if

  • Die Prämienrabattlogik wird wiederverwendet, wodurch auch Doppelungen vermieden werden.
  • Aus der Struktur geht hervor, dass Nicht-Gastbenutzer immer den Rabattprozess durchlaufen. Der Ablauf ist vorhersehbar und leichter zu erfassen.
  • Der erste Block gilt für Gastbenutzer, und der Rest der Bedingungen gilt für Mitglieder. Diese Trennung verbessert die Lesbarkeit.
  • Wenn später weitere Mitgliedschaftstypen hinzugefügt werden oder wenn sich die Rabattlogik ändert, lässt sich dieser Code einfacher erweitern und anpassen. Sie können z. B. ganz einfach einen weiteren Mitgliedschaftstyp hinzufügen, indem Sie einen weiteren else if Block hinzufügen, wobei der Ablauf und die Logik klar bleiben.

Symmetrie erzwingt ein vorhersagbares Muster für Codeleser, einschließlich zukünftiger

2. Vermeiden Sie unnötige Vergleiche

Wenn Sie gerade erst anfangen, Code zu schreiben, ist es eine häufige Angewohnheit, unnötige Vergleiche hinzuzufügen, insbesondere mit booleschen Werten.

Dies liegt daran, dass wir boolesche Logik oft wie den Vergleich von Zahlen behandeln.

Der Vergleich eines booleschen Werts mit true oder flase fügt zusätzliche Komplexität hinzu, die nicht vorhanden sein muss. Es ist, als würden Sie Ihrem Code Rauschen hinzufügen, wodurch er schwerer zu lesen und zu verwalten ist.

Stellen Sie sich vor, Sie bauen ein Smart-Home-System. Sie haben eine Funktion, die prüft, ob die Lichter an oder aus sind, und Sie möchten eine darauf basierende Meldung drucken.

Hier ist ein Codeausschnitt, der unnötige Vergleiche enthält:

class SmartHome {
boolean lightsOn;
void checkLights() {
// Unnecessary comparison with true
if (lightsOn == true) {
System.out.println("The lights are on!");
} else {
System.out.println("The lights are off!");
}
}
}

In diesem Code vergleichen wir explizit mit . Ist aber schon ein boolescher Wert! Dieser Vergleich ist redundant und fügt dem Code Unordnung hinzu.lightsOntruelightsOn

Warum ist das ein Problem?

Bei der Programmierung möchten wir, dass unser Code sauber und leicht zu lesen ist. Zusätzliche Vergleiche wie dieser bringen keinen Mehrwert, machen den Code jedoch länger und schwieriger zu verstehen, insbesondere wenn es sich um größere Projekte handelt. Außerdem erhöhen sie das Risiko, Fehler zu machen oder Fehler zu verursachen, wenn der Code wächst.

Stell dir vor, du liest ein Buch und jedes Mal, wenn du das Wort „Ja“ siehst, fügt der Autor hinzu: „Ist das wirklich ja?“. Das wäre ärgerlich, oder? Das Gleiche gilt für Ihren Code!

Vereinfachen des Codes

Lassen Sie uns das vorherige Beispiel bereinigen, indem wir den unnötigen Vergleich entfernen:

class SmartHome {
boolean lightsOn;
void checkLights() {
// No need to compare with true
if (lightsOn) {
System.out.println("The lights are on!");
} else {
System.out.println("The lights are off!");
}
}
}

In dieser bereinigten Version prüfen wir einfach direkt lightsOn, ob es wahr oder falsch ist. Das if (lightsOn) erledigt die Arbeit, ohne es mit true vergleichen zu müssen.

Lassen Sie uns dies in eine reale Perspektive bringen. Stellen Sie sich vor, Sie richten ein Smart-Home-System in Ihrem Haus ein. Was Sie nicht sagen:

„Wenn das Licht an ist gleich true, dann lass es an, sonst schalte es aus.“

Du sagst einfach:

„Wenn das Licht an ist, lassen Sie es an. Wenn nicht, schalten Sie es aus.“

Es ist einfacher und die Bedeutung ist ohne zusätzliche Vergleiche klar. Das Gleiche gilt für Ihren Code. Sie müssen nicht noch einmal etwas überprüfen, das bereits von Natur aus boolesch ist.

3. Vermeiden Sie Negationen in Ihrem Code: Denken Sie positiv!

Beim Schreiben von Code gibt es ein gemeinsames Muster, in das viele Entwickler verfallen – die Verwendung von Negationen (wie !isSomething()). Diese Negationen funktionieren zwar gut, machen den Code aber oft verwirrender, als er sein müsste.

Denken Sie darüber nach: Wäre es nicht einfacher, wenn wir unsere Bedingungen positiv schreiben würden? Warum das Vermeiden von Negationen Ihren Code klarer und lesbarer machen kann, zeigen wir Ihnen in diesem Artikel anhand einfacher Beispiele aus dem Alltag und der Programmierung.

Lassen Sie es uns anhand eines Beispiels aufschlüsseln und verstehen, warum positive Ausdrücke besser für die Lesbarkeit und Wartung des Codes sind.

Das Problem mit den Negationen

Stellen Sie sich vor, Sie programmieren eine App, die den Motor eines Autos überwacht. Sie möchten wissen, ob der Motor des Autos überhitzt. Hier ist ein Beispiel, in dem Negationen verwendet werden:

class CarEngine {
boolean isOverheating;
void checkEngine() {
// Using negation to check if the engine is NOT overheating
if (!isOverheating) {
System.out.println("The engine is running fine.");
} else {
System.out.println("Warning: The engine is overheating!");
}
}
}

In diesem Beispiel sagen wir: Wenn der Motor NICHT überhitzt, sagen Sie, dass alles in Ordnung ist.“ Das funktioniert zwar, fügt aber eine zusätzliche Komplexitätsebene hinzu.

Ihr Gehirn muss innehalten und denken: Okay, nicht überhitzt… Also ist es in Ordnung.“ Aber wäre es nicht einfacher, wenn wir es positiv formulieren würden?

Warum positive Ausdrücke besser sind

In den meisten Fällen sind positive Ausdrücke leichter zu lesen und zu verstehen. Sie eliminieren diesen zusätzlichen mentalen Schritt der Verarbeitung einer Negation. Stell es dir so vor, als würde dir jemand sagen:

  • „Ich mag Kaffee nicht.“ 
  • Anstatt nur zu sagen: „Ich mag Kaffee.“ 

Was ist klarer? Das Positive, natürlich!

Wenn Sie Negationen vermeiden, wird Ihr Code einfacher. Und in größeren Codebasen hilft jedes noch so kleine bisschen Vereinfachung.

Vereinfachen des Codes

Gehen wir zurück zum Beispiel des Automotors und schreiben wir es mit positiver Logik neu:

class CarEngine {
boolean isOverheating;
void checkEngine() {
// Using a positive expression
if (isOverheating) {
System.out.println("Warning: The engine is overheating!");
} else {
System.out.println("The engine is running fine.");
}
}
}

In dieser Version haben wir die Negation (!isOverheating) durch ein einfaches positives Häkchen (isOverheating) ersetzt.

Auf diese Weise fragen wir direkt: Überhitzt der Motor? Der Ablauf der Bedingung ist auf einen Blick leichter zu verstehen.

Nehmen wir an, Sie überwachen den Akkustand Ihres Telefons. Du würdest niemandem sagen:

„Wenn der Akku NICHT schwach ist, verwenden Sie das Telefon weiter.“

Du würdest einfach sagen:

„Wenn der Akku voll ist, verwenden Sie das Telefon weiter.“ 

So fühlen sich positive Ausdrücke im Code an – einfacher, sauberer und viel leichter zu verstehen.

POSITIVER CODE IST SAUBERER CODE.

4. Vermeiden Sie NullPointerExceptions in Bedingungen

Lassen Sie uns über etwas sprechen, mit dem Java-Entwickler oft konfrontiert sind: die gefürchtete NullPointerException (NPE)!

Wenn Sie in Java programmiert haben, sind Sie wahrscheinlich schon einmal darauf gestoßen. Es wird normalerweise angezeigt, wenn Sie versuchen, eine Methode aufzurufen oder auf ein Attribut für eine Referenz zuzugreifen, die null ist. Heute sehen wir uns an, wie Sie NPEs in Bedingungen vermeiden und saubereren, sichereren Code schreiben können.

Stellen Sie sich vor, Sie protokollieren Nachrichten in einer Datei mit einer . Hier ist ein Beispiel dafür, wie das aussehen könnte, aber mit ein paar Problemen.Logbook

Der Problemcode

class Logbook {
void writeMessage(String message, Path location) throws IOException {
if (Files.isDirectory(location)) {
throw new IllegalArgumentException("The path is invalid!");
}
if (message.trim().equals("") || message == null) {
throw new IllegalArgumentException("The message is invalid!");
}
String entry = LocalDate.now() + ": " + message;
Files.write(location, Collections.singletonList(entry),
StandardCharsets.UTF_8, StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
}
}

Auf den ersten Blick sieht der Code in Ordnung aus – er sucht nach ungültigen Eingaben, bevor er in die Protokolldatei schreibt.

Aber es gibt eine versteckte Falle, die unser Programm zum Absturz bringen könnte!

Lassen Sie es uns aufschlüsseln und verbessern. Diese Methode hat zwei Hauptprobleme:

  1. NPE in Files.isDirectory(location): If location is null, löst die Methode eine NullPointerException aus, bevor wir überhaupt zu unserer Validierungslogik gelangen.
  2. NPE in message.trim().equals(""): Wenn ja, versuchen wir zuerst message.trim() anzurufen, und bumm! Eine weitere NullPointerException.

In beiden Fällen gehen wir davon aus, dass diese Parameter ( und ) nicht sind, was in realen Apps gefährlich ist. Lassen Sie uns das beheben! 🛠️locationmessagenull

So beheben Sie das Problem

Die goldene Regel bei der Validierung von Eingaben lautet: Immer zuerst null prüfen ! Danach können Sie den tatsächlichen Inhalt oder die Struktur der Daten überprüfen.

Hier ist eine verbesserte Version des Codes:

class Logbook {
void writeMessage(String message, Path location) throws IOException {
if (message == null || message.trim().isEmpty()) {
throw new IllegalArgumentException("The message is invalid!");
}
if (location == null || Files.isDirectory(location)) {
throw new IllegalArgumentException("The path is invalid!");
}
String entry = LocalDate.now() + ": " + message;
Files.write(location, Collections.singletonList(entry),
StandardCharsets.UTF_8, StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
}
}
  • Reihenfolge der Prüfungen: Zuerst prüfen wir, ob message und location null sind. Dadurch werden riskante Methodenaufrufe wie trim() oder Files.isDirectory() für null Werte vermieden.
  • Eingebaute Methode für leere Zeichenfolgen: Wir verwenden sie (message.trim().isEmpty()) , um zu überprüfen, ob die Nachricht nur Leerzeichen oder leer ist, was ein saubererer Ansatz ist als die Überprüfung von equals(„“).

Überprüfen Sie immer auf null zuerst, bevor Sie Methoden für Ihre Objekte aufrufen.

Fazit

Das Schreiben von sauberem, wartbarem und verständlichem Code ist eine essentielle Fähigkeit für jeden Entwickler. Es geht nicht nur darum, dass der Code funktioniert, sondern auch darum, dass er von anderen Entwicklern einfach verstanden und weiterentwickelt werden kann. Die vier Tipps, die wir hier behandelt haben – die Gewährleistung von Code-Symmetrie, das Vermeiden unnötiger Vergleiche, das Arbeiten mit positiven Ausdrücken statt Negationen und das Verhindern von NullPointerExceptions – helfen dabei, den Code klarer, effizienter und weniger fehleranfällig zu gestalten.

Indem Entwickler diese bewährten Praktiken umsetzen, tragen sie nicht nur zur Verbesserung der Codequalität bei, sondern auch zur langfristigen Wartbarkeit und Erweiterbarkeit ihrer Projekte. Guter Code ist schließlich wie eine Visitenkarte, die nicht nur die eigenen Fähigkeiten widerspiegelt, sondern auch den Respekt vor denjenigen, die den Code später verstehen und pflegen müssen.

Weiterer interessanter Beitrag: Geheime Linux-Befehle

com

Newsletter Anmeldung

Bleiben Sie informiert! Wir informieren Sie über alle neuen Beiträge (max. 1 Mail pro Woche – versprochen)