Ruby Software Complexity Metrics (Part three: Interdependence — Mathematical Analysis)

Cyclomatic Complexity, Perceived Complexity, and ABC Metric

The story has three parts:

Goal

In Part Two: Calculations, we have seen a couple of examples to understand the metrics calculations. In this part, we will analyze the interdependence of the metrics and figuring out the maximum values for the RuboCop analysis.

Cyclomatic Complexity and Perceived Complexity

The cyclomatic and perceived complexity values are determined by if, and, or, case, when, for, while, until, and, rescue nodes:

The if…elsif…else construct with n elsif branches, then, the contribution to cyclomatic complexity is n + 1 , and perceived complexity is n + 2 because an else branch contributes to perceived complexity score.

construct with elsif branches, then, the contribution to is , and is because an else branch contributes to perceived complexity score. Each of the and , or , for , while , until , and, rescue nodes, the contributions to cyclomatic and perceived complexity values are the same.

, , , , , and, nodes, the contributions to cyclomatic and perceived complexity values are the same. The case…when…else construct has n when branches, then, the contribution to cyclomatic complexity is n, and perceived complexity is 0.8 + 0.2 * n.

It is not possible to predict perceived complexity value given the cyclomatic complexity value, but we can derive an equation.

Let E be the total number of else branches for if only.

be the total number of branches for only. Let W be the total number of when branches.

be the total number of branches. Let C be the total number of case nodes.

be the total number of nodes. Let CC be cyclomatic complexity .

be . Let PC be perceived complexity.

Then, PC = CC + E − 0.8 * (W − C)

Proof

Let a be the total number of if constructs without any elsif and else branch.

be the total number of constructs without any elsif and else branch. Let b be the total number of if…elsif constructs without any else branch, and each such construct have f1 , f2 , …, fb elsif branches respectively.

be the total number of constructs without any else branch, and each such construct have , , …, elsif branches respectively. Let c be the total number of if…elsif…else constructs with else branch and each such construct have g1 , g2 , …, gc elsif branches respectively.

be the total number of constructs with else branch and each such construct have , , …, elsif branches respectively. Let d be the total number of case…when…else constructs and each such construct have h1 , h2 , …., hd when branches.

be the total number of constructs and each such construct have , , …., when branches. Let e the total number of and, or, for, when, until, and, rescue nodes collectively.

Therefore,

CC = a + b + f1 + f2 + ... + fb + c + g1 + g2 + ... + gc + h1 + h2 + ... + hd + e PC = a + b + f1 + f2 + ... + fb + c + g1 + g2 + ... + gc + c + 0.8 * d + 0.2 * (h1 + h2 + ... + hd) + e => PC - CC = c + 0.8 * d - 0.8 * (h1 + h2 + ... + hd)

=> PC = CC + c + 0.8 * d - 0.8 * (h1 + h2 + ... + hd)

Note that, c is the total number of else branches, d is the total number of case nodes, and, h1 + h2 + … + hd is the total number of when branches. So using E, C, and, W per the assumption:

PC = CC + E + 0.8 * C - 0.8 * W

=> PC = CC + E - 0.8 * (W - C)

It is easy to count the values of E, C, and, W from the code itself, so, we need not calculate the perceived complexity using the pseudocode from part two.

def hoge -- CC = 1

return :foobar if foo && bar -- CC = 3 baz =

case msg -- C = 1

when :qux -- W = 1, CC = 4

qux

when :quux -- W = 2, CC = 5

quux

else

:baz

end plugh =

if corge -- CC = 6

:corge

elsif grault && garply -- CC = 8

:waldo

elsif grault -- CC = 9

:grault

elsif garply -- CC = 10

:garply

else -- E = 1

:plugh

end quuz =

case xyzzy -- C = 2

when :foo -- W = 3, CC = 11

foo

when :bar -- W = 4, CC = 12

bar

when :thud -- W = 5, CC = 13

thud

end if baz && (plugh || quuz) -- CC = 16

do_this

else -- E = 2

do_that

end

end PC = CC + E - 0.8 * (W - C)

= 16 + 2 - 0.8 * (5 - 2)

= 16 + 2 - 2.4

= 16 - 0.4

= 15.6

Cyclomatic Complexity and ABC Metric

The conditions component of the vector <A, B, C> is the sum of the total number of comparison operations except for the spaceship operator and cyclomatic complexity. No direct relation between cyclomatic complexity and ABC metric is possible.

Let a — the largest component, be the total number of assignments, b be the total number of branches, and, c be the total number of conditions. For 0 ≤ x, y ≤ a, b = a − x and c = a − y. So, a² + b² + c² = a² + (x − a)² + (y − b)². Consider a square of length a, and quarter-circles on the bottom-right and top-right vertices with a radius of (a − x) and (a − y) respectively, then the area of these shapes describes the behavior of a² + b² + c².