visit
Photo by on
def is_even(number):
if number % 2 == 0:
number_type = "Even"
else:
number_type = "Odd"
print(number_type)
def combining_lists():
list_1 = [1, "a"]
list_2 = [1, 2, 3]
list_3 = ["c", "d"]
list_of_lists = [list_1, list_2, list_3]
joined_lists = []
for i in len(list_of_lists):
joined_lists.append(list_of_lists[i])
print(joined_lists)
Once you have a Flow Graph you can quickly calculate the complexity for any piece of code by:
In Example 2 we again have 6 nodes & 6 edges, so we have a total complexity of 2. Both the if
statement in Example 1 & the for
loop in Example 2 drive up this complexity.
In general, with any of the software complexity metrics, a smaller number is better, and there isn’t an upper limit to what a piece of software’s cyclomatic complexity can be. But what’s a “good” score for cognitive complexity? And how high of a complexity score is too high? Thankfully, NIST
< 10 - a simple program with little risk 11 – 20 - more complex programs with moderate risk 21 – 50 - high complexity with high risk > 50 - an untestable program with very high risk.
# Example 1
def sumOfPrimes(max):
total = 0 # + 1
for i in range(1, max): # + 1
for j in range(2, j): # + 1
if i % j == 0: # + 1
break
j = j + 1
total = total + 1
i = i + 1
return total # Total Cyclomatic Complexity = 4
# Example 2
def getWords(number): # + 1
if number == 1: # + 1
return "one"
elif number == 2: # + 1
return "a couple"
elif number == 3: # + 1
return "a few"
else:
return "lots" # Total Cyclomatic Complexity = 4
Both of these cases have Cyclomatic Complexity scores of 4, but the example below is clearly cleaner. We need a metric that can easily distinguish cases like this and that gives us an easy idea of whether code is easy or hard to understand.
Enter Cognitive Complexity.
# Example 1
def sumOfPrimes(max):
total = 0
for i in range(1, max): # + 1
for j in range(2, j): # + 2
if i % j == 0: # + 3
break
j = j + 1
total = total + 1
i = i + 1
return total # Total Cognitive Complexity = 6
# Example 2
def getWords(number): # + 1
if number == 1: # + 1
return "one"
elif number == 2: # + 1
return "a couple"
elif number == 3: # + 1
return "a few"
else:
return "lots" # Total Cognitive Complexity = 4
Let’s take a look at our good old friend, the
class GildedRose:
def update_quality(self):
for item in self.items: # + 1
if (
item.name != "Aged Brie"
and item.name != "Backstage passes to a TAFKAL80ETC concert"
): # + 2
if item.quality > 0: # + 3
if item.name != "Sulfuras, Hand of Ragnaros": # + 4
item.quality = item.quality - 1
else:
if item.quality < 50: # + 3
item.quality = item.quality + 1
if item.name == "Backstage passes to a TAFKAL80ETC concert": # + 4
if item.sell_in < 11: # + 5
if item.quality < 50: # + 6
item.quality = item.quality + 1
if item.sell_in < 6: # + 5
if item.quality < 50: # + 6
item.quality = item.quality + 1
if item.name != "Sulfuras, Hand of Ragnaros": # + 2
item.sell_in = item.sell_in - 1
if item.sell_in < 0: # + 2
if item.name != "Aged Brie": # + 3
if item.name != "Backstage passes to a TAFKAL80ETC concert": # + 4
if item.quality > 0: # + 5
if item.name != "Sulfuras, Hand of Ragnaros": # + 6
item.quality = item.quality - 1
else: # + 4
item.quality = item.quality - item.quality
else:
if item.quality < 50: # + 4
item.quality = item.quality + 1
# Total Cognitive Complexity = 69
Let’s just take a look at the last section of the Gilded Rose - starting with if item.sell_in < 0:
. We can take 3 initial steps to make big improvements here:
1. The for block is split into three if conditions - Let's look at the last one first. In the nested condition it checks if item.name != "Aged Brie"
.
if item.sell_in < 0:
if item.name == "Aged Brie":
if item.quality < 50:
item.quality = item.quality + 1
else:
if item.name != "Backstage passes to a TAFKAL80ETC concert":
if item.quality > 0:
if item.name != "Sulfuras, Hand of Ragnaros":
item.quality = item.quality - 1
else:
item.quality = item.quality - item.quality
2. Let's go ahead and invert the"Backstage passes"
the condition here too, for the same reason.
if item.sell_in < 0:
if item.name == "Aged Brie":
if item.quality < 50:
item.quality = item.quality + 1
else:
if item.name == "Backstage passes to a TAFKAL80ETC concert":
item.quality = item.quality - item.quality
else:
if item.quality > 0:
if item.name != "Sulfuras, Hand of Ragnaros":
item.quality = item.quality - 1
if item.sell_in < 0:
if item.name == "Aged Brie":
if item.quality < 50:
item.quality = item.quality + 1
elif item.name == "Backstage passes to a TAFKAL80ETC concert":
item.quality = item.quality - item.quality
else:
if item.quality > 0:
if item.name != "Sulfuras, Hand of Ragnaros":
item.quality = item.quality - 1
if item.sell_in < 0: # + 2
if item.name != "Aged Brie": # + 3
if item.name != "Backstage passes to a TAFKAL80ETC concert": # + 4
if item.quality > 0: # + 5
if item.name != "Sulfuras, Hand of Ragnaros": # + 6
item.quality = item.quality - 1
else: # + 4
item.quality = item.quality - item.quality
else:
if item.quality < 50: # + 4
item.quality = item.quality + 1
if item.sell_in < 0: # + 2
if item.name == "Aged Brie": # + 3
if item.quality < 50: # + 4
item.quality = item.quality + 1
elif item.name == "Backstage passes to a TAFKAL80ETC concert": # + 3
item.quality = item.quality - item.quality
else:
if item.quality > 0: # + 4
if item.name != "Sulfuras, Hand of Ragnaros": # + 5
item.quality = item.quality - 1
We’ve managed to shave off 7 points of complexity fairly quickly. The next steps start to get more complicated – and Nick goes through them in great detail in
def update_quality(self):
for item in self.items: # + 1
if item.name == "Sulfuras, Hand of Ragnaros": # + 2
continue
if item.name == "Aged Brie": # + 2
if item.quality < 50: # + 3
item.quality = item.quality + 1
if item.sell_in < 1 and item.quality < 50: # + 3
item.quality = item.quality + 1
elif item.name == "Backstage passes to a TAFKAL80ETC concert": # + 2
if item.quality < 50: # + 4
item.quality = item.quality + 1
if item.sell_in < 11 and item.quality < 50: # + 4
item.quality = item.quality + 1
if item.sell_in < 6 and item.quality < 50: # + 4
item.quality = item.quality + 1
if item.sell_in < 1: # + 4
item.quality = item.quality - item.quality
else:
if item.quality > 0: # + 3
item.quality = item.quality - 1
if item.sell_in < 1 and item.quality > 0: # + 3
item.quality = item.quality - 1
item.sell_in = item.sell_in - 1
# Total Cognitive Complexity = 35
This is the first part of a seven-part series on code quality, technical debt, and development velocity.