Bild von Stefan Lieser
Stefan Lieser

Optional und das IOSP

Mit dem IOSP steht eines der wichtigsten Prinzipien zur Verfügung, um seinen Code zu strukturieren. IOSP steht für Integration Operation Segregation Principle. Damit ist gemeint, dass Methoden in zwei Kategorien einzuteilen sind: Integration vs. Operation. Die Regeln sind sehr einfach:

Integration

  • Darf nur eigene Methoden aufrufen

Operation

  • Darf nur fremde Methoden aus Frameworks und Runtime aufrufen
  • Darf Ausdrücke enthalten (bspw. „x > 42“ oder „y + 7“)

Die Kriterien sind ausschließlich zu verstehen. Eine Integration darf demnach keine Ausdrücke enthalten und keine fremden Methoden aufrufen.

Leider haben wir zu Beginn, als wir über das IOSP nachgedacht haben, mit syntaktischen Elementen wie if, for, foreach etc. argumentiert. Das hat vermutlich zu unnötiger Verwirrung geführt. Es ging uns darum, eine Integration frei zu halten von Logik, weil diese andernfalls nur schwer automatisiert getestet werden kann. Die gesamte Logik wird also in die Operationen verlagert und ist dort leicht testbar. Das bedeutet aber nicht, dass eine Integration nun kein if-Statement mehr enthalten dürfte. Es muss lediglich dafür gesorgt werden, dass der Ausdruck nicht im if-Statement selbst steht sondern in eine Funktion ausgelagert wird.

Statt

if(x == 42) {
// …
}

schreibt man also

if(IsAnswer(x)) {
// …
}

bool IsAnswer(int x) {
return x == 42;
}

Das hat den Vorteil, dass die Bedingung für die Fallunterscheidung nun isoliert getestet werden kann. Im ursprünglichen Fall ist dies nicht möglich. Der Ausdruck „x == 42“ kann nur getestet werden, in dem die umschließende Methode aufgerufen wird.

Optional

Nun kam in einer Diskussion bei Discord die spannende Frage auf, wie es sich mit dem Datentyp Optional innerhalb einer Integration verhält. Dazu möchte ich zunächst darstellen, in welchem Kontext ich die Verwendung von Optional für sinnvoll halte.

Es gibt Operationen, die zwei unterschiedliche Ergebnisse liefern. Als Beispiel soll eine Methode dienen, die aus den Kommandozeilenargumenten das erste liefern soll, sofern eines vorhanden ist.

				
					public Option<string> GetFilename(string[] args) {
    return args.Length > 0 
        ? Option.Some(args[0]) 
        : Option.None<string>();
}

				
			

Hier kann die Methode nicht in jedem Fall einen string liefern. Das geht nur, sofern das Array mindestens ein Element enthält. Also ist das Ergebnis der Funktion optional. Manchmal wird eben ein string geliefert und manchmal nicht. Aus diversen Gründen soll hier übrigens nicht null verwendet werden um anzuzeigen, dass kein Dateiname geliefert werden kann. Null als Wert ist bei strings immer möglich, da string ein Referenztyp ist. Woran soll der Aufrufer beim Rückgabetyp string erkennen, dass der Dateiname nur optional geliefert wird? Die Begründung dafür, überhaupt Optional anstatt null als Rückgabe zu verwenden, werde ich in einem anderen Beitrag detaillierter ausführen.

Halten wir also fest: eine Operation, die ein Optional als Rückgabewert liefert, wird immer mal wieder auftreten. Doch was bedeutet das nun für die Integration? An irgendeiner Stelle im Code muss die Operation aufgerufen werden. Da es sich dabei um eine eigene Methode handelt, wird der Aufrufer zwangsläufig zur Integration.

Integration mit Optional

Der folgende Codeausschnitt zeigt die Integration von Methoden, die ein Optional als Rückgabewert liefern.

				
					public Option<IEnumerable<Booking>> Create(string[] args) {
    var filename = GetFilename(args);
    if (!filename.HasValue) {
        return Option.None<IEnumerable<Booking>>();
    }
    var content = ReadFile(filename.ValueOrFailure());
    if (!content.HasValue) {
        return Option.None<IEnumerable<Booking>>();
    }

    return CreateBookings(content.ValueOrFailure());
}

				
			

Zunächst wird GetFilename aufgerufen. Da die Funktion nur einen Dateinamen liefern kann, wenn dieser im Array enthalten ist, muss eine Fallunterscheidung her. Wurde kein Dateiname im Array angegeben, soll die Integration ein Option.None Ergebnis liefern. Das hat zur Folge, dass wir nun in der Integration ein if-Statement stehen haben, in dem HasValue überprüft wird. Das if-Statement ist hier übrigens nicht das Problem. Eine Integration darf if-Statements enthalten, solange die eigentliche Entscheidung nicht durch einen Ausdruck oder den Aufruf fremder Methoden getroffen wird.

Leider ist das aber hier genau der Fall: der Aufruf von HasValue stellt einen Verstoß gegen das IOSP dar. Das gleiche gilt für den Aufruf der Methode Option.None. Mein IOSP Plugin erkennt dies sogar und markiert die Methode Create. Du kannst das Plugin über NuGet in deine .NET Projekte einbinden. Sein Name lautet CleanCodeDeveloper.Analyzers.

CCD Analyzer Optional IOSP

Bewertung

Die Anwendung von Prinzipien erfolgt sinnvollerweise in zwei Schritten: der erste Schritt ist die Erkennung eines Verstoßes gegen das Prinzip. Im obigen Fall haben wir festgestellt, dass die Verwendung eines Optionals in der Integration zu einer IOSP Verletzung führt. Doch nun folgt der wichtige zweite Schritt: wir müssen diese Prinzipienverletzung bewerten. Am Ende geht es nämlich nicht darum, dogmatisch alle Prinzipien einzuhalten. Es geht um etwas höheres, das Erreichen der Werte. Im Kontext von Clean Code Development geht es vor allem um die Frage, ob die Werte Wandelbarkeit und Korrektheit erreicht werden.

Zurück zum Beispiel. Hier müssen wir uns die Frage stellen, ob die Kombination aus Integration und Operationen leicht verständlich und gut automatisiert zu testen ist. Für die Integrationstests der Methode Create kommt es vor allem auf die Anzahl der Fallunterscheidungen an. Um eine  minimale Testabdeckung zu erreichen, müssen mindestens alle Pfade einmal durchlaufen werden. Insofern müssen wir demnach mindestens zwei Tests schreiben: beim einen wird ein Dateiname geliefert, beim anderen nicht. Ob die Fallunterscheidung selbst korrekt getroffen wird, können wir über die Unit Tests der Methode GetFilename testen.

Die oben gezeigt Integration lässt sich trotz IOSP Verletzung leicht automatisiert testen. Das liegt daran, dass die if-Statements nicht wirklich einen Ausdruck enthalten und somit die Entscheidung nicht selbst fällen. Die Entscheidung wird von GetFilename bzw. ReadFile getroffen. Beide signalisieren durch den Rückgabewert des Optionals, ob es im if oder else Zweig weitergehen soll. Durch entsprechende Unit Tests auf GetFilename und ReadFile kann überprüft werden, ob das gelieferte Optional den Erwartungen entspricht.

Reinheit

Möchte man die IOSP Verletzung beseitigen, kann man jeweils eine Methode um den Ausdruck „!filename.HasValue“ und alle anderen Aufrufe von „fremden“ Methoden wickeln. Die Integration sieht dann wie folgt aus:

				
					public Option<IEnumerable<Booking>> Create(string[] args) {
    var filename = GetFilename(args);
    if (HasNoFilename(filename)) {
        return EmptyBookings();
    }
    var content = ReadFile(GetValue(filename));
    if (HasNoContent(content)) {
        return EmptyBookings();
    }

    return CreateBookings(GetValue(content));
}

private static bool HasNoFilename(Option<string> filename) {
    return !filename.HasValue;
}

private static bool HasNoContent(Option<IEnumerable<string>> content) {
    return !content.HasValue;
}

private static Option<IEnumerable<Booking>> EmptyBookings() {
    return Option.None<IEnumerable<Booking>>();
}

private static T GetValue<T>(Option<T> value) {
    return value.ValueOrFailure();
}

				
			

Wird dadurch irgendetwas besser? Minimal. Die Integration liest sich nun etwas flüssiger und ist nun eine pure Integration. Das IOSP Plugin hat nichts mehr zu meckern. Auf der anderen Seite entstehen aber viele kleine Methoden, die den Code der Klasse aufblähen. Ich meine, das muss nicht sein und lebe gerne mit der leichten IOSP Verletzung der ursprünglichen Fassung.

Quellcode

Der Quellcode befindet sich bei GitHub im folgenden Repo:

https://github.com/slieser/flowdesignbuch/tree/master/csharp/optionalIOSP/optionalIOSP

Fazit

Die Anwendung von Prinzipien besteht immer aus zwei Schritten: erkennen der Verletzung und anschließender Bewertung nach den Werten Wandelbarkeit und Korrektheit. Im Falle des IOSP gibt es für mich einige Stellen, an denen ich mit einer leichten Verletzung in Integrationsmethoden leben kann, weil der Code dennoch leicht zu verstehen und gut zu testen ist. Hilfreich wäre ein Plugin, bei dem man Ausnahmen definieren kann wie bspw. die Verwendung von Optional in Integrationen. Mein Plan ist schon länger, ein Plugin auf der Basis von JetBrains Rider bzw. ReSharper anzubieten. Das derzeitige Plugin ist ein Roslyn Analyzer, was zumindest den Nachteil hat, dass das NuGet Paket jedem Projekt aufs Neue hinzugefügt werden muss. Sobald ich ein Rider Plugin zur Verfügung stellen kann, werde ich darüber berichten.

Wenn du mehr über das IOSP erfahren möchtest, bietet sich dafür unser Training Clean Code Developer Basics an.

Clean Code Trainings

Geschlossene Firmenkurse

Wir führen alle Seminare als geschlossene Firmenkurse für Sie durch.

Bei Interesse oder Fragen kontaktieren Sie uns gerne.

Unsere Seminare

course
Clean Code Developer Basics

Prinzipien und Tests – Das Seminar wendet sich an Softwareentwickler, die gerade beginnen, sich mit dem Thema Softwarequalität auseinanderzusetzen. Es werden die wichtigsten Prinzipien und Praktiken der Clean Code Developer Initiative vermittelt.

zum Seminar »
course
Clean Code Developer Advanced

Mit Flow Design von den Anforderungen zum Clean Code – Lernen Sie mit Flow Design einen Softwareentwicklungsprozess kennen, der Sie flüssig von den Anforderungen zum Clean Code führt.

zum Seminar »
course
Clean Code Developer Trainer

Seminare als Trainer durchführen – Dieses Seminar wendet sich an Softwareentwickler, die ihr Wissen über die Clean Code Developer Prinzipien und Praktiken bzw. über Flow Design als Trainer an andere weitergeben möchten.

zum Seminar »
course
Clean Code Developer CoWorking

Online CoWorking inkl. Coaching –
Wir werden häufig gefragt, was man als Entwickler tun könne, um kontinuierlich dran zu bleiben am Thema Clean Code Developer. Unsere Antwort: Treffen Sie sich regelmäßig wöchentlich online mit anderen Clean Code Developern.

zum Seminar »

1 Kommentar zu „Optional und das IOSP“

  1. Pingback: 3 Arten der Fehlerbehandlung - CCD Akademie GmbH

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

de_DEGerman