How many constructor arguments is too many?

I see that some people are recommending seven as an upper limit. Apparently it is not true that people can hold seven things in their head at once; they can only remember four (Susan Weinschenk, 100 Things Every Designer Needs to Know about People, 48). Even so, I consider four to be something of a high earth orbit. But that's because my thinking has been altered by Bob Martin.

In Clean Code, Uncle Bob argues for three as an general upper limit for number of parameters. He makes the radical claim (40):

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic) followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn't be used anyway.

He says this because of readability; but also because of testability:

Imagine the difficulty of writing all the test cases to ensure that all various combinations of arguments work properly.

I encourage you to find a copy of his book and read his full discussion of function arguments (40-43).

I agree with those who have mentioned the Single Responsibility Principle. It is hard for me to believe that a class that needs more than two or three values/objects without reasonable defaults really has only one responsibility, and would not be better off with another class extracted.

Now, if you are injecting your dependencies through the constructor, Bob Martin's arguments about how easy it is to invoke the constructor do not so much apply (because usually then there is only one point in your application where you wire that up, or you even have a framework that does it for you). However, the Single Responsibility Principle is still relevant: once a class has four dependencies, I consider that a smell that it is doing a large amount of work.

However, as with all things in computer science, there are doubtless valid cases for having a large number of constructor parameters. Don't contort your code to avoid using a large number of parameters; but if you do use a large number of parameters, stop and give it some thought, because it may mean your code is already contorted.


Two design approaches to consider

The essence pattern

The fluent interface pattern

These are both similar in intent, in that we slowly build up an intermediate object, and then create our target object in a single step.

An example of the fluent interface in action would be:

public class CustomerBuilder {
    String surname;
    String firstName;
    String ssn;
    public static CustomerBuilder customer() {
        return new CustomerBuilder();
    }
    public CustomerBuilder withSurname(String surname) {
        this.surname = surname; 
        return this; 
    }
    public CustomerBuilder withFirstName(String firstName) {
        this.firstName = firstName;
        return this; 
    }
    public CustomerBuilder withSsn(String ssn) {
        this.ssn = ssn; 
        return this; 
    }
    // client doesn't get to instantiate Customer directly
    public Customer build() {
        return new Customer(this);            
    }
}

public class Customer {
    private final String firstName;
    private final String surname;
    private final String ssn;

    Customer(CustomerBuilder builder) {
        if (builder.firstName == null) throw new NullPointerException("firstName");
        if (builder.surname == null) throw new NullPointerException("surname");
        if (builder.ssn == null) throw new NullPointerException("ssn");
        this.firstName = builder.firstName;
        this.surname = builder.surname;
        this.ssn = builder.ssn;
    }

    public String getFirstName() { return firstName;  }
    public String getSurname() { return surname; }
    public String getSsn() { return ssn; }    
}
import static com.acme.CustomerBuilder.customer;

public class Client {
    public void doSomething() {
        Customer customer = customer()
            .withSurname("Smith")
            .withFirstName("Fred")
            .withSsn("123XS1")
            .build();
    }
}