Clean Code: Die Regeln, die wirklich zählen
Weniger Dogma, mehr Pragmatismus
Clean Code ist ein geflügeltes Wort geworden. Leider oft missverstanden – entweder als Religion oder als lästige Pflicht. Zeit für eine pragmatische Einordnung.
Das Problem mit dem Dogma
Ich habe Code Reviews erlebt, in denen stundenlang über Methodenlängen diskutiert wurde:
“Diese Methode hat 23 Zeilen. Clean Code sagt maximal 20.”
Das ist nicht der Punkt.
Was wirklich zählt
1. Verständliche Namen
Der größte Hebel für lesbaren Code:
# Schlecht
def calc(a, b, c):
return a * b * (1 - c)
# Gut
def calculate_discounted_price(unit_price, quantity, discount_rate):
return unit_price * quantity * (1 - discount_rate)
Der zweite Code ist länger. Er ist trotzdem besser. Namen sind Dokumentation.
Faustregel: Wenn du einen Kommentar brauchst, um eine Variable zu erklären, ist der Name falsch.
2. Eine Abstraktionsebene pro Funktion
# Vermischt: High-Level und Low-Level
def process_order(order_data):
# High-Level: Business-Logik
if order_data["total"] > 1000:
apply_premium_discount(order_data)
# Low-Level: Datenbank-Details
conn = psycopg2.connect(host="db.example.com", ...)
cursor = conn.cursor()
cursor.execute("INSERT INTO orders ...")
conn.commit()
# High-Level: Benachrichtigung
notify_warehouse(order_data)
# Besser: Saubere Trennung
def process_order(order_data):
if is_premium_order(order_data):
apply_premium_discount(order_data)
save_order(order_data)
notify_warehouse(order_data)
def save_order(order_data):
# Low-Level-Details hier isoliert
with get_db_connection() as conn:
conn.execute("INSERT INTO orders ...", order_data)
3. Fail Fast
Probleme früh erkennen, nicht verstecken:
# Schlecht: Fehler versteckt
def get_user_email(user_id):
try:
user = db.get_user(user_id)
return user.email
except:
return None # Was ist passiert? Keine Ahnung.
# Besser: Explizite Fehlerbehandlung
def get_user_email(user_id):
user = db.get_user(user_id)
if user is None:
raise UserNotFoundError(f"User {user_id} not found")
return user.email
4. Keine Magic Numbers
# Was bedeutet 86400?
cache_timeout = 86400
# Ah, ein Tag in Sekunden
SECONDS_PER_DAY = 86400
cache_timeout = SECONDS_PER_DAY
# Noch besser in Python
from datetime import timedelta
cache_timeout = timedelta(days=1).total_seconds()
5. Single Responsibility
Nicht auf Klassen-Ebene verzetteln. Auf Funktions-Ebene anfangen:
# Diese Funktion macht zu viel
def handle_user_registration(form_data):
# Validierung
if not form_data.get("email"):
raise ValidationError("Email required")
if not is_valid_email(form_data["email"]):
raise ValidationError("Invalid email")
# Speichern
user = User(email=form_data["email"])
db.session.add(user)
db.session.commit()
# Email senden
send_welcome_email(user.email)
# Tracking
analytics.track("user_registered", user.id)
return user
# Besser: Aufgeteilt
def handle_user_registration(form_data):
validated_data = validate_registration(form_data)
user = create_user(validated_data)
send_welcome_email(user)
track_registration(user)
return user
Die unwichtigen Regeln
Worüber ich nicht (mehr) streite:
- Tabs vs. Spaces – Black oder Formatter entscheidet
- Zeilenlänge – 80, 100, 120? Egal, Hauptsache konsistent
- Methodenlänge – 10 Zeilen oder 30? Kommt auf den Kontext an
- Kommentar-Stil – Docstrings vs. Inline? Team-Entscheidung
Diese Dinge löst man einmal im Team, schreibt sie in die .pre-commit-config.yaml und vergisst sie.
DRY – tot oder lebendiger denn je?
Gerade lese ich überall, dass DRY (Don’t Repeat Yourself) obsolet sei. Die Argumentation: KI generiert Code sowieso, Abstraktionen werden zu komplex, lieber Copy-Paste als überengineerte Generalisierung.
Da ist was dran – aber nur zur Hälfte.
Was am DRY-Bashing richtig ist
Übertriebenes DRY führt zu Monstern:
# Das passiert, wenn DRY zum Selbstzweck wird
def process_entity(entity, entity_type, operation, **kwargs):
handler = get_handler(entity_type, operation)
validator = get_validator(entity_type)
transformer = get_transformer(entity_type, operation)
if validator.validate(entity, **kwargs):
transformed = transformer.transform(entity)
return handler.execute(transformed, **kwargs)
...
Drei Zeilen wurden zu einem Framework, das keiner mehr versteht. Das ist nicht DRY, das ist DROWNED (Don’t Repeat Ourselves With Needlessly Elaborate Designs).
Was am DRY-Bashing falsch ist
DRY heißt nicht “keine Wiederholung”. DRY heißt: Eine Wahrheit, ein Ort.
Wenn ich dieselbe Business-Regel an zwei Stellen habe, wird eine davon irgendwann falsch aktualisiert. Das ist keine Theorie, das ist Montag.
Und: Schon bei zwei Vorkommen kann eine Funktion sinnvoll sein. Die alte Regel “erst ab drei Mal” ignoriert, dass der zweite Ort oft genau der ist, an dem der Bug später entsteht.
Komplexität benennen
Was ich besonders schätze: Komplexe Ausdrücke in benannte Einzeiler packen.
# Das hier will niemand lesen
if re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
# Das hier schon
def is_valid_email(email: str) -> bool:
"""Prüft ob email ein gültiges Format hat (RFC 5322 vereinfacht)."""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
if is_valid_email(email):
Der Regex ist immer noch hässlich. Aber jetzt:
- Hat er einen Namen
- Ist er dokumentiert
- Kann ich ihn testen
- Muss ich ihn nur einmal verstehen
Das ist DRY im besten Sinne: Nicht Zeilen sparen, sondern Wissen zentralisieren.
Die goldene Mitte
Meine Heuristik:
- Zweimal gleicher Code → Überlegen, ob eine Funktion sinnvoll ist
- Komplexer Ausdruck → Immer in benannte Funktion, auch wenn nur einmal verwendet
- Ähnlicher aber nicht gleicher Code → In Ruhe lassen, bis das Muster klar wird
- Generische Lösung würde Parameter-Hölle → Lieber zwei spezifische Funktionen
KI-generierter Code macht DRY übrigens nicht obsolet – im Gegenteil. Wenn 41% des Codes von KI kommt und die KI fleißig copy-pastet, brauchen wir Menschen, die aufräumen. Die Studien zeigen: Code-Duplikation hat sich seit 2020 vervierfacht. Das ist kein Feature.
Was ich im Code Review prüfe
- Verstehe ich in 30 Sekunden, was die Funktion tut?
- Gibt es überraschende Seiteneffekte?
- Was passiert im Fehlerfall?
- Kann ich das testen?
- Würde ich um 3 Uhr nachts diesen Code debuggen wollen?
Die letzte Frage ist die wichtigste.
Pragmatismus vor Perfektion
Clean Code ist kein Selbstzweck. Das Ziel ist:
- Wartbarkeit – Andere (und du in 6 Monaten) verstehen den Code
- Änderbarkeit – Neue Features ohne Angst hinzufügen
- Testbarkeit – Code, den man testen kann
Wenn ein “unsauberer” Hack diese Ziele erfüllt und die “saubere” Lösung drei Tage dauert: Nimm den Hack. Mit einem TODO und einem Ticket.
# TODO(UC-1234): Refactor when we support multiple currencies
# Current hack: Hardcoded EUR conversion
price_eur = price_usd * 0.92
Das ist ehrlicher als eine überengineerte Lösung, die niemand braucht.
Die besten Entwickler, die ich kenne, schreiben nicht den elegantesten Code. Sie schreiben Code, den ihre Kollegen verstehen, ändern und erweitern können. Das ist Clean Code.