“Any darn fool can make something complex; it takes a genius to make something simple.” This quote has been attributed to different people, from Albert Einstein to Pete Seeger. Whatever the origin, I think it really applies to code. When given a task to accomplish, developers should all be able to code something that will work. But how will it be understood and maintained in the future? How can you objectively measure something as complex as... complexity?
For the size of a program, there is the standard Lines of Code. There are also metrics like the Halstead Metrics that help you in comparing programs on a high level, but they really don’t get to the inside the program. In other words, you can’t see that one part of the program is more complex than another. For that, we have to go down a level into the program.
Two programs could have the exact same number of lines, operators, and operands, yet be totally different to maintain. Why? It has to do with how they are coded, what they are doing and how the developer chose to implement the logic.
The core of complexity is made up of decisions. Think about your own life. Decisions can be difficult, but if they’re made one at a time then they aren’t so bad. But what if they are linked — if a decision leads down one path, which calls for other decisions, which lead to other decisions, and so on and so forth? The overall complexity of the situation grows, and so does the importance of making the right decision at each point. This is the problem we face in code, and therefore, we need ways to manage the resulting complexity.
Managing Code Complexity
One way to manage this would be to just code all of these decisions into one area, be it paragraph, procedure, or module. Grouping them together is easy and it’s the way things are often done. But if you group decisions together, understanding the logic becomes very difficult because you have to comb through a large section of code to get to the area in question.
It would be easier if you instead attempted to isolate important pieces of logic into separate areas. So, for example, after the main decision, you would branch to different areas and continue this process. Each section should be similar in size and provide balance.
Think of it like a Calder Mobile. At each point, there is a split with others balancing below it. But if any part gets too large, the balance is gone. This process should make it easier — assuming you have good naming and comments — to quickly locate areas you need to examine. You will have far fewer decisions to understand within those areas.
So it’s better to break down the decisions into the smallest blocks possible and isolate them. In the end, you still have the same number of decisions, but maintenance is far easier.
But how do you judge the size of these blocks? How can we quickly know the number of decisions in each block? How do we ensure the blocks are similar in size and we get the balance that we need?
McCabe Complexity Metric
This is where the McCabe Complexity Metric comes in. The metric was laid out in an article by Thomas McCabe in December of 1976. It relates to the number of decision points (points where the logic path splits) in the section of code.
So, for example, if we had four statements that moved data from one field to another and there are no decisions in it, that section would get a score of one. If we put in one decision there would be two paths and it would be scored as two. If one of the branches has a decision there would be three paths, and it would get a score of three.
This continues until you end up with, at times, pretty big numbers. The higher the number — and I’ve seen it be in the thousands — the harder it will be to understand and implement a change for a section of code. Generally, it’s best to keep each section of code at 10 or less. Ten is about the number you can keep in your brain; any more and the theory is you will get lost.
The McCabe Metric will help you find hidden knots of logic in your programs. We’ve all experienced them: areas in the code that seem to be needlessly complex. They tend to be prone to error so developers will spend their most time in these areas with analysis and debugging.
To quickly spot the areas of greatest complexity, you may want to establish a threshold of 10 or a slightly higher number such as 15 and critically examine anything that rises above it. In the programs I have reviewed, most of a program is usually under 10, but a few areas are higher, and sometimes much higher.
Using McCabe with Halstead When used along with the Halstead Metric, the McCabe Metric can help you objectively assess and compare the complexity of new programs and applications. By using a threshold you can focus on the areas of greatest complexity which can assist you in breaking them into smaller, more manageable, logical sections.
But how can you put the McCabe Complexity Metric to use? Making a practice of assigning a McCabe score, in combination with noting how long it takes to understand each program’s code, helps developers predict how long it will take to implement changes on projects with similar McCabe scores.
For example, they could say to an end-user, “This change is in a block of code that has over 500 paths, but the last one we did for you had only 20. It will take X amount of time longer to assess, change and test.” It can also be useful assigning resources to the project. Managers may want to use the more experienced staff on areas with the highest complexity and task them with not only implementing the change but also simplifying the code, if possible.
Extending the McCabe Metric further in combination with the Halstead Metrics, you can compare applications in your portfolio. For example, the metrics can help you choose whether to acquire a new application or consolidate multiple applications into one.
To make this determination, you would want to look at the functionality and the complexity, as you will need to support it in the future. There are many things you can do with this metric to help in this regard. One is to use a threshold and record how many sections are above the threshold. Another is to calculate an average for the program to aid in comparisons.
The McCabe Complexity Metric is also helpful in determining the minimum number of unique test cases needed to test the code. So if we had a section that had a score of 57, we would know that at the very least 57 unique test cases are necessary to fully test the code.
Using the McCabe Complexity along with a Control Flow diagram you will be able to quickly come up with the necessary test cases. The number then establishes a baseline for complete testing. This, along with code coverage results, can provide valuable proof of your testing efforts.
Over time, using both the Halstead and McCabe metrics you will gain valuable, objective measures of complexity so you can halt — and perhaps reverse — the trend of increasingly more complex programs.