[TIPS] Refactoring - Clean Code - Tip 3: Break Down Large Functions
By JoeVu, at: July 8, 2024, 9:45 a.m.
Refactoring Tip 3: Break Down Large Functions
- Junior Developer: Often writes large, monolithic functions.
- Senior Developer: Breaks down large functions into smaller, single-responsibility functions.
Breaking down large functions into smaller, more focused ones enhances code readability, maintainability, and testability. Here's an example to illustrate the difference between how a junior and a senior developer might approach this principle:
Example 1: Refactoring a User Registration Process
Junior Developer's Approach:
A junior developer might write a large function that handles the entire user registration process.
def register_user(user_data):
# Validate user data
if 'username' not in user_data or 'password' not in user_data:
return 'Invalid data'
# Hash the password
import hashlib
hashed_password = hashlib.sha256(user_data['password'].encode()).hexdigest()
# Save user to database
import sqlite3
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS users
(username TEXT, password TEXT)''')
cursor.execute("INSERT INTO users (username, password) VALUES (?, ?)",
(user_data['username'], hashed_password))
conn.commit()
conn.close()
# Send welcome email
import smtplib
from email.mime.text import MIMEText
msg = MIMEText(f"Welcome {user_data['username']}!")
msg['Subject'] = 'Welcome'
msg['From'] = '[email protected]'
msg['To'] = user_data['email']
s = smtplib.SMTP('localhost')
s.sendmail('[email protected]', [user_data['email']], msg.as_string())
s.quit()
return 'User registered successfully'
# Usage
result = register_user({
'username': 'john_doe',
'password': 'password123',
'email': '[email protected]'
})
Senior Developer's Approach:
A senior developer would break down the large function into smaller, single-responsibility functions.
import hashlib
import sqlite3
import smtplib
from email.mime.text import MIMEText
def validate_user_data(user_data):
if 'username' not in user_data or 'password' not in user_data or 'email' not in user_data:
raise ValueError('Invalid data')
def hash_password(password):
return hashlib.sha256(password.encode()).hexdigest()
def save_user_to_db(username, hashed_password):
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS users
(username TEXT, password TEXT)''')
cursor.execute("INSERT INTO users (username, password) VALUES (?, ?)",
(username, hashed_password))
conn.commit()
conn.close()
def send_welcome_email(username, email):
msg = MIMEText(f"Welcome {username}!")
msg['Subject'] = 'Welcome'
msg['From'] = '[email protected]'
msg['To'] = email
s = smtplib.SMTP('localhost')
s.sendmail('[email protected]', [email], msg.as_string())
s.quit()
def register_user(user_data):
validate_user_data(user_data)
hashed_password = hash_password(user_data['password'])
save_user_to_db(user_data['username'], hashed_password)
send_welcome_email(user_data['username'], user_data['email'])
return 'User registered successfully'
# Usage
result = register_user({
'username': 'john_doe',
'password': 'password123',
'email': '[email protected]'
})
Example 2: Refactoring an Order Processing System
Junior Developer's Approach:
A junior developer might write a large function that handles the entire order processing workflow.
def process_order(order):
# Validate order
if 'items' not in order or 'customer' not in order:
return 'Invalid order'
# Calculate total
total = 0
for item in order['items']:
total += item['price'] * item['quantity']
# Process payment
payment_success = True # Simulate payment processing
if not payment_success:
return 'Payment failed'
# Generate invoice
invoice = {
'customer': order['customer'],
'items': order['items'],
'total': total
}
# Send confirmation email
import smtplib
from email.mime.text import MIMEText
msg = MIMEText(f"Thank you for your order, {order['customer']['name']}! Your total is ${total}.")
msg['Subject'] = 'Order Confirmation'
msg['From'] = '[email protected]'
msg['To'] = order['customer']['email']
s = smtplib.SMTP('localhost')
s.sendmail('[email protected]', [order['customer']['email']], msg.as_string())
s.quit()
return 'Order processed successfully'
# Usage
order = {
'customer': {'name': 'John Doe', 'email': '[email protected]'},
'items': [{'name': 'Item A', 'price': 10, 'quantity': 2}, {'name': 'Item B', 'price': 20, 'quantity': 1}]
}
result = process_order(order)
Senior Developer's Approach:
A senior developer would break down the large function into smaller, single-responsibility functions.
import smtplib
from email.mime.text import MIMEText
def validate_order(order):
if 'items' not in order or 'customer' not in order:
raise ValueError('Invalid order')
def calculate_total(items):
return sum(item['price'] * item['quantity'] for item in items)
def process_payment(order_total):
payment_success = True # Simulate payment processing
if not payment_success:
raise RuntimeError('Payment failed')
def generate_invoice(customer, items, total):
return {
'customer': customer,
'items': items,
'total': total
}
def send_confirmation_email(customer, total):
msg = MIMEText(f"Thank you for your order, {customer['name']}! Your total is ${total}.")
msg['Subject'] = 'Order Confirmation'
msg['From'] = '[email protected]'
msg['To'] = customer['email']
s = smtplib.SMTP('localhost')
s.sendmail('[email protected]', [customer['email']], msg.as_string())
s.quit()
def process_order(order):
validate_order(order)
total = calculate_total(order['items'])
process_payment(total)
invoice = generate_invoice(order['customer'], order['items'], total)
send_confirmation_email(order['customer'], total)
return 'Order processed successfully'
# Usage
order = {
'customer': {'name': 'John Doe', 'email': '[email protected]'},
'items': [{'name': 'Item A', 'price': 10, 'quantity': 2}, {'name': 'Item B', 'price': 20, 'quantity': 1}]
}
result = process_order(order)
Example 3: Refactoring API Integration
Junior Developer's Approach:
A junior developer might write a large function that handles multiple API interactions and processing steps.
import requests
def fetch_and_process(api_url):
response = requests.get(api_url + '/data')
data = response.json()
filtered_data = []
for item in data:
if some_condition(item):
filtered_data.append(item)
processed_data = []
for item in filtered_data:
processed_data.append(some_processing(item))
return processed_data
# Usage
result = fetch_and_process('https://api.example.com')
Senior Developer's Approach:
A senior developer would break down the large function into smaller, single-responsibility functions.
import requests
class DataFetcherProcessor:
def __init__(self, base_url):
self.base_url = base_url
def fetch_data(self, endpoint):
response = requests.get(f"{self.base_url}/{endpoint}")
response.raise_for_status()
return response.json()
def filter_data(self, data, condition):
return [item for item in data if condition(item)]
def process_item(self, item):
return self.some_processing(item)
def fetch_and_process(self, endpoint, condition):
data = self.fetch_data(endpoint)
filtered_data = self.filter_data(data, condition)
return [self.process_item(item) for item in filtered_data]
def some_processing(self, item):
# Implement the processing logic here
pass
# Usage
def some_condition(item):
# Define your condition here
return True
fetcher_processor = DataFetcherProcessor('https://api.example.com')
result = fetcher_processor.fetch_and_process('data', some_condition)
These examples demonstrate how a senior developer's approach to breaking down large functions can lead to code that is more modular, maintainable, and easier to understand. By adhering to the single-responsibility principle, each function becomes more focused and easier to test, which ultimately improves the overall quality of the codebase.