IOSP is one of the most important principles for structuring your code. IOSP stands for Integration Operation Segregation Principle. This means that methods can be divided into two categories: Integration vs. operation. The rules are very simple:
Integration
- May only call its own methods
Operation
- May only call external methods from frameworks and runtime
- May contain expressions (e.g. "x > 42" or "y + 7")
The criteria are to be understood exclusively. An integration must therefore not contain any expressions or call any external methods.
Unfortunately, when we started thinking about the IOSP, we argued with syntactic elements such as if, for, foreach etc.. This probably led to unnecessary confusion. Our aim was to keep an integration free of logic, because otherwise it would be difficult to test it automatically. The entire logic is therefore shifted to the operations and can be easily tested there. However, this does not mean that an integration should no longer contain an if statement. It is only necessary to ensure that the expression is not in the if statement itself but is outsourced to a function.
Instead of
if(x == 42) {
// ...
}
so you write
if(IsAnswer(x)) {
// ...
}
bool IsAnswer(int x) {
return x == 42;
}
This has the advantage that the condition for case differentiation can now be tested in isolation. This is not possible in the original case. The expression "x == 42" can only be tested by calling the enclosing method.
Optional
Now, in a discussion on Discord, the interesting question arose as to how the data type Optional within an integration. To do this, I would first like to explain the context in which I consider the use of Optional to be useful.
There are operations that return two different results. An example is a method that returns the first of the command line arguments, if one is available.
public Option GetFilename(string[] args) {
return args.Length > 0
? Option.Some(args[0])
: Option.None();
}
In this case, the method cannot always provide a string deliver. This is only possible if the array contains at least one element. The result of the function is therefore optional. Sometimes a string is returned and sometimes not. Incidentally, for various reasons zero can be used to indicate that no file name can be supplied. Zero as a value is always possible with strings, as string is a reference type. How should the caller of the return type string recognize that the file name is only optional? I will explain the reasons for using optional instead of null as the return value in more detail in another article.
So let's note: an operation that returns an optional as a return value will occur from time to time. But what does this mean for the integration? The operation must be called at some point in the code. As this is a separate method, the caller inevitably becomes the integration.
Integration with optional
The following code snippet shows the integration of methods that return an optional as a return value.
public Option<IEnumerable> Create(string[] args) {
var filename = GetFilename(args);
if (!filename.HasValue) {
return Option.None<IEnumerable>();
}
var content = ReadFile(filename.ValueOrFailure());
if (!content.HasValue) {
return Option.None<IEnumerable>();
}
return CreateBookings(content.ValueOrFailure());
}
First of all GetFilename is called. As the function can only return a file name if it is contained in the array, a case distinction must be made. If no file name was specified in the array, the integration should return a Option.None result. As a result, we now have an if statement in the integration in which HasValue is checked. Incidentally, the if statement is not the problem here. An integration may contain if statements as long as the actual decision is not made by an expression or by calling external methods.
Unfortunately, this is exactly the case here: the call from HasValue is a violation of the IOSP. The same applies to calling the method Option.None. My IOSP plugin even recognizes this and marks the method Create. You can integrate the plugin into your .NET projects via NuGet. Its name is CleanCodeDeveloper.Analyzers.
Rating
It makes sense to apply principles in two steps: the first step is to recognize a violation of the principle. In the case above, we have determined that the use of an optional in the integration leads to an IOSP violation. But now comes the important second step: we have to evaluate this violation of the principle. In the end, it is not about dogmatically adhering to all principles. It is about something higher, about achieving the values. In the context of clean code development, the main question is whether the values Changeability and Correctness can be achieved.
Back to the example. Here we have to ask ourselves whether the combination of integration and operations is easy to understand and can be tested automatically. For the integration tests of the method Create it depends above all on the number of case distinctions. In order to achieve aย To achieve minimum test coverage, all paths must be run at least once. In this respect, we must therefore write at least two tests: one returns a file name, the other does not. We can check whether the case distinction itself is made correctly using the unit tests of the GetFilename test.
The integration shown above can be easily tested automatically despite the IOSP violation. This is because the if statements do not actually contain an expression and therefore do not make the decision themselves. The decision is made by GetFilename resp. ReadFile is hit. Both signal through the return value of the optional whether it is in the if or else branch should continue. Through appropriate unit tests on GetFilename and ReadFile can be used to check whether the delivered option meets expectations.
Purity
If you want to eliminate the IOSP violation, you can wrap a method around the expression "!filename.HasValue" and all other calls to "foreign" methods. The integration then looks like this:
public Option<IEnumerable> 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 filename) {
return !filename.HasValue;
}
private static bool HasNoContent(Option<IEnumerable> content) {
return !content.HasValue;
}
private static Option<IEnumerable> EmptyBookings() {
return Option.None<IEnumerable>();
}
private static T GetValue(Option value) {
return value.ValueOrFailure();
}
Does this make anything better? Minimal. The integration now reads a little more smoothly and is now a pure integration. The IOSP plugin no longer has anything to complain about. On the other hand, there are many small methods that bloat the code of the class. I don't think that's necessary and am happy to live with the slight IOSP violation of the original version.
Source code
The source code can be found on GitHub in the following repo:
https://github.com/slieser/flowdesignbuch/tree/master/csharp/optionalIOSP/optionalIOSP
Conclusion
The application of principles always consists of two steps: recognizing the violation and then evaluating it according to the values of changeability and correctness. In the case of the IOSP, there are a few places where I can live with a slight violation in integration methods because the code is still easy to understand and easy to test. It would be helpful to have a plugin that allows you to define exceptions, such as the use of optional in integrations. My plan has long been to offer a plugin based on JetBrains Rider or ReSharper. The current plugin is a Roslyn Analyzer, which at least has the disadvantage that the NuGet package has to be added to each project anew. As soon as I can provide a Rider plugin, I will report on it.
If you would like to find out more about the IOSP, our training course Clean Code Developer Basics.
Clean code training
Dates of the individual training days:
Closed company courses
We conduct all seminars as closed company courses for you.
If you are interested or have any questions please contact us.
1 thought on “Optional und das IOSP”
Pingback: 3 types of error handling - CCD Akademie GmbH