21

I don't quite understand the consistent bashing of null references by some programming language folks. What's so bad about them? If I request read access to a file that doesn't exist then I'm perfectly happy to get an exception or a null reference and yet exceptions are considered good but null references are considered bad. What's the reasoning behind this?

4
  • 1
    Related: stackoverflow.com/questions/3989264/…
    – user7043
    May 23, 2011 at 19:39
  • 2
    Some languages crash on null better than others. To a "managed code", such as .Net / Java a null ref is just another type of problem, whereas other native code might not handle this as gracefully (you have not mentioned a specific language). Even in a managed world, sometimes you want to write fail-safe code (embedded?, weapons?), and sometimes you want to bitch loudly ASAP (unit testing). Both types of code could be calling into the same library - that would be a problem. In general I think that code which tries not to hurt computers feelings is a bad idea. Fail-safety is HARD anyway.
    – Job
    May 23, 2011 at 23:02
  • @Job: This is advocating laziness. If you know how to handle an exception, you handle it. Sometimes that handling might involve throwing another exception, but you should never be letting a null reference exception go unhandled. Ever. That is every maintenance programmer's nightmare; it's the most useless exception in the entire tree. Just ask Stack Overflow.
    – Aaronaught
    May 23, 2011 at 23:19
  • or to put it another way - why not return a code of some sort to represent error information. This argument will rage for years to come.
    – gbjbaanb
    May 24, 2011 at 8:27

13 Answers 13

24

Null references aren't "shunned" any more so than exceptions, at least by anyone I've ever known or read. I think you're misunderstanding the conventional wisdom.

What's bad is an attempt to access a null reference (or dereference a null pointer, etc.). This is bad because it always indicates a bug; you would never do something like this on purpose, and if you are doing it on purpose, then that's even worse, because it's making it impossible to distinguish expected behaviour from buggy behaviour.

There are certain fringe groups out there who just really hate the concept of nullity for some reason, but as Ed points out, if you don't have null or nil then you'll just have to replace it with something else, which might lead to something worse than a crash (such as data corruption).

Many frameworks, in fact, embrace both concepts; for example, in .NET, a frequent pattern you'll see is a pair of methods, one prefixed by the word Try (such as TryGetValue). In the Try case, the reference is set to its default value (usually null), and in the other case, an exception is thrown. There's nothing wrong with either approach; both are used frequently in environments that support them.

It really all depends on the semantics. If null is a valid return value, as in the general case of searching a collection, then return null. On the other hand, if it is not a valid return value - for example, looking up a record using a primary key that came from your own database - then returning null would be a bad idea because the caller won't be expecting it and probably won't check for it.

It's really very simple to figure out which semantic to use: Does it make any sense for the result of a function to be undefined? If so, you can return a null reference. If not, throw an exception.

7
  • 5
    Actually there are languages that don't have null or nil and do not "need to replace it with something else". Nullable references imply that there might be something there, or there might not be. If you simply require the user to explicitly check if there is something there, you have solved the problem. See haskell for a real life example. Mar 3, 2013 at 8:21
  • 3
    @ErikKronberg, yes, the "billion dollar mistake" and all that nonsense, the parade of people trotting this out and claiming it to be fresh and fascinating never ends, that's why the previous comment thread was deleted. These revolutionary replacements people never fail to bring up are always some variant of the Null Object, Option or Contract, which don't actually magically eliminate the underlying logic error, they just defer or promote it, respectively. Anyway, this is obviously a question about programming languages which do have null, so really, Haskell is pretty irrelevant here.
    – Aaronaught
    Mar 3, 2013 at 14:07
  • 1
    are you seriously arguing that not requiring a test for null is as good as requiring it? Mar 3, 2013 at 14:08
  • 3
    @ErikKronberg: Yes, I am "seriously arguing" that having to test for null isn't particularly different from (a) having to design every tier of an application around the behaviour of a Null Object, (b) having to pattern-match Options all the time, or (c) not allowing the callee to say "I don't know" and forcing an exception or crash. There's a reason why null has endured so well for so long, and the majority of the people who say otherwise seem to be academics with little experience designing real-world apps with constraints like incomplete requirements or eventual consistency.
    – Aaronaught
    Mar 3, 2013 at 14:18
  • 3
    @Aaronaught: Sometimes I wish there was a downvote button for comments. There is no reason to rant like that. Mar 23, 2013 at 13:08
11

The big difference is that if you leave out code to handle NULLs, your code will continue on quite possibly crashing at a later stage with some unrelated error message, where as with exceptions, the exception would be raised at the initial point of failure (opening a file for reading in your example).

8
  • 4
    Failure to handle a NULL would be willful ignorance of the interface between your code and whatever returned it. Developers who would make that mistake would make others in a language that doesn't do NULLs.
    – Blrfl
    May 23, 2011 at 21:14
  • @Blrfl, ideally methods are quite short, and thus it is easy to figure out exactly where the problem is happening. A good debugger can usually hunt a null reference exception well, even if the code is long. What am I supposed to do if I am trying to read a critical setting from a registry, which is not there? My registry is messed up and I am better of failing and annoying the user, than silently recreating a node and setting it to default. What if a virus did this? So, if I get a null, should I throw a specialized exception or just let it rip? With short methods what is the big difference?
    – Job
    May 23, 2011 at 23:08
  • @Job: What if you don't have a debugger? You do realize that 99.99% of the time your application is going to be running in a release environment? When that happens, you're going to wish that you'd used a more meaningful exception. Your app may still have to fail and annoy the user, but at least it will output debug information that will enable you to promptly track down the issue, thus keeping said annoyance to a minimum.
    – Aaronaught
    May 23, 2011 at 23:13
  • @Birfl, sometimes I don't want to handle the case where a null would be natural to return. For example, suppose I have a container mapping keys to values. If my logic guarantees that I never attempt to read values I didn't first store, then I should never get null returned. In that case, I'd much rather have an exception which provides as much information as possible to indicate what went wrong, rather then returning a null to fail mysteriously someplace else in the program. May 23, 2011 at 23:33
  • To put this another way, with exceptions I have to explicitly handle the unusual case or else my program dies right away. With null references if the code doesn't explicitly handle the unusual case, it will try to limp on. I think it better to take the fail now case. May 23, 2011 at 23:35
8

Because null values are not a necessary part of a programming language and are a consistent source of bugs. As you say, opening a file may result in failure, which could be communicated back as either a null return value or via an exception. If null values were not allowed then there is a consistent, singular way to communicate failure.

Also, this is not the most common issue with nulls. Most people remember to check for null after calling a function which may return it. The problem crops up far more in your own design by allowing variables to be null at various points in your program's execution. You can design your code such that null values are never allowed, but if null were not allowed at the language level none of this would be necessary.

However, in practice you would still need some way to signify if a variable is or is not initialized. You would then have a form of bug wherein your program does not crash, but instead continues along using some possibly invalid, default value. I honestly don't know which is better. For my money I like to crash early and often.

8
  • 1
    Jim Balter: Right, I'm not actually advocating no-nulls, just throwing out the reasons I could think of in favor of it. May 23, 2011 at 23:06
  • 3
    @Jim Balter: I suppose I am confused as to how it would really help in practice though. In any non-trivial program you will have to at some point deal with values that may not be initialized. So, there must be some way to signify a default value. As such, you still have to check for this before going on. So, instead of a potential crash, you are now potentially working with invalid data. May 23, 2011 at 23:19
  • 1
    You also know what happened when you get null as a return value: The information you requested doesn't exist. There's no difference. Either way, the caller has to validate the return value if it actually needs to use that information for some particular purpose. Whether it's validating a null, a null object, or a monad makes no practical difference.
    – Aaronaught
    May 23, 2011 at 23:54
  • 1
    @Jim Balter: Right, I still don't see the practical difference, and I don't see how it makes life easier for people writing real programs outside of academia. Doesn't mean there aren't benefits, simply that they do not seem evident to me. May 24, 2011 at 0:09
  • 1
    I've explained the practical difference with an Uninitialized class twice -- it identifies the origin of the null item -- making it possible to pinpoint the bug when a dereference of null occurs. As for the languages designed around null-less paradigms, they avoid the problem from the get-go -- they provide alternate ways to program.that avoid uninitialized variables; that may seem hard to grasp if you aren't familiar with them. Structured programming, which also avoids a large class of bugs, was also once considered "academic".
    – Jim Balter
    May 24, 2011 at 0:22
4

Tony Hoare, who created the idea of a null reference in the first place, calls it its one million dollar mistake.

The problem is not about null references per-se, but about the lack of proper type-checking in most (otherwise) type safe languages.

This lack of support, from the language, means that bugs "null-bugs" may be lurking in the program for a long time before being detected. Such is the nature of bugs, of course, but the "null-bugs" are now known to be avoidable.

This problem is especially present in C or C++ (for example) because of the "hard" error that it causes (a crash of the program, immediate, with no elegant recovery).

In other languages, there is always the question of how to handle them.

In Java or C# you would get an exception if you attempt to invoke a method on a null reference, and it might be okay. And therefore most of Java or C# programmers are used to this and don't understand why one would want to do otherwise (and laugh at C++).

In Haskell, you have to explicitly provide an action for the null case, and therefore Haskell programmers gloat at their colleagues, because they got it right (right ?).

It is, really, the old error-code / exception debate, but this time with a Sentinel Value in lieu of error-code.

As always whichever is most appropriate really depends on the situation and the semantics you are looking for.

1
  • Precisely. null really is evil, because it subverts the type system. (Granted, the alternative has a drawback in its verbosity, at least in some languages.) May 19, 2012 at 9:57
2

You can't attach a human-readable error message to a null pointer.

(You can, however, leave an error message in a log file.)

In some languages/environments which allow pointer arithmetic, if one of the pointer argument is null and it is allowed into the calculation, the result would be an invalid, non-null pointer. (*) More power to you.

(*) This happens a lot in COM programming, where if you try to call into an interface method but the interface pointer is null, it would result in a call to an invalid address that is almost, but not quite, exactly unlike zero.

2

Returning NULL (or numeric zero, or boolean false) to signal an error is wrong both technically and conceptually.

Technically, you're burdening the programmer with checking the return value right away, at the exact point where it is returned. If you open twenty files in a row, and the error signalling is done by returning NULL, then the consuming code must check each file read individually, and break out of any loops and similar constructs. This is a perfect recipe for cluttered code. If, however, you choose to signal the error by throwing an exception, the consuming code can choose to handle the exception immediately, or let it bubble up as many levels as appropriate, even across function calls. This makes for much cleaner code.

Conceptually, if you open a file and something goes wrong, then returning a value (even NULL) is wrong. You don't have anything to return, because your operation didn't finish. Returning NULL is the conceptual equivalent of "I've successfully read the file, and here's what it contains - nothing". If that's what you want to express (that is, if NULL makes sense as an actual result for the operation in question), then by all means return NULL, but if you want to signal an error, use exceptions.

Historically, errors were reported this way because programming languages like C don't have exception handling built into the language, and the recommended way (using long jumps) is a bit hairy and kind of counter-intuitive.

There's also a maintenance side to the problem: with exceptions, you have to write extra code to handle the failure; if you don't, the program will fail early and hard (which is good). If you return NULL to signal errors, the default behaviour for the program is to ignore the error and just carry on, until it leads to other problems down the road - corrupt data, segfaults, NullReferenceExceptions, depending on the language. To signal the error early and loudly, you have to write extra code, and guess what: that's the part that gets left out when you're on a tight deadline.

1

As already pointed out, many languages don't convert a dereference of a null pointer into a catchable exception. Doing that is a relatively modern trick. When the null pointer issue was first recognised, exceptions hadn't even been invented yet.

If you allow null pointers as a valid case, that is a special case. You need special-case handling logic, often in lots of different places. That's extra complexity.

Whether it relates to potentially null pointers or not, if you don't use exception throws to handle exceptional cases, you must handle those exceptional cases some other way. Typically, every function call must be have checks for those exceptional cases, either to prevent the function call being called inappropriately, or to detect the failure case when the function exits. That's extra complexity that can be avoided using exceptions.

More complexity usually means more errors.

Alternatives to using null pointers in data structures (e.g. to mark the start/end of a linked list) include using sentinel items. These can give the same functionality with a lot less complexity. However, there can be other ways to manage the complexity. One way is to wrap the potentially null pointer in a smart pointer class so that the null checks are only needed in one place.

What to do about the null pointer when it's detected? If you can't build in the exceptional-case handling, you could always throw an exception, and effectively delegate that special-case handling to the caller. And that's exactly what some languages do by default when you dereference a null pointer.

1

Specific to C++, but there null references are shunned there because null semantics in C++ are associated with pointer types. It's quite reasonable for a file open function to fail and return a null pointer; in fact the fopen() function does exactly that.

1
  • Indeed, if you find yourself with a null reference in C++, it is because your program is already broken.
    – Kaz Dragon
    May 24, 2011 at 13:10
1

It depends on the language.

For instance, Objective-C allows you to send a message to a null (nil) object without problems. A call to nil returns nil as well and is considered a language feature.

I personally like it since you can rely on that behavior and avoid all those convoluted nested if(obj == null) constructs.

For instance:

if (myObject != nil && [myObject doSomething])
{
    ...
}

Can be shortened to:

if ([myObject doSomething])
{
    ...
}

In short, it makes your code more readable.

0

I don't give a hoot whether you return a null or throw an exception, as long as you document it.

1
  • And also name it: GetAddressMaybe or GetSpouseNameOrNull.
    – rwong
    May 24, 2011 at 5:19
-1

A Null reference usually occurs because something in the program logic was missed, ie: you got to a line of code without going through the required set-up for that block of code.

On the other hand, if you throw an exception for something it means that you recognized that a particular situation could occur in normal operation of the program, and it's being handled.

1
  • 2
    A null reference can "occur" for a great number of reasons, very few of them pertaining to anything being missed. Maybe you are also getting it confused with a null reference exception.
    – Aaronaught
    May 23, 2011 at 21:09
-1

Null references are often very useful: For example, an element in a linked list can have some successor or no successor. Using null for "no successor" is perfectly natural. Or, a person can have a spouse or not - using null for "person has no spouse" is perfectly natural, much more natural than having some "has no spouse"-special value that the Person.Spouse member could refer to.

But: Many values are not optional. In a typical OOP program, I'd say more than half of the references can't be null after the initialization, or the program will fail. Otherwise the code would have to be riddled with if (x != null) checks. So why should every reference be nullable by default? It should really be the other way round: variables should be non-nullable by default, and you should explicitly have to say "oh, and this value can be null, too".

2
  • is there anything from this discussion you'd like to add back to your answer? This comment thread got a little heated, and we'd like to clean this up. Any extended discussion should be taken to chat.
    – user8
    May 26, 2011 at 14:09
  • In the midst of all that bickering there could have been one or two points of actual interest. Unfortunately I didn't see Mark's comment until I pruned back the extended discussion. In the future please flag your answer for moderators attention if you would like to preserve the comments until you have time to review them and edit your answer appropriately.
    – Josh K
    May 26, 2011 at 17:23
-1

Your question is confusingly worded. Do you mean a null reference exception (which is actually caused by an attempt to dereference null)? The clear reason to not want that type of exception is that it gives you no information as to what went wrong, or even when -- the value could have been set to null at any point in the program. You write "If I request read access to a file that doesn't exist then I'm perfectly happy to get an exception or a null reference" -- but you should not be perfectly happy to get something that gives no indication of the cause. Nowhere in the string "an attempt was made to dereference null" is there any mention of reading or of non-existing files. Perhaps you mean that you would be perfectly happy to get null as a return value from a read call -- that's a quite different matter, but it still gives you no information about why the read failed; it's much better to receive either an error object that details the nature of the error, or to have such an error object thrown (note the distinction, and possible confusion, between an Exception, which is a object, and an exception, which is an event that results in throwing such an object).

8
  • No, I don't mean a null reference exception. In all the languages I know uninitialized but declared variables are some form of null.
    – user7146
    May 23, 2011 at 22:57
  • But your question was not about uninitialized variables. And there are languages that do not use null for uninitialized variables, but instead use wrapper objects that can optionally contain a value -- Scala and Haskell, for example.
    – Jim Balter
    May 23, 2011 at 23:21
  • 1
    "...but instead use wrapper objects that can optionally contain a value." Which is clearly nothing like a nullable type.
    – Aaronaught
    May 24, 2011 at 0:05
  • 1
    In haskell there is no such thing as an uninitialized variable. You could potentially declare an IORef and seed it with None as an initial value but that's pretty much the analog of declaring a variable in some other language and leaving it uninitialized which brings with it all the same problems. Working in the purely functional core outside the IO monad haskell programmers have no recourse to reference types so there is no null reference problem.
    – user7146
    May 24, 2011 at 0:37
  • 1
    If you have an exceptional "Missing" value, that is precisely the same as an exceptional "null" value - if you have the same tools available to handle it. That's a significant "if". You need extra complexity to handle that case either way. In Haskell, pattern matching and the type system provides a way to help manage that complexity. However, there are other tools for managing complexity in other languages. Exceptions are one such tool.
    – user8709
    May 24, 2011 at 4:09