IOSP Roslyn Analyzer
- NuGet Package: https://www.nuget.org/packages/CleanCodeDeveloper.Analyzers/
- GitHub Repository: https://github.com/slieser/ccdanalyzers
Installation
The IOSP Analyzer must be added as a NuGet package to every project that is to be analyzed. Short instructions for the common IDEs follow. There is also a video below.
Rider
The following illustration shows the NuGet Manager from JetBrains Rider to see. If you select the scope Solutionyou can add the Analyzer to all contained projects with one click.
VSCode (Visual Studio Code)
In VSCode, NuGet packages are best added via the command line:
Use "cd" to change to the directory containing a .csproj file to which the package is to be added.
dotnet add package CleanCodeDeveloper.Analyzers
Alternatively, you can specify a directory in which the .csproj file is located:
dotnet add package CleanCodeDeveloper.Analyzers
Using the NuGet package
Install the package in your implementation projects. It can also be helpful in test projects. The Analyzer ignores calls from the test framework, thus reducing false positives.
The following image shows the Analyzer in action. Here JetBrains Rider is used as the IDE. The method name is highlighted because the method violates the IOSP. If you move the mouse over the method name, a description is displayed. The description shows why the IOSP is violated, in which integration and operation components are displayed. In the example, the method calls HandleUseCase on the one hand the method Insert on. This is integration because the method belongs to us. On the other hand, the methods ToString and NewGuid which belong to the .NET framework. In addition, the method contains the expression customer.name == ""which is also only permitted in operations.
Exceptions
The Analyzer currently ignores the following method calls:
- Calls from the namespaceĀ Microsoft.Extensions.Logging. Logging can take place in both integration and operation.
- Calls from Task.run. These are used to outsource a task to a thread. This can be useful in both integration and operation.
- Calls fromĀ ConfigureAwait. These can also be useful in both integration and surgery.
- Calls from the namespaceĀ NUnit.framework. In tests, a separate method is always called (integration) and one or more Asserts executed. Otherwise there would be an IOSP violation.
- Call from base.xxxx in a override method. If methods are overwritten, the base method must often be called. This may be necessary in both integration and operation.
Background: the Integration Operation Segregation Principle (IOSP)
This project began as an article for the German magazine dotnetpro. It contains a Roslyn Analyzer for the Integration Operation Segregation Principle (IOSP).
The package analyzes whether methods Integration Operation Segregation Principle violate. The principle states that a method is either an integration method or an operation. Integration methods call other methods that are defined and implemented in your solution. If one of your methods calls another of your methods, it is the task of this method to integrate the called methods. On the other hand, an operation must not call any of your own methods. An operation calls APIs (framework and runtime methods) and contains expressions such as "x + 2 < 42". API calls are calls to external methods that are not defined in your code. For example Console.WriteLine or object.ToString API calls.
In order to clearly separate integration and operation, we can establish the following simple rules:
- Integration:
- Calls only your own methods
- Operation:
- Only calls external methods from runtime and frameworks
- May contain expressions
The reasoning behind the IOSP is as follows: If we strictly separate integration from operation, this leads to a better understanding of the code structure. This is because the level of abstraction is clearly separated into high-level vs. low-level code. Integration methods are at a high level of abstraction. They only call other methods in order to integrate them. Therefore, it is easy to understand such a high-level integration method. On the other hand, an operation is easy to read because it only contains code at a low level. Given that an operation is also assigned to the Single Responsibility Principle (SRP), an operation is easy to read and understand because it only contains low-level code that solves a specific problem. To understand an operation, it is not necessary to understand other methods from the solution. It is only necessary to understand calls to runtime or framework functionalities.
The second advantage of conformity with the IOSP is testability. If we strictly separate integration from operation, we can test the operations in isolation. Since operations do not call other methods from our solution, each operation is a separate functional unit. Therefore, we do not need mocks to separate an operation from its dependencies. It has no dependencies, otherwise it would mix operational code with integration and thus violate the IOSP. The integration methods do not contain any logic. It is therefore not necessary to separate the integration methods from their dependencies. The only task of the integration methods is to integrate calls to other methods. So we test the integration methods with integration tests.
Integration methods can include control structures such as foreach, for, while, if or switch/case-instructions. They just must not contain expressions. It is fine to call a function that contains the expression:
if(x + 2 == 42) { ... } // violates IOSP if contained in an integration
vs.
if(ThisIsTheAnswer(x)) { ... } // ok because it calls other method