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

NULL Is Legit When Used Correctly

Icon 2 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! :)

2 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
Page 1 of 1