rspec/rules/S3776/python/rule.adoc

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[]