DEV Community

Sanjeev
Sanjeev

Posted on

1 1

How to Write Better Functions: A Clean Code Checklist

Functions are the building blocks of your code - they break down complex operations into manageable, reusable pieces. Well-designed functions are key to creating systems that are easy to understand, test, and maintain over time.

This post presents four key checkpoints for reviewing functions, inspired by the book Clean Code. Each checkpoint includes practical examples showing common problems and their solutions.

Function Review Checklist

  1. Is the function small, focused, and does its name match what it does?
  2. Does the function have too many arguments?
  3. Are errors handled with exceptions instead of error codes?
  4. Is there any unnecessary duplication?

Checkpoint 1: Is the function small, focused, and does its name match what it does?

A function should do one thing and do it well. Here are three key signs of a focused function:

a. It works at one level of abstraction

Problem: High-level logic mixed with low-level logic

void processOrder(Order order) {
    if (!order.hasValidPayment()) {
        throw new InvalidOrderException("Payment validation failed");
    }
    // Low-level database operation mixed with high-level logic
    orderRepository.executeSQL("INSERT INTO orders VALUES (" + order.getId() + ", ...)");
}
Enter fullscreen mode Exit fullscreen mode

Solution: Consistent abstraction level

void processOrder(Order order) {
    validateOrder(order);
    saveOrder(order);
}
Enter fullscreen mode Exit fullscreen mode

b. It either does something (command) or answers something (query), but not both

Problem: Mixed command and query

int incrementAndGet() {
    count++;
    return count;
}
Enter fullscreen mode Exit fullscreen mode

Solution: Separate command and query

void increment() {
    count++;
}

int getCount() {
    return count;
}
Enter fullscreen mode Exit fullscreen mode

c. It doesn't have hidden side effects

Problem: Hidden side effect

double calculateTotal(Order order) {
    if (order.getTotal() > 100) {
        order.applyDiscount(10);  // Surprising! Calculating total but modifying order
    }
    return order.getTotal();
}
Enter fullscreen mode Exit fullscreen mode

Solution: No side effects

double calculateTotal(Order order) {
    return order.getTotal();
}

void applyDiscountIfEligible(Order order) {
    if (calculateTotal(order) > 100) {
        order.applyDiscount(10);
    }
}
Enter fullscreen mode Exit fullscreen mode

Checkpoint 2: Does the function have too many arguments?

Functions should have as few arguments as possible. Functions with too many arguments are hard to read, use, and test.
Here are common solutions to handle multiple parameters effectively:

a. Group related parameters into an object

Problem: Too many related arguments

void createUser(String name, String email, String phone, String address) {
    // User creation logic
}
Enter fullscreen mode Exit fullscreen mode

Solution: Group related data into an object

void createUser(UserProfile profile) {
    // User creation logic
}
Enter fullscreen mode Exit fullscreen mode

b. Use builder pattern for optional parameters

Problem: Multiple constructor variations

class Report {
    Report(String title) { ... }
    Report(String title, Date from) { ... }
    Report(String title, Date from, Date to) { ... }
}
Enter fullscreen mode Exit fullscreen mode

Solution: Builder pattern for clear, flexible construction

Report report = new ReportBuilder()
    .setTitle("Sales Report")
    .setFromDate(startDate)    // Optional
    .setToDate(endDate)        // Optional
    .build();
Enter fullscreen mode Exit fullscreen mode

Checkpoint 3: Are errors handled with exceptions instead of error codes?

Error codes mix error handling with normal flow and are easy to ignore. Exceptions make error cases explicit and separate them from the main logic. Example:

a. Use exceptions instead of error codes

Problem: Using error codes

int withdraw(Account account, double amount) {
    if (amount <= 0) return -1;        // Invalid amount
    if (amount > account.balance) return -2;  // Insufficient funds
    account.balance -= amount;
    return 0;  // Success
}
Enter fullscreen mode Exit fullscreen mode

Solution: Using specific exceptions

void withdraw(Account account, double amount) {
    if (amount <= 0) {
        throw new InvalidAmountException("Amount must be positive");
    }
    if (amount > account.balance) {
        throw new InsufficientFundsException("Not enough balance");
    }
    account.balance -= amount;
}
Enter fullscreen mode Exit fullscreen mode

Checkpoint 4: Is there any unnecessary duplication?

Duplicated code increases the risk of bugs and makes changes harder. Here's how to identify and eliminate common types of duplication:

a. Extract repeated validation logic

Problem: Duplicated validation

class UserService {
    void createUser(String email) {
        if (email == null || !email.contains("@")) {
            throw new InvalidEmailException();
        }
        // Create user
    }

    void updateEmail(String email) {
        if (email == null || !email.contains("@")) {
            throw new InvalidEmailException();
        }
        // Update email
    }
}
Enter fullscreen mode Exit fullscreen mode

Solution: Single validation function

class UserService {
    private void validateEmail(String email) {
        if (email == null || !email.contains("@")) {
            throw new InvalidEmailException();
        }
    }

    void createUser(String email) {
        validateEmail(email);
        // Create user
    }

    void updateEmail(String email) {
        validateEmail(email);
        // Update email
    }
}
Enter fullscreen mode Exit fullscreen mode

b. Create utility functions for common operations

Problem: Repeated string manipulation

String name1 = input1.trim().toLowerCase().replace(" ", "");
String name2 = input2.trim().toLowerCase().replace(" ", "");
Enter fullscreen mode Exit fullscreen mode

Solution: Utility function

String standardizeName(String input) {
    return input.trim().toLowerCase().replace(" ", "");
}
Enter fullscreen mode Exit fullscreen mode

Wrap-up

Well-designed functions make code easier to understand, test, and maintain. When reviewing code, check for:

  1. Single responsibility with appropriate abstraction levels
  2. Minimal, well-organized parameters
  3. Clear error handling with exceptions
  4. No unnecessary duplication

These improvements might seem small, but they compound to create significantly more maintainable systems.

Top comments (0)

👋 Kindness is contagious

Discover fresh viewpoints in this insightful post, supported by our vibrant DEV Community. Every developer’s experience matters—add your thoughts and help us grow together.

A simple “thank you” can uplift the author and spark new discussions—leave yours below!

On DEV, knowledge-sharing connects us and drives innovation. Found this useful? A quick note of appreciation makes a real impact.

Okay