[TIPS] Refactoring - Clean Code - Tip 6 - Avoid Deep Nesting
By JoeVu, at: Aug. 4, 2024, 11:29 a.m.
Refactoring Tip 6: Avoid Deep Nesting
-
Junior: May write deeply nested code making it hard to read and maintain.
-
Senior: Refactors to reduce nesting by using early returns, guard clauses, and splitting complex logic.
Avoiding deep nesting is crucial for writing readable and maintainable code. Here's an example to illustrate the difference between how a junior and a senior developer might approach this principle.
Example 1: Deeply Nested Conditional Logic
Junior Developer's Approach
A junior developer might write deeply nested code for handling multiple conditions:
def process_order(order):
if order:
if order['status'] == 'new':
if order['items']:
total = 0
for item in order['items']:
if item['quantity'] > 0:
total += item['price'] * item['quantity']
if total > 0:
return f"Order processed: Total is {total}"
return "Invalid order"
Senior Developer's Approach
A senior developer would use early returns and guard clauses to reduce nesting:
def process_order(order):
if not order:
return "Invalid order"
if order['status'] != 'new':
return "Invalid order"
if not order['items']:
return "Invalid order"
total = sum(item['price'] * item['quantity'] for item in order['items'] if item['quantity'] > 0)
if total <= 0:
return "Invalid order"
return f"Order processed: Total is {total}"
Example 2: Reducing Nesting in Loops
Junior Developer's Approach
A junior developer might have deeply nested loops and conditionals:
def find_valid_items(items):
valid_items = []
for item in items:
if 'name' in item:
if item['name']:
if 'price' in item:
if item['price'] > 0:
valid_items.append(item)
return valid_items
Senior Developer's Approach
A senior developer would refactor to reduce nesting using guard clauses and list comprehensions:
def find_valid_items(items):
return [
item for item in items
if 'name' in item and item['name'] and 'price' in item and item['price'] > 0
Example 3: Splitting Complex Logic into Functions
Junior Developer's Approach
A junior developer might write a single function with complex, deeply nested logic:
def handle_user_request(request):
if request:
if request['user']:
user = get_user(request['user'])
if user:
if user.is_active:
if request['action'] == 'update':
if request['data']:
user.update(request['data'])
return "User updated"
else:
return "No data provided"
elif request['action'] == 'delete':
user.delete()
return "User deleted"
else:
return "Invalid action"
else:
return "User not active"
return "Invalid request"
Senior Developer's Approach
A senior developer would split the complex logic into smaller functions:
def handle_user_request(request):
if not request or not request.get('user'):
return "Invalid request"
user = get_user(request['user'])
if not user or not user.is_active:
return "User not active"
action = request.get('action')
if action == 'update':
return update_user(user, request.get('data'))
elif action == 'delete':
return delete_user(user)
else:
return "Invalid action"
def update_user(user, data):
if not data:
return "No data provided"
user.update(data)
return "User updated"
def delete_user(user):
user.delete()
return "User deleted"
Key Improvements:
- Early Returns and Guard Clauses: Use early returns to handle invalid cases upfront, reducing the need for deep nesting.
- List Comprehensions: Simplify loops and conditionals using list comprehensions where appropriate.
- Function Decomposition: Split complex logic into smaller, more manageable functions.
Reducing deep nesting improves code readability and maintainability, making it easier to understand and modify. Using early returns, guard clauses, and function decomposition are effective strategies to achieve this.