Monday, October 7, 2013

Too Many Parameters in Java Methods, Part 1: Custom Types

I consider lengthy parameters lists in constructors and methods to be another "red flag" in Java development that may not necessarily be "wrong" in terms of logic and functionality, but often hint at the high possibility of current or future errors. In a small series of posts, I look at some of the approaches that can be used to reduce the numbers of parameters to methods or constructors or to at least make lengthy lists of parameters more readable and less error-prone. Each approach has its own set of advantages and disadvantages. This post begins that series with focus on improving the readability and safety of a long method/constructor parameter list via the use of custom types.

Lengthy lists of parameters to methods and constructors have several drawbacks. A large number of parameters can be tedious and difficult for calling code to use. Long lists of parameters can also lead to inadvertent switching of parameters in invocations. These bugs can be difficult to find in certain cases. Fortunately, most of don't have to deal with another disadvantage of lengthy parameter lists: the JVM limiting the number of parameters to a method via compile-time error.

One approach that does not reduce the number of parameters to a method or constructor, but that does make these long parameter lists more readable and less likely to be provided in the wrong order, is the use of custom types. These custom types might be implemented as Data Transfer Objects (DTOs), as JavaBeans, as Value Objects, as Reference Objects, or any other custom type (in Java, typically a class or enum).

Here is a contrived example of a method that accepts several parameters, many of type String and many of type boolean.

   /**
    * Instantiate a Person object.
    * 
    * @param lastName
    * @param firstName
    * @param middleName
    * @param salutation
    * @param suffix
    * @param streetAddress
    * @param city
    * @param state
    * @param isFemale
    * @param isEmployed
    * @param isHomeOwner
    * @return 
    */
   public Person createPerson(
      final String lastName,
      final String firstName,
      final String middleName,
      final String salutation,
      final String suffix,
      final String streetAddress,
      final String city,
      final String state,
      final boolean isFemale,
      final boolean isEmployed,
      final boolean isHomeOwner)
   {
      // implementation goes here
   }

It is easy to switch these accidentally and pass them in the wrong order. Although I generally would prefer to reduce the parameters, some improvement can be made by varying the types in the parameter list. The next code listings show some examples of these custom types that can be used for names, addresses, city, and the boolean parameters.

The three name parameters can each be changed to a custom type of Name rather than String. That Name type is defined next.

Name.java
package dustin.examples;

/**
 * Name representation.
 * 
 * @author Dustin
 */
public final class Name
{
   private final String name;

   public Name(final String newName)
   {
      this.name = newName;
   }

   public String getName()
   {
      return this.name;
   }

   @Override
   public String toString()
   {
      return this.name;
   }
}

The salutation and suffix String types can also be replaced with custom types as shown in the next two code listings.

Salutation.java
package dustin.examples;

/**
 * Salutations for individuals' names.
 * 
 * @author Dustin
 */
public enum Salutation
{
   DR,
   MADAM,
   MISS,
   MR,
   MRS,
   MS,
   SIR
}
Suffix.java
package dustin.examples;

/**
 * Suffix representation.
 * 
 * @author Dustin
 */
public enum Suffix
{
   III,
   IV,
   JR,
   SR
}

The other parameters could also be replaced by custom types. The next code listings show custom enums that can replace the booleans to improve readability.

Gender.java
package dustin.examples;

/**
 * Gender representation.
 * 
 * @author Dustin
 */
public enum Gender
{
   FEMALE,
   MALE
}
EmploymentStatus.java
package dustin.examples;

/**
 * Representation of employment status.
 * 
 * @author Dustin
 */
public enum EmploymentStatus
{
   EMPLOYED,
   NOT_EMPLOYED
}
HomeOwnerStatus.java
package dustin.examples;

/**
 * Representation of homeowner status.
 * 
 * @author Dustin
 */
public enum HomeownerStatus
{
   HOME_OWNER,
   RENTER
}

The address information for this person can also be passed in using custom types defined as shown in the next code listings.

StreetAddress.java
package dustin.examples;

/**
 * Street Address representation.
 * 
 * @author Dustin
 */
public final class StreetAddress
{
   private final String address;

   public StreetAddress(final String newStreetAddress)
   {
      this.address = newStreetAddress;
   }

   public String getAddress()
   {
      return this.address;
   }

   @Override
   public String toString()
   {
      return this.address;
   }
}
City.java
package dustin.examples;

/**
 * City representation.
 * 
 * @author Dustin
 */
public final class City
{
   private final String cityName;

   public City(final String newCityName)
   {
      this.cityName = newCityName;
   }

   public String getCityName()
   {
      return this.cityName;
   }

   @Override
   public String toString()
   {
      return this.cityName;
   }
}
State.java
package dustin.examples;

/**
 * Simple representation of a state in the United States.
 * 
 * @author Dustin
 */
public enum State
{
   AK,
   AL,
   AR,
   AZ,
   CA,
   CO,
   CT,
   DE,
   FL,
   GA,
   HI,
   IA,
   ID,
   IL,
   IN,
   KS,
   KY,
   LA,
   MA,
   MD,
   ME,
   MI,
   MN,
   MO,
   MS,
   MT,
   NC,
   ND,
   NE,
   NH,
   NJ,
   NM,
   NV,
   NY,
   OH,
   OK,
   OR,
   PA,
   RI,
   SC,
   SD,
   TN,
   TX,
   UT,
   VA,
   VT,
   WA,
   WI,
   WV,
   WY
}

With these custom types implemented, the signature of our original method becomes much more readable and less likely to have parameters accidentally provided in the wrong order. This is shown in the next code listing.

   public Person createPerson(
      final Name lastName,
      final Name firstName,
      final Name middleName,
      final Salutation salutation,
      final Suffix suffix,
      final StreetAddress address,
      final City city,
      final State state,
      final Gender gender,
      final EmploymentStatus employment,
      final HomeownerStatus homeowner)
   {
      // implementation goes here
   }

In the code listing above, the compiler will now aid the developer by not allowing most of the previous String or boolean parameters to be accidentally mixed in order. The three names are still a potential issue as the caller could provide them out of order, but I could have written specific types (classes) for FirstName, LastName, and MiddleName if I was concerned about that. My preference instead is to use a new class that represents a full name and has all three of those names as its attributes, but that approach will be the topic of a future post on dealing with too many parameters to a Java method.

Benefits and Advantages

The advantages of writing and using custom types when dealing with multiple parameters on a given method include readability for the code maintainer and for the developer using the API. Having multiple parameters of the same type not only makes it easy for the developer to mix up their order, but reduces the ability of the IDE to match the appropriate suggestion with the parameter when using code completion. Proper naming can help the IDE, but nothing is as helpful to an IDE as static compile-time checking that can be accomplished with these custom types. In general, I prefer to move as much automatic checking as I can from runtime to compile time and having these statically defined custom types rather than generic types accomplishes this.

Furthermore, the existence of these custom types makes it easier to add more details in the future. For example, I might add the full state name or other details about the states to that enum in the future without changing the interface. I could not have done that with a simple String representing state. An additional implied benefit of this is that even with these simple custom types I have more flexibility to change implementation without changing the interface. The custom classes (but not the enums) could be extended (the primitives and built-in String typically cannot) and I have flexibility to add other implementation details to the custom types that I cannot add to the built-in types.

Costs and Disadvantages

One of the most frequently cited disadvantages of the custom type approach is the overhead of extra instantiations and use of memory. For example, the Name class requires instantiation of the Name class itself AND its encapsulated String. However, it is my opinion that this argument is often made more from the perspective of premature optimization than a legitimate measured performance issue. There are situations in which the extra instantiations are too costly to justify the enhanced readability and compile-time checking, but many (perhaps most) situations can afford the extra instantiations with negligible observable impact. It is especially difficult for me to believe that use of a custom enum instead of a String or boolean will introduce a performance issue in the majority of cases.

Another argued downside of employing custom types rather than built-in types is the extra effort to write and test these custom types. However, as my examples in this post have shown, there are typically very simple classes or enums and are not difficult to write or test. With a good IDE and a good scripting language like Groovy, these are particularly easy to write and test, often automatically.

Conclusion

I like the use of custom types to improve readability and to shift more of the burden of parameter type checking onto the compiler. The biggest reason I don't use this approach by itself more in improving the readability of methods and constructors with very long parameter lists is that it doesn't, by itself, reduce the number of parameters. It makes the long list more readable and safer to use, but callers still must write clunky client-side code to invoke the method or constructor. Because of this, I often use techniques other than or in addition to custom types when improving a method accepting a long list of parameters. These other techniques will be explored in future posts.

3 comments:

JP said...

Why not having the type name encapsulate the first, middle, last name and salutation a suffix?

The same applies to address, a type Address can encapsulate street, state, country, zip, and other attributes that pertain to the address.

@DustinMarx said...

JP,

Thanks for the feedback. Just published Part 2 talks about that approach.

Dustin

@DustinMarx said...

The blog post Microtyping in Java revisited and feedback comments on it at Reddit provide interesting perspectives related to this approach.