All code can be classified into two distinct roles; code that does work
(algorithms) and code that coordinates work (coordinators).
The real complexity that gets introduced into a code bases is usually directly related to the creation of classes that group together both of these roles under one roof.
I’m guilty of it myself. I would say that 90% of the code I have written does not nicely divide my classes into algorithms and coordinators.
Most of us are familiar with common algorithms in Computer Science like a Bubble Sort or a Binary Search, but what we don’t often realize is that all of our code that does something useful contains within it an algorithm.
What I mean by this is that there is a clear distinct set of instructions or steps by which some problem is solved or some work is done. That set of steps does not require external dependencies, it works solely on data, just like a Bubble Sort does not care what it is sorting.
Take a moment to wrap your head around this. I had to double check myself a couple of times to make sure this conclusion was right, because it is so profound.
It is profound because it means that all the code we write is essentially just as testable, as provable and potentially as dependency free as a common sorting algorithm if only we can find the way to express it so.
What is left over in our program (if we extract out the algorithms) is just glue.
Think of it like a computer. Computer electronics have two roles: doing work and binding together the stuff that does the work. If you take out the CPU, the memory and all the other components that actually do some sort of work, you’ll be left with coordinators. Wires and busses that bind together the components in the system.
Let’s address why first.
The biggest benefit to pulling algorithmic code into separate classes from any coordinating code is that it allows the algorithmic code to be free of dependencies. (Practically all dependencies.)
Once you free this algorithmic code of dependencies you’ll find 3 things immediately happen to that code:
I remember when I was first standing on the street corners proclaiming that all code should be TDD with 100% code coverage. I was thought pretty crazy at the time, because there really weren’t any mocking frameworks and no IoC containers, so if you wanted to write all your code using TDD approaches, you’d actually have to separate out your algorithms. You’d have to write classes that had minimal dependencies if you wanted to be able to truly unit test them.
Then things got easier by getting harder. Many developers started to realize that the reason why TDD was so hard was because in the real world we usually write code that has many dependencies. The problem with dependencies is that we need a way to create fake versions of them. The idea of mocking dependencies became so popular that entire architectures were based on the idea and IoC containers were brought forth.
We, as a development community, essentially swept the crumbs of difficult unit testing under the rug. TDD and unit testing in general became ubiquitous with writing good code, but one of the most important values of TDD was left behind, the forced separation of algorithmic code from coordinating code.
TDD got easier, but only because we found a way to solve the problems of dependencies interfering with our class isolation by making it less painful to mock out and fake the dependencies rather than getting rid of them.
Let me show you a pretty simple example, but one that I think clearly illustrates how code can be refactored to remove dependencies and clearly separate out logic.
Take a look at this simplified calculator class:
This class does simple add calculations and stores the results in a storage service while keeping track of the adding session.
It’s not extremely complicated code, but it is more than just an algorithm. The Calculator class here is requiring a dependency on a storage service.
But this code can be rewritten to extract out the logic into another calculator class that has no dependencies and a coordinator class that really has no logic.
Now you can see that the BasicCalculator class has no external
dependencies and thus can be easily unit tested. It is also much easier
to tell what it is doing because it contains all of the real logic,
while the Calculator class has now become just a coordinator,
coordinating calls between the two classes.
This is of course a very basic example, but it was not contrived. What I mean by this is that even though this example is very simple, I didn’t purposely create this code so that I could easily extract out the logic into an algorithm class.
I’m still working on mastering this skill myself, because it is quite difficult to do, but I believe the rewards are very high for those that can do it. In code where I have been able to separate out algorithm from coordination, I have seen much better designs that were more maintainable and easier to understand.
The real complexity that gets introduced into a code bases is usually directly related to the creation of classes that group together both of these roles under one roof.
I’m guilty of it myself. I would say that 90% of the code I have written does not nicely divide my classes into algorithms and coordinators.
Defining things a bit more clearly
Before I dive into why we should be dividing our code into clear algorithmic or coordinating classes, I want to take a moment to better define what I mean by algorithms and coordinators.Most of us are familiar with common algorithms in Computer Science like a Bubble Sort or a Binary Search, but what we don’t often realize is that all of our code that does something useful contains within it an algorithm.
What I mean by this is that there is a clear distinct set of instructions or steps by which some problem is solved or some work is done. That set of steps does not require external dependencies, it works solely on data, just like a Bubble Sort does not care what it is sorting.
Take a moment to wrap your head around this. I had to double check myself a couple of times to make sure this conclusion was right, because it is so profound.
It is profound because it means that all the code we write is essentially just as testable, as provable and potentially as dependency free as a common sorting algorithm if only we can find the way to express it so.
What is left over in our program (if we extract out the algorithms) is just glue.
Think of it like a computer. Computer electronics have two roles: doing work and binding together the stuff that does the work. If you take out the CPU, the memory and all the other components that actually do some sort of work, you’ll be left with coordinators. Wires and busses that bind together the components in the system.
Why dividing code into algorithms and coordinators is important.
So now that we understand that code could potentially be divided into two broad categories, the next question of course is why? And can we even do it?Let’s address why first.
The biggest benefit to pulling algorithmic code into separate classes from any coordinating code is that it allows the algorithmic code to be free of dependencies. (Practically all dependencies.)
Once you free this algorithmic code of dependencies you’ll find 3 things immediately happen to that code:
- It becomes easier to unit test
- It becomes more reusable
- Its complexity is reduced
I remember when I was first standing on the street corners proclaiming that all code should be TDD with 100% code coverage. I was thought pretty crazy at the time, because there really weren’t any mocking frameworks and no IoC containers, so if you wanted to write all your code using TDD approaches, you’d actually have to separate out your algorithms. You’d have to write classes that had minimal dependencies if you wanted to be able to truly unit test them.
Then things got easier by getting harder. Many developers started to realize that the reason why TDD was so hard was because in the real world we usually write code that has many dependencies. The problem with dependencies is that we need a way to create fake versions of them. The idea of mocking dependencies became so popular that entire architectures were based on the idea and IoC containers were brought forth.
We, as a development community, essentially swept the crumbs of difficult unit testing under the rug. TDD and unit testing in general became ubiquitous with writing good code, but one of the most important values of TDD was left behind, the forced separation of algorithmic code from coordinating code.
TDD got easier, but only because we found a way to solve the problems of dependencies interfering with our class isolation by making it less painful to mock out and fake the dependencies rather than getting rid of them.
There is a better way!
We can still fix this problem, but we have to make a concerted effort to do so. The current path of least resistance is to just use an IoC container and write unit tests full of mocks that break every time you do all but the most trivial refactoring on a piece of code.Let me show you a pretty simple example, but one that I think clearly illustrates how code can be refactored to remove dependencies and clearly separate out logic.
Take a look at this simplified calculator class:
01.
public
class
Calculator
02.
{
03.
private
readonly IStorageService storageService;
04.
private
List<
int
> history =
new
List<
int
>();
05.
private
int
sessionNumber =
1
;
06.
private
bool newSession;
07.
08.
public
Calculator(IStorageService storageService)
09.
{
10.
this
.storageService = storageService;
11.
}
12.
13.
public
int
Add(
int
firstNumber,
int
secondNumber)
14.
{
15.
if
(newSession)
16.
{
17.
sessionNumber++;
18.
newSession =
false
;
19.
}
20.
21.
var result = firstNumber + secondNumber;
22.
history.Add(result);
23.
24.
return
result;
25.
}
26.
27.
public
List<
int
> GetHistory()
28.
{
29.
if
(storageService.IsServiceOnline())
30.
return
storageService.GetHistorySession(sessionNumber);
31.
32.
return
new
List<
int
>();
33.
}
34.
35.
public
int
Done()
36.
{
37.
if
(storageService.IsServiceOnline())
38.
{
39.
foreach(var result in history)
40.
storageService.Store(result, sessionNumber);
41.
}
42.
newSession =
true
;
43.
return
sessionNumber;
44.
}
45.
}
It’s not extremely complicated code, but it is more than just an algorithm. The Calculator class here is requiring a dependency on a storage service.
But this code can be rewritten to extract out the logic into another calculator class that has no dependencies and a coordinator class that really has no logic.
01.
public
class
Calculator_Mockless
02.
{
03.
private
readonly StorageService storageService;
04.
private
readonly BasicCalculator basicCalculator;
05.
06.
public
Calculator_Mockless()
07.
{
08.
this
.storageService =
new
StorageService();
09.
this
.basicCalculator =
new
BasicCalculator();
10.
}
11.
12.
public
int
Add(
int
firstNumber,
int
secondNumber)
13.
{
14.
return
basicCalculator.Add(firstNumber, secondNumber);
15.
}
16.
17.
public
List<
int
> GetHistory()
18.
{
19.
return
storageService.
20.
GetHistorySession(basicCalculator.SessionNumber);
21.
}
22.
23.
public
void
Done()
24.
{
25.
foreach(var result in basicCalculator.History)
26.
storageService
27.
.Store(result, basicCalculator.SessionNumber);
28.
29.
basicCalculator.Done();
30.
}
31.
}
32.
33.
public
class
BasicCalculator
34.
{
35.
private
bool newSession;
36.
37.
public
int
SessionNumber { get;
private
set; }
38.
39.
public
IList<
int
> History { get;
private
set; }
40.
41.
public
BasicCalculator()
42.
{
43.
History =
new
List<
int
>();
44.
SessionNumber =
1
;
45.
}
46.
public
int
Add(
int
firstNumber,
int
secondNumber)
47.
{
48.
if
(newSession)
49.
{
50.
SessionNumber++;
51.
newSession =
false
;
52.
}
53.
54.
var result = firstNumber + secondNumber;
55.
History.Add(result);
56.
57.
return
result; ;
58.
}
59.
60.
public
void
Done()
61.
{
62.
newSession =
true
;
63.
History.Clear();
64.
}
65.
}
This is of course a very basic example, but it was not contrived. What I mean by this is that even though this example is very simple, I didn’t purposely create this code so that I could easily extract out the logic into an algorithm class.
Parting advice
I’ve found that if you focus on eliminating mocks or even just having the mindset that you will not use mocks in your code, you can produce code from the get go that clearly separates algorithm from coordination.I’m still working on mastering this skill myself, because it is quite difficult to do, but I believe the rewards are very high for those that can do it. In code where I have been able to separate out algorithm from coordination, I have seen much better designs that were more maintainable and easier to understand.
Sign up here with your email
ConversionConversion EmoticonEmoticon