Es hat sich herausgestellt, dass guter Code meist die gleichen Eigenschaften hat. Er ist leicht zu lesen, er ist leicht zu verstehen, er ist leicht zu pflegen und zu erweitern. Er ist so komplex wie nötig, aber so einfach wie möglich. In diesem Beitrag wird versucht zu beschreiben, was „guter Code“ bedeutet.
1. Die Grundlagen verstehen
Beginnen wir mit etwas Offensichtlichem (aber wirklich Wichtigem). Egal, was man im Leben tut, man sollte die Grundlagen verstehen. Das gilt besonders für die Programmierung. Es gibt Leute, die Code von StackOverflow kopieren, ihn in ihren Editor einfügen und sich dann wundern, warum etwas nicht funktioniert. Das macht aber keinen Sinn. Wenn man nicht grundlegend versteht, was man tut, wird man eine schlechte Zeit haben. Man hat zu lernen, wie Datentypen, Algorithmen und Datenstrukturen funktionieren. Man sollte sich mit verschiedenen Typen und Arten von Programmiersprachen vertraut machen. Man hat sicher zu stellen, dass man seine Werkzeuge kennt, bevor man sich in etwas Größeres stürzt.
2. Zuerst nachdenken, dann schreiben
Wenn man als Softwareentwickler reift, wird man wahrscheinlich feststellen, dass man mehr Zeit damit verbringt, über ein Problem und seine Lösung nachzudenken, als sich tatsächlich hinzusetzen und Code zu schreiben. Das liegt daran, dass Programmieren Denken bedeutet. Wenn man zuerst über das Problem nachdenkt und bereits eine Lösung im Kopf hat, wird der eigentliche Codierungsteil einfach. Programmiersprachen sind ein Werkzeug. Sie sehen unterschiedlich aus, sie funktionieren unterschiedlich, sie haben ihre Stärken und Schwächen. Aber wenn man logisches Denken und Problemlösung beherrscht, kann man mit ein bisschen Erfahrung jede Programmiersprache verwenden.
3. Konsistent sein
Konsistenzdürfte einer der wichtigsten Faktoren beim Schreiben von Software sein. Wenn man in der gesamten Codebasis die gleichen Regeln befolgt, ist es für andere viel einfacher, den Code zu lesen, ihm zu folgen und ihn zu verstehen. Der Code wird lesbarer, verständlicher und wartungsfreundlicher. Auch für einen selbst ist es einfacher. Man sollte den Code sauber halten, einfach und konsistent.
Dies gilt z.B. für:
- Benennung von Dateien
- Benennung von Variablen
- Benennung von Funktionen
- Einrückung (indentation)
- Verwendung von einfachen/doppelten Anführungszeichen
— SCHLECHT
// users-Service.ts
const sendEmail = async (mailSubject, body: string) => {
const mailClient = new EmailClient()
const response = await emailClient.sendEmail(mailSubject, body)
return response.status
}
// orders.ts
let stat = await sendEmail("Thank you for your order",
'Your package is on the way.')
+++ GUT
// users.ts
const sendEmail = async (subject: string, body: string) => {
const emailClient = new EmailClient()
const response = await emailClient.sendEmail(subject, body)
return response.status
}
// orders.ts
const status = await sendEmail('Thank you for your order', 'Your package is on the way.')
4. Aufgaben trennen
Man sollte sich immer für einen Code einsetzen, der so wenig Seiteneffekte und Abhängigkeiten wie möglich hat. Die Bereiche sollten klar voneinander getrennt werden, bestimmte Codeteile sollten nur eine Aufgabe erfüllen, und das auch nur für eine Sache. Wenn man z. B. an einem Datenbankdienst arbeitet, sollte der Zugriff auf den Anwendungsstatus vermieden werden, um die aktuelle Benutzer-ID abzurufen. Man sollte stattdessen einen Parameter hinzufügen, mit dem der Aufrufer diese Information selbst übergeben kann. Auf diese Weise wissen die Anwender genau, welche Parameter für den Aufruf einer Funktion erforderlich sind. Außerdem ist das Programm nicht von anderen Teilen der Software abhängig und verhält sich unabhängig vom Zustand der Anwendung gleich.
— SCHLECHT
// orders.ts
import { store } from 'state'
const getUserOrders = () => {
// Der Datenbankaufruf hängt vom gemeinsamen Anwendungsstatus ab
// Für den Anwender ist dies schwer zu verstehen
// Es kann unangenehme Nebeneffekte haben und macht es schwer, Probleme zu entdecken
return orders.filter(o => o.userId === store.userId)
}
+++ GUT
// orders.ts
const getUserOrders = (userId: string) => {
// Benutzer-ID wird als Parameter übergeben
// Die Funktion ist "reiner" und hängt nicht von anderen Komponenten/Zuständen ab
return orders.filter(o => o.userId === userId)
}
5. Keine „magic numbers“ verwenden
Magische Zahlen und Strings sind schlecht. Es handelt sich dabei meist um Inline-Variablen, die für einen bestimmten Zweck verwendet werden, der beim Betrachten des Codes nicht sofort klar ist, was das Lesen und Verstehen des Codes erschwert. Anstatt eine „magische Zahl“ einzufügen, sollte man eine Konstante mit einem Namen extrahieren, der den Zweck klar angibt.
— SCHLECHT
if (users.length >= 100) {
// Which limit? What does 100 mean?
console.log('Limit reached')
return
}
+++ GUT
// Add separate variable that clearly indicates what "100" stands for
const earlyAccessUserLimit = 100
if (users.length >= earlyAccessUserLimit) {
console.log('Early access user limit reached')
return
}
Quelle: Artikel in Ronald Blüthl’s Blog