Clean Code: Taming Long Parameter Lists with Objects
Imagine ordering a coffee and having to say: "I'd like a large, with skim milk, no sugar, one shot of espresso, with whipped cream, no cinnamon, in a ceramic mug, for here." It's a mouthful, and it's easy to get the order wrong. It would be much simpler to hand over a note with your preferences written down.
Functions with a long list of parameters are just like that complicated coffee order. They're hard to read, easy to call incorrectly, and a pain to modify. The solution is simple: bundle those parameters into a single "parameter object."
From a Long List to a Tidy Object
Let's look at a function for searching for flights. As more search criteria are added, the parameter list grows longer and longer.
// 👎 Bad: Too many parameters are hard to read and error-prone.
function searchFlights(
from: string,
to: string,
departureDate: Date,
returnDate: Date | null,
passengers: number,
cabinClass: 'economy' | 'business' | 'first',
isFlexible: boolean
) {
// ... searching logic
}
// Calling it is a nightmare. What was the 5th parameter again?
searchFlights('JFK', 'LAX', new Date(), null, 1, 'economy', false);
This is not ideal. It's hard to remember the order of the arguments, and adding a new one (like maxStops
) would be a breaking change for every place this function is called.
Now, let's group these into a parameter object.
// 👍 Good: A single object makes the function signature clean and the call site readable.
interface FlightSearchOptions {
from: string;
to: string;
departureDate: Date;
returnDate?: Date; // Optional parameters are now easy to handle
passengers: number;
cabinClass: 'economy' | 'business' | 'first';
isFlexible?: boolean;
}
function searchFlights(options: FlightSearchOptions) {
const {
from,
to,
departureDate,
returnDate,
passengers,
cabinClass,
isFlexible,
} = options;
// ... searching logic
}
// The call is now self-documenting!
searchFlights({
from: 'JFK',
to: 'LAX',
departureDate: new Date(),
passengers: 1,
cabinClass: 'economy',
});
The Benefits of Parameter Objects
- Readability: The code at the call site becomes self-documenting. The property names (
from
,to
, etc.) make it clear what each value represents. - Flexibility: You can easily add new optional parameters to the
FlightSearchOptions
interface without having to update every single function call. - Simpler Function Signatures: The function signature is clean and simple: it just takes
options
. - Reusability: The
FlightSearchOptions
type can be reused in other parts of your application that deal with flight searches.
Great for Configuration
This pattern is especially useful for functions that take a lot of configuration options, like a function that sends an email.
// 👍 Good: A configuration object makes complex options manageable.
interface EmailOptions {
to: string;
subject: string;
body: string;
from?: string;
cc?: string[];
attachments?: File[];
}
function sendEmail(options: EmailOptions) {
// Set default values for optional properties
const fromAddress = options.from ?? 'noreply@example.com';
// ... logic to send email
}
Next time you find yourself adding a third or fourth parameter to a function, take a moment to consider if it's time to create a parameter object. It's a small change that can make your code significantly cleaner and easier to work with.