Friday, February 24, 2006

Code Advice #9: Avoid null - use objects

WARNING: This blog entry was imported from my old blog on blogs.sun.com (which used different blogging software), so formatting and links may not be correct.


(See intro for a background and caveats on these coding advice blog entries.)



When a method is supposed to return an object, and it returns "null",
the null is used to convey a number of meanings:


  • Empty (when it's supposed to return an array or a list, but the list is empty)

  • Can't (when some error happens and an object to be constructed failed; the caller
    is now supposed to gracefully handle the error)

  • Unknown (when a method is supposed to compute some result that it couldn't figure out)


...and probably many more.



Whenever possible, avoid using nulls. There are several better alternatives.





  1. When a method needs to return an array, and you know the array is empty,
    don't be lazy and just return null. Perhaps you're not being lazy, but believe
    you're being efficient, because by returning null you can communicate empty
    to the caller without having to construct an object (the empty array or list).
    However, the performance impact is minimal. It's very cheap to construct
    short-lived objects in Java - much less than malloc cost in C.
    However, you can use the following pattern if this is going to be frequent:

    class Window {
    private static final Window[] NO_WINDOWS = new Window[0];

    public getActiveWindows() {
    if (whatever) {
    return null;
    return NO_WINDOWS;
    }
    ...
    }
    ...

    An approach like this has the advantage that you can start simplifying your
    own code, since you won't be doing null checks everywhere. If you call the
    getActiveWindows() method above, you can simply iterate over
    the results directly rather than branching on null first.






  2. When the "null" return value means something like "unknown", it's even
    better to use a special marker object. This has a number of advantages.


    • The intent is more clearly communicated, so the code is easier to read.





    • The marker object can customize some of its methods such that it reacts
      appropriately. This allows you to move some handling code into the object
      itself rather than in the client code. (As a simple example, your
      toString method can be written to be more informative.)





    • The class containing the marker object can also be written to consider
      marker objects in a correct way; for example, the right equals()
      semantics can be written.





    The above is a bit abstract so let me illustrate with another example. Let's
    say we're writing a word processor. We have a Document class, representing a document,
    and a Caret class, representing a visual caret (text insertion cursor). The caret
    has an associated position in the document, and this is represented by a Position object.
    Thus, the caret has the following method:

    public Position getPosition();

    Sometimes a document doesn't have an associated caret position - for
    example, when the document does not have focus. We could simply have
    getPosition() return null for this. But that would mean that client
    code relying on positions would always need to check for null before calling methods
    on the position they receive. Instead, the Position class should have a static
    field representing a non-existent position:

    class Position {
    public static final Position NONE = new Position(xxx);
    ...

    Now, most client code can simply call Position methods without worrying whether
    it will be null or not. The Position class itself can be modified such that
    various methods handle the NONE case correctly: not only can
    toString() do something like this:

    public String toString() {
    if (this == NONE) {
    return "Position.NONE";
    } else {
    ...

    but for example position comparison methods can have special considerations for how
    to handle positions where one or more represent the nonexistent position.
    Yes, this could be achieved by consistent use of null instead, but it's a lot
    less readable, and harder to search.




Null handling has gotten some renewed attention recently. With the advent of
annotations, you can annotate whether a method is expected to return null or not
(e.g. with @notnull).
This allows tools to perform static analysis of your code and find potential
bugs. For example, if you indicate that a method cannot return null, the tool
can obviously flag any "return null" statements within that method (or returning
expressions calling methods that may return null). But they
can also flag code calling this method that unnecessarily check the return value
for null, and more importantly, flag missing return value checks in code calling
methods that may return null.



IntelliJ has added

this feature

already. Looks like it has
been
around
for at least 10 years with lint checkers in C. I look forward to getting this
in my favorite IDE
(why oh why isn't @notnull and @nullable part of the
standard annotations set?)
- but in any case, try to avoid nulls by using
place holder objects where appropriate.


1 comment:

  1. There is also a Design Pattern that deals with this subject: NullObject Pattern. Whereas the name NullObject is a bit misleading, the pattern deals exactly with what you describe: Don't return nulls; return objects instead.

    ReplyDelete