Keep Unit Interfaces Small

Limit the number of parameters per unit to at most 4. Do this by extracting parameters into objects.

We can agree that a method with zero arguments is a good method, we don’t have to worry too much about it. The perfect world will be if all methods have zero arguments, but we know that’s not possible. Sometimes we’ll end up writing a method containing six or more arguments, like this method:


void createUser(String email, String userName, String password, 
    String country, String city, String street, String phone) { 
    // ...
}

// createUser("cleancode@gpcoder.com", "gpcoder", "str0ng-p4wd", null, null, null, "+84xxxxxxxxx");

In the method above, we have seven arguments in total, and apparently, they don’t have a logical order. We could try to make sense of these arguments, but we can only guess by just looking at the arguments.

In the Clean Code book, Uncle Bob (Robert C. Martin) explains that "an ideal argument’s number for a function is zero (niladic function). Followed by one (monadic function) and closely by two arguments (dyadic function). Functions with three arguments (triadic function) should be avoided if possible. More than three arguments (polyadic function) are only for very specific cases and then shouldn’t be used anyway".

In my opinion, this a great guideline to follow to write better code, but they’re just guidelines, not strict rules that you have to follow no matter what.

Motivation

Reducing the number of arguments in methods offers several advantages:

  • More readable, and easier to understand because probably the methods are smaller and follow the Single Responsibility Principle.

  • Easier to reuse and modify because they do not depend on too much external input.

  • Easier to test: by making the method smaller, testing problems individually are pretty simple. We can test the paths in the main function and have a collection of smaller tests for each individual method.

How to Apply the Guideline

Having small interfaces is a good idea. How small should an interface be? In practice, an upper bound of four seems reasonable: a method with four parameters is still reasonably clear, but a method with five parameters is already getting difficult to read and has too many responsibilities.

The Replace Method with the Data Transfer Object Refactoring Technique

One way to solve this is to use the Replace Method with the Data Transfer Object (DTO or parameter objects) refactoring technique.

In the next example, I’m going to show how to use the Parameter Object to reduce the arguments’ number using the sample function shown earlier:

class User {
    private String email;
    private String userName;
    private String password;
    private String phone;
    private Address address;
}

class Address {
    private String country;
    private String city;
    private String street;
}

void createUser(User user) {
    // ...
}

We’re able to reduce the argument’s number to only one.

Common Objections to Keeping Unit Interfaces Small

Objection: Parameter Objects with Large Interfaces

“The parameter object I introduced now has a constructor with too many parameters.”

class User {
    private String email;
    private String userName;
    private String password;
    private String phone;
    private String country;
    private String city;
    private String street;
    
    public User(String email, String userName, String password, String phone, String country, String city, String street) {
    }
}

If all went well, you have grouped a number of parameters into an object during the refactoring of a method with a large interface. It may be the case that your object now has a lot of parameters because they apparently fit together. Instead of having a constructor with many arguments, we can actually group parameters together inside another object, like the example above, instead of having 7 parameters in a constructor, we can create an Address object to group related fields together (country, address, street).

In general, you should not refuse to introduce a parameter object, but rather think about the structure of the object you are introducing and how it relates to the rest of your code.

Another way to avoid constructors with too many parameters is to use the Builder Design Pattern.

Refactoring Large Interfaces Does Not Improve My Situation

“When I refactor my method, I am still passing a lot of parameters to another method.”

Getting rid of large interfaces is not always easy. It usually takes more than refactoring one method. Normally, you should continue splitting responsibilities into your methods, so that you access the most primitive parameters only when you need to manipulate them separately.

Frameworks or Libraries Prescribe Interfaces with Long Parameter Lists

“The interface of a framework we’re using has nine parameters. How can I implement this interface without creating a unit interface violation?”

Sometimes frameworks/libraries define interfaces or classes with methods that have long parameter lists. Implementing or overriding these methods will inevitably lead to long parameter lists in your own code. These types of violations are impossible to prevent, but their impact can be limited.

  • To limit the impact of violations caused by third-party frameworks or libraries, it is best to isolate these violations—for instance, by using wrappers or adapters.

  • Selecting a different framework/library is also a viable alternative, although this can have a large impact on other parts of the codebase.

Last updated