12

Thanks to a question over at Code Review I got into a little disagreement (which essentially is an opportunity to learn something) about what exactly the Cyclomatic Complexity is for the below code.

public static void main(String[] args) {
    try {
        thro();
        thro();
        thro();
        thro();
        thro();
        thro();
        thro();
    }
    catch (NullPointerException e) {
    }
}

private static Random random = new Random();

public static void thro() throws NullPointerException {
    if (random.nextBoolean())
        throw new NullPointerException();
    System.out.println("No crash this time");
}

When writing this code in Eclipse and using the Eclipse metrics plugin, it tells me that the McCabe Cyclomatic Complexity for the main method is 2, and for the thro method it says 2.

However, someone else tells me that the complexity of calling thro multiple times is number of calls * method complexity, and therefore claims that the complexity of the main method is 7 * 2 = 14.

Are we measuring different things? Can both of us be correct? Or what is the actual cyclomatic complexity here?

Simon Forsberg
  • 371
  • 4
  • 16
  • 5
    The CC of the *function* is two, as there are only two paths through. The CC of the *program* is higher. This is a complete stab in the dark, but I assume code analysis software takes each function as a separate black box due to the infeasibility of calculating the CC of an entire complex application in one go. – Phoshi Nov 29 '13 at 14:57
  • @Phoshi If you would write that as an answer and (if possible) provide links that shows that there is a separation of the two, I'd gladly accept that answer. – Simon Forsberg Nov 30 '13 at 16:56
  • If you count all paths caused by possible exceptions in CC measurements, god help the guy who asked the question on refactoring some trivial code to get the number under 10. – mattnz Dec 05 '13 at 20:42

2 Answers2

9

When I understood this correctly, the Cyclomatic Complexity of main is 8 - that is the number of linearly independent paths through the code. You either get an exception at one of the seven lines, or none, but never more than one. Each of that possible "exception points" corresponds exactly to one different path through the code.

I guess when McCabe invented that metric, he did not have programming languages with exception handling in mind.

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
  • But does it really matter which of the lines that throws the exception? – Simon Forsberg Nov 29 '13 at 15:21
  • 5
    @SimonAndréForsberg: yes, it does. Think of "thro" having a side effect where it increments a global counter when it is called (that would not change the possible paths through the code). The possible outcomes of that counter then are 0 to 7, so this proves that the CC is *at least* 8. – Doc Brown Nov 29 '13 at 15:24
  • Would you say that the metrics plugin I am using is reporting an **incorrect** value for the `main` method? – Simon Forsberg Nov 29 '13 at 18:26
  • @SimonAndréForsberg: well, I don't know your metrics plugin, but 2 is obviously not 8. – Doc Brown Nov 29 '13 at 18:35
  • There's a link to the metrics plugin in my question.... – Simon Forsberg Nov 29 '13 at 19:06
  • @SimonAndréForsberg: honestly, you don't expect me to test that for you, don't you? I am not an Eclipse user. Why don't you just test it for yourself, change your code above and see what different kind of results the plugin tells you? If you think its bogus, ask the author of the plugin. – Doc Brown Nov 30 '13 at 07:53
  • No, I don't expect you to test it for me. (Just found it ironic that you said "I don't know your metrics plugin" when I had linked to it, then again... perhaps I misunderstood that). I find it really hard to accept an answer here though, since there are no citations/sources in these answers so I still don't know what is "right". – Simon Forsberg Nov 30 '13 at 12:12
  • @SimonAndréForsberg: well, you could start with the Wikipedia definition of CC, draw a control flow graph and start counting edges, nodes and exit points by yourself. – Doc Brown Nov 30 '13 at 12:27
  • At first I thought this is correct but after some reading I do not think so anymore - all the statements in the Try clause can fail but then the Catch clause will be invoked, that is why all metrics tools show CC 2 for such a case. I believe this is because even if you do not use exceptions, every statement could fail and lead to an unexpected exit, creating a possible paths. But these are not considered. Because otherwise you would have to add 1 to CC for every, any statement in the Try clause, which I believe is not correct, even though I understand the argument about side effects. – John V Jul 02 '19 at 14:00
  • 1
    ..continued: I believe the argument about side-effects does not hold here - for example, having a while cycle that does GlobalCount++ and has a completely unrelated exit condition, there are still just two execution paths, regardless of the value (internal state) of GlobalCount at the time of exiting the cycle. And that is different from short-circuiting of boolean operators which do increase CC as they generate implicit decision nodes. – John V Jul 02 '19 at 14:12
6

Being 'the other guy', I'll answer here, and be precise about what I say (which I was not particularly precise with over on other formums).

Using the code example above, I calculate the cyclomatic complexity as 8, and I have comments in the code to show how I calculate that. To describe the paths I will consider a successful loop through all the thro() calls as the 'main' 'code path' (or 'CP=1'):

public static void main(String[] args) {
  try {
             // This is the 'main' Code Path: CP = 1
    thro();  // this has a branch, can succeed CP=1 or throw CP=2
    thro();  // this has a branch, can succeed CP=1 or throw CP=3
    thro();  // this has a branch, can succeed CP=1 or throw CP=4
    thro();  // this has a branch, can succeed CP=1 or throw CP=5
    thro();  // this has a branch, can succeed CP=1 or throw CP=6
    thro();  // this has a branch, can succeed CP=1 or throw CP=7
    thro();  // this has a branch, can succeed CP=1 or throw CP=8
  }
  catch (NullPointerException e) {
  }
}

So, I count 8 code paths in this main method, which, to me is a Cyclomatic Complexity of 8.

In Java terms, each mechanism for exiting a function counts to it's complexity, so, a method that has a success-state, and, throws, for example, possibly up to 3 exceptions, has 4 documented exit paths.

The complexity of a method which calls such a function, is:

CC(method) = 1 + sum (methodCallComplexity - 1)

I think other things to consider, is that, in my opinion, the catch clause does not contribute to the complexity of the method, the catch is simply the target of a throws branch, and thus a catch block that is the target of multiple throws counts 1 time for each throw, and not just once for everything.

rolfl
  • 734
  • 5
  • 8
  • Are you counting the possible branches for OutOfMemoryExceptions too? I mean pedantically they can cause code branches, but nobody counts them as they dilute the usefulness of the metric. – Telastyn Nov 29 '13 at 15:37
  • No, I'm not... and you're right, but, in the context of this argument, I count only the exceptions the method is declared to throw. Also, if a method declares three exceptions, but the callinch code does a `catch (Throwable t) {...` then I gues it does not matter how many exceptions it declares to throw. – rolfl Nov 29 '13 at 15:40