3

Cyclomatic Complexity provides a rough metric for how hard to understand a given function is, or how much potential for containing bugs it has. In the implementations I've read about, usually all of the basic control flow constructs (if, case, while, for, etc.) increase the complexity of a function by 1. It appears to me given that cyclomatic complexity is intended to determine "the number of linearly independent paths through a program's source code" that virtual function calls should increase the cyclomatic complexity of a function as well, because of the ambiguity of which implementation will be called at runtime (the call creates another branch in the path of execution).

However, penalizing the function the same amount that one would if it contained an equivalent switch statement (one point for every 'case' keyword, with one case keyword for every class in the hierarchy implementing the virtual function in question) feels overly harsh, because a virtual function call is generally regarded as much better programming practice.

What should the cost in cyclomatic complexity of a virtual function call be? I'm not sure if my reasoning is an argument against the utility of cyclomatic complexity as a metric or one against the use of virtual functions or something different.

Edit: After people's responses I realized that it shouldn't add to cyclomatic complexity because we could consider the virtual function call equivalent to a call to a global function that contains the massive switch statement. Even though that function will get a bad score, it only exists once in the program, whereas replacing each virtual function call directly with switch statement would cause the cost many times.

Bernhard Barker
  • 54,589
  • 14
  • 104
  • 138
Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165
  • 1
    IMO your question points out a great example of the difference between "global complexity", which basically measures how hard it is to understand what the program does, and "local complexity", which basically measures how hard it is to write test coverage for all defined methods. As software projects grow I expect these notions of complexity will tend to converge, but for small codebases I think it's fair to say that Best Practices based on metrics like cyclomatic complexity are biased in favor of "global complexity" (i.e. virtual functions) and against "local complexity" (i.e. "switch"). – Peter Aug 05 '11 at 19:59
  • 1
    In retrospect I think the answer to this question is that it should be marked worse than a switch statement, not better, because it's really an unbounded switch when you consider the polymorphic call in an isolated module (you have no idea what the derived class may really be). From the perspective of whole program knowledge it should be considered as a switch though. Just because we consider them equivalent *in this metric* doesn't mean you can't evaluate the quality of the code along other metrics (like number of lines you need to change to get a desired effect, where polymorphism wins). – Joseph Garvin Oct 25 '11 at 17:48

4 Answers4

5

Cyclomatic complexity usually does not apply across function call boundaries, but is an intra-function metric. Hence, virtual calls do not count any more than non-virtual, static function calls.

Antti Huima
  • 25,136
  • 3
  • 52
  • 71
2

A virtual function call does not increase the cyclomatic complexity, because the "ambiguity [over] of which implementation will be called" is external to the function call. Once the objects value is set, there is no ambiguity. We know exactly what methods will be called.

BaseClass baseObj = null;
// this part has multiple paths & add to CC
if (x == y)
     baseObj = new Derived1();
else
     baseObj = new Derived2();

// this part has one path and does not add to the CC
baseObj.virtualMethod1();
baseObj.virtualMethod2();
baseObj.virtualMethod3();
Peter
  • 2,526
  • 1
  • 23
  • 32
James Curran
  • 101,701
  • 37
  • 181
  • 258
1

virtual function calls should increase the cyclomatic complexity of a function as well, because of the ambiguity of which implementation will be called at runtime

Ah, but it isn't ambiguous at runtime (unless you're doing metaprogramming / monkey patching); it's completely determined by the type/class of the receiver.

MarkusQ
  • 21,814
  • 3
  • 56
  • 68
0

I'm not a big fan of cyclomatic complexity, but in this case you're calling a function. It will do approximately the same thing (unless the class hierarchy design is really screwed up), with some variations depending on that calls it. Thing is, if you call any function, you can get some varied behavior depending on the arguments you pass in, and this isn't counted in CC.

Therefore, I'd completely ignore that cost.

David Thornley
  • 56,304
  • 9
  • 91
  • 158
  • "It will do approximately the same thing (unless the class hierarchy design is really screwed up), with some variations depending on that calls it" - Barbara Liskov, 1987. – Daniel Earwicker Mar 18 '09 at 19:52