C#Clean Code

Branching – Alternativen durch Verzweigung

Kontrollstrukturen ermöglichen im Code eine Verzweigung. Verzweigungen steigern die Komplexität von Software. Ziel ist es trotz komplexer Anforderungen, ein einfaches System zu realisieren.

Abzweigungen finden sich überall im Code
Zach Reiner

Die bisher gelösten Aufgaben waren alle recht linear. Es gab im Prinzip keine alternativen Pfade. Eine solche Verzweigung wird meist mit Kontrollstrukturen wie einer if-Anweisung realisiert. Größere Programme kommen ohne diese nicht aus. Wie kann dies also mit Flow-Design umgesetzt werden? Insbesondere, wenn wir das IOSP einhalten wollen und somit in Integrationen keine Logik zulassen.

Als Beispiel für so eine Situation hier ein kleines Flow-Design.

Flow Design Beispiel

Die Funktionseinheit Parse hat die Aufgabe eine Zeichenkette zu parsen. Handelt es sich um eine ganze Zahl, so soll diese über die Funktionseinheit PrintNumber auf dem Bildschirm ausgegeben werden. Ansonsten soll die Zeichenkette selbst über die Funktionseinheit PrintText auf dem Bildschirm ausgegeben werden. Die dazugehörigen Datenflüsse, die aus Parse herauskommen, tragen jeweils einen Namen OnString und OnInt.

Was aus dem Diagramm zwar nicht direkt hervorgeht, aber aufgrund der sinnvollen Benennung der Ausgänge OnString sowie OnInit hergeleitet werden kann, ist, dass es sich in diesem Falle um Alternativen handelt. Möchte man das deutlicher kennzeichnen, so kann das bei Bedarf leicht im Diagramm ergänzt werden, auch wenn die Notation ein solches Oder eigentlich nicht abdeckt.

Flow Design Beispiel mit Or-Ergnzung

Wie kann den nun aber ein Diagramm mit einer solchen Verzweigung in Code umgesetzt werden? Die Antwort variiert hier je nach Programmiersprache. In C# kann man hier z.B. auf Events oder Delegates in Kombination mit Lambda-Ausdrücken zurückgreifen. Ersteres findet man auch unter dem Begriff Event-based Components. Die zweite Umsetzungsvariante basiert auf dem Konzept des Continuation Passing Style, vereinfacht auch Continuations genannt.

Werden Event-based Components im großen Stil in der eigenen Anwendung eingesetzt, so wird dafür häufig auf ein Framework zurückgegriffen. Da die Verdrahtung nicht immer leicht nachvollziehbar ist, wird gerne ein Tool zur visuellen Darstellung und Erzeugung dieser Verbindungen verwendet. Ein solches Framework bzw. Tool ist z.B. NoFlo. Dieses orientiert sich am Flow-based Programming, dass sehr starke Ähnlichkeiten zu Flow Design besitzt.

Für einfache Verdrahtungen sind aber weder Framework noch ein visuelles Entwurfswerkzeug notwendig. Diese können in C# dank des Schlüsselwortes event einfach umgesetzt werden. Hier die Umsetzung für das obige Flow-Design mithilfe von C# und Events.

using System;

internal class Parser
{
  public Action<int> OnInt;
  public Action<string> OnString;

  public void Parse(string text)
  {
    int value;
    if (int.TryParse(text, out value))
    {
      OnInt(value);
    }
    else
    {
      OnString(text);
    }
  }
}

internal static class Program
{
  private static void Main(string[] args)
  {
    var parser = new Parser();
    parser.OnInt += PrintNumber;
    parser.OnString += PrintText;

    parser.Parse(args[0]);
  }

  private static void PrintNumber(int number)
  {
    Console.WriteLine("Number is {0}", number);
  }

  private static void PrintText(string text)
  {
    Console.WriteLine("Text is {0}", text);
  }
}

Hier ist schön zu sehen, dass in diesem Falle aus der Funktionseinheit Parse eine ganze Klasse wird. Der Rückgabewert wich nun zwei Events. Die Verdrahtung erfolgt dann über die Registrierung der Methoden bei diesen Events. Gleichzeitig ist aber auch das Problem zu sehen, dass durch die Verdrahtung via Events, besonders bei größeren Projekten, nicht mehr sofort ersichtlich ist, was mit wem zusammenhängt. Aus diesem Grund nutze ich Events nur in bestimmten Situationen, wie z.B. der Entkopplung des UIs.

Meine bevorzugte Lösung für solche Probleme setzt in C# auf Continuations und Lambda-Ausdrücke. Hier die Lösung mit Continuations:

using System;

internal static class Program
{
  private static void Main(string[] args)
  {
    Parse(args[0], PrintNumber, PrintText);
  }

  private static void Parse(string text, Action<int> onInt, Action<string> onString)
  {
    int value;
    if (int.TryParse(text, out value))
    {
      onInt(value);
    }
    else
    {
      onString(text);
    }
  }

  private static void PrintNumber(int number)
  {
    Console.WriteLine("Number is {0}", number);
  }

  private static void PrintText(string text)
  {
    Console.WriteLine("Text is {0}", text);
  }
}

Sicherlich wirkt der Code etwas gewöhnungsbedürftig, vor allem dann, wenn man mit Delegates bzw. Continuations nicht vertraut ist. Aber neben dem knapperen Code ist eben die Verdrahtung und damit die Verzweigung direkt beim Methodenaufruf sichtbar.

Letztendlich kann mit beiden Varianten PoMO und IOSP eingehalten werden.

Anmerkung: Bei diesem Text handelt es sich um einen überarbeiteten Repost eines alten Blog-Artikels aus 2015 von mir.