This rule raises an issue when the code cognitive complexity of a function is above a certain threshold.
Cognitive Complexity is a measure of how hard it is to understand the control flow of a unit of code. Code with high cognitive complexity is hard to read, understand, test, and modify.
As a rule of thumb, high cognitive complexity is a sign that the code should be refactored into smaller, easier-to-manage pieces.
Here are the core concepts:
The method of computation is fully detailed in the pdf linked in the resources.
equals and hashCode methods are ignored because they might be automatically generated and might end up being difficult to
understand, especially in the presence of many fields.
Reducing cognitive complexity can be challenging.
Here are a few suggestions:
Extraction of a complex condition in a new function.
The code is using a complex condition and has a cognitive cost of 3.
double calculateFinalPrice(User user, Cart cart) {
double total = calculateTotal(cart);
if (user.hasMembership() // +1 (if)
&& user.ordersCount() > 10 // +1 (more than one condition)
&& user.isAccountActive()
&& !user.hasDiscount()
|| user.ordersCount() == 1) { // +1 (change of operator in condition)
total = applyDiscount(user, total);
}
return total;
}
Even if the cognitive complexity of the whole program did not change, it is easier for a reader to understand the code of the
calculateFinalPrice function, which now only has a cognitive cost of 1.
double calculateFinalPrice(User user, Cart cart) {
double total = calculateTotal(cart);
if (isEligibleForDiscount(user)) { // +1 (if)
total = applyDiscount(user, total);
}
return total;
}
boolean isEligibleForDiscount(User user) {
return user.hasMembership()
&& user.ordersCount() > 10 // +1 (more than one condition)
&& user.isAccountActive()
&& !user.hasDiscount()
|| user.ordersCount() == 1; // +1 (change of operator in condition)
}
Break down large functions.
For example, consider a function that calculates the total price of a shopping cart, including sales tax and shipping.
Note: The code
is simplified here, to illustrate the purpose. Please imagine there is more happening in the for loops.
double calculateTotal(Cart cart) {
double total = 0;
for (Item item : cart.items()) { // +1 (for)
total += item.price;
}
// calculateSalesTax
for (Item item : cart.items()) { // +1 (for)
total += 0.2 * item.price;
}
//calculateShipping
total += 5 * cart.items().size();
return total;
}
This function could be refactored into smaller functions: The complexity is spread over multiple functions and the complex
calculateTotal has now a complexity score of zero.
double calculateTotal(Cart cart) {
double total = 0;
total = calculateSubtotal(cart, total);
total += calculateSalesTax(cart, total);
total += calculateShipping(cart, total);
return total;
}
double calculateShipping(Cart cart, double total) {
total += 5 * cart.items().size();
return total;
}
double calculateSalesTax(Cart cart, double total) {
for (Item item : cart.items()) { // +1 (for)
total += 0.2 * item.price;
}
return total;
}
double calculateSubtotal(Cart cart, double total) {
for (Item item : cart.items()) { // +1 (for)
total += item.price;
}
return total;
}
Avoid deep nesting by returning early.
The below code has a cognitive complexity of 6.
double calculateDiscount(double price, User user) {
if (isEligibleForDiscount(user)) { // +1 ( if )
if (user.hasMembership()) { // +2 ( nested if )
return price * 0.9;
} else if (user.ordersCount() == 1) { // +1 ( else )
return price * 0.95;
} else { // +1 ( else )
return price;
}
} else { // +1 ( else )
return price;
}
}
Checking for the edge case first flattens the if statements and reduces the cognitive complexity to 3.
double calculateDiscount(double price, User user) {
if (!isEligibleForDiscount(user)) { // +1 ( if )
return price;
}
if (user.hasMembership()) { // +1
return price * 0.9;
}
if (user.ordersCount() == 1) { // +1 ( if )
return price * 0.95;
}
return price;
}
As this code is complex, ensure that you have unit tests that cover the code before refactoring.