Correctly using null
There are different ways of using null
. The most common and semantically correct way is to use it when you may or may not have a single value. In this case a value either equals null
or is something meaningful like a record from database or something.
In these situations you then mostly use it like this (in pseudo-code):
if (value is null) {
doSomethingAboutIt();
return;
}
doSomethingUseful(value);
Problem
And it has a very big problem. The problem is that by the time you invoke doSomethingUseful
the value may not have been checked for null
! If it wasn't then the program will likely crash. And the user may not even see any good error messages, being left with something like "horrible error: wanted value but got null!" (after update: although there may be even less informative error like Segmentation fault. Core dumped.
, or worse yet, no error and incorrect manipulation on null in some cases)
Forgetting to write checks for null
and handling null
situations is an extremely common bug. This is why Tony Hoare who invented null
said at a software conference called QCon London in 2009 that he made the billion-dollar mistake in 1965:
https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
Avoiding the problem
Some technologies and languages make checking for null
impossible to forget in different ways, reducing the amount of bugs.
For example Haskell has the Maybe
monad instead of nulls. Suppose that DatabaseRecord
is a user-defined type. In Haskell a value of type Maybe DatabaseRecord
can be equal Just <somevalue>
or it may be equal to Nothing
. You can then use it in different ways but no matter how you use it you cannot apply some operation on Nothing
without knowing it.
For instance this function called zeroAsDefault
returns x
for Just x
and 0
for Nothing
:
zeroAsDefault :: Maybe Int -> Int
zeroAsDefault mx = case mx of
Nothing -> 0
Just x -> x
Christian Hackl says C++17 and Scala have their own ways. So you may want to try to find out if your language has anything like that and use it.
Nulls are still in wide use
If you don't have anything better then using null
is fine. Just keep watching out for it. Type declarations in functions will help you somewhat anyway.
Also that may sound not very progressive but you should check if your colleagues want to use null
or something else. They may be conservative and may not want to use new data structures for some reasons. For instance supporting older versions of a language. Such things should be declared in project's coding standards and properly discussed with the team.
On your proposal
You suggest using a separate boolean field. But you have to check it anyway and still may forget to check it. So there is nothing won here. If you can even forget something else, like updating both values each time, then it's even worse. If the problem of forgetting to check for null
is not solved then there is no point. Avoiding null
is difficult and you should not do it in such a way that makes it worse.
How not to use null
Finally there are common ways to use null
incorrectly. One such way is to use it in place of empty data structures such as arrays and strings. An empty array is a proper array like any other! It is almost always important and useful for data structures, that can fit multiple values, to be able to be empty, i.e. has 0 length.
From algebra standpoint an empty string for strings is much like 0 for numbers, i.e. identity:
a+0=a
concat(str, '')=str
Empty string enables strings in general to become a monoid:
https://en.wikipedia.org/wiki/Monoid
If you don't get it it's not that important for you.
Now let's see why it's important for programming with this example:
for (element in array) {
doSomething(element);
}
If we pass an empty array in here the code will work fine. It will just do nothing. However if we pass a null
here then we will likely get a crash with an error like "can't loop through null, sorry". We could wrap it in if
but that's less clean and again, you might forget to check it
How to handle null
What doSomethingAboutIt()
should be doing and especially whether it should throw an exception is another complicated issue. In short it depends on whether null
was an acceptable input value for a given task and what is expected in response. Exceptions are for events that were not expected. I will not go further into that topic. This answer very is long already.
std::optional
orOption
. In other languages, you may have to built an appropriate mechanism yourself, or you may actually resort tonull
or something similar because it's more idiomatic.lastChangePasswordTime
may be an uninitialised pointer there, and comparing it to anything would be undefined behaviour. Not a really compelling reason not to initialise the pointer toNULL
/nullptr
instead, especially not in modern C++ (where you wouldn't use a pointer at all), but who knows? Another example would be languages without pointers, or with bad support for pointers, maybe. (FORTRAN 77 comes to mind...)