Monitoring Cyclomatic Complexity
From NeoWiki
(→A sea of numbers) |
|||
Line 46: | Line 46: | ||
Studies have shown that the average person has the capacity to handle about seven pieces of data in her or his head, plus or minus two. That is why most people can easily remember phone numbers but have a more difficult time memorizing credit card numbers, launch sequences, and other number sequences higher than seven. | Studies have shown that the average person has the capacity to handle about seven pieces of data in her or his head, plus or minus two. That is why most people can easily remember phone numbers but have a more difficult time memorizing credit card numbers, launch sequences, and other number sequences higher than seven. | ||
− | This principle also applies to understanding code. You've probably seen a snippet of code like the one in Listing 1 before: | + | This principle also applies to understanding code. You've probably seen a snippet of code like the one in Listing 1 before: |
+ | '''Listing 1. Numbers at work''' | ||
+ | if (entityImplVO != null) { | ||
+ | List actions = entityImplVO.getEntities(); | ||
+ | if (actions == null) { | ||
+ | actions = new ArrayList(); | ||
+ | } | ||
+ | Iterator enItr = actions.iterator(); | ||
+ | while (enItr.hasNext()) { | ||
+ | entityResultValueObject arVO = (entityResultValueObject) actionItr.next(); | ||
+ | Float entityResult = arVO.getActionResultID(); | ||
+ | if (assocPersonEventList.contains(actionResult)) { | ||
+ | assocPersonFlag = true; | ||
+ | } | ||
+ | if (arVL.getByName( | ||
+ | AppConstants.ENTITY_RESULT_DENIAL_OF_SERVICE).getID().equals(entityResult)) { | ||
+ | if (actionBasisId.equals(actionImplVO.getActionBasisID())) { | ||
+ | assocFlag = true; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | if (arVL.getByName(AppConstants.ENTITY_RESULT_INVOL_SERVICE) | ||
+ | .getID().equals(entityResult)) { | ||
+ | if (!reasonId.equals(arVO.getStatusReasonID())) { | ||
+ | assocFlag = true; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } else { | ||
+ | entityImplVO = oldEntityImplVO; | ||
+ | } | ||
+ | Listing 1 shows up to nine different paths. The snippet is actually part of a 350-plus-line method that was shown to have 41 distinct paths. Imagine if you were tasked to modify this method for the purpose of adding a new feature. If you didn't write the method, do you think you could make the requisite changes without introducing a defect? | ||
+ | Of course, you'd write a test case, but do you think your test case could isolate your particular change in that sea of conditionals? | ||
+ | ==Measuring path complexity== | ||
+ | Cyclomatic complexity, pioneered during those studies I previously mentioned, precisely measures path complexity. By counting the distinct paths through a method, this integer-based metric aptly depicts method complexity. In fact, various studies over the years have determined that methods having a cyclomatic complexity (or CC) greater than 10 have a higher risk of defects. Because CC represents the paths through a method, this is an excellent number for determining how many test cases will be required to reach 100 percent coverage of a method. For example, the following code (which you might remember from the first article in this series) includes a logical defect: | ||
+ | |||
+ | '''Listing 2. PathCoverage has a defect!''' | ||
==Resources== | ==Resources== |
Revision as of 08:50, 3 March 2007
- What to do when code complexity is off the charts
Andrew Glover, President, Stelligent Incorporated
28 Mar 2006
- If complexity has been shown to correlate to defects, doesn't it make sense to monitor your code base's complexity values? Andrew Glover shows you how to use simple code metrics and Java™-based tools to monitor cyclomatic complexity.
Every developer has an opinion about what code quality means and most have ideas about how to spot poorly written code. Even the term code smell has entered the collective vocabulary as a way to describe code in need of improvement.
One code smell that usually divides straight down the line with developers, interestingly, is the smell of too many code comments. Some claim judicious code commenting is a good thing, while others claim it only serves as a mechanism to explain overly complex code. Clearly, Javadocs™ serve a useful purpose, but how many inline comments are adequate to maintain code? If the code is written well enough, shouldn't it explain itself?
What this tells us about code smell as a mechanism for evaluating code is that it's subjective. What I might deem as terribly smelly code could be the finest piece of work someone else has ever written. Do the following phrases sound familiar?
- Sure, it's a bit confusing (at first), but look how extensible it is!!
or
- It's confusing to you because you obviously don't understand patterns.
What we need is a means to objectively evaluate code quality, something that can tell us, definitively, that the code we're looking at is risky. Believe it or not, that something exists! The mechanisms for objectively evaluating code quality have been around for quite a while, it's just that most developers ignore them. They're called code metrics.
Contents |
A history of code metrics
Decades ago, a few super smart people began studying code hoping to define a system of measurements that could correlate to defects. This was an interesting proposition: by studying patterns in buggy code, they hoped to create formal models that could then be evaluated to catch defects before they became defects.
Somewhere along the way, some other super smart people also decided to see if, by studying code, they could measure developer productivity. The classic metric of lines of code per developer seemed fair enough on the surface:
- Joe produces more code than Bill; therefore, Joe is more productive and worth every penny we pay him. Plus, I noticed Bill hangs out at the water cooler a lot. I think we should fire Bill.
But this productivity metric was a spectacular disappointment in practice, mostly because it was easily abused. Some code measurement included in-line comments, and the metric actually favored cut-and-paste style development.
- Joe wrote a lot of defects! Every other defect is assigned to him. It's too bad we fired Bill -- his code is practically defect free.
Predictably, the productivity studies proved wildly inaccurate, but not before the metrics were widely used by a management body eager to account for the value of each individual's abilities. The bitter reaction from the developer community was justifiable, and for some, the hard feelings have never really gone away.
Diamonds in the rough
Despite these failures, there were some gems in those complexity-to-defect correlation studies. Most developers have long since forgotten them, but for those who go digging -- especially if you're digging in pursuit of code quality -- there is value to be found in applying them today. For example, have you ever noticed that long methods are sometimes hard to follow? Ever had trouble understanding the logic in an excessively deep nested conditional? Your instinct for eschewing such code is correct. Long methods and methods with a high number of paths are hard to understand and, interestingly, tend to correlate to defects.
I'll use some examples to show you what I mean.
A sea of numbers
Studies have shown that the average person has the capacity to handle about seven pieces of data in her or his head, plus or minus two. That is why most people can easily remember phone numbers but have a more difficult time memorizing credit card numbers, launch sequences, and other number sequences higher than seven.
This principle also applies to understanding code. You've probably seen a snippet of code like the one in Listing 1 before:
Listing 1. Numbers at work
if (entityImplVO != null) { List actions = entityImplVO.getEntities(); if (actions == null) { actions = new ArrayList(); } Iterator enItr = actions.iterator(); while (enItr.hasNext()) { entityResultValueObject arVO = (entityResultValueObject) actionItr.next(); Float entityResult = arVO.getActionResultID(); if (assocPersonEventList.contains(actionResult)) { assocPersonFlag = true; } if (arVL.getByName( AppConstants.ENTITY_RESULT_DENIAL_OF_SERVICE).getID().equals(entityResult)) { if (actionBasisId.equals(actionImplVO.getActionBasisID())) { assocFlag = true; } } if (arVL.getByName(AppConstants.ENTITY_RESULT_INVOL_SERVICE) .getID().equals(entityResult)) { if (!reasonId.equals(arVO.getStatusReasonID())) { assocFlag = true; } } } } else { entityImplVO = oldEntityImplVO; }
Listing 1 shows up to nine different paths. The snippet is actually part of a 350-plus-line method that was shown to have 41 distinct paths. Imagine if you were tasked to modify this method for the purpose of adding a new feature. If you didn't write the method, do you think you could make the requisite changes without introducing a defect?
Of course, you'd write a test case, but do you think your test case could isolate your particular change in that sea of conditionals?
Measuring path complexity
Cyclomatic complexity, pioneered during those studies I previously mentioned, precisely measures path complexity. By counting the distinct paths through a method, this integer-based metric aptly depicts method complexity. In fact, various studies over the years have determined that methods having a cyclomatic complexity (or CC) greater than 10 have a higher risk of defects. Because CC represents the paths through a method, this is an excellent number for determining how many test cases will be required to reach 100 percent coverage of a method. For example, the following code (which you might remember from the first article in this series) includes a logical defect:
Listing 2. PathCoverage has a defect!
Resources
- Learn
- "TestNG makes Java unit testing a breeze" (Filippo Diotalevi, developerWorks, January 2005): TestNG isn't just really powerful, innovative, extensible, and flexible; it also illustrates an interesting application of Java annotations.
- "An early look at JUnit 4" (Elliotte Rusty Harold , developerWorks, September 2005): Obsessive code tester Elliotte Harold takes JUnit 4 out for a spin and details how to use the new framework in your own work.
- Using JUnit extensions in TestNG (Andrew Glover, thediscoblog.com, March 2006): Just because a framework claims to be a JUnit extension doesn't mean it can't be used within TestNG.
- Statistical Testing with TestNG (Cedric Beust, beust.com, February 2006): Advanced testing with TestNG, written by the project's founder.
- "Rerunning of failed tests" (Andrew Glover, testearly.com, April 2006): A closer look at rerunning failed tests in TestNG.
- In pursuit of code quality: "Resolve to get FIT" (Andrew Glover, developerWorks, February 2006): The Framework for Integrated Tests facilitates communication between business clients and developers.
- JUnit 4 you (Fabiano Cruz, Fabiano Cruz's Blog, June 2006): An interesting entry on JUnit 4 ecosystem support.
- Code coverage of TestNG tests (Improve your code quality forum, March 2006): Join the discussion on integrating code coverage tools with TestNG.
- In pursuit of code quality series (Andrew Glover, developerWorks): See all the articles in this series ranging from code metrics to testing frameworks to refactoring.
- Get products and technologies
- Download TestNG: Take TestNG for a test drive.
- Download JUnit: Find out what's new with JUnit 4.
- Discuss
About the author
Andrew Glover is president of Stelligent Incorporated, which helps companies address software quality with effective developer testing strategies and continuous integration techniques that enable teams to monitor code quality early and often. He is the co-author of Java Testing Patterns (Wiley, September 2004).
- "When you have learned to snatch the error code from the trap frame, it will be time for you to leave.", thus spake the master programmer.