There more to consider than whether to return null

With the previous discussions about whether or not we should return null from a method, I think that there is a few important points that were missed. I wanted to weigh in one last time, before I personally declare this topic beaten to death.

In the example that was given, it was asked what to do have something like:

  1. VendingMachine vendingMachine = new VendingMachine();
  2. vendingMachine.addMoney(Money.ONE_DOLLAR); // not enough money, drinks cost $1.50
  3. Drink drink = vendingMachine.getDrink();

// What now? Oh Crap, we can’t have a drink at this point. We didn’t put enough money in the machine.

In the case that there isn’t enough money, we could have the getDrink() method could return null, return a Drink.NULL. In any case, we are defining an invariant for the use of getDrink(). We are promising that, in the event that getDrink() is called and not enough money was given, we are going to do one of those two things. Alternatively, we are saying that if and only if there is enough money added, will it return a valid drink object.

Also, with this example, the assumption is that we are going to check the post-condition of the operation in order to detect failure:

  1. Drink drink = vendingMachine.getDrink();
  2. if (drink == null)  // Oh Crap!
  3.  
  4. or with a null object
  5.  
  6. Drink drink = vendingMachine.getDrink();
  7. if (drink.isNull()) // Oh Crap!
  8.  

Ideally, I don’t ever want my code to veer from the happy path. I don’t want to ever have my code reach an invalid situation where it cannot return a drink. I also don’t want to expend much effort as a developer handling the scenario. I would rather write tests that asserts that it can’t happen in relation to the client object.

One such option, is to provide a different invariant scenario - One where we check a pre-condition:

  1. VendingMachine vendingMachine = new VendingMachine();
  2. while (vendingMachine.needsMoreMoneyForADrink()) {
  3.     vendingMachine.addMoney(Money.ONE_DOLLAR);
  4. }
  5. Drink drink = vendingMachine.getDrink(); // we know for sure that we have enough money for a drink here
  6.  

or

  1. if (!vendingMachine.needsMoreMoneyForADrink()) {
  2.     Drink drink = vendingMachine.getDrink();
  3. }

The invariant is now that if needsMoreMoneyForADrink() returns false, we will always get a drink.

Personally, I like the precondition approach (in this particular example). It reads well, and it adheres to the principle of Command-Query separation. Also, in the post-condition example, null (or a wrapper) is being used to communicate that not enough money has been given to the vending machine, and that more should be added. Personally, I’d like to ask the vending machine if I’m supposed to give more money. Note that situation is similar to an iterator pattern.

Along the way, Andy referred to using exceptions because they follow the Principle of “Tell, Don’t Ask.” I don’t believe that it qualifies as a good representation of that principle, but I have an example a little later that I believe is better.

Andy’s example of throwing an exception when getDrink() is called under conditions when it shouldn’t have been raises an important question for me. Should we have a method that fails, and then communicates failure back to the client object (such as with throwing an exception)? Perhaps we should not have a method that is expected to return a drink at all.

In the world of object to object communication, there are only a few ways for an object to respond to the communication of another object, with each having strengths and weaknesses.

1.) You can return a value, or an object reference. Null is the equivalent of reponding with “No, I can’t or won’t give that to you.”

2.) You can return some kind of communication object or value, one that perhaps allows you to communicate a number of message types. I consider a Null object a variation of this. You could also return an enum, where each enum value represents a different type of message.

3.) You could set some kind of global variable to hold a message - something that can be read by the client object. This is generally considered terrible OO, but I see it from time to time in the form of Singletons.

4.) In languages like Java, you can throw an exception. Exceptions generally signify an exceptional error situation. Exceptions can be handled, but if not, will continue to bubble throughout the code, interrupting every called frame, finally interrupting the flow of execution if not handled.

5.) You can respond with a method call. This is the standard “Proper” way of having objects communicate. Of the different message/communication types available to object, this one is the richest and most intentional (most explicit). You can send a message (a method call) and send parameters as details with the message.

I generally avoid using exceptions to communicate between objects. I try to reserve them for really exceptional situations. I will spend time looking at important code and deciding on how to do it well, but many, many time before, I have written methods or functions that just return null, and I also wrote the code that used it. It’s simple, and people who look at that kind of logic will be familiar with it, so it’s not going to be a surprise.

Anyway, I wanted to weigh in on a different way of “handling nulls.” I created a sample solution to the VendingMachine::getDrink() dilemma; one that doesn’t assume that there is a getDrink() method at all. It’s an example of the VendingMachine responding to the client object (a Person) with a richer object communication (and a better example of “Tell, don’t ask” than using exceptions). Imagine that the interaction begins with someone calling Person::getARefreshingDrink() and passing in a VendingMachine.

  1. public class Person {
  2.     VendingMachine vendingMachine;
  3.    
  4.     void mustAddMoreMoney() {
  5.         vendingMachine.addMoney(Money.ONE_DOLLAR, this);
  6.     }
  7.  
  8.     public void getARefreshingDrink(VendingMachine machine) {
  9.         this.vendingMachine = machine;
  10.         vendingMachine.addMoney(Money.ONE_DOLLAR, this);
  11.     }
  12.    
  13.     void enoughMoneyGiven() {
  14.         vendingMachine.enterDrinkSelection(DrinkType.COKE, this);
  15.     }
  16.    
  17.     void givePersonChangeAndADrink(Money change, Drink drink) {
  18.         // let’s guzzle that drink and pocket the change
  19.     }
  20. }
  21.  
  22.  
  23. public class VendingMachine {
  24.     private static final Money COST_PER_DRINK = Money.valueOf(“$1.50″);
  25.     private Money totalMoney = Money.ZERO;
  26.  
  27.     public void addMoney(Money money, Person person) {
  28.         totalMoney = totalMoney.add(money);
  29.         if (notEnoughMoney()) {
  30.             person.mustAddMoreMoney();
  31.         } else {
  32.             person.enoughMoneyGiven();
  33.         }
  34.     }
  35.    
  36.     public void enterDrinkSelection(DrinkType type, Person person) {
  37.         if (notEnoughMoney()) {
  38.             person.mustAddMoreMoney();
  39.         } else {
  40.             person.givePersonChangeAndADrink(getChange(), getDrinkFromType(type));
  41.             totalMoney = Money.ZERO;
  42.         }
  43.     }
  44.  
  45.     private Drink getDrinkFromType(DrinkType type) {
  46.         return new Drink(type);
  47.     }
  48.  
  49.     private boolean notEnoughMoney() {
  50.         return COST_PER_DRINK.greaterThan(totalMoney);
  51.     }
  52.  
  53.     private Money getChange() {
  54.         return totalMoney.minus(COST_PER_DRINK);
  55.     }
  56.    
  57. }

I’m not arguing that this is optimal either, but I got other blog posts to write, and stuff to do. It’s meant to represent a way of having objects interact in a rich manner. I’m sure you can imagine other ways to make this better, such as moving the Person method definition into a “Drinker” interface.

5 Responses to “There more to consider than whether to return null”

  1. Bill Comer Says:

    Kris,

    Slightly off topic but please could you expand on the code behind the isNull() method.

    Thx.
    Bill

  2. Bernd Eckenfels Says:

    The pre-condition case has two problems (however I still think it is usefull):

    a) you still need to prepare for abusive usage of the API - i.e. somebody is calling getDrink() without checking the money, the operating temperature, the maintance state (and other) preconditions.

    b) in a multi threaded environment some moethods tends to be atomic so you do not have to hold a lock over multiple invocations. In this scenario a pre condition check might not be valid anymore unti you get to the getter. Somebody stolen your can.

  3. Kris Kemper Says:

    @Bill

    I’ve seen Null object pattern versions of Value Objects before (ie: Money, Date, etc). You have a isNull method in the case that you want to ask whether or not you have a value. For instance, there may not be an acceptable default value for a Date, so you can’t prevent it from being null. The NullDate class would return true for isNull so I can detect that. Interally, it’ll just be doing a “return value == null”

    @Bernd

    I agree, the precondition approach has it’s issues - I favor it because of it’s readability and simplicity. As far as preparing for the abuse of the api - well, that’s an issue with having a getDrink() method at all.

  4. Anders Says:

    Hi Kris,

    I personally use the Drink.NULL or Drink.NONE or whatever pattern.

    Because returning a Drink object instead of null means that usually I don’t need to check post conditions and certainly not immediately on return. If, on the other hand, it really is bad that no non-null Drink could be returned an exception is required.

    Drink.NULL just needs to have a reasonable toString() etc. and in general behave like a Drink.

    -Anders

  5. sandrar Says:

    Hi! I was surfing and found your blog post… nice! I love your blog. :) Cheers! Sandra. R.

Leave a Reply