Getting Rid of Nasty Parameters – PMD Rule “Excessive Parameter List” Explained

I bet you’ve seen code like this when your IDE auto-completed a function call in your code and there’s no source code or Javadoc found:

emailSender.sendEmail(String arg1, 
    InternetAddress arg2, InternetAddress arg3, InternetAddress arg4, 
    String arg5, String arg6, List<EmailAttachment> arg7);

But what does this mean? In this article, I will disclose this secret and show you a few techniques for refactoring (or avoiding!) these parameter lists.

The following code snippet shows a function that is used to send an email to a recipient with configurable sender address and reply address, some content in different formats and a list of attachments.

public void sendEmail(String subject, 
    InternetAddress addressTo, InternetAddress addressFrom, InternetAddress addressReplyTo, 
    String htmlContent, String plainContent, List<EmailImageAttachment> images);

All these parameters are useful. So, where is the problem? Ok, let’s switch to the caller’s side and see what the signature looks like if no source code or Javadocs are available.

emailSender.sendEmail(String arg1, InternetAddress arg2, InternetAddress arg3, InternetAddress arg4, 
    String arg5, String arg6, List<EmailAttachment> arg7);

Most times when we write code, especially when we have to fix bugs, we have to read code. For every single written line of code, there may be several hundred of lines of code to read. So what do you think about the following code snippet on the caller’s side? Isn’t it pretty hard to find out where the differences between content1 and content2 is and what “test” stands for? You have to look into the Javadocs or the source of the sender. This is a waste of time and very annoying.

emailSender.sendEmail("test", adressTo, addressFrom, addressReplyTo, content1, content2, attachments);

As you can see, the signature is really hard to read. But how can we solve this?

FIRST ATTEMPT: USING A PARAMETER OBJECT

A parameter object is an encapsulation of single but related parameters to a container object. Refactoring the original source may lead to the following:

The signature:

public void sendEmail(Email email);

and the parameter object:

public class Email {
 
    private String subject;
    private InternetAddress addressTo;
    private InternetAddress addressFrom;
    private InternetAddress addressReplyTo;
    private String htmlContent;
    private String plainContent;
    private List<EmailAttachment> images;
 
    public void setSubject(String subject) {
        this.subject = subject;
    }
 
    public String getSubject() {
        return subject;
    }
 
    public void setAddressTo(InternetAddress addressTo) {
        this.addressTo = addressTo;
    }
 
    public void setAddressFrom(InternetAddress addressFrom) {
        this.addressFrom = addressFrom;
    }
 
    public void setAddressReplyTo(InternetAddress addressReplyTo) {
        this.addressReplyTo = addressReplyTo;
    }
 
    public void setHtmlContent(String htmlContent) {
        this.htmlContent = htmlContent;
    }
 
    public void setPlainContent(String plainContent) {
        this.plainContent = plainContent;
    }
 
    public void setImages(List<EmailAttachment> images) {
        this.images = images;
    }
 
    public InternetAddress getAddressTo() {
        return addressTo;
    }
 
    public InternetAddress getAddressFrom() {
        return addressFrom;
    }
 
    public InternetAddress getAddressReplyTo() {
        return addressReplyTo;
    }
 
    public String getHtmlContent() {
        return htmlContent;
    }
 
    public String getPlainContent() {
        return plainContent;
    }
 
    public List<EmailAttachment> getImages() {
        return images;
    }
}

and now the caller

public void sendingEmailWithParameterObjectSender() {
        EmailSenderWithParameterObject emailSender = new EmailSenderWithParameterObject();
        Email email = new Email();
        email.setSubject("subject");
        email.setAddressFrom(fromAddress);
        email.setAddressTo(toAddress);
        email.setAddressReplyTo(replyAddress);
        email.setHtmlContent(htmlContent);
        email.setPlainContent(plainContent);
        email.setImages(images);
        emailSender.sendEmail(email);
    }

The call to send the mail is much more readable. But there are still issues on the caller’s side. You might forget to set an attribute, toAddress for instance. The readability for the call itself is improved, but with all the setter calls, the code doesn’t really look better. Let’s see if there are ways to improve this as well.

SECOND ATTEMPT: TEST-DRIVEN DEVELOPMENT

Imagine instead of writing the sender first, write a caller. This can be easily done with an xUnit-Test. But why should you do this? In this scenario it gives you the opportunity to define the mail sender interface the way you need it as a caller. The result could be quite similar to the refactoring using the parameter object as it feels much more natural to pass simply one object to the (not yet existing) mailSender than to use 7 parameters in a cryptic order.

THIRD ATTEMPT: PARAMETER OBJECT, TEST-DRIVEN DEVELOPMENT AND FLUENT INTERFACES

We now start again by writing the test code before the production code. But this time, we try to avoid using tons of setters of a parameter object as it is not very readable. But what is better?

Let’s remind ourselves of the goal: We want to send an email with SenderAddress, replyAddress, attachments, plain content, HTML content and a subject to one or more recipients. So why don’t we use this simply in our code?

@Test
public void shouldSendEmail() throws IOException {
    emailSender.prepareMail()
        .withSenderAddress(senderAddress)
        .withReplyAddress(replyAdress)
        .withAttachments()
        .withHtmlContent(htmlContent)
        .withPlainContent(plainContent)
        .withSubject(subject)
        .addRecipientAddress(firstRecipientAddress)
        .addRecipientAddress(secondRecipientAddress)
        .send();
    }

We need an additional interface that defines the method which replaces the setters. To avoid trying to send an email without a recipient, we define one more interface which defines the send() method. It will be given back by the addRecipientAddress() method.

Let’s look at the code. First the EmailBuilder, which replaces the interface to the ParameterObject:

public interface EmailBuilder {
    EmailBuilder withSenderAddress(InternetAddress senderAddress);
    EmailBuilder withReplyAddress(InternetAddress replyAdress);
    EmailBuilder withAttachments(List<EmailAttachment> attachments);
    EmailBuilder withHtmlContent(String htmlContent);
    EmailBuilder withPlainContent(String plainContent);
    EmailBuilder withSubject(String subject);
    EmailSender addRecipientAddress(InternetAddress firstRecipientAddress);
}

…and now the EmailSender which extends the EmailBuilder to have the possibility to add more than one recipient and ignore any specific order for setting the parameters for the email:

public interface EmailSender extends EmailBuilder {
    void send();
}

…and finally the implementation of the fluent interface:

/**
 * Builder that creates an Email object serving as fluent interface.
 */
public class EmailSendingEnabledBuilder implements EmailSender {
 
    private final EmailSenderTask emailSenderTask;
    private final Email email = new Email();
 
    public EmailSendingEnabledBuilder(EmailSenderTask emailSenderTask) {
        this.emailSenderTask = emailSenderTask;
    }
 
    @Override
    public EmailBuilder withSenderAddress(InternetAddress senderAddress) {
        email.setAddressFrom(senderAddress);
        return this;
    }
 
    @Override
    public EmailBuilder withReplyAddress(InternetAddress replyAdress) {
        email.setAddressReplyTo(replyAdress);
        return this;
    }
 
    @Override
    public EmailBuilder withAttachments(List<EmailAttachment> attachments) {
        email.setImages(attachments);
        return this;
    }
 
    @Override
    public EmailBuilder withHtmlContent(String htmlContent) {
        email.setHtmlContent(htmlContent);
        return this;
    }
 
    @Override
    public EmailBuilder withPlainContent(String plainContent) {
        email.setPlainContent(plainContent);
        return this;
    }
 
    @Override
    public EmailBuilder withSubject(String subject) {
        email.setSubject(subject);
        return this;
    }
 
    @Override
    public EmailSender addRecipientAddress(InternetAddress firstRecipientAddress) {
        return this;
    }
 
    public void send() {
        emailSenderTask.send(email);
    }
}

For sure, there is now more code (in lines) to be written — although there are Fluent interface builder plugins — at least for Eclipse). But the code is much more readable and xUnit-Tests are much easier to write. The combination of Test-Driven Development and Fluent Interfaces improves the quality of the production code as well as that of the testing code.

Further reading:
Test Driven Development
Fluent Interface
PMD Ruleset
Clean Code

This blog post has also been published on the eBay technology blog (DE)

Leave a Comment