Thursday, October 17, 2013

Too Many Parameters in Java Methods, Part 5: Method Naming

In my previous post (Part 4 of my series on dealing with too many parameters in Java methods), I looked at method overloading as one approach to providing clients with versions of methods or constructors requiring fewer parameters. I described some disadvantages of that approach and suggested that breaking loose from method overloading to use differently named methods could at least partially address some of these disadvantages. In this post, I look more deeply at how careful naming of methods (including construction methods) can be used to remove lengthy parameters lists and avoid some of the issues with method overloading.

From the perspective of reducing the number of parameters required in method and constructor calls, some of the most significant issues associated with method overloading surround a limitation of how many times the same method name can be overloaded for a large set of parameters, especially if some of the parameters share the same data type. For example, if I have a class with three String attributes and want to write three constructors to accept only one of these attributes each, I really cannot do this with method overloading. If I tried, the constructor accepting a single String would have to be used for one of the three attributes and only a Javadoc comment could explain which of the three attributes that single-argument constructor sets. Breaking loose from the limitation of using the same name for all methods and construction methods allows one to be more expressive in code regarding the expected and assumed parameters.

The following code listing contains some examples of various methods for asking an independent class (not the Person class) to provide an instance of Person (a class referenced in my earlier posts on too many Java parameters). These methods have lengthy names that describe much about what is expected in the parameters. This means that less needs to be described in Javadoc comments, methods' invocations are more readable to clients, and there are more possibilities and permutations of parameters that can be supported than by method overloading.

Examples of Instance Methods Named to Describe What They Do
   public Person createPersonWithFirstAndLastNameOnly(final String firstName, final String lastName)
   {
      // implementation goes here ...
   }

   public Person createEmployedHomeOwningFemale(final FullName name, final Address address)
   {
      // implementation goes here ...
   }

   public Person createEmployedHomeOwningMale(final FullName name, final Address address)
   {
      // implementation goes here ...
   }

   public Person createUnemployedHomeOwningFemale(final FullName name, final Address address)
   {
      // implementation goes here ...
   }
   
   public Person createEmployedRentingMale(final FullName name, final Address address)
   {
      // implementation goes here ...
   }

The longer method names shown in the code above are descriptive and provide the client a good head start in knowing what parameters to provide. Of course, I could have written many more methods like those above to cover the various permutations of parameters, but the small set I did list cover the point. Notice that I also used parameters objects (FullName and Address defined in my post on parameters objects) in these code examples to reduce the number of parameters further.

My code examples above demonstrated providing different and descriptive names for instance methods to imply what parameters to pass and to even in some cases imply which parameters do not need to be provided because they are implied in the method name. Those new to Java might think this approach cannot be used with object instantiation/construction because Java class constructors must be named with the same name as the class. This implies that constructors can only be overloaded based on method signature. Fortunately, Josh Bloch addressed this in the very first item of both editions of Effective Java. As Bloch describes there, we can employ static initialization factory methods to provide instances of our classes. One of the benefits Bloch cites in this Item #1 is the ability to name these methods as we see fit.

The next code listing demonstrates the power of these static initialization factories. When I implement these, I like to implement one or a very small number of private constructors (cannot be instantiated by external classes) that are called only by my static initialization factories. This allows me to leave the constructor's method signature as potentially a little less than desirable because only my class must use it and the static initialization factories others use are easier to use and hide the ugliness of the many parameters constructor. More specifically, if the constructor takes null for optional parameters, I can write various static initialization factories so that my clients don't need to pass null, but my factory methods can instead pass the null to the constructor. In short, static initialization factory methods allow me to present a cleaner, more pleasant interface to clients and hide ugliness in the internals of my class. I cannot as easily provide this with multiple constructors directly because of the inability to name them anything with explanatory detail. Another advantage of these static initialization methods is that I can have them accept "raw" types if desired and build these into custom types and parameters objects internally. All of these cases are shown in the next code listing.

Static Initialization Factories Demonstrated
   /**
    * Parameterized constructor can be private because only my internal builder
    * needs to call me to provide an instance to clients.
    * 
    * @param newName Name of this person.
    * @param newAddress Address of this person.
    * @param newGender Gender of this person.
    * @param newEmployment Employment status of this person.
    * @param newHomeOwner Home ownership status of this person.
    */
   private Person(
      final FullName newName, final Address newAddress,
      final Gender newGender, final EmploymentStatus newEmployment,
      final HomeownerStatus newHomeOwner)
   {
      this.name = newName;
      this.address = newAddress;
      this.gender = newGender;
      this.employment = newEmployment;
      this.homeOwnerStatus = newHomeOwner;
   }
 
   public static Person createInstanceWithNameAndAddressOnly(
      final FullName newName, final Address newAddress)
   {
      return new Person(newName, newAddress, null, null, null);
   }
   
   public static Person createEmployedHomeOwningFemale(
      final FullName newName, final Address newAddress)
   {
      return new Person(
         newName, newAddress, Gender.FEMALE, EmploymentStatus.EMPLOYED, HomeownerStatus.HOME_OWNER);
   }

   public static Person createEmployedHomeowningMale(
      final FullName newName, final Address newAddress)
   {
      return new Person(
          newName, newAddress, Gender.MALE, EmploymentStatus.EMPLOYED, HomeownerStatus.HOME_OWNER);
   }

   public static Person createUnemployedMaleRenter(
      final FullName newName, final Address newAddress)
   {
      return new Person(
         newName, newAddress, Gender.MALE, EmploymentStatus.NOT_EMPLOYED, HomeownerStatus.RENTER);
   }

   public static Person createPersonWithFirstNameLastNameAndAddress(
      final Name newFirstName, final Name newLastName, final Address newAddress)
   {
      return new Person(
         new FullName.FullNameBuilder(newLastName, newFirstName).createFullName(),
         newAddress, null, null, null);
   }

   public static Person createPersonWithFirstNameLastNameAndAddress(
      final String newFirstName, final String newLastName, final Address newAddress)
   {
      return new Person(
         new FullName.FullNameBuilder(new Name(newLastName), new Name(newFirstName)).createFullName(),
         newAddress, null, null, null);
   }

As the above examples indicate, the clients of these methods can use very readable methods and don't need to worry too much about providing numerous parameters. The last two methods in the previous code listing are an example of combining method overloading with static initialization factory methods.

I want to focus on one additional example of a specific case in which meaningful method naming can remove the need for a parameter. It is very commonplace in Java code to have methods such as the Window.setVisible(boolean). Methods constructed in this fashion typically set something (visibility in this case) one way or the other. However, the need to pass a flag can be removed by simply writing two methods instead of one with each method clearly stating what it does. For example, Window.setVisible(boolean) could be replaced with Window.setVisible() and Window.setNotVisible() (or Window.setInvisible()). Robert Martin writes that "flag arguments are ugly" in Clean Code and warns against passing booleans to methods, which he labels "a truly terrible practice" (p. 41).

Benefits and Advantages

Using appropriately named methods that include information about expected and implied parameters in those methods' names bring some advantages over simple method/constructor overloading. Because the methods' names can be customized for what each method expects and assumes, the intent is clearer to the invoking code. As the examples above show, the methods can imply what parameters need not be explicitly provided because they are assumed as part of that method (and that intent is communicated via the methods' names rather than simply via Javadoc).

I did not focus on it explicitly here, but another advantage of carefully chosen method names over simple method overloading is the ability to include units or other context information in the method name. For example, instead of having setLength() methods that accept int and double, I could provide methods such as setWholeLengthInMeters(int) and setFractionalLengthInFeet(double).

Costs and Disadvantages

Using differently named instance methods and static initialization factory methods definitely provides some advantages over method overloading, but unfortunately still bears some of the disadvantages of method overloading from a reduction in parameters perspective. One disadvantage that differently named methods share with overloaded methods is the potential to have to write a lot of methods to support the various combinations and permutations of parameters that might be used. If a method was written only for every combination of gender, homeowner status, and employment status in the examples above, there would need to be eight methods (2 to the 3rd power). If any individual parameter can have more than 2 possibilities, then the number of different combinations of named methods just to handle the different possibilities for that one increase. Of course, parameters without finite possibilities cannot have a method written for every possible value and so will have to be passed in rather than assumed.

Although the highly descriptive method names are easy to read, the issue of having potentially many of them can reduce overall readability as the client must wade through a long list of methods when calling the class. Also, some people may not like the long method names and their taking of significant space on the screen. I personally don't mind the long names because I think the readability they provide is worth the cost of additional text on the screen. IDEs and code completion mean very few of us type these names out anymore and large and multiple monitors for developers make the issue of long method names less bothersome.

Conclusion

Methods' names can be used to communicate significant meaning to clients. In the case of our effort to clarify the parameters to be passed to a particular method (including reducing the number of parameters), naming methods appropriately can imply default settings so that parameters do not need to be provided and can explain order and other characteristics of other parameters that do need to be applied.

2 comments:

Unknown said...

It confirms the idea I have had about method names. They should be readable and easy to understand what they do.

@DustinMarx said...

I added one paragraph to this blog post since its original publication. The paragraph on using method naming to avoid flag arguments was added.