Managing and Minimizing Code Complexity

A quick intro to complexity in your code and some tools to help you deal with it.

Programmersatwork evklvk

Keeping the complexity of your functions/methods to a minimum is a best-practice. Why is this? Complex code is harder to maintain, harder to test, harder to modify, harder to debug, and harder to understand (by others and you, especially six months from now).

Back when I was an undergrad, the computer language to know was C. One of my professors loved to quiz us with obfuscated code where a lot of logic was fit into as few lines as possible. He presented over-clever code as ideal. It wasn't the worst test of our grasp of syntax and semantics, but thank goodness those days are gone! We know better, we've known better for a while now.

kill me now Here's an example of some of that ridiculousity. If you don't know C, don't worry about it. If you know C, you probably still don't know what the hell is going on here:


main(v,c)char**c;{for(v[c++]="Hello, world!\n)";
(!!c)[*c]&&(v--||--c&&execlp(*c,*c,c[!!c]+!!c,!c));
**c=!c)write(!!*c,*c,!!**c);}

Reference: catb.org

Douglas Crockford said "Writing software is the hardest thing people do." Though I don't agree*, he has a point! Programmers spend a lot of effort and time on finding solutions to problems and implementing them. We often spend even more time debugging. Reducing needless complexity leads to higher maintanability, easier testability, and ultimately to less debugging.
(*With all due respect to Mr.Crockford, the hardest thing? Come on! How about dealing with your unconscious emotional issues? How about giving up caffeine? Now, those are hard! Writing code - that's cake!)

Some complexity in code is unavoidable

Any real piece of software has x amount of necessary complexity which can't be avoided. The trick is to avoid writing needlessly complex code. How do we avoid that? How do we keep the complexity of our functions/methods to a minimum?

One indicator of code complexity is code size; and its something that correlates strongly with bug potential. The total number of lines of code that you may need to get a job done may not be malleable, but the number of functions and size of those functions can be. Methods/functions that are bigger and longer are harder to test, harder to maintain, easier to break.

These days with TDD and BDD, unit tests are getting a lot of love. And with good reason. Unit tests are all about isolating code and exercising (most often) one function. Unit tests help us in our battle to keep complexity under control. You should be writing a lot of unit tests and chances are that, these days, you know this.

Enter Cyclomatic Complexity

This is a number that rates the complexity of a chunk of your code. Here is the obligitory Wikipedia definition. To sum it up: "The cyclomatic complexity of a section of source code is the count of the number of linearly independent paths through the source code."

Think of it as a measure of the number of independent paths through your code - it can indicate the (minimum) number of unit tests you need to write to get full code-coverage. Here is pseudo-code example:


function mult(x, y) {
	if either x or y is not a number
		throw error
	else
		return a * b
}

This function has a cyclomatic complexity of two. Two possible paths through the code, two unit tests to write.

What is an level of acceptable cyclomatic complexity?

Well, it really depends on the nature of your program. Some programming efforts are much easier to keep simple than others. Shoot for less than 5. If you are under ten, you're still doing ok. Start to really worry as you get above sixteen! Refactor when possible. Refer to this paper by McCabe in IEEE Transactions on Software Engineering for more detail.

Another problem with code with high cyclomatic complexity(CC) is that chances of introducing new bugs go up dramatically when it comes time to update or otherwise change that code. A measure of "bad fix probability" quadruples as you approach CC values of 20 as compared to 10. More info here.

Complexity

Quoting Trostler from Testable Javascript, large CC values are usually due to a lot of if/then/else or switch statements. The simplest fix being to do something like break the function into smaller functions, or to use a lookup table. So instead of a lot of unit tests for one function, you get to write a lot of unit tests for a lot of smaller functions; but the advantage being that your code is signifigantly more maintainable.

Tools to check Cyclomatic Complexity

There is some great software already written to help check your software! Most of them check more than just the CC value.

In the Ruby world, take a look at the Saikuro gem. This blog post has some good information about it.

In the Javascript world, jsmeter and jscheckstyle used to be two tools in our utility belts for such efforts. At this point in time I think JSHint is your go-to solution; here it is as an online tool. Here is a version that integrates with Grunt.

After a while you won't even need a tool for checking CC, you'll get in the habit of not writing overly complex functions and methods. In the meantime, code responsibly, fasten your seat-belts, and use these great tools to help you write sane code.

comments powered by Disqus