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.

Warp.dev image

Warp is the highest-rated coding agent—proven by benchmarks.

Warp outperforms every other coding agent on the market, and gives you full control over which model you use. Get started now for free, or upgrade and unlock 2.5x AI credits on Warp's paid plans.

Download Warp

Top comments (0)

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

👋 Kindness is contagious

Dive into this thoughtful piece, beloved in the supportive DEV Community. Coders of every background are invited to share and elevate our collective know-how.

A sincere "thank you" can brighten someone's day—leave your appreciation below!

On DEV, sharing knowledge smooths our journey and tightens our community bonds. Enjoyed this? A quick thank you to the author is hugely appreciated.

Okay