Integration Operation Segregation Principle (IOSP)
If there is one principle in clean code development that's my north star, then that's the Integration Operation Segregation Principle, the IOSP. It guides my code design, it guides my code refactoring, it guides my code reviews.
It's very, very simple and clear. But it has a profound impact on a codebase. Sometimes it seems impossible to follow — but once you follow it, you don't want to turn back.
Here is the IOSP in its most simple form:
Functions shall either only contain logic or they shall only call other functions.
Why "shall"? Principles to me are like virtues. That means they suggest a certain behavior, they even beg you to always act in a certain way — but at the same time they acknowledge that this is impossible in reality. There are competing forces and conflicting principles. So it's ok if you try, try hard, and sometimes fail. That all, to me, is expressed by "shall".
"Logic" is the part of code which creates the actual software behavior. It's the transformations, control structures, and I/O- or API-calls provided by third parties.
"other functions" are functions of the same codebase which are under your control and not deemed to be internal frameworks (which would move them into the realm of logic).
To make this more tangible let me explain using a simple example. The following function contains only logic. This kind of function is called an operation from the point of view of the IOSP:
There are transformations and control structures in this function, but no API-calls.
In contrast take the following function. It does not contain any logic. It only calls other functions. This kind of function is an integration from the point of view of the IOSP:
The other functions an integration calls can be operations or other integrations. In this example they are operations like:
Integrations and operations both compose behavior. Integrations compose it from other functions, operations from logic.
Functions which do not follow the IOSP are called hybrids, for example:
This function calls other functions and also contains logic.
The Structure of a Codebase
With or without the IOSP a codebase consists of trees of functions of great depth: functions calling functions calling functions calling yet other functions etc. etc.
Functions on one level in these trees depend on functions on the next lower level. And mostly those functions are hybrids in today's codebases: they contain some logic plus they call lower level functions. That's called functional dependency.
Following the IOSP does not get rid of functions depending on other functions, but it does away with functional dependencies. That’s an important, albeit for some a subtle difference.
The Benefits Coming From the IOSP
Functional dependencies might not be bad for your health, but they are bad nevertheless:
Functional dependencies make logic in the depending/calling function hard to test in isolation.
Functional dependencies are invitations to more logic and more dependencies. They are the main cause for indefinitely growing functions.
Observing the IOSP and getting rid of functional dependencies thus has many benefits:
It hugely decreases function size. Integrations with more than 10-15 lines are rare, operations with more than 20-30 lines are rare.
Integrations make behavior creation much easier to understand since it's described by a sequence of method calls put next to each other instead of being smeared vertically across the function tree.
A resulting increase in small functions leads to a larger number of more fine grained modules.
Smaller functions are more focused and thus make responsibilities easier to test.
Collecting logic in the leafs of function trees greatly reduces the need for additional complexity through DIP+IoC.
In short: the IOSP leads to higher testability and greater evolvability of code.
And all that by suggesting a simple constrained form for code.
It's hard to fathom the positive effects of the IOSP without trying it. But at least that's simple because an integration and an operation are not in the eye of the beholder. The criteria are straightforward. That cannot be said about many other principles like SRP, SLA, ISP etc.
Has the SRP been applied correctly and to the fullest? There will always be discussions about that. But for the IOSP that's crystal clear at a glance.
Let me invite you to try the IOSP for yourself. Not just once but several times in different contexts. Try to use an integration and operation segregation as a target image for your refactoring during TDD, for example.
But of course beware of the "shiny principle fallacy"😉 No principle is more important than the effects it is supposed to further! Hence, should you find that testability and/or evolvability are slightly higher by deviating from the IOSP, then that's fine.
This logic I call dark logic because it has a visible effect which cannot be explained by then visible operations alone when looking at a data flow diagram which easily can be derived from IOSP code.
By the way: The IOSP is a very special case of the SRP because is separates two formal responsibilities. Composing logic to create behavior is very different from composing functions.
@Ralf Thanks for writing another Article on the IOSP. This is the one that I liked the most!