Creating short, extensible method signatures is a great way to build maintainable, readable code. Having too many parameters to a method is a common mistake people make when defining method signatures that hurts readability and extensibility.
Consider the following method for transferring money between accounts
interface Bank {
void transferMoney(Long timestamp,
BigDecimal amount,
String fromAccount,
String toAccount,
String memo);
}
- the method has five input parameters, which makes each invocation difficult to read and easy to exceed line lengths
- it's easy to mix up the consecutive parameters of the same type. Looking at
bank.transferMoney(System.currentTimeMillis(),
BigDecimal.of("50.00"),
"XXX", "YYY", null);
which of the parameters is fromAccount
and which one is toAccount
?
- Extending this method is difficult. Let's say we need to send an email notification upon successful transfer of the money. With this current interface, we can
- add another string parameter
emailAddress
, but then we will be forced to update every place ourtransferMoney
function is called to include the new parameter. This is a breaking API change that forces callers to pass an extra parameter. The best case is that this interface is only used in our own code and we just have to go update every invocation oftransferMoney
in our own code. If this is code consumed by external callers, we have bigger problems. - add a new method for transferring money that has an extra parameter for
emailAddress
. Not a big deal, but the more we add additional parameters or functionality, the more the interface is polluted with many variations of essentially the same operation.
- add another string parameter
We can improve this by bundling up the parameters in to an object.
class TransferDetails {
final Long timestamp;
final String fromAccount;
final String toAccount;
final String memo;
//...
}
interface Bank {
void transferMoney(TransferDetails details);
}
This improved method signature is easier to read and extend. Combined with a builder pattern, parameters are easily understood and labeled even when we have many parameters of similar types.
final TransferDetails transferDetails =
TransferDetails.builder()
.fromAccount("XXX")
.toAccount("YYY")
.timestamp(System.currentTimeMilis())
.amount(BigDecimal.of("50.00"))
.build();
bank.transferMoney(transferDetails)
Additionally, adding a new parameter to the method does not require breaking changes or overloading our method signature. We can simply add the new field to the input object and callers are free to add or ignore the new field without breaking existing invocations of the transferMoney
method.
class TransferDetails {
final Long timestamp;
final String fromAccount;
final String toAccount;
final String memo;
final String emailAddress;
//...
}
interface Bank {
void transferMoney(TransferDetails details);
}
//...
// our previous invocation still works
final TransferDetails transferDetails =
TransferDetails.builder()
.fromAccount("XXX")
.toAccount("YYY")
.timestamp(System.currentTimeMilis())
.amount(BigDecimal.of("50.00"))
.build();
bank.transferMoney(transferDetails)
// but we can also start sending in email address
final TransferDetails transferDetails =
TransferDetails.builder()
.fromAccount("XXX")
.toAccount("YYY")
.timestamp(System.currentTimeMilis())
.amount(BigDecimal.of("50.00"))
.emailAddress("someone@gmail.com")
.build();
bank.transferMoney(transferDetails);
Use parameter objects as a simple way to make your methods easier to read and extend. Happy coding :)
Top comments (0)