Railway Oriented Programming ist Scott Wlaschins Metapher aus den Jahren 2013 und 2014 für das Komponieren fehlbarer Funktionen: eine Erfolgsspur und eine Fehlerspur, an jedem Schritt eine Weiche dazwischen. Fehler werden zu Werten in einem Result-artigen Rückgabetyp, Kombinatoren wie bind, map und mapError verbinden die Schritte, und der erste Fehler überspringt alle weiteren. Wlaschin selbst begrenzt den Stil auf erwartete Fehlschläge an den Rändern, nicht auf jeden Fehler.

Railway Oriented Programming

Details
Vollständiger Name

Railway Oriented Programming, Scott Wlaschins Metapher für das Komponieren fehlbarer Funktionen — Artikel auf F# for Fun and Profit (2013) und NDC-Vortrag (2014), erweitert in Domain Modeling Made Functional (2018)

Auch bekannt als

ROP, Zweispur-Programmierung, die Eisenbahn-Metapher, Result-basierte Fehlerbehandlung, Fehler als Werte

Kernkonzepte:

Ein Kompositionsstil, keine Bibliothek

ROP benennt die Form des Codes: zwei Spuren, an jedem Schritt eine Weiche und ein Kompositionsoperator, der beide verbindet. Es ist kein Paket, das man installiert. ROP in einer Java- oder TypeScript-Codebasis zu nennen, verlangt diese Form — umgesetzt mit dem Either-artigen Typ, der schon da ist.

Zwei Spuren und eine Weiche

Eine Spur trägt den Erfolg, eine den Fehler. Jeder Schritt ist eine Weiche: Er hält den Wert auf dem glücklichen Pfad oder lenkt ihn auf die Fehlerspur. Auf der Fehlerspur werden alle späteren Schritte übersprungen — Kurzschluss --, und die Pipeline liest sich von oben nach unten, ohne verschachtelte Bedingungen, die Fehlerfälle abzählen.

Fehler sind Werte, nicht Kontrollfluss

Fehlschläge werden Teil des Rückgabetyps statt eines unsichtbaren zweiten Ausgangs aus der Funktion. Eine Signatur, die Result zurückgibt, sagt dem Aufrufer, dass Fehlschlag erwartet ist und behandelt werden muss; eine Signatur, die wirft, sagt ihm nichts.

Die Kombinatoren sind das Vokabular

bind (auch flatMap, andThen) hebt eine Weichenfunktion zur Zweispurfunktion. map hebt eine gewöhnliche Einspurfunktion auf die Erfolgsspur. mapError transformiert die Fehlerspur. Diese Namen zu nennen ist meist der schnellste Weg zu einer Pipeline statt zu einer Kaskade früher Returns.

Entsprechungen in anderen Sprachen

F# Result<'Success, 'Failure>; Rust Result<T, E> mit ? für den Kurzschluss; Scala Either und Try, Vavrs Either und Try in Java, fp-ts und neverthrow in TypeScript, Arrow in Kotlin, Gos explizites Paar (value, err) als nicht-monadischer Verwandter. Die Metapher ist in F# geboren, die Übersetzung muss in anderen Sprachen also ausdrücklich benannt werden.

Kurzschluss oder Sammeln

Pipeline-Schritte schließen kurz — der erste Fehler gewinnt, was richtig ist, wenn Schritt zwei von Schritt eins abhängt. Validierung will meist das Gegenteil: alle Fehler sammeln und zusammen melden. Das ist ein Applicative, kein monadisches bind; wer beides verwechselt, baut Formulare, die Feld für Feld meckern.

Domänenfehler werden modelliert, nicht in Strings gelegt

Die Fehlerspur trägt einen entworfenen Typ — typischerweise eine Union der Fehler, die diese Operation tatsächlich haben kann --, keinen String. Hier trifft ROP auf domain-driven-design: Die Fehlerfälle gehören zum Domänenmodell.

An den Rändern, nicht überall

Der Stil gehört dorthin, wo Fehlschlag erwartet und Teil des Vertrags ist: Parsen, Validieren, Orchestrierung von Use Cases (postels-law am eingehenden Rand). In einer reinen Berechnung, die nicht fehlschlagen kann, fügt ein Result nur Zeremonie hinzu.

Wann zu verwenden:

  • Bei der Orchestrierung von Use Cases, in denen mehrere fehlbare Schritte hintereinander komponiert werden: validieren, Eindeutigkeit prüfen, speichern, benachrichtigen

  • Beim Parsen und Validieren an einer Grenze, wo dem Aufrufer gesagt werden muss, was falsch war

  • Zum Ersetzen einer Kette von try/catch-Blöcken, deren einziger Zweck das Umwandeln einer Exception in eine andere ist

  • Um erwarteten Fehlschlag in Signaturen sichtbar zu machen, damit Reviewer oder Modell sehen, welche Aufrufe Behandlung brauchen

  • Um den Stil über Sprachgrenzen zu benennen, wenn der Typ im Team Either, Try oder ein selbstgebautes Result heißt und die Zweispur-Form gemeint ist, nicht die Idiome dieser Bibliothek

  • Neben design-by-contract: Contracts schieben den Fehlschlag in Vorbedingungen, ROP trägt ihn im Rückgabetyp — die Wahl zwischen beiden lohnt eine bewusste Entscheidung

Häufige Missverständnisse:

  • ❌ „Railway Oriented Programming ist eine F#-Bibliothek“ — es ist ein Kompositionsstil; Result ist ein Typ, und der Stil ist älter und langlebiger als jedes Paket

  • ❌ „Result für jeden Fehler verwenden“ — Wlaschin hat gegen genau das einen eigenen Beitrag geschrieben; Infrastrukturfehler und Programmierfehler sind keine Domänenfehler

  • ❌ „ROP ersetzt Exceptions“ — es ersetzt sie nur für erwartete Fehlschläge; Panics und nicht behebbare Störungen bleiben außerhalb der zwei Spuren

  • ❌ „Die Fehlerspur ist für Fehlermeldungen“ — sie trägt einen modellierten Typ; eine String-Fehlerspur wirft die Information weg, nach der der Aufrufer verzweigen muss

  • ❌ „bind und Applicative sind austauschbar“ — bind schließt kurz, Applicative sammelt; Validierung braucht meist das Zweite

  • ✓ „Zwei Spuren, je Schritt eine Weiche und ein modellierter Fehlertyp an den Rändern“ — das ist der Anker, und er überträgt sich über Sprachgrenzen

Kritik:

  • Wlaschin hat das Gegengewicht selbst geschrieben, und es ist die schärfste verfügbare Kritik. Fünf Jahre nach dem Vortrag, der die Metapher bekannt machte, veröffentlichte er „Against Railway-Oriented Programming“ (20. Dezember 2019). Sein einleitendes Zugeständnis: „I do think it is often used thoughtlessly, especially if it’s a shiny new technique that you’ve just added to your toolbox.“ Die Ersetzung, die die meisten Leser vornehmen, schließt er aus: „Don’t reinvent 'try-catch'!“ Die Grenze zieht er über den Zweck — „Result is only for expected control-flow, not for unexpected situations“ --, was genau die Fälle ausschließt, in denen man einen Stack Trace will. Und den Reflexgebrauch innerhalb eines Moduls lehnt er ab: „If you are doing some complex control flow but the logic is hidden from the outside world, don’t use Result just for the sake of it.“ Wlaschin, „Against Railway-Oriented Programming“.

  • Kein zweiter namentlicher Kritiker auf Englisch oder Deutsch gefunden. Gesucht wurde in beiden Sprachen nach dokumentierten Einwänden eines benannten Autors. Was auf Deutsch existiert, ist Werbung für den Stil, nicht Kritik: Stefan Macke, „Railway Oriented Programming in Java“, heise (20. Januar 2017), stellt den Stil für Java vor, und die kritischen Seiten des Artikels liegen hinter der heise+-Bezahlschranke — daraus wird hier keine Stelle zitiert. Die vielfach wiederholten Einwände — verschachtelte Container, wenn schon eine zweite Monade im Spiel ist, und Weitschweifigkeit in Sprachen ohne do-Notation oder ? — kursieren in Forenfäden ohne zitierbaren Autor und bleiben deshalb weg, statt niemandem zugeschrieben zu werden.

Aktueller Stand:

  • Wlaschins eigene Einschränkungen gehören inzwischen zum Kanon. Der Vortrag von 2014 und der Beitrag „Against ROP“ von 2019 stehen beide aktuell auf F# for Fun and Profit — die maßgebliche Quelle des Begriffs trägt also ihre eigenen Grenzen mit. Wer ROP ohne diese Grenzen zitiert, zitiert die Hälfte.

  • Die Gastsprachen haben Platz dafür gemacht, jede zu ihrer Zeit. Kotlin ist der dokumentierte Fall: „In versions before Kotlin 1.5 Result<T> cannot be used as a direct result type of Kotlin functions, properties of Result type are also restricted“, weil „future versions of Kotlin might wish to expand and/or change semantics of functions that return Result type“ — gefolgt von „UPDATE: These limitations are lifted since Kotlin 1.5.“ Kotlin KEEP, proposals/stdlib/result.md. Rusts ?-Operator und die Either-artigen Bibliotheken in Java und TypeScript sind die andere Hälfte des Bildes: Der Stil ist heute überall verfügbar, mit unterschiedlicher Ergonomie.

  • Den Begriff zu nennen erzeugt den Typ; eine Baseline griff ungefragt danach. Gemessen auf claude-haiku-4-5 im Clean Room am 3. Oktober 2026: Bei einem 900-zeiligen Datenjob, dessen einziges Fehlersignal eine Logzeile mit None war, griff eine von zwei Baselines von sich aus zu Either. Mit dem Begriff benannt, entstanden ein ausdrücklicher Result-Typ und die Erfolgs-/Fehlerspur. Wlaschin 2014 wird auf der schwachen Stufe mit hoher Zuversicht korrekt zugeschrieben.