Thứ Sáu, 18 tháng 10, 2013

Too Many Parameters in Java Methods, Part 6: Method Returns

In the current series of posts I am writing on reducing the number of parameters required to call Java methods and constructors, I have focused so far on approaches that directly affect the parameters themselves (custom types, parameters objects, builder pattern, method overloading, and method naming). Given this, it might seem surprising for me to devote a post in this series to how Java methods provide return values. However, methods' return values can impact the parameters the methods accept when developers choose to provide "return" values by setting or changing provided parameters rather than or in addition to more traditional method return mechanisms.

The "traditional ways" that a non-constructor method returns a value can both be specified in the method signature. The most commonly recognized approach for returning a value from a Java method is via its declared return type. This often works well, but one of frustrations that most commonly occurs is being allowed to return only one value from a Java method..

Java's exception handling mechanism is also another approach for retaining a "result" of a method to callers. Checked exceptions, in particular, are advertised to the caller via the throws clause. In fact, Jim Waldo, in his book Java: The Good Parts, states that it is easier to understand Java exceptions when one thinks of Java exceptions as another type of method return limited to being a Throwable type.

Although the method's return type and thrown exceptions are intended as the primary approaches for methods to return information to callers, it is sometimes tempting to return data or state via the parameters passed into the method. When a method needs to return more than one piece of information, the single-value returns of Java methods can seem limiting. Although exceptions provide another way to communicate back to the caller, it seems almost universally agreed that exceptions should only be used for reporting exceptional situations and not for reporting "normal" data or used in control flow. Given that only one object or primitive can be returned from a method and that exceptions only allow returning of a Throwable and should only be used to report exceptional situations, it becomes increasingly attractive for the Java developer to hijack parameters as an alternate route for returning data to the caller.

The technique that a developer can use to apply method parameters as carriers for return data is to accept parameters that are mutable and to mutate the passed-in objects' state. These mutable objects can have their contents changed by the method and then the caller can access the object it provided to determine its new state settings that have been applied by the called method. Although this can be done with any mutable object, collections seem particularly attractive to the developer trying to pass values back to the caller via parameters.

There are some disadvantages to passing state back to the called via the provided parameters. This approach often violates the principle of least astonishment as most Java developers probably expect parameters to be INcoming rather than OUTgoing (and Java doesn't provide any code support to specify the difference). Bob Martin puts it this way in his book Clean Code, "In general, output arguments should be avoided." Another disadvantage of using arguments as a means for a method to provide state or output to the caller is that this adds to the clutter of arguments passed to a method. With this in mind, the remainder of this post focuses on alternatives to returning multiple values via passed-in parameters.

Although Java methods can only return a single object or primitive, this is really not much of a limitation when one considers that an object can be just about anything we want it to be. There are several approaches that I've seen but don't recommend. One of these is returning an array or collection of Object instances with each Object being a disparate and distinct and often unrelated "thing." For example, the method might return three values as three elements of an array or collection. A variation of this approach is to use a pair tuple or n-sized tuple to return multiple associated values. One other variation on this approach is to return a Java Map that maps arbitrary keys to their associated value. As with the other solutions, this approach places undue burden on the client to know what those keys are and to access the map values through those keys.

The next code listing contains several of these less attractive approaches for returning multiple values without hijacking the method parameters to return multiple values.

Returning Multiple Values via Generic Data Structures

// ===============================================================
// NOTE: These examples are intended solely to illustrate a point
// and are NOT recommended for production code.
// ===============================================================

/**
* Provide movie information.
*
* @return Movie information in form of an array where details are mapped to
* elements with the following indexes in the array:
* 0 : Movie Title
* 1 : Year Released
* 2 : Director
* 3 : Rating
*/
public Object[] getMovieInformation()
{
final Object[] movieDetails =
{"World War Z", 2013, "Marc Forster", "PG-13"};
return movieDetails;
}

/**
* Provide movie information.
*
* @return Movie information in form of a List where details are provided
* in this order: Movie Title, Year Released, Director, Rating.
*/
public List<Object> getMovieDetails()
{
return Arrays.<Object>asList("Ender's Game", 2013, "Gavin Hood", "PG-13");
}

/**
* Provide movie information.
*
* @return Movie information in Map form. Characteristics of the movie can
* be acquired by looking in the map for these key elements: "Title", "Year",
* "Director", and "Rating"./
*/
public Map<String, Object> getMovieDetailsMap()
{
final HashMap<String, Object> map = new HashMap();
map.put("Title", "Despicable Me 2");
map.put("Year", 2013);
map.put("Director", "Pierre Coffin and Chris Renaud");
map.put("Rating", "PG");
return map;
}

The approaches shown above do meet the intent of not passing data back to the caller via the invoked methods' parameters, but there is still unnecessary burden placed on the caller to know intimate details of the returned data structure. It's nice to reduce the number of parameters to the method and not violate the principle of least surprise, but it's not so nice to require the client to know the intricacies of a complex data structure.

I prefer to write custom objects for my returns when I need to return more than one value. It's a bit more work than using an array, collection, or tuple structure, but the very small amount of extra work (typically a few minutes with modern Java IDEs) pays off with readability and fluency that is not available with these more generic approaches. Rather than having to explain with Javadoc or require users of my code to read my code carefully to know which parameters are provided in which order in the array or collection or which value is which in the tuple, my custom return objects can have methods defined on them that tell the client exactly what they are providing.

The code snippets that follow illustrate a simple Movie class largely generated by NetBeans that can be used as the return type along with the code that could return an instance of that class rather than a more generic and less readable data structure.

Movie.java

package dustin.examples;

import java.util.Objects;

/**
* Simple Movie class to demonstrate how easy it is to provide multiple values
* in a single Java method return and provide readability to the client.
*
* @author Dustin
*/
public class Movie
{
private final String movieTitle;
private final int yearReleased;
private final String movieDirectorName;
private final String movieRating;

public Movie(String movieTitle, int yearReleased, String movieDirectorName, String movieRating)
{
this.movieTitle = movieTitle;
this.yearReleased = yearReleased;
this.movieDirectorName = movieDirectorName;
this.movieRating = movieRating;
}

public String getMovieTitle()
{
return movieTitle;
}

public int getYearReleased()
{
return yearReleased;
}

public String getMovieDirectorName()
{
return movieDirectorName;
}

public String getMovieRating()
{
return movieRating;
}

@Override
public int hashCode()
{
int hash = 3;
hash = 89 * hash + Objects.hashCode(this.movieTitle);
hash = 89 * hash + this.yearReleased;
hash = 89 * hash + Objects.hashCode(this.movieDirectorName);
hash = 89 * hash + Objects.hashCode(this.movieRating);
return hash;
}

@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final Movie other = (Movie) obj;
if (!Objects.equals(this.movieTitle, other.movieTitle))
{
return false;
}
if (this.yearReleased != other.yearReleased)
{
return false;
}
if (!Objects.equals(this.movieDirectorName, other.movieDirectorName))
{
return false;
}
if (!Objects.equals(this.movieRating, other.movieRating))
{
return false;
}
return true;
}

@Override
public String toString()
{
return "Movie{" + "movieTitle=" + movieTitle + ", yearReleased=" + yearReleased + ", movieDirectorName=" + movieDirectorName + ", movieRating=" + movieRating + '}';
}
}
Returning Multiple Details in Single Object

/**
* Provide movie information.
*
* @return Movie information.
*/
public Movie getMovieInfo()
{
return new Movie("Oblivion", 2013, "Joseph Kosinski", "PG-13");
}

The simple writing of the Movie class took me about 5 minutes. I used the NetBeans class creation wizard to select the class name and package and then I typed in the four attributes of the class. From there, I simply used NetBeans's "Insert Code" mechanism to insert "get" accessor methods along with overridden toString(), hashCode(), and equals(Object) methods. If I didn't think I needed some of that, I could keep the class simpler, but it really is easy to create as is. Now, I have a much more usable return type and this is reflected by the code that uses the class. It doesn't need nearly as much Javadoc comments on the return type because that type speaks for itself and advertises its content with its "get" methods. I feel that the small amount of additional effort to create these simple classes for returning multiple values pays off with huge dividends when compared to alternatives such as returning state via method parameters or using more generic and harder to use return data structures.

It is not too surprising that a custom type to hold the multiple values to be returned to a caller is an attractive solution. After all, this is conceptually very similar to the concepts I blogged about previously related to using custom types and parameters objects for passing in multiple related parameters rather than passing them all in individually. Java is an object-oriented language and so it surprises me when I don't see objects used more often in Java code for organizing parameters AND return values in a nice package.

Benefits and Advantages

The advantages of using custom parameter objects to represent and encapsulate multiple return values are obvious. Parameters to the method can remain "input" parameters because all output information (except for error information communicated via the exception mechanism) can be provided in the custom object returned by the method. This is a cleaner approach than using generic arrays, collections, maps, tuples, or other generic data structures because all of those alternative approaches shift development effort onto all potential clients.

Costs and Disadvantages

I see very little downside to writing custom types with multiple values to be used as return types from Java methods. Perhaps the most often claimed cost is the price of writing and testing these classes, but that cost is pretty small because these classes tend to be simple and because modern IDEs do most of the work for us. Because the IDEs do it automatically, the code is typically correct. The classes are so simple that they are easily readable by code reviewers and they are easy to test.

Stretching to find other costs and disadvantages, one might argue that these classes can bloat code bases and packages, but I don't see that as a strong argument. Although there may be a very small risk of a badly implemented custom class, I think the chance of client code messing up interpretation of a more generic return type is more likely to occur. One other small risk is that developers might throw a lot of unrelated stuff into the same class with the only relationship between those items being the fact that the same method needs to return them. Even with this, the only better alternative I can see would be to modify the code to not need to return multiple values. Returning otherwise unrelated items in a custom object still seems better than returning this set of unrelated data in a generic data structure. In fact, those generic data structures become more unwieldy and difficult to use as the values which they hold become less related.

Conclusion

Custom types and parameters objects help us directly address the problem of too many parameters to a Java method. Fortunately, these custom types and return values objects can also help us indirectly reduce our number of required parameters by allowing us to return multiple values through the custom types and return values objects rather than needing to add additional parameters meant only to convey output information back to the caller.

Thứ Năm, 17 tháng 10, 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.

Thứ Ba, 15 tháng 10, 2013

Too Many Parameters in Java Methods, Part 4: Overloading

One of the problems with expecting too many parameters to be passed to a Java method is that it is more difficult for the client of that method to be determine that they are passing the appropriate values in the appropriate order. In previous posts, I have described how custom types, parameters objects, and builders can be used to address this issue. Another way to address this issue, and the subject of this post, is to provide overloaded versions of the same method for the clients to use the one that best fits their needs. As with my earlier posts on the subject of too many method parameters, I will end this post with a brief discussion of the advantages and disadvantages of this approach.

Java supports method overloading, the ability to have different version of the same method differentiated by their method signatures. Note that a different return type as sole difference between two methods is generally not sufficient for overloading.

Method overloading might be applied for a number of reasons. One objective for overloading methods might be to support the same functionality on different types (especially if generics cannot be used to allow the method to support different types or if the methods were written before generics were available). Examples of method overloading with this intent in mind include String.valueOf(boolean), String.valueOf(char), String.valueOf(double), String.valueOf(long), String.valueOf(Object), and a few more versions of String.valueOf overloaded on a few additional types.

Another reason one might choose to overload methods is so that a client can call the appropriate version of the method for supplying just the necessary parameters. This can be done, for example, to remove the need for the client to pass in one or more nulls for parameters that don't apply or are optional. Examples of overloaded methods written to achieve this objective include the Date class constructors such as Date(int, int, int), Date(int, int, int, int, int), and Date(int, int, int, int, int, int, int).

This approach of having many overloaded versions constructors, each accepting a different number of parameters, is known as telescoping constructors and has been labeled an anti-pattern by some. In fact, the downsides of this telescoping constructors approach is one of the drivers for Josh Bloch's focus on the Builder pattern in Item #2 of the Second Edition of Effective Java. Incidentally, the Date class also provides some overloaded constructors intended to accomplish the previously mentioned objective as well, allowing Date to be constructed from a String, for example.

The idea of supplying multiple overloaded methods and constructors to accept a reduced set of required or minimally applicable parameters can be applied to our own classes. The next code listing provides the original method with far too many parameters and then shows some potential overloaded versions of that method that accept a reduced set of parameters. For purposes of this discussion, we are assuming that any parameter not provided in one of the overridden method signatures is optional or not applicable for that particular method call. The comments on the code explain how each method makes certain assumptions to reduce its parameter count.

Example of Parameter with Too Many Methods and Overloaded Versions

/**
* Generates and provides an instance of the Person class. This method
* expects all characteristics of the populated Person instance and so any
* optional or not applicable characteristics must be passed in as null.
*
* @param lastName
* @param firstName
* @param middleName
* @param salutation
* @param suffix
* @param streetAddress
* @param city
* @param state
* @param isFemale
* @param isEmployed
* @param isHomeOwner
* @return A Person object.
*/
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...
}

/**
* Generate and provide an instance of the Person class that has only a first
* and last name and address information. This method does not make any
* assumptions about other characteristics of the instantiated Person, but
* simply leaves those attributes undefined.
*
* @param lastName
* @param firstName
* @param streetAddress
* @param city
* @param state
* @return Instance of Person class with no middle name and without specified
* gender, employment status, or home ownership status.
*/
public Person createPerson(
final String lastName,
final String firstName,
final String streetAddress,
final String city,
final String state)
{
// implementation goes here...
}

/**
* Generate and provide instance of Person class with no middle name and
* with specified home ownership status. All instances of Person returned
* from this method are assumed to be Female and to be Employed, but have no
* address information.
*
* @param lastName
* @param firstName
* @param homeOwnerStatus
* @return Instance of Person with provided first name, provided last name,
* and provided home ownership status, and assumed to be an employed
* female.
*/
public Person createPerson(
final String lastName,
final String firstName,
final boolean homeOwnerStatus)
{
// implementation goes here...
}

The overloaded methods' Javadoc descriptions tell a little about their differing approach. The first method expected all characteristics of the Person instance to be provided and null would need to be provided for parameters that are not applicable (such as if a person does not have a middle name or that middle name is not important for the use in this case). The second overloaded version did not expect all the parameters to be provided and assumed that the parameters it did not expect would remain undefined in the returned Person instance.

The third overloaded method version mostly made assumptions about the characteristics for which it did not provide an explicit parameter. For example, it assumed that the instantiated Person is both female and employed. There is no way with that third approach to instantiate a person who is either male or unemployed. This illustrates a weakness of dealing with too many parameters with simple method overloading (overloading methods with same name based only on number and types of parameters).

I have not shown any constructors of my own in this post, but the same issues and approaches apply as shown for the non-constructor methods above. Likewise, overloaded constructors share the same advantages and disadvantages as overloaded non-constructor methods.

Benefits and Advantages

Method overloading in Java seems easy to understand and is common in several languages including C/C++ and C#. Method overloading is particularly effective when parameters are optional. For example, method overloading that removed the expectation of a middle name being passed in was far more effective in my examples than the method overloading making assumptions about a specific instantiation being an employed female. If the characteristics of middle name, gender, and employment status are all truly optional, then not assuming a value for any of them at all seems better than assuming a specific value for them.

Costs and Disadvantages

Judicious method overloading can be useful, but method overloading must be used carefully. The Defining Methods section of the Classes and Objects lesson of the Learning the Java Language trail warns: "Overloaded methods should be used sparingly, as they can make code much less readable."

Even my simple three examples showed how overloading can quickly become difficult to read. In my examples, the reader or user of this code would either need to read the Javadoc carefully and trust it to be current and accurate or would need to delve into the implementation to see what each overloaded version of the method did. In an IDE, especially if there are numerous overloaded versions of the same method, it can be difficult to see which one applies in a given situation.

My example showed that comments must be used to explain assumptions made by the overloaded methods. As just mentioned, these could be out of date or inaccurate or not even available if the developer did not bother to write them. It would obviously be better to be able to name the methods differently so that the name of the method could give clues about its assumptions rather than relying solely on Javadoc. Using named methods in this way will be the subject of a later post, but using different names for the methods by definition makes them no longer overloaded methods.

My examples showed a particular limitation of using overloaded (same named) methods with multiple parameters of the same type. The third example accepts a single boolean, but only the Javadoc and the name of that parameter could tell me that it applied to home ownership and not to gender or employment status. I cannot provide similar overloaded methods to take the same name information and a boolean indicating something different (such as gender or employment status) because that method would have the same signature as the method where the boolean indicated home ownership status. This again could be remedied through the use of differently named methods that indicated for which boolean condition they applied.

Another way to address this last mentioned limitation would be to use custom types and/or parameters objects and provide various versions of overloaded methods accepting different combinations of those custom types. This is shown with custom types in the next code listing which shows how the method accepting two name Strings can be overloaded by a single third parameter for all three cases when those three cases don't each need to share the same type.

Custom Types Enable Improved Method/Constructor Overloading

public Person createPerson(
final String lastName,
final String firstName,
final HomeownerStatus homeOwnership)
{
// implementation goes here...
}

public Person createPerson(
final String lastName,
final String firstName,
final Gender gender)
{
// implementation goes here...
}

public Person createPerson(
final String lastName,
final String firstName,
final EmploymentStatus employmentStatus)
{
// implementation goes here...
}

One final disadvantage I want to mention related to using method overloading to address the problems associated with too many parameters to a method or constructor is that such an approach can lead to significant maintenance work in the future. Any time an attribute to that class (constructors) or a parameter to a method is added or removed or even changed, multiple constructors and/or methods may need to be individually reviewed and potentially changed.

Conclusion

Overloaded methods do have their place and can be a convenient way to provide more understandable and readable methods and constructors for clients. However, I find this approach to be the "best" approach less often than some of the other approaches already covered (custom types, parameters objects, builders) and even less often than some of the approaches I intend to cover (such as differently and explicitly named versions of the same methods and constructors). Some of the limitations and disadvantages of the method overloading approach can be reduced through the use the method overloading in conjunction with some of these other approaches. For example, the use of custom types and parameters objects can significantly improve one's ability to more narrowly tailor the various versions of an overloaded method or constructor to what is desired.

Thứ Bảy, 12 tháng 10, 2013

Too Many Parameters in Java Methods, Part 3: Builder Pattern

In my two immediately previous posts, I looked at reducing the number of parameters required for a constructor or method invocation via custom types and parameter objects. In this post, I look at use of the builder pattern to reduce the number of parameters required for a constructor with some discussion on how this pattern can even help with non-constructor methods that take too many parameters.

In the Second Edition of Effective Java, Josh Bloch introduces use of the builder pattern in Item #2 for dealing with constructors that require too many parameters. Bloch not only demonstrates how to use the Builder, but explains it advantages over constructors accepting a large number of parameters. I will get to those advantages at the end of this post, but think it's important to point out that Bloch has devoted an entire item in his book to this practice.

To illustrate the advantages of this approach, I'll use the following example Person class. It doesn't have all the methods I would typically add to such a class because I want to focus on its construction.

Person.java (without Builder Pattern)

package dustin.examples;

/**
* Person class used as part of too many parameters demonstration.
*
* @author Dustin
*/
public class Person
{
private final String lastName;
private final String firstName;
private final String middleName;
private final String salutation;
private final String suffix;
private final String streetAddress;
private final String city;
private final String state;
private final boolean isFemale;
private final boolean isEmployed;
private final boolean isHomewOwner;

public Person(
final String newLastName,
final String newFirstName,
final String newMiddleName,
final String newSalutation,
final String newSuffix,
final String newStreetAddress,
final String newCity,
final String newState,
final boolean newIsFemale,
final boolean newIsEmployed,
final boolean newIsHomeOwner)
{
this.lastName = newLastName;
this.firstName = newFirstName;
this.middleName = newMiddleName;
this.salutation = newSalutation;
this.suffix = newSuffix;
this.streetAddress = newStreetAddress;
this.city = newCity;
this.state = newState;
this.isFemale = newIsFemale;
this.isEmployed = newIsEmployed;
this.isHomewOwner = newIsHomeOwner;
}
}

This class's constructor works, but it is difficult for client code to use properly. The Builder pattern can be used to make the constructor easier to use. NetBeans will refactor this for me as I have written about previously. An example of the refactored code is shown next (NetBeans does this by creating all new Builder class).

PersonBuilder.java

package dustin.examples;


public class PersonBuilder
{
private String newLastName;
private String newFirstName;
private String newMiddleName;
private String newSalutation;
private String newSuffix;
private String newStreetAddress;
private String newCity;
private String newState;
private boolean newIsFemale;
private boolean newIsEmployed;
private boolean newIsHomeOwner;

public PersonBuilder()
{
}

public PersonBuilder setNewLastName(String newLastName) {
this.newLastName = newLastName;
return this;
}

public PersonBuilder setNewFirstName(String newFirstName) {
this.newFirstName = newFirstName;
return this;
}

public PersonBuilder setNewMiddleName(String newMiddleName) {
this.newMiddleName = newMiddleName;
return this;
}

public PersonBuilder setNewSalutation(String newSalutation) {
this.newSalutation = newSalutation;
return this;
}

public PersonBuilder setNewSuffix(String newSuffix) {
this.newSuffix = newSuffix;
return this;
}

public PersonBuilder setNewStreetAddress(String newStreetAddress) {
this.newStreetAddress = newStreetAddress;
return this;
}

public PersonBuilder setNewCity(String newCity) {
this.newCity = newCity;
return this;
}

public PersonBuilder setNewState(String newState) {
this.newState = newState;
return this;
}

public PersonBuilder setNewIsFemale(boolean newIsFemale) {
this.newIsFemale = newIsFemale;
return this;
}

public PersonBuilder setNewIsEmployed(boolean newIsEmployed) {
this.newIsEmployed = newIsEmployed;
return this;
}

public PersonBuilder setNewIsHomeOwner(boolean newIsHomeOwner) {
this.newIsHomeOwner = newIsHomeOwner;
return this;
}

public Person createPerson() {
return new Person(newLastName, newFirstName, newMiddleName, newSalutation, newSuffix, newStreetAddress, newCity, newState, newIsFemale, newIsEmployed, newIsHomeOwner);
}

}

I prefer to have my Builder as a nested class inside the class whose object it builds, but the NetBeans automatic generation of a standalone Builder is very easy to use. Another difference between the NetBeans-generated Builder and the Builders I like to write is that my preferred Builder implementations have required fields provided in the Builder's constructor rather than provide a no-arguments constructor. The next code listing shows my Person class from above with a Builder added into it as a nested class.

Person.java with Nested Person.Builder

package dustin.examples;

/**
* Person class used as part of too many parameters demonstration.
*
* @author Dustin
*/
public class Person
{
private final String lastName;
private final String firstName;
private final String middleName;
private final String salutation;
private final String suffix;
private final String streetAddress;
private final String city;
private final String state;
private final boolean isFemale;
private final boolean isEmployed;
private final boolean isHomewOwner;

public Person(
final String newLastName,
final String newFirstName,
final String newMiddleName,
final String newSalutation,
final String newSuffix,
final String newStreetAddress,
final String newCity,
final String newState,
final boolean newIsFemale,
final boolean newIsEmployed,
final boolean newIsHomeOwner)
{
this.lastName = newLastName;
this.firstName = newFirstName;
this.middleName = newMiddleName;
this.salutation = newSalutation;
this.suffix = newSuffix;
this.streetAddress = newStreetAddress;
this.city = newCity;
this.state = newState;
this.isFemale = newIsFemale;
this.isEmployed = newIsEmployed;
this.isHomewOwner = newIsHomeOwner;
}

public static class PersonBuilder
{
private String nestedLastName;
private String nestedFirstName;
private String nestedMiddleName;
private String nestedSalutation;
private String nestedSuffix;
private String nestedStreetAddress;
private String nestedCity;
private String nestedState;
private boolean nestedIsFemale;
private boolean nestedIsEmployed;
private boolean nestedIsHomeOwner;

public PersonBuilder(
final String newFirstName,
final String newCity,
final String newState)
{
this.nestedFirstName = newFirstName;
this.nestedCity = newCity;
this.nestedState = newState;
}

public PersonBuilder lastName(String newLastName)
{
this.nestedLastName = newLastName;
return this;
}

public PersonBuilder firstName(String newFirstName)
{
this.nestedFirstName = newFirstName;
return this;
}

public PersonBuilder middleName(String newMiddleName)
{
this.nestedMiddleName = newMiddleName;
return this;
}

public PersonBuilder salutation(String newSalutation)
{
this.nestedSalutation = newSalutation;
return this;
}

public PersonBuilder suffix(String newSuffix)
{
this.nestedSuffix = newSuffix;
return this;
}

public PersonBuilder streetAddress(String newStreetAddress)
{
this.nestedStreetAddress = newStreetAddress;
return this;
}

public PersonBuilder city(String newCity)
{
this.nestedCity = newCity;
return this;
}

public PersonBuilder state(String newState)
{
this.nestedState = newState;
return this;
}

public PersonBuilder isFemale(boolean newIsFemale)
{
this.nestedIsFemale = newIsFemale;
return this;
}

public PersonBuilder isEmployed(boolean newIsEmployed)
{
this.nestedIsEmployed = newIsEmployed;
return this;
}

public PersonBuilder isHomeOwner(boolean newIsHomeOwner)
{
this.nestedIsHomeOwner = newIsHomeOwner;
return this;
}

public Person createPerson()
{
return new Person(
nestedLastName, nestedFirstName, nestedMiddleName,
nestedSalutation, nestedSuffix,
nestedStreetAddress, nestedCity, nestedState,
nestedIsFemale, nestedIsEmployed, nestedIsHomeOwner);
}
}
}

The Builder can be even nicer when enhanced through use of custom types and parameters objects as outlined in my first two posts on the "too many parameters" problem. This is shown in the next code listing.

Person.java with Nested Builder, Custom Types, and Parameters Object

package dustin.examples;

/**
* Person class used as part of too many parameters demonstration.
*
* @author Dustin
*/
public class Person
{
private final FullName name;
private final Address address;
private final Gender gender;
private final EmploymentStatus employment;
private final HomeownerStatus homeOwnerStatus;

/**
* 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 FullName getName()
{
return this.name;
}

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

public Gender getGender()
{
return this.gender;
}

public EmploymentStatus getEmployment()
{
return this.employment;
}

public HomeownerStatus getHomeOwnerStatus()
{
return this.homeOwnerStatus;
}

/**
* Builder class as outlined in the Second Edition of Joshua Bloch's
* Effective Java that is used to build a {@link Person} instance.
*/
public static class PersonBuilder
{
private FullName nestedName;
private Address nestedAddress;
private Gender nestedGender;
private EmploymentStatus nestedEmploymentStatus;
private HomeownerStatus nestedHomeOwnerStatus;

public PersonBuilder(
final FullName newFullName,
final Address newAddress)
{
this.nestedName = newFullName;
this.nestedAddress = newAddress;
}

public PersonBuilder name(final FullName newName)
{
this.nestedName = newName;
return this;
}

public PersonBuilder address(final Address newAddress)
{
this.nestedAddress = newAddress;
return this;
}

public PersonBuilder gender(final Gender newGender)
{
this.nestedGender = newGender;
return this;
}

public PersonBuilder employment(final EmploymentStatus newEmploymentStatus)
{
this.nestedEmploymentStatus = newEmploymentStatus;
return this;
}

public PersonBuilder homeOwner(final HomeownerStatus newHomeOwnerStatus)
{
this.nestedHomeOwnerStatus = newHomeOwnerStatus;
return this;
}

public Person createPerson()
{
return new Person(
nestedName, nestedAddress, nestedGender,
nestedEmploymentStatus, nestedHomeOwnerStatus);
}
}
}

The last couple of code listings show how a Builder is typically used - to construct an object. Indeed, the item on the builder (Item #2) in Joshua Bloch's Second Edition of Effective Java is in the chapter on creating (and destroying) object. However, the builder can help indirectly with non-constructor methods by allowing an easier way to build parameters objects that are passed to methods.

For example, in the last code listing, the methods have some parameters objects (FullName and Address) passed to them. It can be tedious for clients to have to construct these parameters objects and the builder can be used to make that process less tedious. So, although the builder is used for construction in each case, it indirectly benefits non-constructor methods by allowing for easier use of the parameters objects that reduce a method's argument count.

The new definitions of the FullName and Address classes to be used as parameters objects and using the Builder themselves are shown next.

FullName.java with Builder

package dustin.examples;

/**
* Full name of a person.
*
* @author Dustin
*/
public final class FullName
{
private final Name lastName;
private final Name firstName;
private final Name middleName;
private final Salutation salutation;
private final Suffix suffix;

private FullName(
final Name newLastName,
final Name newFirstName,
final Name newMiddleName,
final Salutation newSalutation,
final Suffix newSuffix)
{
this.lastName = newLastName;
this.firstName = newFirstName;
this.middleName = newMiddleName;
this.salutation = newSalutation;
this.suffix = newSuffix;
}

public Name getLastName()
{
return this.lastName;
}

public Name getFirstName()
{
return this.firstName;
}

public Name getMiddleName()
{
return this.middleName;
}

public Salutation getSalutation()
{
return this.salutation;
}

public Suffix getSuffix()
{
return this.suffix;
}

@Override
public String toString()
{
return this.salutation + " " + this.firstName + " " + this.middleName
+ this.lastName + ", " + this.suffix;
}

public static class FullNameBuilder
{
private final Name nestedLastName;
private final Name nestedFirstName;
private Name nestedMiddleName;
private Salutation nestedSalutation;
private Suffix nestedSuffix;

public FullNameBuilder(
final Name newLastName, final Name newFirstName)
{
this.nestedLastName = newLastName;
this.nestedFirstName = newFirstName;
}

public FullNameBuilder middleName(final Name newMiddleName)
{
this.nestedMiddleName = newMiddleName;
return this;
}

public FullNameBuilder salutation(final Salutation newSalutation)
{
this.nestedSalutation = newSalutation;
return this;
}

public FullNameBuilder suffix(final Suffix newSuffix)
{
this.nestedSuffix = newSuffix;
return this;
}

public FullName createFullName()
{
return new FullName(
nestedLastName, nestedFirstName, nestedMiddleName,
nestedSalutation, nestedSuffix);
}
}
}
Address.java with Builder

package dustin.examples;

/**
* Representation of a United States address.
*
* @author Dustin
*/
public final class Address
{
private final StreetAddress streetAddress;
private final City city;
private final State state;

private Address(final StreetAddress newStreetAddress, final City newCity, final State newState)
{
this.streetAddress = newStreetAddress;
this.city = newCity;
this.state = newState;
}

public StreetAddress getStreetAddress()
{
return this.streetAddress;
}

public City getCity()
{
return this.city;
}

public State getState()
{
return this.state;
}

@Override
public String toString()
{
return this.streetAddress + ", " + this.city + ", " + this.state;
}

public static class AddressBuilder
{
private StreetAddress nestedStreetAddress;
private final City nestedCity;
private final State nestedState;

public AddressBuilder(final City newCity, final State newState)
{
this.nestedCity = newCity;
this.nestedState = newState;
}

public AddressBuilder streetAddress(final StreetAddress newStreetAddress)
{
this.nestedStreetAddress = newStreetAddress;
return this;
}

public Address createAddress()
{
return new Address(nestedStreetAddress, nestedCity, nestedState);
}
}
}

With the above builders included in the classes, a Person instance can be created as shown in the next code listing. A more traditional instantiation of a Person instance is shown after that for comparison.

Two Examples of Client Code Instantiating a Person with Builders

final Person person1 = new Person.PersonBuilder(
new FullName.FullNameBuilder(
new Name("Dynamite"), new Name("Napoleon")).createFullName(),
new Address.AddressBuilder(
new City("Preston"), State.ID).createAddress()).createPerson();

final Person person2 = new Person.PersonBuilder(
new FullName.FullNameBuilder(
new Name("Coltrane"), new Name("Rosco")).middleName(new Name("Purvis")).createFullName(),
new Address.AddressBuilder(
new City("Hazzard"), State.GA).createAddress())
.gender(Gender.MALE).employment(EmploymentStatus.EMPLOYED).createPerson();
Instantiating a Person Without a Builder

final person = new Person("Coltrane", "Rosco", "Purvis", null, "Hazzard", "Georgia", false, true, true);

As the previous code snippets show, the client code for calling a traditional Java constructor is far less readable and far easier to mess up than use of the builder classes. The variety of the same types (strings and booleans) and the necessity to place nulls in the constructor call for optional attributes make provide many ways for this approach to end badly.

Benefits and Advantages

There is a considerable cost to the Builder pattern in that one must essentially double the number of lines of code each attribute and for setting those attributes. This price pays off, however, when the client code benefits greatly in terms of usability and readability. The parameters to the constructor are reduced and are provided in highly readable method calls.

Another advantage of the Builder approach is the ability to acquire an object in a single statement and state without the object in multiple states problem presented by using "set" methods. I am increasingly appreciating the value of immutability in a multi-core world and the Builder pattern is perfectly suited for an immutable class when that class features a large number of attributes. I also like that there is no need to pass in null for optional parameters to the constructor.

The Builder pattern not only makes the code more readable, but makes it even easier to apply an IDE's code completion feature. Further benefits of the Builder pattern when used with constructors are outlined in Item #2 of the Second Edition of Effective Java.

Costs and Disadvantages

As shown and mentioned above, the number of lines of code of a given class must be essentially doubled for "set" methods with the builder approach. Furthermore, although client code is more readable, the client code is also more verbose. I consider the benefit of greater readability worth the cost as the number of arguments increase or as more arguments share the same type or as the number of optional arguments increase.

More lines of code in the class with the builder sometimes mean that developers may forget to add support for a new attribute to the builder when they add that attribute to the main class. To try to help with this, I like to nest my builders inside the class that they build so that it's more obvious to the developer that there is a relevant builder that needs to be similarly updated. Although there is still risk of the developer forgetting to add support for a new attribute to the builder, this is really no different than the risk of forgetting to add a new attribute to a class's toString(), equals(Object), hashCode() or other methods often based on all attributes of a class.

In my implementation of the Builder, I made the client pass required attributes into the builder's constructor rather than via "set" methods. The advantage of this is that the object is always instantiated in a "complete" state rather than sitting in an incomplete state until the developer calls (if ever calls) the appropriate "set" method to set additional fields. This is necessary to enjoy the benefits of immutability. However, a minor disadvantage of that approach is that I don't get the readability advantages of methods named for the field I am setting.

The Builder, as its name suggests, is really only an alternative to constructors and not directly used to reduce the number of non-constructor method parameters. However, the builder can be used in conjunction with parameters objects to reduce the number of non-constructor method arguments. Further arguments against use of the Builder for object construction can be found in a comment on the A dive into the Builder pattern post.

Conclusion

I really like the Builder pattern for constructing objects when I have a lot of parameters, especially if many of these parameters are null and when many of them share the same data type. A developer might feel that the extra code to implement a Builder might not justify its benefits for a small number of parameters, especially if the few parameters are required and of different types. In such cases, it might be considered desirable to use traditional constructors or, if immutability is not desired, use a no-argument constructor and require the client to know to call the necessary "set" methods.

Thứ Tư, 9 tháng 10, 2013

Too Many Parameters in Java Methods, Part 2: Parameters Object

In my previous post, I looked at some of the problems associated with long parameters lists for methods and constructors. In that post, I discussed replacing primitives and built-in types with custom types to improve readability and type safety. That approached made the numerous parameters to a method or constructor more readable, but did nothing to reduce the number of parameters. In this post, I look at use of a Parameter Object to reduce the number of parameters to a method or constructor.

It is generally not a good idea to make "junk drawer" objects that couple unrelated parameters whose only relationship to one another is that they need to be passed to the same method or constructor. However, when related parameters are being passed to a constructor or method as part of a highly cohesive object, the refactoring known as Introduce Parameter Object is a nice solution. This refactoring's usage is described as "group[ing] of parameters that naturally go together." I will demonstrate this refactoring in this post.

To demonstrate the utility of the Introduce Parameter Object refactoring, let's first look at the example from the last post which uses numerous String and boolean parameters in a method call.


/**
* 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
}

As I discussed in the previous post, this approach is tedious for callers, makes it all too easy to pass parameters in the wrong order with little type safety, and can reduce readability of the code. Fortunately, the parameters in this example provide some good opportunities to apply the Introduce Parameter Object refactoring. The "names" parameters (including salutation and suffix) could be included in a single full name class. The address parameters (street address, city, and state) could be in a single address object. The other parameters might not be so easily grouped into a single class with high cohesion.

With the suggested applications of the Introduce Parameter Object refactoring, the previously shown method call is simpler thanks to the reduced number of parameters. This is shown in the next code listing.


public Person createPerson(
final FullName fullName,
final Address address,
final boolean isFemale,
final boolean isEmployed,
final boolean isHomeOwner)
{
return new Person();
}

The above example now only has five parameters and is more readable and easier to use by clients. It is also more safe from a typing perspective as it is nearly impossible to confuse strings of names with strings of address in this case. Unfortunately, the three boolean parameters remain a source of potential confusion and cloud readability a bit. The next code listings show potential implementations of the FullName and Address classes.

FullName.java (Simple)

package dustin.examples;

/**
* Full name of a person.
*
* @author Dustin
*/
public final class FullName
{
private final String lastName;
private final String firstName;
private final String middleName;
private final String salutation;
private final String suffix;

public FullName(
final String newLastName,
final String newFirstName,
final String newMiddleName,
final String newSalutation,
final String newSuffix)
{
this.lastName = newLastName;
this.firstName = newFirstName;
this.middleName = newMiddleName;
this.salutation = newSalutation;
this.suffix = newSuffix;
}

public String getLastName()
{
return this.lastName;
}

public String getFirstName()
{
return this.firstName;
}

public String getMiddleName()
{
return this.middleName;
}

public String getSalutation()
{
return this.salutation;
}

public String getSuffix()
{
return this.suffix;
}

@Override
public String toString()
{
return this.salutation + " " + this.firstName + " " + this.middleName
+ this.lastName + ", " + this.suffix;
}
}
Address.java (Simple)

package dustin.examples;

/**
* Representation of a United States address.
*
* @author Dustin
*/
public final class Address
{
private final String streetAddress;
private final String city;
private final String state;

public Address(final String newStreetAddress, final String newCity, final String newState)
{
this.streetAddress = newStreetAddress;
this.city = newCity;
this.state = newState;
}

public String getStreetAddress()
{
return this.streetAddress;
}

public String getCity()
{
return this.city;
}

public String getState()
{
return this.state;
}

@Override
public String toString()
{
return this.streetAddress + ", " + this.city + ", " + this.state;
}
}

Although the code is improved, there are still some issues that can be improved. In particular, the original method with too many parameters still has three boolean parameters that can be easily confused with one another. Although the String parameters to that method were factored into two new classes, those two new classes still each consist of a bunch of Strings. In these cases, one might want to supplement the Introduce Parameter Object refactoring with use of custom types. Using the custom types I showed in my last post, the method with too many parameters now looks like that shown in the next code listing.


public Person createPerson(
final FullName fullName,
final Address address,
final Gender gender,
final EmploymentStatus employment,
final HomeownerStatus homeownerStatus)
{
// implementation goes here
}

The method now has fewer parameters and the parameters it does have are all of distinct types. IDEs and the Java compiler can now be especially helpful in ensuring that clients use this interface properly. Applying custom types (written in last post) to the FullName and Address classes result in the next two new code listings for those classes.

FullName.java (Custom Types)

package dustin.examples;

/**
* Full name of a person.
*
* @author Dustin
*/
public final class FullName
{
private final Name lastName;
private final Name firstName;
private final Name middleName;
private final Salutation salutation;
private final Suffix suffix;

public FullName(
final Name newLastName,
final Name newFirstName,
final Name newMiddleName,
final Salutation newSalutation,
final Suffix newSuffix)
{
this.lastName = newLastName;
this.firstName = newFirstName;
this.middleName = newMiddleName;
this.salutation = newSalutation;
this.suffix = newSuffix;
}

public Name getLastName()
{
return this.lastName;
}

public Name getFirstName()
{
return this.firstName;
}

public Name getMiddleName()
{
return this.middleName;
}

public Salutation getSalutation()
{
return this.salutation;
}

public Suffix getSuffix()
{
return this.suffix;
}

@Override
public String toString()
{
return this.salutation + " " + this.firstName + " " + this.middleName
+ this.lastName + ", " + this.suffix;
}
}
Address.java (Custom Types)

package dustin.examples;

/**
* Representation of a United States address.
*
* @author Dustin
*/
public final class Address
{
private final StreetAddress streetAddress;
private final City city;
private final State state;

public Address(final StreetAddress newStreetAddress, final City newCity, final State newState)
{
this.streetAddress = newStreetAddress;
this.city = newCity;
this.state = newState;
}

public StreetAddress getStreetAddress()
{
return this.streetAddress;
}

public City getCity()
{
return this.city;
}

public State getState()
{
return this.state;
}

@Override
public String toString()
{
return this.streetAddress + ", " + this.city + ", " + this.state;
}
}

All of my examples thus far have been of standalone public classes. I often find that if I need a parameter object simply for passing information between methods and constructors in the same package that it can be useful to make these parameter object classes package scope. Nested classes can also be used for these parameter objects in some cases.

Benefits and Advantages

The most obvious benefit of the parameter object is the reduction in number of parameters passed to a method or constructor. This encapsulation of related parameters makes it easier to quickly ascertain what types are being passed to the method or constructor. It is easier for a developer to understand a smaller number of parameters.

Parameter objects share one of the same benefits provided by custom types: the ability to add additional behaviors and characteristics to the parameter object for convenience functions. For example, having an Address class rather than a bunch of String types allows one to validate addresses.

Costs and Disadvantages

The primary drawback to the parameter object is a little extra work to design, implement, and test the class. However, these are pretty easy to write and test and modern tools such as IDEs and scripting languages make it even easier to automate the most mundane and tedious portions of those tasks. An even smaller argument against this approach is that it can be abused. If a developer starts bundling unrelated parameters together into a class just to reduce the number of parameters, that doesn't necessarily help the situation. That approach does indeed reduce the number of parameters, but the ultimate goal of improving readability is not achieved and it could be argued that this approach is even less readable.

Conclusion

Parameter objects provide a nice clean approach to appropriately encapsulating related parameters to reduce the total parameter count to a method or constructor. They are easy to implement and can significantly enhance the readability and type safety parameters passed to method and constructor calls. Parameter objects can be enhanced further through the use of custom types as explained in my previous post.

Thứ Hai, 7 tháng 10, 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.