Subscribe to Martyr2's Programming Underground        RSS Feed
****- 2 Votes

NULL Is Legit When Used Correctly

Icon 4 Comments
I was doing some research on a topic about compulsory parameters to constuctors for a project I was doing when I came across a topic about the use of NULL as a return value. Don't ask me how I stumble into these things. As you can imagine, as with everything in software engineering circles, there was a raging debate about whether or not NULL (or its equivalents like None in Python) was an acceptable return value. In this article I am going to express my opinion on why NULL is perfectly acceptable, but only in certain circumstances. I am also going to show why the "special case pattern" outlined by Martin Fowler is just a bad idea for large scale applications and perhaps even some smaller ones. Yes I know, I am going to make a case against a Martin Fowler concept. Let's dive in!

When is NULL Acceptable and When is it Not?

To best answer the question, let's fall back on what we know in every day life and the expectations we have for interactions. I mean, we should be designing things with readability but also matches our ideas and concepts of the things we are trying to model in life. We will assume we are at a grocery store and we are on the hunt for a particular brand of cereal. I mean, when I was younger there was no replacement for Lucky Charms... ask any kid if you don't believe me. If I go into the grocery store and can't find the Lucky Charms on the shelf, I would go to a store employee and ask "Do you carry any Lucky Charms cereal?" He might look at me funny given my age but never mind that part. What would my expectations be for his response? He might say "Yes, we have it right here." He would then hand me a box. The other option would be "No, we don't have it in stock. Sorry." and I would be left with nothing.

Did you catch the subtle hint? You are left with NOTHING. You expected to be handed an object, a box of cereal, but you got nothing. You were not told "false", you were not told "-1", you were not told "here is this box called missing Lucky Charms cereal". You get nothing! This is important. It is not what we want in some cases, but that is life.

It is acceptable to return NULL (programming speak form of "nothing", "the lack of anything", "the void") if you expect to be getting an object and it is just not there. Now if there was an error finding it, sure throw an exception or whatever. If it is just not there, you get nothing.

Yes this means you have to check if you received nothing before you can work with what you got. Yes it might be a bit more tedious or repetitive that you have to check if you got something, but if you expected to get something and you didn't then that is what you have to work with. If you are smart, perhaps check once you didn't get something and remember it some how rather than checking a million times.

Now I am not saying returning NULL is universal to all functions. If you expect to get a string, returning an empty string might be acceptable. If you are expecting an integer and returning an invalid integer might also be acceptable. Perhaps you are expecting a number between 0 and 100 where you return 0 as being the closest value to -1 that too might be acceptable. What I am saying here with NULL applies mostly to objects. If I expect a concrete object to work with, I expect that or nothing. Don't hand me something as a proxy or hand me something that is not what I am expecting. Don't hand me any old box of generic cereal to say we didn't find your cereal, but take this to work with. Most cases I am just going to throw it away once I realize I didn't get what I was looking for.

Special Case Pattern

Now for the heresy. In the article I stumbled across, one poster referred to Special Case Pattern which explains returning a class with the same interface as what is expected to be returned to avoid having to check for NULL, or preventing NULL exceptions (trying to use a class that is null). There are some fundamental problems with this approach...

  • We still have to check if the return class is exactly what we asked for. Using the example he outlines, I have to still determine if what I got is the person I wanted or a missing customer. Or is it unknown customer? This is still a check I have to make. I can't just use it as if nothing happened. If I was printing their name, perhaps it makes no difference if it comes up as "Unknown" vs their actual name. But anything more I have to know what I have.
  • What is the difference between the missing customer and an unknown customer? I now have to remember the difference. Can you remember in 6 months? Is the missing one with an ID of -1? I would probably have to go look at the classes to know. Good naming may help, but as far as what I can do with it vs the other class is something I still have to look up.
  • It just doesn't scale. His example is just two derived classes, but my function will have to know which special case to return for what circumstance. I can't create like 10 classes for missing, unknown, not found by name, deleted, etc. In a system of hundreds of classes, I can't have hundreds more for special cases just to avoid a NULL. And no, you can't just say I have a bad design then. Enterprise developers know that even the best designs would have this kind of problem.
  • Creating classes just for these special cases leads to code bloat with a ton of these special classes floating around to hide the fact that I just didn't get my box of Lucky Charms!


Sometimes I think we get into our own heads a bit too much when it comes to what we are trying to communicate with our software designs. We make elaborate setups to avoid simple rules of our own understanding. We are in the middle of the isle of our grocery store with either a box of Lucky Charms in our hand or we are not. We don't have fake boxes of cereal, we don't have a boolean value of false, we don't have a hair dryer as our closest "proxy" to what we asked for. We just don't have anything! NULL, I think, adequately describes our expectations of having an object or not. Don't over complicate things.

If you liked this article and want to practice what you have learned, be sure to check out my project ebook titled "The Programmers Idea Book 200 Software Project Ideas And Tips To Developing Them". In the book you can get a ton of project ideas, some small, some larger, all language neutral (can be written in your language of choice). You can also find more material and solutions at https://www.coderlexicon.com

Thanks for reading! :)

4 Comments On This Entry

Page 1 of 1

jon.kiparsky 

24 November 2019 - 05:16 PM
Why not use exceptions?

Quote

It is acceptable to return NULL (programming speak form of "nothing", "the lack of anything", "the void") if you expect to be getting an object and it is just not there. Now if there was an error finding it, sure throw an exception or whatever. If it is just not there, you get nothing.


If a function is meant to return an object of some sort, and it cannot, that sounds like an exception to me. I mean, yes, now you have to handle the exception, but gee, I have to handle the "None" case anyway, so that's not such a thing.
0

xclite 

25 November 2019 - 11:05 AM
The only language I've ever used which handled pervasive null well was clojure. This is because Clojure does nil punning well, with much of the standard library accepting null and doing the right thing.

My general rule of thumb:

* Do not accept null as a parameter
* If the language has an Option type, use it instead
* Empty collections are always better than null to return
0

h4nnib4l 

18 December 2019 - 11:34 AM

jon.kiparsky said:

Why not use exceptions?
If a function is meant to return an object of some sort, and it cannot, that sounds like an exception to me.


From the consumer's perspective, it is not necessarily exceptional that the store is out of Lucky Charms.

For the purposes of the thought exercise, let's suppose there are two potential "no" responses to the request for an instance of Lucky Charms:

1. No, because we do not currently have any in stock.
2. No, because we never carry Lucky Charms.

If the system is modeled in a way that a consumer could encounter both scenarios, then returning zero instances of the object is a non-exceptional scenario. However, that doesn't answer whether or not null should be returned vs. an empty collection for case one. I'd posit that it comes down to design, and I can see null being useful. If the system supports free-form search of inventory, then perhaps case two isn't actually an exceptional scenario either, in which case maybe an empty collection makes sense for case one and null makes sense for case two. If case two is considered exceptional, then we're back to whether or not null vs. an empty collection is valid for case one. In that scenario, I'd have to agree with xclite that the latter makes more sense.
0

BetaWar 

10 January 2020 - 07:42 AM
You can also get into the fun cases where languages don't have exceptions. Look at C or golang. Neither provide exceptions, but there are cases where you won't have your expected return value. In C, you can easily just return NULL; in golang, it is typically done slightly differently by returning a nil and an error object; the fun here is what all of the sudden instead of having if (myReturnedPointer == NULL) { throughout your code, you are checking if the returned error is nil or not and potentially having to cascade error returns throughout your code.

Another possible example of why you may not want to use an exception is because they take extra processing. Most of the time, and exception being thrown breaks out of standard program flow. This typically means less than 1ms extra time, but when you are dealing with an application that is supposed to be doing millions of operations per second, every little bit of overhead adds up. In those sorts of applications though, there is great care taken to optimize everything (I have literally seen ones where the entire application is running in kernel space so there was no copy between user-space memory and kernel-space memory).

At my current job, we are suffering from people making our database columns nullable without good reason. Literally 85% of our columns are nullable. The funny thing is that in some cases a column in one table may be nullable, and a column with the same name in another table may not be; so it can get painful if you are attempting to migrate data around.
0
Page 1 of 1

February 2020

S M T W T F S
      1
2345678
9101112131415
16 17 1819202122
23242526272829

Recent Entries

Recent Comments

Search My Blog

4 user(s) viewing

4 Guests
0 member(s)
0 anonymous member(s)