My colleague Andy posted today about Returning null considered Dishonest and while I agree with his sentiments, I disagree with his solution.
Andy’s premise is that you should never expect or return a null object as “Returning null is dishonest. It requires others to check that we’ve upheld our side of the bargain“. His solution is to throw meaningful exceptions within the method, so the happy path will return a not-null object, and the unhappy path throws exceptions.
This highlights one practice which I get annoyed with when I see it. Programming by exception is never a good idea. Exceptions should be reserved for exceptional circumstances only and should not contain business logic. If there is a possibility that the system can get into a state which returns null, then it should handle that situation, instead of letting it throw a tantrum and yell at the caller.
A better alternative would be to return an object which may or may not contain the value required. This Maybe object can have two implementations: when it can return the value, it returns a Something which contains the value; and when it cannot return the value (ie value would be null) it returns a Nothing (perhaps with a message detailing why).
To continue Andy’s example of the Vending Machine which dispenses a drink only if enough money has been inserted and there is enough stock, we can take a look at how Maybe objects can be used.
public Maybe<drink> giveMeADrink()
{
if (weDontHaveEnoughMoney)
{
return new Nothing<drink>(notEnoughMoneyMessage);
}
if (weDontHaveEnoughStock)
{
new Nothing<drink>(weDontHaveEnoughStockMessage);
}
return new Something<drink>(new Drink());
}
// CallerMaybe[Drink] drink = giveMeADrink()if (drink.IsPresent){ SlideDownChute(drink.Force())} else { ReportProblem(drink.Force())}
To me its a much cleaner version, you are coding for the situations that can actually occur, and not relying on try-catch statements around the place.
My advice? Steer clear of exceptions and return Maybes instead of nulls.
Thanks for the response
I’m not advocating using exceptions to control the flow of the application. *shudder*
As Kris mentioned in his response, my leanings here are much towards DBC preconditions.
I quite like the Maybe concept, but it is probably too abstract for the level that this workshop is pitched at. It’s something that we may work towards (although I suspect we’re more likely to end up with a product receiver that acts as a collection of vended products)
At this stage, I want to avoid a response object which has to be queried for whether it was a success response or a fail response, as this is not much removed from a successful call being a Drink and an unsuccessful call being null.
I hope this helps to explain where I’m coming from.
“Programming by exception is never a good idea. Exceptions should be reserved for exceptional circumstances only and should not contain business logic.”
[Citation needed]
That’s quite an assertion. Why?
Monads are a wonderful concept and Java sucks for not having implemented it properly. IMO the closest Java has to Monads are exceptions.
Checked exceptions are bad and clutter the code but runtimes are not bad. As an example Spring uses them very wisely.
@andy – I understand that your example was to illustrate a point in your workshop, and so I agree for your purpose, some sort of checking was more appropriate than none at all. However, on a more wider note I have been through code bases where exceptions are thrown for the normal flow of the application basically just to change the direction of the flow (not report the failure) which has lead me to my dislike for exceptions.
@simon –
One reason is that it should not be up to the method to determine what to do if certain preconditions have not be met, but throwing an error forces the caller to handle it. If the caller is happy for the method not to return a Drink, and can continue its flow without checking, then why force it to have to handle an exception :
try {
giveMeADrink()
} catch {
// do nothing
}
Also, this may be a symptom of how you develop code, but during TDD, I can’t think of many situations in which I would write a test to actively catch an exception, as opposed to handle the case in such a way as exceptions do not need to be thrown (except perhaps on the user layer of the application).
I have also seen code where the exception handling alters the application flow. This is a very silly and contrived example, but I have seen code like the following:
try {
giveMeADrink()
} catch(OutOfStockException) {
tellDispenserToOrderMoreStock()
displayTheOutOfStockLight()
}
and this is the sort of programming by exception that I refer to.
Of course, exceptions have their place, eg FileIO exceptions, but to use them to capture business logic? Not for me.
@daniel – Yes, Monads are wonderful. There is a lot of very cool and clever ideas from programming languages such as Haskall that we can, could and even should use. I am currently developing in C# 2.5 and find their lack of list functions frustrating (although I understand 3.0 have improved on this). So, our team has implemented whole variety of them which makes list handling that much better.
@Sarah
“Throwing an error forces the caller to handle it”. Precisely. The caller *should* be forced to handle it. If they don’t, the null value will float around the system, eventually causing a NPE. These NPEs can be caused a long way from where the problem actually occurred, and can be vile to debug. Trust me, I’ve been there.
It’s The Samurai Principle for me, every time.
Another alternative is to pass a delegate/block into the method like so:
public void giveMeADrink(FailedDrinkBehaviour sorry)
{
if (weDontHaveEnoughMoney)
{
sorry.needMoreMoney(50)
}
}
This makes the contract far more explicit and gives the client an clear opportunity to respond without having to do a load of ifs, catches or switches:
void needMoreMoney(int amount)
{
machine.insert(amount);
tryAgain();
}
@simon – By no means am I advocating passing nulls. Actually, my preferred pattern is the monadic style approach. It doesn’t break the contract, because the method signature say it May or May not return a result, and it always returns an object which can be used through the code flow.
@jupitarmoonbeam – interesting alternative.