Clean Code and Battle Scarred Architecture
Clean Code and Battle Scarred Architecture
Posted by Uncle Bob on Wednesday, May 20, 2009
If you go here you’ll see that I struggle to keep the CRAP out of FitNesse. Despite the whimsical name of this metric (which I take a perverse delight in), I find it to be remarkably useful.
CRAP is a metric that is applied to each function in the system. The formula forCRAP is: CRAP = comp(f)2. X (1 – cov(f)/100)3. + comp(f)
Where comp(f) = the cyclomatic complexity of the function f. and cov(f) = the unit test coverage of function f.
So a function’s CRAP will be small iff the cyclomatic complexity is low and the test coverage is high. CRAP will be huge if cyclomatic complexity is high, and there is no coverage.
What does this have to do with architecture? Read on…
I work very hard to keep the ratio of crappy methods near .1%. Of the 5643 methods in FitNesse, only 6 are crappy, and five of those I have no control over.
If you study the graph you can see how quickly I react to even the slightest uptick in crap. I don’t tolerate it because it means that either I’ve got a horrifically complex method that needs to be refactored, or (and this is far more likely) I’ve got a method that isn’t sufficiently tested.
Why am I so fastidious about this? Why am I so concerned about keeping the crap out of FitNesse? The reason is pretty simple. It’s the least I can do.
If you look inside of FitNesse, you’ll find that there are lots of structures and decisions that don’t seem to make a lot of sense at first reading. There are complexities and abstractions that will leave you shaking your head.
For example. We generate all our HTML in code. Yes, you read that correctly. We write Java code that constructs HTML. And yes, that means we are slinging angle brackets around.
To be fair, we’ve managed to move most of the angle bracket slingers into a single module that hides the HTML construction behind an abstraction barrier. This helps a lot, but cripe who would sling angle brackets when template system are so prevalent? I hope nobody. But FitNesse was not conceived at a time when template systems made sense (at least to us).
Fear not, I am working through the Fitnesse code replacing the HTML generation with Velocity templates. It’ll take some time, but I’ll get it done. The point is, that just like every other software system you’ve seen, FitNesse is a collection of historical compromises. The architecture shows the scars of many decisions that have since had to be reversed or deeply modified.
What does this have to do with CRAP? Simply this. The battle scarred architecture is something that will never really go away. I can stop the bleeding, and disinfect the wounds, but there will always be evidence of the battle.
That scarring makes the system hard to understand. It complicates the job of adding features and fixing bugs. It decreases the effectiveness of the developers who work on FitNesse. And though I work hard to massage the scars and bandage the wounds, the war goes on.
But I can keep the CRAP out! I can keep the code so clean and simple at the micro level, that the poor folks who try to make sense out of the macro scale aren’t impeded by huge deeply nested functions that aren’t tested!
Think of it this way. CRAP is disease at the cellular level. CRAP is a rot so pervasive that it can infest every nook and cranny of the system. My system may have scars,but it’s not diseased! In fact, despite the evidence of battles long past, the FitNesse code is very healthy.
And I aim to keep it that way.
Comments
asdf about 5 hours later:
you are misusing iff :/
yachris about 14 hours later:
@asdf—how so? I’ve always understood that ‘iff’ expands to ‘if and only if’, which makes sense in Uncle Bob’s sentence.
Bjørn Sønsteby 1 day later:
How do you keep the crap factor low on business objects?
If I have an object designed for holding patient data, typically used for sending data to the database layer or some OR-mapping entity class, this will largely be composed of properties with get- and set-methods, and a few of them might have simple logic, thus driving the cyclomatic complexity up. Do you just test the hell out of it to keep the crap factor down?
This patient class may not be the SRP-purists favourite class, but dividing this type of class into smaller ones will only cause more greef when I need the entire Patient object as input parameter to a method. Another problem emerging when making business objects comply with the SRP is that introducing several more specialised patient objects will probably force me to increase the number of parameters for most functions, which is also a bad thing. So, do I have it all backwards, or is there some special principle for business objects?
Morgan 1 day later:
At what CRAP number is a method considered a crappy method?
Sebastian Kübeck 2 days later:
Bjørn,
as a first step, you cold try to refactor the patient class into an inheritance chain. That way, you can increase cohesion AND preserve the interface by passing the bottom class around. That way, you don’t need to change the clients. In a further step, you could figure out which client uses which methods of the bottom class, change access to classes up the chain and finally refactor the inheritance chain into a tree. I have done this a couple of times and it worked pretty well. In my experience, those kinds of classes you mention tend to get bloated quickly and the sooner you step in, the easier it is to break up the class.
You might also have a look into Michael Feathers’ book “Working Effectively with Legacy Code” and Joshua Kerievsky’s book “Refactoring to Patterns” for further information.
Bjørn Sønsteby 2 days later:
Sebastian, Thank you for the tip, I’ll check out the books mentioned.
About the inheritance chain, I must admit I am sceptical. This is probably because I have seen the results of inheritance-mania at the previous company I worked at. They had migrated from java to .net, and still had some maintenance responsibility of the java code. Most of that code was written over a period of 10 years, and most of those 10 years were part of the “cowboy era” of the company. Long story short: I don’t think I’m lying to much if I estimate that you could find anything from 2 to 8 levels of inheritance. Refactoring, adding new features or introducing new developers to the code were all daunting tasks.
However, I do agree that inheritance, used with caution, is one possible solution to this particular situation. Any other suggestions?
Sebastian Kübeck 3 days later:
Bjørn,
you can turn everything into a nightmare if you try hard enough ;-). However, inheritance (+delegation) helped me with the problem you mention. If you need further information you could place your question at stackoverflow.
Good luck!
unclebob 5 days later:
When coverage is 100%, Crap == CC. If you have simple logic in your business object, then CC will be around 3 or 4. So Crap will bevery low. So, yes, test the hell out of it.
chrisb 8 days later:
We need CRAP4.net
Jon Bettinger 8 days later:
I really enjoyed this bit: just like every other software system you’ve seen, FitNesse is a collection of historical compromises.