Ein Journal über Zeit, Fokus und gute ArbeitUnabhängig · sorgfältig · praktisch
Dossier

Anti-IF: bedingte Logik verstehen und Software schrittweise besser strukturieren

Die historische Anti-IF-Idee, ein durchgängiges Refactoring-Beispiel sowie Entscheidungskriterien für Polymorphie, Tabellen und einfache Bedingungen.

Die Anti-IF-Idee richtet sich gegen problematische Verzweigungen, nicht gegen jedes Vorkommen des Schlüsselworts if. Ein kurzes, verständliches Schutzkriterium kann ausgezeichneten Code ergeben. Schwierigkeiten entstehen, wenn dieselbe fachliche Unterscheidung in vielen Methoden wiederholt wird, neue Varianten zahlreiche Dateien verändern und kleine Änderungen unerwartete Seiteneffekte auslösen. Dann ist die Bedingung häufig nur die sichtbare Stelle eines tiefer liegenden Entwurfsproblems: Wissen über eine Entscheidung ist im System verstreut.

Die archivierte Kampagnenseite von 2017 beschreibt eine 2007 von Francesco Cirillo angestoßene Initiative. Im Mittelpunkt standen wirkungsvolle Entwurfspraktiken und der bewusste Einsatz von Objekten. Die historische Adresse bleibt hier als fachlicher Einstieg erhalten. Der folgende Leitfaden ist eine unabhängige heutige Erklärung mit eigenen Beispielen; er ist kein offizieller Kurs, keine Zertifizierung und keine Zusage aktueller Veranstaltungen der früheren Kampagne.

Warum eine harmlose Bedingung wachsen kann

Stellen Sie sich eine Versandberechnung vor. Zunächst gibt es Standardversand und Expressversand. Eine Funktion wählt den Preis, eine zweite erzeugt den Beschreibungstext, eine dritte berechnet die Lieferfrist. Alle drei prüfen denselben Versandtyp. Später kommen Abholung, Auslandsversand und kundenspezifische Ausnahmen hinzu. Jetzt müssen Entwickler bei jeder neuen Variante daran denken, sämtliche betroffenen Stellen zu finden. Das Problem ist nicht die einzelne Abfrage, sondern die fehlende gemeinsame Struktur für zusammengehöriges Verhalten.

Eine wichtige Diagnosefrage lautet deshalb: Was müsste ich ändern, wenn morgen eine weitere fachliche Variante hinzukommt? Wenn die Antwort fünf unabhängige Bedingungen und mehrere Testsammlungen umfasst, ist die Entscheidung wahrscheinlich verteilt. Wenn dagegen nur ein klar begrenzter Zweig einer einzigen Funktion betroffen ist, kann die bestehende Lösung völlig angemessen sein. Entwurfsqualität zeigt sich im Umgang mit wahrscheinlichen Änderungen, nicht in der möglichst kleinen Anzahl bestimmter Sprachkonstrukte.

Gemeinsames Prüfen eines einfachen Prototyps

Drei unterschiedliche Probleme auseinanderhalten

Lange Verschachtelung erschwert das Lesen, weil zahlreiche Voraussetzungen gleichzeitig im Kopf bleiben müssen. Wiederholte Typunterscheidungen erschweren Änderungen, weil dieselbe fachliche Entscheidung mehrfach implementiert ist. Große Methoden vermischen häufig mehrere Verantwortlichkeiten, unabhängig davon, ob sie viele Bedingungen enthalten. Diese Probleme können gemeinsam auftreten, benötigen aber nicht automatisch dieselbe Lösung. Eine neue Klassenhierarchie beseitigt beispielsweise nicht unbedingt eine unverständliche Reihenfolge von Datenbankzugriffen und Fehlermeldungen.

Beginnen Sie daher mit einer Beschreibung des konkreten Schmerzes. Ist eine Änderung langsam, weil unklar ist, welche Stelle zuständig ist? Entstehen Fehler durch vergessene Varianten? Sind Tests schwierig, weil die Berechnung gleichzeitig Netzwerkaufrufe ausführt? Oder ist der Code lediglich länger als gewünscht? Die letzte Beobachtung allein rechtfertigt keine umfassende Umstrukturierung. Ein kleiner, nachvollziehbarer Schritt mit erkennbarem Nutzen ist wertvoller als eine abstrakte Architektur, deren Vorteile nur vermutet werden.

Vor dem Umbau das Verhalten sichern

Refactoring verändert die innere Struktur, während das beobachtbare Verhalten erhalten bleiben soll. Schreiben Sie zunächst Tests für die relevanten Fälle des vorhandenen Codes. Dazu gehören normale Eingaben, fachliche Grenzwerte und bisher unterstützte Fehlerfälle. Bei einem Altsystem dokumentieren solche Tests zunächst das tatsächliche Verhalten, auch wenn es überraschend erscheint. Ein mutmaßlicher Fehler sollte separat entschieden werden; andernfalls vermischen sich Strukturänderung und fachliche Korrektur in einem schwer überprüfbaren Schritt.

Für das Versandbeispiel prüfen Sie mindestens einen Auftrag je Versandart, einen Grenzfall beim Gewicht und das Verhalten bei unbekannten Typen. Beachten Sie auch Währungen, Rundung und gegebenenfalls Steuern. Ein kurzer Lehrcode bildet solche fachlichen Regeln nicht vollständig ab. In einem echten System müssen sie ausdrücklich beschrieben sein. Gerade scheinbar nebensächliche Details werden sonst beim Aufräumen versehentlich geändert und erst durch Kundenreklamationen sichtbar.

Kleine Arbeitsbesprechung mit vorbereiteter Agenda

Ein kleines Beispiel mit wiederholter Typprüfung

Der folgende bewusst vereinfachte Python-Code zeigt die Verteilung einer Entscheidung. Preise sind als ganze Centbeträge dargestellt. Das Beispiel soll Struktur erklären und ist kein fertiges Versandmodul für produktive Abrechnung.

def price_cents(kind):
    if kind == "standard":
        return 490
    if kind == "express":
        return 990
    raise ValueError("Unknown delivery type")

def label(kind):
    if kind == "standard":
        return "Standard delivery"
    if kind == "express":
        return "Express delivery"
    raise ValueError("Unknown delivery type")

Eine neue Versandart erfordert hier Änderungen in beiden Funktionen. Bei zwei stabilen Varianten ist das noch überschaubar. Problematisch wird das Muster, wenn weitere Eigenschaften und Verhaltensweisen hinzukommen. Statt sofort zehn Klassen einzuführen, prüfen wir zuerst, ob die Unterschiede überhaupt Verhalten darstellen oder lediglich Daten sind. Diese Frage verhindert eine häufige Überreaktion: Eine einfache Zuordnung wird mit Vererbung komplizierter gemacht, obwohl eine klar benannte Tabelle ausreichen würde.

Wenn eine Tabelle genügt

DELIVERY = {
    "standard": {"price_cents": 490, "label": "Standard delivery"},
    "express": {"price_cents": 990, "label": "Express delivery"},
}

def delivery_details(kind):
    if kind not in DELIVERY:
        raise ValueError("Unknown delivery type")
    return DELIVERY[kind].copy()

Die gemeinsame Entscheidung liegt nun an einer Stelle. Eine weitere reine Datenvariante kann als Eintrag ergänzt werden. Die Prüfung einer unbekannten Versandart bleibt absichtlich erhalten: Sie macht eine fachliche Grenze deutlich. Das Beispiel zeigt, weshalb „keine IFs“ kein brauchbares Ziel ist. Die relevante Verbesserung besteht darin, zusammengehöriges Wissen zusammenzuführen. Gleichzeitig sollte eine veränderbare Konfiguration validiert werden; eine Tabelle ist kein Ersatz für die Prüfung unvollständiger oder widersprüchlicher Einträge.

Eine datengetriebene Lösung wird weniger passend, wenn jede Variante zahlreiche eigene Berechnungsschritte besitzt. Dann entsteht leicht eine Tabelle voller Funktionsnamen, Sonderfelder und Ausnahmen, die nur noch mit zusätzlicher Verzweigungslogik interpretiert werden kann. Beurteilen Sie die Lesbarkeit aus Sicht einer konkreten Änderung. Kann jemand die Regel an einem Ort verstehen und sicher erweitern? Wenn nicht, lohnt sich ein anderes Modell. Der passende Entwurf hängt von der tatsächlichen Art der Variation ab.

Eine begonnene Arbeitscharge in einer Bäckerei abschließen

Wann Polymorphie hilfreich wird

Polymorphie bedeutet hier, verschiedene Implementierungen über dieselbe fachliche Schnittstelle anzusprechen. Eine Versandstrategie kann beispielsweise Preis und Frist berechnen, während der aufrufende Code nicht mehr wissen muss, welche Variante gewählt wurde. Das kann sinnvoll sein, wenn sich zusammengehörige Verhaltensweisen je Variante unterscheiden. Die Entscheidung für eine Strategie wird an einer klaren Grenze getroffen; danach arbeitet der restliche Ablauf mit dem vereinbarten Verhalten.

Martin Fowlers Refactoring-Katalog beschreibt dieses Muster als gezielte Umformung einer bedingten Struktur. Daraus folgt keine Verpflichtung, jede Fallunterscheidung zu ersetzen. Kosten entstehen durch zusätzliche Typen, Dateien und Indirektion. Wer für drei konstante Werte drei umfangreiche Klassen erzeugt, hat möglicherweise mehr Wartungsaufwand geschaffen. Entscheidend ist, ob die neue Struktur zukünftige fachliche Änderungen lokalisiert und verständlicher macht.

Verantwortung und Auswahl sauber trennen

Bei einer strategiebasierten Lösung bleibt meist eine Auswahlstelle übrig: Aus dem eingegebenen Versandtyp wird die passende Implementierung erzeugt. Diese Stelle ist nicht automatisch ein Fehler. Sie bildet eine Systemgrenze ab, etwa das Einlesen einer Anfrage oder Konfiguration. Problematisch wäre erst, wenn dieselbe Auswahl anschließend erneut in Preisberechnung, Darstellung und Versandabwicklung auftaucht. Zentralisierte Auswahl und verstreute Typprüfung sind strukturell verschiedene Situationen.

Auch eine Fabrikfunktion sollte nicht zum neuen Sammelbecken sämtlicher Geschäftsregeln werden. Sie entscheidet, welches Verhalten benötigt wird; die Umsetzung gehört in die entsprechende Komponente. Halten Sie Abhängigkeiten sichtbar und vermeiden Sie versteckte globale Zustände. So können Tests eine Strategie direkt prüfen und der Ablauf kann mit einer einfachen Testimplementierung arbeiten. Gute Grenzen erleichtern sowohl Erweiterung als auch Fehlersuche, ohne dass das gesamte System für jeden Test gestartet werden muss.

Arbeitsplatz mit Laptop und analogem Timer

Schutzklauseln können die beste Lösung sein

Manche verschachtelten Bedingungen lassen sich durch frühzeitige Rückgaben deutlich verständlicher machen. Wenn eine Anfrage ohne gültige Kennung nicht verarbeitet werden darf, kann die Funktion diesen Fall gleich zu Beginn behandeln. Der normale Ablauf bleibt anschließend auf einer geraden Linie lesbar. Das entfernt keine fachliche Regel, sondern ordnet sie so an, dass Leser weniger gleichzeitig im Kopf behalten müssen. Eine Klassenhierarchie wäre dafür oft unnötig.

Dabei müssen Ressourcen und Nebenwirkungen berücksichtigt werden. Eine frühe Rückgabe darf beispielsweise keine notwendige Transaktionsbeendigung oder Freigabe überspringen. Nutzen Sie die dafür vorgesehenen Sprachmechanismen, statt manuelle Aufräumlogik an viele Ausgänge zu verteilen. Die richtige Refactoring-Frage lautet deshalb nicht nur „Ist die Methode kürzer?“, sondern auch „Bleiben Lebensdauer, Fehlerbehandlung und Verantwortlichkeiten nachvollziehbar?“ Lesbarkeit und korrektes Verhalten gehören zusammen.

Boolean-Parameter und versteckte Betriebsarten

Ein Aufruf wie send(order, True, False) verrät wenig über seine Absicht. Mehrere boolesche Parameter können ein Hinweis darauf sein, dass eine Funktion unterschiedliche Betriebsarten vermischt. Benannte Parameter verbessern zunächst die Lesbarkeit. Manchmal sind getrennte fachliche Operationen noch klarer, etwa Angebot vorbereiten und verbindlichen Auftrag versenden. Die Entscheidung hängt davon ab, ob die Varianten wirklich dieselbe Verantwortung teilen oder nur zufällig in einer Funktion gelandet sind.

Ein eigener Typ für einen fachlichen Zustand kann außerdem ungültige Kombinationen verhindern. Wenn drei Schalter acht Kombinationen erlauben, aber nur drei davon sinnvoll sind, sollte das Modell diese Grenze ausdrücken. Andernfalls müssen zahlreiche Bedingungen immer wieder dieselben ungültigen Zustände abfangen. Trotzdem ist nicht jeder Schalter problematisch: Eine klar benannte, unabhängige Darstellungsoption kann völlig angemessen sein. Analysieren Sie Bedeutung und Kombinationen, nicht nur die Syntax.

Hände bewegen unbeschriftete Notizzettel auf einer Tafel

Ein sicherer Ablauf für ein bestehendes System

Eine zusätzliche Testfrage betrifft die Reihenfolge der Entscheidungen. Manche Bedingungen sehen unabhängig aus, besitzen aber eine fachlich bedeutsame Priorität. Ein Rabatt kann beispielsweise nur gelten, wenn zuvor eine andere Voraussetzung erfüllt wurde. Wird daraus eine ungeordnete Sammlung von Regeln, kann sich das Ergebnis ändern, obwohl jede Einzelregel korrekt übertragen scheint. Dokumentieren Sie deshalb nicht nur einzelne Eingaben, sondern auch Fälle, in denen mehrere Bedingungen gleichzeitig zutreffen. Gerade diese Kombinationen zeigen, ob die neue Struktur dieselben Entscheidungen in derselben fachlichen Reihenfolge trifft.

Prüfen Sie außerdem den Fehlerpfad. Eine bisher ausdrücklich abgelehnte unbekannte Variante darf nach dem Umbau nicht still auf eine Standardbehandlung fallen. Eine solche Voreinstellung macht Tests möglicherweise bequem, versteckt aber unvollständige Konfigurationen. Der passende Standard ist eine fachliche Entscheidung und kein beiläufiges Detail der Programmierung. Wenn ein Standard tatsächlich vorgesehen ist, sollte ein Test seinen Zweck erklären. Wenn keiner vorgesehen ist, bleibt ein verständlicher Fehler häufig die sicherere und ehrlichere Reaktion.

Wählen Sie einen kleinen, häufig geänderten Bereich mit ausreichender Testmöglichkeit. Notieren Sie das heutige Verhalten und die erwartete nächste fachliche Erweiterung. Ergänzen Sie fehlende Tests. Extrahieren Sie zunächst klar benannte Funktionen, bevor Sie die Struktur grundsätzlich ändern. Verschieben Sie anschließend eine einzige Variante in die neue Form und prüfen Sie das Verhalten erneut. Erst wenn dieser Schritt verständlich bleibt, übertragen Sie weitere Varianten.

Trennen Sie nach Möglichkeit Umbenennungen, Strukturänderungen und neue Funktionen in überprüfbare Änderungen. Ein Review kann dann nachvollziehen, welche Teile lediglich verschoben wurden und wo sich Verhalten tatsächlich ändert. Bei riskanten Systemen ist zusätzlich eine kontrollierte Einführung mit Beobachtung relevanter Fehler- und Ergebnisgrößen sinnvoll. Ein umfassender Umbau ohne Rückweg ist kein Qualitätsbeweis. Kleine Schritte erleichtern das Lernen und machen falsche Entwurfsannahmen früher sichtbar.

Was ein gutes Review prüfen sollte

Fragen Sie zuerst, ob die neue Struktur die konkrete Änderung erleichtert, die Anlass des Refactorings war. Sind zusammengehörige Regeln lokalisiert? Sind Namen fachlich verständlich? Können Tests die Varianten unabhängig prüfen? Gibt es nun weniger Stellen, an denen dieselbe Entscheidung getroffen wird? Prüfen Sie anschließend die zusätzlichen Kosten: mehr Indirektion, neue Abhängigkeiten, schwer auffindbare Implementierungen oder ein komplizierter Erzeugungsmechanismus. Eine Verbesserung muss diese Kosten rechtfertigen.

Zählen Sie Bedingungen nicht als alleinige Erfolgsgröße. Der Austausch eines verständlichen if gegen schwer durchschaubare dynamische Aufrufe kann die Zahl reduzieren und den Entwurf trotzdem verschlechtern. Auch Testabdeckung allein beweist keine ausreichende Qualität; entscheidend ist, ob relevante Verhaltensunterschiede und Grenzen geprüft werden. Ein gutes Review kombiniert konkrete Änderungsbeispiele, lesbare Tests und eine Erklärung, warum die gewählte Struktur zum Problem passt.

Hände ordnen unbeschriftete Notizzettel an einer Wand

Verbindung zu agiler Arbeit und Zeitmanagement

Verstreute Entscheidungslogik beeinflusst nicht nur Entwickler. Fachliche Änderungen werden schwer schätzbar, Tests aufwendig und Rückmeldungen langsam. Kleine Refactoring-Schritte können helfen, Änderungen überschaubarer zu halten. Das ersetzt jedoch keine Priorisierung und keine fachliche Verständigung. Wenn unklar ist, welche Varianten überhaupt unterstützt werden sollen, kann eine bessere Struktur diese Entscheidung nicht stellvertretend treffen. Entwurf und Zusammenarbeit müssen auf dieselben fachlichen Begriffe Bezug nehmen.

Für die Arbeitsorganisation eignen sich klar begrenzte Untersuchungsschritte: einen problematischen Änderungsfall nachvollziehen, einen Test ergänzen, eine Regel extrahieren und das Ergebnis prüfen. Ein WIP-Limit verhindert, dass mehrere halbfertige Umbauten gleichzeitig liegen bleiben. Die Pomodoro-Technik kann einen Arbeitsrhythmus liefern, ist aber kein Entwurfsverfahren. Strukturqualität entsteht durch fachliche Analyse und Rückmeldung, nicht durch die Anzahl abgeschlossener Timerintervalle.

Häufige Fehlentscheidungen vermeiden

Ersetzen Sie nicht vorsorglich jede Bedingung durch Erweiterungspunkte, deren Bedarf unbekannt ist. Vermeiden Sie außerdem eine Hierarchie, die fachlich unabhängige Dimensionen vermischt, etwa Kundentyp, Land und Versandart. Solche Kombinationen können die Zahl der Varianten stark vergrößern. Getrennte, gut begrenzte Regeln oder komponierbare Strategien sind dann oft verständlicher. Welche Form sinnvoll ist, muss anhand konkreter Anforderungen geprüft werden, nicht anhand einer Vorliebe für ein bestimmtes Muster.

Ebenso problematisch ist das endlose Aufschieben notwendiger Verbesserung. Wenn dieselben Fehler wiederkehren und jede kleine Variante viele Stellen berührt, ist der Wartungsaufwand bereits real. Dokumentieren Sie einen solchen Fall, schätzen Sie einen begrenzten Verbesserungsschritt und vergleichen Sie dessen Nutzen mit anderen Aufgaben. Technische Qualität wird dadurch besprechbar. Statt „Der Code ist hässlich“ lautet die Begründung beispielsweise: „Diese Regel wird dreimal umgesetzt und bei Änderungen regelmäßig uneinheitlich angepasst.“

Eine praktische Übung und der nächste Schritt

Suchen Sie eine kleine wiederholte Typprüfung in Ihrem eigenen Code. Beschreiben Sie drei bestehende Fälle und eine plausible Erweiterung. Schreiben Sie auf, welche Stellen sich dafür ändern müssten. Prüfen Sie dann drei Alternativen: besser benannte Funktionen, eine validierte Datentabelle und getrennte Verhaltensimplementierungen. Wählen Sie die einfachste Alternative, die das konkrete Problem löst. Halten Sie ausdrücklich fest, weshalb die anderen Varianten hier nicht nötig sind.

Als Ausgangspunkt dienen die historische Kampagnenseite und der offizielle Refactoring-Katalog. Für eine breitere Einordnung verbindet unser Beitrag über agile Prinzipien und Praktiken Entwurfsarbeit mit kurzen Rückkopplungen. Der Kern der Anti-IF-Idee bleibt eine überprüfbare Frage: Wird eine fachliche Änderung durch den Entwurf sicherer und verständlicher? Wenn ja, ist die Struktur besser geworden, auch wenn weiterhin sinnvolle Bedingungen im Code stehen.