[TIPS] Refactoring - Clean Code - Tip 3: Break Down Large Functions

By JoeVu, at: July 8, 2024, 9:45 a.m.

Estimated Reading Time: 7 min read

[TIPS] Refactoring - Clean Code - Tip 3: Break Down Large Functions
[TIPS] Refactoring - Clean Code - Tip 3: Break Down Large Functions

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.


Subscribe

Subscribe to our newsletter and never miss out lastest news.