Fundierte Artikel über Technologien, die das Kommende formen.

Rob Pikes Programmierregeln sind immer noch richtig

Rob Pike formulierte 1989 fünf Programmierregeln. Heute sind sie relevanter denn je, besonders die, die wir ständig ignorieren.

Alte Karteikarte neben einem vollgestellten modernen Schreibtisch unter einer einzigen Lampe

Im Jahr 1989 hielt Rob Pike, der später Go, UTF-8 und Plan 9 mitentwickelte, fünf Programmierregeln fest. Sie sind kurz genug, um auf eine Karteikarte zu passen, und tiefgründig genug, dass die Programmierwelt seit 37 Jahren darüber diskutiert. Die meisten Entwickler haben mindestens einige davon in Blogposts oder Konferenzvorträgen gesehen. Weniger haben sie wirklich verinnerlicht, was schade ist, denn sie würden viel verschwendeten Aufwand ersparen.

Die Regeln sind verblüffend einfach. Sie handeln nicht von Syntax oder Architekturmustern, sondern davon, wo Programmierer regelmäßig Zeit verschwenden und wie sie damit aufhören.

Die fünf Regeln

Lass mich sie zuerst unverblümt nennen, bevor wir sie auseinandernehmen:

  1. Du kannst nicht vorhersagen, wo ein Programm seine Zeit verbringen wird. Engpässe tauchen an überraschenden Stellen auf. Versuch also nicht, es besser zu wissen, und baue keinen Geschwindigkeitstrick ein, bevor du bewiesen hast, dass genau dort der Engpass liegt.
  2. Messen. Optimiere nicht auf Geschwindigkeit, bevor du gemessen hast, und selbst dann nur, wenn ein Teil des Codes den Rest dominiert.
  3. Ausgefeilte Algorithmen sind bei kleinem n langsam, und n ist meist klein. Ausgefeilte Algorithmen haben große Konstanten. Solange du nicht weißt, dass n häufig groß wird, werde nicht clever.
  4. Ausgefeilte Algorithmen sind fehleranfälliger als einfache und viel schwerer zu implementieren. Nutze einfache Algorithmen ebenso wie einfache Datenstrukturen.
  5. Daten dominieren. Wenn du die richtigen Datenstrukturen gewählt und alles gut organisiert hast, ergeben sich die Algorithmen fast von selbst. Datenstrukturen, nicht Algorithmen, sind das Zentrum der Programmierung.

Die Regeln 1 und 2 betreffen Optimierung. Die Regeln 3 und 4 betreffen Komplexität. Regel 5 betrifft Design. Zusammen bilden sie eine Philosophie, die im Kern von Demut geprägt ist: die Einsicht, dass unsere Intuition über Performance falsch liegt, dass Komplexität Kosten verursacht, die wir unterschätzen, und dass gute Datenstrukturen wichtiger sind als cleverer Code.

Regel 1: Du weißt nicht, wo der Engpass liegt

Das ist die Regel, gegen die Entwickler am selbstbewusstesten verstoßen. „Ich weiß, dass diese Funktion langsam ist, weil sie eine verschachtelte Schleife hat.“ „Ich sollte hier eine Hash-Map nehmen, weil Lookups O(1) sind.“ „Ich reserviere diesen Array vorab, weil Allokation teuer ist.“ Das klingt vernünftig. Oft liegt man damit trotzdem falsch.

Ich habe genug Produktivsysteme profiliert, um eine Sammlung von Beispielen zu haben, in denen der vermeintliche Engpass nicht der echte war. Ein System, in dem alle davon ausgingen, die Datenbank sei der Flaschenhals, bis das Profiling zeigte, dass JSON-Serialisierung 60 % der Anfragezeit verbrauchte. Eine Datenpipeline, in der die „teure“ Matrixmultiplikation 5 % der Laufzeit ausmachte, während das CSV-Parsing 70 % fraß. Eine Webanwendung, deren Team monatelang Datenbankabfragen optimierte, während der eigentliche Engpass die DNS-Auflösung bei jedem ausgehenden HTTP-Request war.

Das menschliche Gehirn ist ein miserabler Profiler. Wir überschätzen Operationen, die konzeptionell teuer wirken (Datenbankabfragen, Netzwerkaufrufe), und unterschätzen solche, die billig wirken (String-Konkatenation, JSON-Parsing, Speicherallokation). Moderne Hardware verschärft das noch: CPU-Caches, Branch Prediction und Out-of-Order-Ausführung sorgen dafür, dass der Zusammenhang zwischen Code-Komplexität und Ausführungszeit zutiefst unintuitiv ist.

Regel 2: Erst messen, dann optimieren

Diese Regel ist die praktische Konsequenz aus Regel 1. Optimiere nicht nach Bauchgefühl. Profiliere. Finde den tatsächlichen Hotspot. Dann optimiere genau den und nur den.

Die zweite Hälfte dieser Regel („nicht, außer ein Teil des Codes überlagert den Rest“) ist genauso wichtig und wird seltener zitiert. Wenn dein Profiler zeigt, dass sich die Laufzeit gleichmäßig auf 20 Funktionen verteilt, die jeweils 5 % ausmachen, gibt es keinen einzelnen Engpass. Eine Funktion doppelt so schnell zu machen, spart 2,5 % der Gesamtlaufzeit. Das lohnt sich selten für die zusätzliche Komplexität. Dann brauchst du einen grundlegend anderen Ansatz, statt einzelne Funktionen zu optimieren.

# Profile first. Always.
# Python
python -m cProfile -s cumulative my_script.py
# Node.js
node --prof app.js
node --prof-process isolate-*.log > profile.txt
# Go
go test -bench=. -cpuprofile=cpu.prof
go tool pprof cpu.prof
# Rust
cargo flamegraph
# General (Linux)
perf record ./my_program && perf report
# The flamegraph (brendangregg.com/flamegraphs) is the most
# informative visualization. It shows you exactly where time
# is spent, in a format that makes bottlenecks visually obvious.

Regel 3: Ausgefeilte Algorithmen sind bei kleinem N langsam

Das ist die Regel, die die Informatikausbildung verkehrt herum vermittelt. Wir lehren, dass O(n log n) besser ist als O(n²), und asymptotisch stimmt das auch. Aber bei n = 20 ist eine gut implementierte Insertion Sort mit O(n²) schneller als ein Merge Sort mit O(n log n), wegen Konstanten, Cache-Verhalten und Overhead.

Praxisbeispiele gibt es zuhauf. Lineare Suche in einem sortierten Array mit 50 Elementen ist schneller als binäre Suche, weil die lineare Suche ein perfektes Cache-Verhalten hat und keine Branch-Mispredictions verursacht. Eine einfache verkettete Liste schlägt einen balancierten Binärbaum bei Sammlungen unter etwa 100 Elementen, weil das Hinterherhangeln durch Zeiger in einem Baum die Cache-Lokalität zerstört. Hash-Maps haben zwar amortisiert O(1)-Lookups, aber die Konstante ist so hoch, dass lineare Suche in einem Array bei Sammlungen unter etwa 30–50 Elementen schneller ist.

Die Standardbibliotheken wissen das. Pythons sorted() verwendet Timsort, der für kleine Teilfolgen auf Insertion Sort zurückfällt. Die std::sort von C++ wechselt unterhalb einer Schwelle (typischerweise 16–32 Elemente) zu Insertion Sort. Rusts sort_unstable kombiniert Quicksort und Insertion Sort. Der „ausgefeilte“ Algorithmus kommt nur dort zum Einsatz, wo n tatsächlich groß genug ist, damit er gewinnt.

Die allgemeine Lehre: Kenne dein n. Wenn du zwischen einem einfachen O(n²)-Algorithmus und einem komplexen O(n log n) wählst, frag dich, wie groß n in der Praxis tatsächlich wird. Liegt es unter ein paar Hundert, ist der einfache Algorithmus so gut wie sicher in Ordnung, und er lässt sich leichter schreiben, debuggen und warten.

Regel 4: Einfach ist besser als clever

Regel 4 erweitert Regel 3 über die Performance hinaus. Ausgefeilte Algorithmen sind nicht nur bei kleinem n langsamer, sie sind auch fehleranfälliger. Ein Rot-Schwarz-Baum hat mehr Randfälle als ein sortiertes Array. Eine lock-freie nebenläufige Datenstruktur hat subtilere Fehlermodi als eine mit Mutex geschützte. Ein eigener Speicherallokator kann mehr Wege haben, den Speicher zu korrumpieren, als der Systemallokator.

Ich habe Teams gesehen, die wochenlang einen eigenen LRU-Cache mit O(1)-Operationen implementiert und debuggt haben, obwohl ein einfaches begrenztes Array mit linearer Verdrängung an einem Nachmittag geschrieben gewesen wäre, auf Anhieb korrekt und für ihre Last schnell genug (es ging um höchstens ein paar Hundert Cache-Einträge).

Die Kosten der Komplexität entstehen nicht nur bei der initialen Implementierung. Sie fallen bei jedem künftigen Entwickler an, der sie verstehen, ändern und debuggen muss. Ein einfacher Algorithmus, den das ganze Team versteht, ist mehr wert als ein cleverer, den nur der ursprüngliche Autor warten kann. Und der ursprüngliche Autor ist sechs Monate später praktisch eine andere Person, die außerdem vergessen hat, wie es funktioniert.

Debugging ist doppelt so schwer wie das Schreiben des Codes. Wenn du den Code also so clever wie möglich schreibst, bist du per Definition nicht schlau genug, ihn zu debuggen. — Brian Kernighan

Regel 5: Daten dominieren

Das ist Pikes wichtigste Regel und die, die Entwickler am ehesten übersehen, die sich auf Algorithmen und Entwurfsmuster konzentrieren. Die Aussage: Wenn deine Datenstrukturen stimmen, ergeben sich die Algorithmen von selbst. Stimmen sie nicht, rettet dich keine algorithmische Raffinesse.

Fred Brooks sagte Ähnliches: „Zeig mir deine Flussdiagramme und verberge deine Tabellen, und ich bleibe ratlos. Zeig mir deine Tabellen, und deine Flussdiagramme brauche ich meist gar nicht.“ Linus Torvalds griff es auf: „Schlechte Programmierer machen sich Sorgen um den Code. Gute Programmierer machen sich Sorgen um Datenstrukturen und deren Beziehungen.“

Dieses Prinzip taucht in der Praxis ständig auf. Eine Codebasis, die Benutzerrechte als flache Liste von Berechtigungs-Strings speichert, sammelt im ganzen Code verstreute, fehleranfällige Prüflogik an. Strukturierst du die Daten als Rollenhierarchie um, wird die Prüflogik trivial. Ein System, das Events als JSON-Blobs speichert, braucht an jedem Konsumenten aufwendiges Parsing und Validieren. Strukturierst du die Events als typisierte Records mit expliziten Schemata, vereinfachen sich die Konsumenten drastisch.

# Bad data structure → complex algorithm
class OrderSystem:
def __init__(self):
self.orders = []  # flat list of all orders
def get_user_orders(self, user_id):
return [o for o in self.orders if o.user_id == user_id]  # O(n)
def get_pending_orders(self):
return [o for o in self.orders if o.status == 'pending']  # O(n)
def get_user_pending_orders(self, user_id):
return [o for o in self.orders
if o.user_id == user_id and o.status == 'pending']  # O(n)
# Better data structure → algorithms become obvious
class OrderSystem:
def __init__(self):
self.orders_by_user = {}      # user_id → [orders]
self.orders_by_status = {}    # status → [orders]
def get_user_orders(self, user_id):
return self.orders_by_user.get(user_id, [])  # O(1)
def get_pending_orders(self):
return self.orders_by_status.get('pending', [])  # O(1)
# The right data structure makes the code self-evident.
# You don't need to think about algorithms at all.

Wie Go diese Regeln verkörpert

Wenn man Pikes Regeln betrachtet, erkennt man darin schon im Keim Go's Designphilosophie. Go, das Pike 20 Jahre nach diesen Regeln mitentwickelte, ist eine Sprache, die systematisch Einfachheit über Cleverness stellt.

  • Keine Generics (anfangs), was einfache Datenstrukturen erzwingt. (Generics kamen erst mit Go 1.18, nachdem man sich jahrelang dagegen gesträubt hatte, bis ein einfach genug wirkendes Design gefunden war.)
  • Kein Operator-Overloading. Code bedeutet, was er aussieht.
  • Keine impliziten Typkonvertierungen. Explizitheit statt Cleverness.
  • Keine Exceptions. Fehler werden dort behandelt, wo sie entstehen.
  • Minimale Algorithmen in der Standardbibliothek. Nutze Slices und Maps statt ausgefallener Datenstrukturen.
  • Eingebautes Profiling (pprof). Messen statt raten.

Go wird von Entwicklern, die ausdrucksstärkere Sprachen bevorzugen, oft als „langweilig“ kritisiert. Genau darum geht es. Pikes Regeln sind ein Rezept für langweiligen Code: Code, der einfach, messbar und auf guten Datenstrukturen statt cleverer Algorithmen aufgebaut ist. Go ist das, was herauskommt, wenn man dieses Rezept in eine Sprache gießt.

Wo die Regeln nicht gelten

Keine Regelsammlung ist universell, und Pikes Regeln haben berechtigte Ausnahmen. Performance-kritische Systeme (Game-Engines, Datenbank-Interna, Compiler) brauchen manchmal ausgefeilte Algorithmen, weil ihr n tatsächlich groß ist. Infrastrukturcode, der millionenfach pro Sekunde läuft, rechtfertigt Optimierungen, die sich für Anwendungscode nicht lohnen. Und manchmal hat der „einfache“ Algorithmus O(n³)-Komplexität, die selbst bei moderatem n schlicht inakzeptabel ist.

Die Regeln sind Heuristiken, keine Gesetze. Ihr Wert liegt darin, eine verbreitete Neigung zu korrigieren: Entwickler optimieren zu früh, wählen zu komplexe Algorithmen und denken zu viel über Code und zu wenig über Datenstrukturen nach. Pikes Regeln wirken diesen Tendenzen entgegen. Wenn du dich in der seltenen Situation wiederfindest, in der die gegenteilige Verzerrung gilt (du brauchst tatsächlich mehr Komplexität, nicht weniger), dann nur zu, werde ausgefeilt. Aber miss vorher.

Siebenunddreißig Jahre nach Pikes Niederschrift gehören die Regeln immer noch zu den besten Programmierratschlägen, die je veröffentlicht wurden. Nicht weil sie überraschend wären, denn die meisten erfahrenen Entwickler denken beim Lesen: „Ja, klar.“ Der Wert liegt darin, sie so klar formuliert zu haben, dass man sie konsequent anwenden kann. Das nächste Mal, wenn du zu einem Rot-Schwarz-Baum, einem eigenen Allokator oder einer „Optimierung“ greifst, die du nicht profiliert hast, denk daran: Erst messen, einfach halten und die Datenstrukturen richtig wählen.