picture home | pixelblog | qt_tools

David Van Brink // Wed 2007.08.1 18:16

Basics: Null and Nuller

I write a lot of code which other people call. One of the basic problems to wrestle with is: “How do I handle unusual inputs?” This note shall discuss some aspects of this, focusing on the use of “null”. Although the examples are phrased in Java, the ideas may be of general interest

Basics: Null and Nuller

Exceptions: An Inconvenient Shape

Opinions vary regarding the use of Java exceptions as part of an API. Most agree that they should be only used to indicate an “error”. But what exactly is an error? It depends solely on your definition of valid input.

In general, I prefer not to throw exceptions. I find it cumbersome to pepper my code with lots of try and catch blocks; I presume that you feel similarly. In general I’d rather check a result for “null” or “false” or some other indication of “what happened”. (This narrows the range of possible outcomes, which could be seen as limiting, but I prefer to think of it as simplifying.) To avoid throwing exceptions I try to define widely the range of valid input to my methods.

Toleration

In general, I try to make my methods tolerate, or even expect, null inputs. A null value for a parameter should be treated in the most innocuous fashion. If it’s an input, give it some default behavior. If it’s an output — such as a logger or list to be modified — let it do nothing. Return the best possible output.

As a result, I often have related convenience method variants like this:

public static void foo(String name) {
   foo(name,null,0.0);
}
public static void foo(String name,Version version) {
   foo(name,version,0.0);
}
public static void foo(String name,Version version,double scanTime) {
   ...do the operation...
}

Crash Your Code, Not Mine

Since you may not have access to my source code for debugging, I’ll try not to crash and instead push the problem back to your code. So, specifically, I’ll make sure that my method won’t ever fail on null; instead, I’ll do nothing, or return a null, so that your code will see the problem. Perhaps your code will crash; if so, you have your own source code to debug and correct. Also, in some cases, I’ll return a boolean which indicates “true” for “success”. It’s your option to read it, but is available as needed.

Return Null or an Empty List?

Some methods return a Array, List or Map, or similar objects, based on some input. If the input is all legal and normal but produces no results, I’ll return an empty list. That way you can iterate on it, check the size, and so forth, without any special case for “no results”. On the other hand, if some “necessary” input is null, I’ll return null to indicate that. Your code can then explicitly check it if needed.

Masking A Problem

The notion has occasionally been put forth that tolerant code hides errors. After all, it can be argued, if it fixes stuff up, one might think all is well. In general, I reject this claim. A method that accepts a wide range of possible inputs and has a well-defined behavior for them is simply doing as its told.

The situations alluded to where “tolerant” code hides a deep problem have, in my experience, been always linked to side effects. A typically terrifying example would be some innocent looking method thatlooks like a query but actually always fixes up your input document — just in case it was a little bit off. This is not “tolerant”, this is “toxic”.

Preflighting

How many times have you seen this code fragment:

    String s;
    int n;
    ...
    try {
      n = Integer.parseInt(s);
    } catch(NumberFormatException e) {
      n = 0;
    }

This lets any value of s, including null or a nondecimal value, evaluate to 0. But achoo, look at that try/catch peppering. I’ll always package that up in some tolerant parser. (And this gives a place to allow 0×123 or other numeric formats, if desired.) But sometimes you need to know if the input was rigorously acceptable, so we have a preflight method, as well.

    String s;
    int n;
    ...
    boolean formatGood = MyNumberUtils.isInt(s);
    n = MyNumberUtils.parseInt(s); // never excepts, 0 for bad number format or null

Neat and tidy! And all the information there, ready to steer the program as needed.

But That’s A Lot Of Bother

Hardening libraries against even these minor abuses takes effort. If some library is being used in many places, this effort — and much more — is well-spent. It offers value to every client. On the other hand, libraries always start out with zero users… but you have to start somewhere.


(c) 2003-2011 omino.com / contact poly@omino.com