🕯️ Magic Note
A well-designed function is like a good story. It has a clear beginning (parameters), a logical middle (processing), and a satisfying end (return value). It handles unexpected situations gracefully. It does one thing and does it well. These principles are not rules. They are guidelines that come from decades of programming experience.
Python
# Bad: One function doing too much
def process_user_data(user_data):
# Validates, cleans, formats, and saves
if not user_data.get(“name”):
return None
clean_name = user_data[“name”].strip().title()
age = int(user_data[“age”])
formatted = f”{clean_name} ({age})”
with open(“users.txt”, “a”) as f:
f.write(formatted + “\n”)
return formatted
# Good: Multiple focused functions
def validate_user_data(user_data):
return bool(user_data.get(“name”) and user_data.get(“age”))
def clean_user_name(name):
return name.strip().title()
def format_user_display(name, age):
return f”{name} ({age})”
def save_user_to_file(formatted_user):
with open(“users.txt”, “a”) as f:
f.write(formatted_user + “\n”)
Python
# Bad: Deep nesting
def process_order(order):
if order:
if order.get(“items”):
if len(order[“items”]) > 0:
total = calculate_total(order[“items”])
if total <= order.get(“budget”, 0):
return confirm_order(order, total)
else:
return “Insufficient budget”
else:
return “No items”
else:
return “Missing items”
else:
return “No order”
# Good: Guard clauses (early returns)
def process_order(order):
if not order:
return “No order”
if not order.get(“items”):
return “Missing items”
if len(order[“items”]) == 0:
return “No items”
total = calculate_total(order[“items”])
if total > order.get(“budget”, 0):
return “Insufficient budget”
return confirm_order(order, total)
🕯️ Magic Note
Guard clauses flatten your code. They make the happy path linear and easy to follow. The function starts with checking what could go wrong, then proceeds with the main logic.
Python
# Bad: Inconsistent return types
def find_user(user_id):
if user_id in database:
return database[user_id] # Returns dict
else:
return None # Returns None
# Caller must check: result might be dict or None
# Better: Raise exception for missing user
def find_user(user_id):
if user_id not in database:
raise KeyError(f”User {user_id} not found”)
return database[user_id] # Always returns dict
# Or return a default value of the same type (if appropriate)
def find_user(user_id):
return database.get(user_id, {}) # Always returns dict (empty if not found)
Python
# Pure function (no side effects)
def add(a, b):
return a + b
# Same input always gives same output. No external changes.
# Impure function (has side effects)
counter = 0
def increment():
global counter
counter += 1 # Modifies global state
print(counter) # Prints to console
return counter
# Better: Pass and return state explicitly
def increment(counter):
return counter + 1
# Caller manages the state
counter = increment(counter)
🕯️ Magic Note
Pure functions are easier to test, easier to debug, and easier to reason about. They do not depend on external state. Their output is determined solely by their input. Use pure functions whenever possible.
Python
# Handle error inside (hide it from caller)
def divide_safe(a, b):
try:
return a / b
except ZeroDivisionError:
return None # Caller checks for None
# Propagate error (let caller handle it)
def divide_unsafe(a, b):
return a / b # ZeroDivisionError propagates
# Document which approach you choose
def parse_int(value):
“””Return int(value) or None if conversion fails.”””
try:
return int(value)
except ValueError:
return None
Python
# Single return (not typical Python style)
def classify_age_single(age):
if age < 0:
result = “Invalid”
elif age < 13:
result = “Child”
elif age < 18:
result = “Teenager”
else:
result = “Adult”
return result
# Multiple returns (more Pythonic)
def classify_age(age):
if age < 0:
return “Invalid”
if age < 13:
return “Child”
if age < 18:
return “Teenager”
return “Adult”
🕯️ Magic Note
Python encourages guard clauses and early returns. The “single exit point” rule from C does not apply. Multiple returns make code clearer and reduce nesting.
Python
# Bad: Long, complex function
def process_order(order, user, inventory, payment):
# 50+ lines of validation, calculation,
# inventory update, payment processing, email sending…
pass
# Good: Multiple small functions
def validate_order(order):
# 5 lines
pass
def calculate_total(order):
# 5 lines
pass
def update_inventory(order):
# 5 lines
pass
def process_payment(order, payment):
# 5 lines
pass
def send_confirmation(order, user):
# 5 lines
pass
def process_order(order, user, inventory, payment):
if not validate_order(order):
raise ValueError(“Invalid order”)
total = calculate_total(order)
update_inventory(inventory, order)
process_payment(payment, total)
send_confirmation(user, order)
return total
Python
# Bad: Unnecessary if-else
def is_adult(age):
if age >= 18:
return True
else:
return False
# Good: Return the comparison directly
def is_adult(age):
return age >= 18
# Also good for complex logic
def is_valid_email(email):
return “@” in email and “.” in email.split(“@”)[-1]
def can_vote(age, is_citizen):
return age >= 18 and is_citizen
Python
def fibonacci(n):
“””
Return the nth Fibonacci number.
The Fibonacci sequence starts with 0, 1, 1, 2, 3, 5, 8, …
Each number is the sum of the two preceding ones.
Parameters:
n (int): The position in the sequence (0-indexed)
Returns:
int: The nth Fibonacci number
Raises:
ValueError: If n is negative
Example:
>>> fibonacci(0)
0
>>> fibonacci(6)
8
“””
if n < 0:
raise ValueError(“n must be non-negative”)
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
- Modifying mutable parameters that should not be changed
- Returning inconsistent types (sometimes dict, sometimes None)
- Too many parameters (more than 5 is usually too many)
- Functions that are too long (more than 30 lines)
- Deep nesting (more than 3 levels)
- Side effects hidden inside pure-looking functions
- Not handling edge cases (empty lists, zero, negative numbers)
- What is the single responsibility principle?
- What is a guard clause and why is it useful?
- What is a pure function? Give an example.
- Why is returning None sometimes problematic?
- How long should a typical function be?
- Rewrite if condition: return True else: return False properly.
⚡ Whisper
The logic inside a function is its soul. The parameters are its senses, reaching out to the world. The return value is its voice, speaking back. But the logic is the thinking. Good function logic is linear, not twisted. It handles errors first, then proceeds. It does one thing well, then returns. It does not hide surprises. It does not mutate the world without warning. When you write function logic, imagine you are giving directions to a friend. Be clear. Be direct. Handle obstacles early. Do not send them down dark alleys. Do not change their map without telling them. The best functions read like instructions: “If this, tell them that. Else, do this. Then return that.” That is clarity. That is craftsmanship. That is the mark of a thoughtful programmer.