140 lines
4.0 KiB
Plaintext
140 lines
4.0 KiB
Plaintext
include::../intro.adoc[]
|
|
|
|
== Why is this an issue?
|
|
|
|
include::../why.adoc[]
|
|
|
|
=== What is the potential impact?
|
|
|
|
include::../impact.adoc[]
|
|
|
|
== How to fix it
|
|
|
|
include::../how.adoc[]
|
|
|
|
=== Code examples
|
|
|
|
**Extraction of a complex condition in a new function.**
|
|
|
|
==== Noncompliant code example
|
|
|
|
The code is using a complex condition and has a cognitive cost of 5.
|
|
|
|
[source,python,diff-id=1,diff-type=noncompliant]
|
|
----
|
|
def process_eligible_users(users):
|
|
for user in users: # +1 (for)
|
|
if ((user.is_active and # +1 (if) +1 (nested) +1 (multiple conditions)
|
|
user.has_profile) or # +1 (mixed operator)
|
|
user.age > 18 ):
|
|
user.process()
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
Even if the cognitive complexity of the whole program did not change, it is easier for a reader to understand the code of the `process_eligible_users` function, which now only has a cognitive cost of 3.
|
|
|
|
[source,python,diff-id=1,diff-type=compliant]
|
|
----
|
|
|
|
def process_eligible_users(users):
|
|
for user in users: # +1 (for)
|
|
if is_eligible_user(user): # +1 (if) +1 (nested)
|
|
user.process()
|
|
|
|
def is_eligible_user(user):
|
|
return ((user.is_active and user.has_profile) or user.age > 18) # +1 (multiple conditions) +1 (mixed operators)
|
|
----
|
|
|
|
**Break down large functions.**
|
|
|
|
==== Noncompliant code example
|
|
|
|
|
|
__Note:__ The code is simplified here, to illustrate the purpose.
|
|
Please imagine there is more happening in the process. +
|
|
|
|
The bellow code has a cognitive complexity score of 8.
|
|
|
|
[source,python,diff-id=3,diff-type=noncompliant]
|
|
----
|
|
def process_user(user):
|
|
if user.is_active(): # +1 (if)
|
|
if user.has_profile(): # +1 (if) +1 (nested)
|
|
... # process active user with profile
|
|
else: # +1 (else)
|
|
... # process active user without profile
|
|
else: # +1 (else)
|
|
if user.has_profile(): # +1 (if) +1 (nested)
|
|
... # process inactive user with profile
|
|
else: # +1 (else)
|
|
... # process inactive user without profile
|
|
----
|
|
|
|
This function could be refactored into smaller functions:
|
|
The complexity is spread over multiple functions and the breaks in flow are no more nested. +
|
|
The `process_user` has now a complexity score of two.
|
|
|
|
==== Compliant solution
|
|
|
|
[source,python,diff-id=3,diff-type=compliant]
|
|
----
|
|
def process_user(user):
|
|
if user.is_active(): # +1 (if)
|
|
process_active_user(user)
|
|
else: # +1 (else)
|
|
process_inactive_user(user)
|
|
|
|
def process_active_user(user):
|
|
if user.has_profile(): # +1 (if) +1 (nested)
|
|
... # process active user with profile
|
|
else: # +1 (else)
|
|
... # process active user without profile
|
|
|
|
def process_inactive_user(user):
|
|
if user.has_profile(): # +1 (if) +1 (nested)
|
|
... # process inactive user with profile
|
|
else: # +1 (else)
|
|
... # process inactive user without profile
|
|
----
|
|
|
|
**Avoid deep nesting by returning early.**
|
|
|
|
==== Noncompliant code example
|
|
|
|
The below code has a cognitive complexity of 6.
|
|
|
|
[source,python,diff-id=4,diff-type=noncompliant]
|
|
----
|
|
def calculate(data):
|
|
if data is not None: # +1 (if)
|
|
total = 0
|
|
for item in data: # +1 (for) +1 (nested)
|
|
if item > 0: # +1 (if) +2 (nested)
|
|
total += item * 2
|
|
return total
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
Checking for the edge case first flattens the `if` statements and reduces the cognitive complexity to 4.
|
|
|
|
[source,python,diff-id=4,diff-type=compliant]
|
|
----
|
|
def calculate(data):
|
|
if data is None: # +1 (if)
|
|
return None
|
|
total = 0
|
|
for item in data: # +1 (for)
|
|
if item > 0: # +1 (if) +1 (nested)
|
|
total += item * 2
|
|
return total
|
|
----
|
|
|
|
=== Pitfalls
|
|
|
|
As this code is complex, ensure that you have unit tests that cover the code before refactoring.
|
|
|
|
include::../resources.adoc[]
|
|
|
|
include::../rspecator.adoc[] |