Friday, November 16, 2012

Part V - Effective Java - Methods


This is the fifth in the series of posts based on the book Effective Java by Joshua Bloch. The previous post was about best practices in converting C structures to Java.

This post gives tips and best practices on how to write good methods in the classes.

Check Parameters for Validity

Methods that expect parameters expect the parameters to adhere to certain rules. In all public methods one should validate the parameters to ensure that they adhere to the expected rules and if not they should throw the right exception. It is also important that the constraints on the parameters be well documented.
For private methods we should ensure that we pass only the right attributes and instead of checking and throwing exceptions use Assertions.
It is very important that the parameters to the constructors are validated correctly.
The only exception under which one should not validate the parameters is, when there is a huge overhead in the validation.

Make Defensive Copies when needed

Normally a class should not expect an external class to be able to change the values of its attributes without its knowledge. But when a class contains mutable objects it is easy to allow clients to change the attributes without the class knowing it.
E.g. consider the following class:
public final class Period {
    private final Date start;
    private final Date end;
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0) {
            throw new IllegalArgumentException(start + “ after “ + end);
        }
        this.start = start;
        this.end = end;
    }
    public Date start() {
        return start;
    }
    public Date end() {
        return end;
    }
}
This class seems to be immutable as the only way to set the values of the start and end dates are through the constructors. But the Date class itself is a mutable class and there are two ways in which the values of the class can be changed:
1.       A client which passes the start and end date to the constructor can hold on the instance of the date and then at a later stage can change the value of these dates using the setYear, setMonth or any of the other methods.
2.       Similarly there is nothing to stop a client from invoking something as Period.start.setYear(someYear).
To avoid these we need to make copies of such mutable classes. In the constructor we can create new Date objects and store them instead of the passed date objects.
    public Period(Date start, Date end) {
        this.start = new Date(start.time());
        this.end= new Date(end.time());
        if (this.start.compareTo(this.end) > 0) {
            throw new IllegalArgumentException(start + “ after “ + end);
        }
    }
Now even if the client changes the objects that it passed to the constructor the values of the dates in the class will not change. Note that the validation was done after the copy was done. This is to prevent the situation where the value of the dates are changed after the validation and before the creation of the new dates in some other thread.
Also the clone method is not used as Date can be subclassed and a subclassed Date cannot be trusted.
Similarly we need to change the start and end methods to:
    public Date start() {
        return start.clone();
    }
    public Date end() {
        return end.clone();
    }
This will ensure that no client will be able to change the start and end attributes of this class. Note that clone has been used here as we are sure that we are using the java.util.Date and not a untrusted subclass of Date.
This is very important if we are writing a library for use by a large audience over which we have no control.
Defensive copying can be avoided if we are working with a set of trusted clients.

Design Method Signatures carefully

The following should be kept in mind when writing methods:
1.       Choose method names carefully: A method name should convey to its user the exact operation it is performing. It should not be necessary to further explain it in the method comments. What will be required in the method comments will be to specify how it can be used and what the parameter constraints are.
2.       Do not go overboard and provide too many convenience methods: Too many makes understanding the class that much difficult and that much more difficult to test. Provide convenience calls only when it is expected that the method will be used frequently. If in doubt do not provide.
3.       Avoid long parameter list: It will be very difficult to use a method which has a large number of parameters even if the users have the benefit of an IDE which prompts for parameters. This is even more problematic if there is sequence of parameters of the same type. Consider creating a helper class or splitting the methods into multiple methods which requires lesser number of parameters.
4.        When a parameter being passed to a method has an interface define the parameter as the interface rather than the concrete class that implements that interface. E.g. if a list is expected as an input specify the parameter of type List rather than specifying it of type ArrayList.
5.       Use Function Objects judiciously: Function Objects have their place in software but when overused it can make understandability of the program very difficult. It is not be shunned but it is to be used only in situations where it makes sense and allows others to understand it easily.

Function Objects are Objects which contain only Functions and no attributes. These are typically used to handle callbacks in Java. E.g. a GUI event handlers, or a class that implements Comparator interface and implements only the compare method.

Use Overloading Judiciously

One needs to be careful when overloading functions and very clear. We need to keep the following rule when we use overloading “selection among overloaded methods is static (i.e. determined at compile time), while selection among overridden methods is dynamic”. So if we overload methods then the method to be executed will be determined at compile time and irrespective of the actual run time datatype the predetermined method will be invoked.
As far as possible avoid having overloaded methods with the same number of parameters. This makes overloading very safe. If one needs to write overloaded methods with the same number of parameters then the parameter types should be very different from each other to avoid confusion.

Return Zero-length Arrays and not nulls

If a method returns an Array and conditionally it returns a null then client that invokes this method needs to check for nulls before proceeding. And if some client were to forget checking for null we will end up with Null Pointer Exception.
Instead if we return a zero length array as the output then no client needs to check for the validity of the array and we can avoid Null Pointer Exceptions.
Note that in most case the clients would have a loop which would be looping through the array and doing some processing and this will be unaffected if one returns a zero-length array.

Write doc comments for all exposed API elements

It is important to document all the public methods and attributes of a class. This will help the users of the class use it in a much better way. It is advisable to also document the protected and private methods if one wishes the code to maintainable.
The method comments must have the following:
1.       What the method does. This should pretty much be clear from the name of the method but in certain scenarios it will help if there is some additional information provided. It should never mention how the method functions.
2.       Do not have two methods or constructors with the same summary description.
3.       It should mention all of the preconditions, i.e. all the conditions that should be true before the method can be invoked.
4.       It should mention all of the postconditions, i.e. all of the things that will be true after the method has been invoked.
5.       It should mention any side effects of invoking the method
6.       The thread safety of the method should be specified especially if it is not thread safe
7.       The purpose of the parameter along with its constraints should be specified
8.       All the exceptions that the method can throw should be documented and if possible with the condition under which it can be thrown
9.       Do not have comments for getters and setters unless something unusual is being done in the method.

In the next post we will read about best General Programming practices.

No comments: