178

I've heard it said that the inclusion of null references in programming languages is the "billion dollar mistake". But why? Sure, they can cause NullReferenceExceptions, but so what? Any element of the language can be a source of errors if used improperly.

And what's the alternative? I suppose instead of saying this:

Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

You could say this:

if (Customer.ExistsWithLastName("Goodman"))
{
    Customer c = Customer.GetByLastName("Goodman") // throws error if not found
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!"); 
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

But how is that better? Either way, if you forget to check that the customer exists, you get an exception.

I suppose that a CustomerNotFoundException is a bit easier to debug than a NullReferenceException by virtue of being more descriptive. Is that all there is to it?

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
Tim Goodman
  • 2,644
  • 2
  • 28
  • 25
  • 63
    I'd be wary of the "if Customer exists / then get Customer" approach, as soon as you write two lines of code like that, you're opening the door for race conditions. Get, and then check for nulls / catch exceptions is safer. – Carson63000 Oct 19 '10 at 02:48
  • 28
    If only we could eliminate the source of all runtime errors, our code would be guaranteed to be perfect! If NULL references in C# break your brain, try invalid, but non-NULL, pointers in C ;) – Left For Archive Oct 19 '10 at 05:44
  • there are the null coalescing and safe navigation operators in some languages though – jk. Nov 13 '11 at 22:22
  • 40
    null per se isn't bad. A type system that makes no difference between type T and type T+null is bad. – Ingo Mar 12 '14 at 22:26
  • Agreed with @Ingo. Personally I'm a big fan of C++ references, where you can have a reference that is guaranteed to be non-null. Saves a lot of the null issues in constructor checks etc that I have done in C#. And if I need to, I supply a pointer instead. I think the scenario the OP is supplying here is only half the story: the check could reasonably fail. There are many scenarios when allowing something to be null represents a violation of preconditions, and a good number of those are in constructors where objects are composed only of other valid objects. – J Trana Mar 13 '14 at 03:54
  • 31
    Coming back to my own question a few years later, I'm now totally a convert of the Option / Maybe approach. – Tim Goodman Mar 13 '14 at 20:16
  • This question popped up today: http://programmers.stackexchange.com/questions/152094/null-pointers-vs-null-object-pattern I just want to link both. – User May 02 '14 at 17:31
  • 1
    When Tony Hoare called null a billion dollar mistake, he was talking about language design. If you are designing a language, do it in a manner that does not allow null references. If your language already has null references, you have to kludge you way around them. Of the modern languages, I know Perl does not allow null references and I think Python and Ruby do not also. Unfortunately, Java does. – shawnhcorey Dec 24 '15 at 15:27
  • 1
    Please note that the alternative above is subject to race conditions. – Tristan Reid Feb 14 '18 at 17:09
  • C# 8 onwards - there won't be any null reference exceptions. – JᴀʏMᴇᴇ Jul 13 '18 at 14:49
  • I actually prefer the first option that makes proper use of null. We don't necessarily know the implementation of `Customer`. For all we know it could require DB or network calls for it to function. Thus a single call (fetch) is more efficient than two calls (check and fetch). – gregsdennis Mar 20 '22 at 00:48
  • I also thought that Objective C had a nice solution. Instead of methods, everything was modelled as messages with a possible response: `[myObject methodToCall]`. If `myObject` is null, then there is no one to receive the `methodToCall` message. The net effect is that the method call is silently ignored. It doesn't stop a value from being null; it just handles null better as part of the language. – gregsdennis Mar 20 '22 at 00:54

22 Answers22

141

The problem is that because in theory any object can be a null and toss an exception when you attempt to use it, your object-oriented code is basically a collection of unexploded bombs.

You're right that graceful error handling can be functionally identical to null-checking if statements. But what happens when something you convinced yourself couldn't possibly be a null is, in fact, a null? Kerboom. Whatever happens next, I'm willing to bet that 1) it won't be graceful and 2) you won't like it.

And do not dismiss the value of "easy to debug." Mature production code is a mad, sprawling creature; anything that gives you more insight into what went wrong and where may save you hours of digging.

BlairHippo
  • 8,663
  • 5
  • 41
  • 46
  • 28
    +1 Usually errors in production code _NEED_ to be identified as fast as possible so they can be fixed (usually data error). The easier it is to debug, the better. –  Oct 18 '10 at 20:46
  • 3
    +1 Very good points. I never even thought of it that way. (ie I've never really had this debate before, and always took null references as a given, as a C/C++/Java person). – Bobby Tables Oct 18 '10 at 21:13
  • 4
    This answer is the same if applied to either of the OP's examples. Either you test for error conditions or you don't, and you either handle them gracefully or you don't. In other words, removing null references from the language doesn't change the classes of errors that you will experience, it will just subtly change the manifestation of those errors. – dash-tom-bang Oct 18 '10 at 23:54
  • 25
    +1 for unexploded bombs. With null references, saying `public Foo doStuff()` doesn't mean "do stuff and return the Foo result" it means "do stuff and return the Foo result, OR return null, but you have no idea if that will happen or not." The result is that you have to null check EVERYWHERE to be certain. Might as well remove nulls and use a special type or pattern (like a value type) to indicate a meaningless value. – Matt Olenik Oct 19 '10 at 18:25
  • 3
    That argument can be applied to literally anything -- take for example `ints`. You expected there is non-negative number, and then you have negative one. And even worse, instead of kaboom your program continues to run making mess with data. Apart from that "kaboom" is actually a good sign of error -- because you are aware that something crashed. The worst errors are those undetected. – greenoldman May 22 '13 at 13:22
  • 1
    @dash-tom-bang, of course removing null refs changes the class of errors: the semantics in the system have changed. When devs no longer resort to null as a valid value in code they must pick another means of expressing their intent, but whatever it is, it won't be the same value throughout the system. Try it in your own code and you'll see. – Huperniketes May 26 '13 at 04:04
  • 16
    @greenoldman, your example is doubly effective to demonstrate our point in that the use of primitive types (whether null or integers) as semantic models is poor practice. If you have a type for which negative numbers isn't a valid answer, you should create a new class of types to implement the semantic model with that meaning. Now all the code to handle the modeling of that non-negative type is localized to its class, isolated from the rest of the system, and any attempt to create a negative value for it can be caught immediately. – Huperniketes May 26 '13 at 04:10
  • 3
    @Huperniketes, you are wrong, sorry. Take for example just math with 4 ops (add, sub, div, mul) with R domain. You cannot divide by zero, but this does not mean you should pull out zero itself, because zero could be a result of 3 others operations. And you cannot predict the outcome. Thus you have to live with "invalid" values, but you also have to check those before each operation. – greenoldman May 27 '13 at 17:43
  • 2
    @Huperniketes anyone is welcome to generate their own classes for whatever problem they want to solve. That's not really helpful when you come across a situation where the data is not in the expected set of possible values. E.g. if you don't allow negative values but do allow subtraction you have to handle the case of the right side being greater than the left in some way. Either you check at the point where the operation is performed or you hope some other code is doing it; if you don't check at all then who knows what you'll get? – dash-tom-bang May 29 '13 at 02:41
  • 4
    @dash-tom-bang, creating your own type isn't helpful for semantically modeling data to match the domain? You're actually claiming that? Software developers are the only ones who can ensure the system performs according to needed tolerances. Adding a test to your class for the occasions in which the right side is greater than the left side, and determining the proper course of action on those occasions is precisely what you're supposed to do, and the domain-specific class is where you do it. – Huperniketes Jun 06 '13 at 10:10
  • 11
    @greenoldman, you continue to prove the point that primitive types are inadequate for modeling. First it was over negative numbers. Now it's over the division operator when zero is the divisor. If you know you can't divide by zero then you **can** predict the outcome isn't going to be pretty, and are responsible for ensuring you test for, and provide the appropriate "valid" value for that case. Software developers are responsible for knowing the parameters in which their code operates, and coding appropriately. It's what distinguishes engineering from tinkering. – Huperniketes Jun 06 '13 at 10:17
  • There's no fundamental mathematics behind nullable references. Many languages have non-nullable references without many problems (I think the main problem is that can't be accessible before being initialized) – user253751 Mar 13 '14 at 08:55
  • +1 @Huperniketes: _you should create a new class of types to implement the semantic model with that meaning_. Mega-dittos. We have very wet, and too often broken, string manipulation _everywhere_. And a domain concept of grouping quantities by "box", "dozen", "each", etc. that has been broken for years that even after a deliberate fix effort there still is not a definitive class! I've observed a prevelent oddity: once a programmer hits primitive types for representing domain stuff, he backs up one thought-level, ram-dumps all OO thinking, and begins coding. – radarbob Jul 29 '14 at 17:33
  • About unexploded bombs. A method `int getAge()` is actually a disease that can be unleashed at any time, because it could return anything, even negative values, wreaking havoc without you ever even knowing about it, infecting anything that is derived from it -- arguably worse than the aforementioned bomb. I'd prefer a bomb, then I can fix it. There are solutions to this problem (the function could give some guarantees) -- those solutions can be equally easily applied to the `null` case. – john16384 Dec 09 '20 at 20:44
100

null is evil

There is a presentation on InfoQ on this topic: Null References: The Billion Dollar Mistake by Tony Hoare

Option type

The alternative from functional programming is using an Option type, that can contain SOME value or NONE.

A good article The “Option” Pattern that discuss the Option type and provide an implementation of it for Java.

I have also found a bug-report for Java about this issue: Add Nice Option types to Java to prevent NullPointerExceptions. The requested feature was introduced in Java 8.

HAEM
  • 503
  • 1
  • 5
  • 12
Jonas
  • 14,867
  • 9
  • 69
  • 102
  • 1
    +1 for the Option pattern article. I wasn't familiar with option types before this thread. It seems using option types and pattern matching has some of the same benefits I liked in `Note to self`'s answer. – Tim Goodman Oct 19 '10 at 17:10
  • 2
    Is option pattern different from `Nullable` in C# language? – Job Nov 14 '11 at 02:05
  • 5
    Nullable in C# is a similar concept but falls way short of being an implementation of the option pattern. The main reason is that in .NET System.Nullable is limited to value types. – MattDavey Jul 13 '12 at 17:47
  • 32
    +1 For the Option type. After getting familiar with Haskell's _Maybe_, nulls start looking weird... – Andres F. Jul 13 '12 at 17:53
  • 4
    It looks like some authors criticize things and fail to demonstrate the alternative (maybe they suppose it's obvious?). The Option type is great, but if I don't know it yet, a rant saying that null references are the "billion dollar mistake" without mentioning Option will be preaching to the converted. If I recall correctly, the famous "Goto considered harmful" did receive a reply along the lines of "If we sholdn't use GOTO, what are we supposed to use? Fortran's arithmetic IF?" – marcus Jul 14 '12 at 01:46
  • 11
    Developer can make error anywhere, so instead of null pointer exception you will get empty option exception. How the latter is better than the former? – greenoldman May 22 '13 at 13:24
  • 9
    @greenoldman there is no such thing as "empty option exception". Try inserting NULL values into database columns defined as NOT NULL. – Jonas May 22 '13 at 15:58
  • 2
    You pointed out Option pattern which clearly throws an exception (so entire "progress" is switching from bad NPE to good UnsupportedOperationException). As now you mention database, you will get error. And it is no surprise, you cannot escape from errors if you start using values (nulls, nones, whatever) out of domain of current computation. – greenoldman May 22 '13 at 21:36
  • 1
    I don't see a difference between representing "nothing" with a special value called "NONE" vs. using a special value called "null". Sure, you can use a design pattern like this to circumvent a language's treatment of nulls, but Hoare was talking about designing the language itself. I don't see how baking "NONE" into a language is different from baking "null" into it. – Justin Morgan Mar 12 '14 at 21:50
  • 41
    @greenoldman The problem with nulls is that _anything_ can be null, and as a developer you have to be extra cautious. A `String` type isn't _really_ a string. It's a `String or Null` type. Languages that fully adhere to the idea of option types disallow null values in their type system, giving a guarantee that if you define a type signature that requires a `String` (or anything else), it **must** have a value. The `Option` type is there to give a type-level representation of things that _may or may not_ have a value, so you know where you must explicitly handle those situations. – KChaloux Mar 13 '14 at 15:01
  • 2
    @greenoldman Option types lose almost all of their usefulness in dynamic languages, where you can't provide a non-null type guarantee in the type signature of a function. This is probably why the only languages to have really adopted Option types are statically checked languages. – KChaloux Mar 13 '14 at 15:03
  • 1
    Can you put some highlights from the presentation in the answer as to actually answer the question (as opposed to pretty much being a [link-only answer](http://meta.stackexchange.com/questions/8231/are-answers-that-just-contain-links-elsewhere-really-good-answers))? – Bernhard Barker May 03 '14 at 12:19
  • @KChaloux I realize this answer and it's string of comments is a bit old, but I had to get some clarification. Is the whole "point" of an Option Type to make it easier to distinguish a specific kind of error (a NullString, as opposed to just a generic 'null' exception)? If there's more to it... I think I missed something. – chamberlainpi Aug 07 '14 at 17:05
  • 3
    @bigp The point of _any_ type system is to help distinguish and disallow certain kinds of errors as early as possible (at compile time). If your language has an "Option" type, it's free to disallow any non-Option values from being null. There will no longer be any chance of a null exception caused by a function that accepts a `String`, because the `String` type cannot hold null. The type `Option` explicitly tells the type system "This may or may not have a value, and you need to deal with that" at compile time. It's a way to help you catch missing-value errors as early as possible. – KChaloux Aug 07 '14 at 19:27
  • 4
    @bigp Ultimately, null exceptions aren't usually exceptional cases. We shove "null" into places where we just don't have a value, and then we have no way of distinguishing whether or not our inputs are going to cause a crash when we try to access them. Using an `Option` type to model things that may or may not have values, and always assuming non-option types have values, is a much clearer way of distinguishing intent. – KChaloux Aug 07 '14 at 19:35
  • 1
    Isn't this a link-only answer ? – Tulains Córdova Aug 26 '14 at 16:30
  • For the video that you linked to, could you please provide a TL;DR of what he was trying to say? I tried watching that a few months ago, but even after 10 minutes, he keeps on rambling unfortunately. – Panzercrisis Aug 26 '14 at 20:03
  • @KChaloux I wish you'd write an answer to the question, your comments alone are more informative than this answer. – Asad Saeeduddin Oct 27 '14 at 23:32
  • 1
    The problem isn't NULL, it's that languages don't handle it well. SQL handles NULLs well; any query against a NULL doesn't return that row unless IS NULL is specified (niether SELECT * FROM x WHERE name = 'BOB' and SELECT * FROM x WHERE name <> 'BOB' will return a row with a null name). This works well and doesn't force tons of extra error handling code. myObject.myField should return a NULL if myObject is null, not throw and error; for value types return a certain value (such as zero for numerics). For purists, have an OPTION STRICT NULL to throw exceptions. – qwerty13579 Nov 21 '14 at 18:57
  • 1
    Java 1.8 introduced the `Optional` type: http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html – Ky - Mar 09 '15 at 18:00
  • 1
    Null being the problem is a very popular standpoint. Trouble is, it's mis-identifying the problem, unescapable nullability: http://programmers.stackexchange.com/a/253819 The Java enhancement request linked actually shows it well. – Deduplicator Apr 10 '15 at 21:54
  • 1
    @Deduplicator there are more issues: `null` is typeless. Also, `Maybe` and `Either` are monads and allow for nice composition of functions with graceful error handling and other higher-order function operations. – sara May 27 '16 at 20:57
  • 1
    Alternate perspective - `null` is an empty bucket. When I say `int? foo = null` in C#, I'm saying, "There's a bucket called `foo` that holds either a whole number, or nothing." We are saying that a particular variable can be empty. This sort of case is where I ally myself with the viewpoint that null is a failure of a type system, rather than 'evil'. While this answer about the nature of `null` raises good points, it's describing a symptom, not the source problem, which are type systems that sometimes have default values, but sometimes don't. In other words, the language is inconsistent. – Andrew Gray Apr 04 '17 at 20:10
  • @MattDavey The main reason is that in .NET System.Nullable is limited to value types This will change with c# 8 – John Demetriou Oct 14 '17 at 14:05
  • @MattDavey, with regards to `Nullable` you could say it's the reverse of the concept. They took something that cannot be null and wrapped it in something that can. – ChiefTwoPencils Jul 29 '20 at 16:41
  • 1
    This is something I think TypeScript and Rust do way better than most languages. In Rust you have that Option type built in. You HAVE to handle nulls. In Typescript you have to specify when a value can be null or undefined, then the compiler forces you to handle these cases. Really obvious stuff and I can't believe C# doesn't have it. – BenMcLean981 Dec 07 '22 at 15:50
53

There are several problems with using null references in code.

First, it's generally used to indicate a special state. Rather than defining a new class or constant for each state as specializations are normally done, using a null reference is using a lossy, massively generalized type/value.

Second, debugging code becomes more difficult when a null reference appears and you attempt to determine what generated it, which state is in effect and its cause even if you can trace its upstream execution path.

Third, null references introduces additional code paths to test.

Fourth, once null references are used as valid states for parameters as well as return values, defensive programming (for states caused by design) requires more null reference checking to be done in various places…just in case.

Fifth, the language's runtime is already performing type checks when it performs selector lookup on an object's method table. So you're duplicating effort by checking if the object's type is valid/invalid and then having the runtime check the valid object's type to invoke its method.

Why not use the NullObject pattern to take advantage of the runtime's check to have it invoke NOP methods specific to that state (conforming to the regular state's interface) while also eliminating all the extra checking for null references throughout your codebase?

It involves more work by creating a NullObject class for each interface with which you want to represent a special state. But at least the specialization is isolated to each special state, rather than the code in which the state might be present. IOW, the number of tests are reduced because you have fewer alternate execution paths in your methods.

Huperniketes
  • 2,205
  • 14
  • 17
  • 8
    ...because figuring out who generated (or rather, didn't bother to change or initialize) the garbage data that is filling your supposedly useful object is so much easier than tracking a NULL reference? I don't buy it, although I'm mostly stuck in statically typed land. – dash-tom-bang Oct 19 '10 at 00:00
  • Garbage data is so much rarer than null references, though. Particularly in managed languages. – Dean Harding Oct 19 '10 at 01:05
  • @dash-tom-bang, comparing their relative difficulty isn't relevant. Isn't it easier to debug garbage data when it isn't getting clobbered with null references along the way? And don't you figure having to mentally juggle the pathways through null-reference checks makes the debugging work harder? Why add the extra null-reference checking code to begin with? Because it's the way K&R did it? Why is it important to spend time and effort adding, testing and debugging null-reference checks when we don't have to? What are we buying with that investment? – Huperniketes Oct 19 '10 at 01:55
  • 6
    -1 for Null Object. – DeadMG Jul 13 '12 at 22:18
  • 5
    Points (4) and (5) are solid, but the NullObject is not a remedy. You will fall into even worse trap -- logic (workflow) errors. When you know EXACTLY the current situation, sure, it can help, but using it blindly (instead of null) will cost you more. – greenoldman May 22 '13 at 13:29
  • @greenoldman, you are incorrect. NullObject is the standard answer in an object-oriented system. It is null reference which is not a remedy. Use of null reference will cost more in maintenance and debugging effort. By eliminating the use of, and testing for, null references in our code we simplify the logic and code flow. We also restrict the special cases to the null object. So we'll always know the current situation because an instance of the correct class is handling it. – Huperniketes May 26 '13 at 02:56
  • Google Guava has a great generic class to solve this: https://code.google.com/p/guava-libraries/wiki/UsingAndAvoidingNullExplained – Jake Toronto Dec 17 '14 at 19:56
  • @Huperniketes: Objective-C combines both by allowing messages to be sent to nil pointers, resulting in no action and a zero return value. On the other hand, messages to the NSNull class are definitely _not_ no-ops; most will crash. – gnasher729 Apr 11 '15 at 11:45
  • 1
    @gnasher729, while Objective-C's runtime won't throw a null-pointer exception like Java and other systems do, it doesn't make using null references any better for the reasons I outlined in my answer. For example, if there's no navigationController in `[self.navigationController.presentingViewController dismissViewControllerAnimated: YES completion: nil];` the runtime treats it as a sequence of no-ops, the view isn't removed from the display, and you might sit in front of Xcode scratching your head for hours trying to figure out why. – Huperniketes Apr 13 '15 at 02:31
51

Nulls aren't so bad, unless you're not expecting them. You should need to explicitly specify in code that you're expecting null, which is a language-design problem. Consider this:

Customer? c = Customer.GetByLastName("Goodman");
// note the question mark -- 'c' is either a Customer or null
if (c != null)
{
    // c is not null, therefore its type in this block is
    // now 'Customer' instead of 'Customer?'
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

If you try to invoke methods on a Customer?, you should get a compile-time error. The reason more languages don't do this (IMO) is that they don't provide for the type of a variable to change depending on what scope it's in. If the language can handle that, then the problem can be solved entirely within the type system.

There's also the functional way to handle this problem, using option-types and Maybe, but I'm not as familiar with it. I prefer this way because correct code should theoretically only need one character added to compile correctly.

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
  • 13
    Wow, that's pretty cool. In addition to catching a lot more errors at compile time, it spares me from having to check the documentation to figure out whether `GetByLastName` returns null if not found (as opposed to throwing an exception) -- I can just see if it returns `Customer?` or `Customer`. – Tim Goodman Oct 18 '10 at 19:37
  • What language is this? Objects are nullable by design - no such thing as a 'Nullable', which ? is a shorthand for. Also, The Null Object Pattern is something completely different - please check GoF book or Huperniketes' answer. – JBRWilkinson Oct 18 '10 at 21:55
  • 14
    @JBRWilkinson: This is just a cooked-up example to show the idea, not an example of an actual implementation. I actually think it's a pretty neat idea. – Dean Harding Oct 18 '10 at 23:12
  • 1
    You're right that it's not the null object pattern, though. – Dean Harding Oct 18 '10 at 23:15
  • @Tim - Yes, documentation is a huge benefit of this approach. It's not just objects -- imagine a `String.Find` method which returns `int?` -- that's also much easier to use as well. – Note to self - think of a name Oct 18 '10 at 23:31
  • 15
    This looks like what Haskell calls a `Maybe` -- A `Maybe Customer` is either `Just c` (where `c` is a `Customer`) or `Nothing`. In other languages, it's called `Option` -- see some of the other answers on this question. – Tyler Aug 22 '11 at 18:38
  • There's still an issue with this example. Just because you know that c is not null, you still can't be sure that c.FirstName is not null and c.LastName is not null. – Simon B Mar 13 '14 at 12:12
  • 2
    @SimonBarker: you *can* be sure if `Customer.FirstName` is of type `String` (as opposed to `String?`). That's the real benefit - only variables/properties that have a meaningful *nothing* value should be declared as nullable. – Yogu May 02 '14 at 16:17
  • _Nulls aren't so bad,_ [they're just coded that way](https://www.youtube.com/watch?v=TO_OA9tQDAY) – radarbob Jul 29 '14 at 17:57
  • 1
    As a concrete example, [Kotlin](https://kotlinlang.org/) implements exactly this. See also [Null Safety in Kotlin](https://kotlinlang.org/docs/reference/null-safety.html). – r0estir0bbe Apr 22 '17 at 09:34
  • C# 8 introduced nullable reference types. It's an opt-in mechanism to make reference types non-nullable by default, unless they are declared nullable. – Domi Oct 27 '21 at 14:04
21

There's plenty of excellent answers that cover the unfortunate symptoms of null, so I'd like to present an alternative argument: Null is a flaw in the type system.

The purpose of a type system is to ensure that the different components of a program "fit together" properly; a well-typed program can't "off the rails" into undefined behavior.

Consider a hypothetical dialect of Java, or whatever your preferred statically-typed language is, where you can assign the string "Hello, world!" to any variable of any type:

Foo foo1 = new Foo();  // Legal
Foo foo2 = "Hello, world!"; // Also legal
Foo foo3 = "Bonjour!"; // Not legal - only "Hello, world!" is allowed

And you can check variables like so:

if (foo1 != "Hello, world!") {
    bar(foo1);
} else {
    baz();
}

There's nothing impossible about this - someone could design such a language if they wanted to. The special value need not be "Hello, world!" - it could've been the number 42, the tuple (1, 4, 9), or, say, null. But why would you do this? A variable of type Foo should only hold Foos - that's the whole point of the type system! null is not a Foo any more than "Hello, world!" is. Worse, null is not a value of any type, and there's nothing you can do with it!

The programmer can never be sure that a variable actually holds a Foo, and neither can the program; in order to avoid undefined behavior, it has to check variables for "Hello, world!" before using them as Foos. Note that doing the string check in the previous snippet doesn't propagate the fact that foo1 is really a Foo - bar will likely have its own check as well, just to be safe.

Compare that to using a Maybe/Option type with pattern matching:

case maybeFoo of
 |  Just foo => bar(foo)
 |  Nothing => baz()

Inside the Just foo clause, both you and the program know for sure that our Maybe Foo variable truly does contain a Foo value - that information is propagated down the call chain, and bar doesn't need to do any checks. Because Maybe Foo is a distinct type from Foo, you're forced to handle the possibility that it could contain Nothing, so you can never by blindsided by a NullPointerException. You can reason about your program much more easily and the compiler can omit null checks knowing that all variables of type Foo really do contain Foos. Everyone wins.

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
Doval
  • 15,347
  • 3
  • 43
  • 58
  • What about null-like values in Perl 6? They are still nulls, except they are always typed. Is it still a problem you can write `my Int $variable = Int`, where `Int` is null value of type `Int`? – Konrad Borowski Apr 25 '14 at 12:00
  • @xfix I'm not familiar with Perl, but as long as you don't have a way of enforcing `this type only contains integers` as opposed to `this type contains integers and this other value`, you'll have a problem every time you *only* want integers. – Doval Apr 25 '14 at 12:29
  • 1
    +1 for pattern matching. With the number of answers above that mention Option types, you'd think somebody would have mentioned the fact that a simple language feature like pattern matchers can make them much more intuitive to work with than nulls. – Jules Apr 10 '15 at 22:30
  • 1
    I think monadic binding is even more useful than pattern matching (although of course bind for Maybe is implemented using pattern matching). I think it's kind of amusing seeing languages like C# putting special syntax in for lots of things (`??`, `?.`, and so on) for things that languages like haskell implement as library functions. I think it's way more neat. `null`/`Nothing` propagation isn't "special" in any way, so why should we have to learn more syntax to have it? – sara Jun 13 '16 at 06:29
19

A null pointer is a tool

not an enemy. You just ought to use 'em right.

Think just minute about how long it takes to find and fix a typical invalid pointer bug, compared to locating and fixing a null pointer bug. It's easy to check a pointer against null. It is a PITA to verify whether or not a non-null pointer points to valid data.

If still not convinced, add a good dose of multithreading to your scenario, then think again.

Moral of the story: Don't throw the kids with the water. And don't blame the tools for the accident. The man who invented the number zero long time ago was quite clever, since that day you could name the "nothing". The null pointer is not so far away from that.


EDIT: Although the NullObject pattern seems to be a better solution than references that may be null, it introduces problems on its own:

  • A reference holding a NullObject should (according to the theory) do nothing when a method is called. Thus, subtle errors can be introduced due to errorneously unassigned references, which now are guaranteed to be non-null (yehaa!) but perform an unwanted action: nothing. With an NPE it is next to obvious where the problem lies. With a NullObject that behaves somehow (but wrong), we introduce the risk of errors being detected (too) late. This is not by accident similar to an invalid pointer problem and has similar consequences: The reference points to something, that looks like valid data, but isn't, introduces data errors and can be hard to track down. To be honest, in these cases I would without a blink prefer an NPE that fails immediately, now, over a logical/data error which suddenly hits me later down the road. Remember the IBM study about cost of errors being a function of at which stage they are detected?

  • The notion of doing nothing when a method is called on a NullObject does not hold, when a property getter or a function is called, or when the method returns values via out. Let's say, the return value is an int and we decide, that "do nothing" means "return 0". But how can we be sure, that 0 is the right "nothing" to be (not) returned? After all, the NullObject should not do anything, but when asked for a value, it has to react somehow. Failing is not an option: We use the NullObject to prevent the NPE, and we surely won't trade it against another exception (not implemented, divide by zero, ...), do we? So how do you correctly return nothing when you have to return something?

I can't help, but when someone tries to apply the NullObject pattern to each and every problem, it looks more like trying to repair one mistake by doing another one. It is without much doubt a good and useful solution in some cases, but it surely isn't the magic bullet for all cases.

JensG
  • 2,423
  • 1
  • 19
  • 25
  • Can you present an argument for why `null` should exist? It's possible to handwave just about any language flaw with "it's not a problem, just don't make a mistake". – Doval Mar 13 '14 at 19:52
  • To what value would you set an invalid pointer? – JensG Mar 13 '14 at 19:59
  • Well, you don't set them to any value, because you shouldn't allow them at all. Instead, you use a variable of a different type, perhaps called `Maybe T` which may contain either `Nothing`, or a `T` value. The key thing here is that you're forced to check if a `Maybe` really does contain a `T` before you're allowed to use it, and provide a course of action in case it doesn't. And because you've disallowed invalid pointers, now you never have to check anything that's not a `Maybe`. – Doval Mar 13 '14 at 20:05
  • Ok, you say I'm forced to check it, but even if you bring guns, I still might forget to check. What happens in that case (except pulling the trigger)? And how different is an `InvalidTypeException` compared to a `NPE` to the typical user? And why is a `Nothing` proxy instance simulating a "valid invalid object" better than a real `nothing`? – JensG Mar 13 '14 at 20:08
  • @JensG The point is, you're forced to check it by the *compiler*. – Tim Goodman Mar 13 '14 at 20:13
  • Why can't the compiler force me the same way to check against null? – JensG Mar 13 '14 at 20:14
  • @JensG You can't forget to check it. A `Maybe Gun` is not a `Gun`. You can't call `Gun` methods on it, or pass it to methods expecting a `Gun`. Attempting to do so will result in a compile time error, no different from trying to call `List` methods on a `Gun`. You **have** to unwrap it and determine whether it contains `Nothing` or a `Gun`. – Doval Mar 13 '14 at 20:15
  • @JensG The compiler can't force you to check against `null` because any variable could be `null`. Keeping track of whether a varible is `null` or not would require executing the program. The other alternative is to throw a compile time error any time it can't figure out whether something contains `null` or not, which would obviously rule out tons of perfectly valid programs. – Doval Mar 13 '14 at 20:18
  • With the Option type, a "String that could be null" is a different type than a `String`, so the compiler can recognize when you're using a "String that could be null" as if it's a `String` without first doing the null check and extracting the `String`. If *all* strings can be null, then there's no reliable way for the compiler to tell. Heck, maybe the value of the String is coming from a closed-source third-party library... What's the compiler to do, read the documentation? – Tim Goodman Mar 13 '14 at 20:24
  • So the compiler can force me to unwrap the `maybe` and check whether it is `Nothing`. But enforcing the same check against `null` is "technically" impossible, because it ruins existing code. Is that what you are saying? – JensG Mar 13 '14 at 20:29
  • Well, the "same check against null is 'technically' impossible" may be impossible at compile time. If I have a variable Thing t, make some function calls, then reference `t.doSomething()`, the compiler can't know whether t is valid. So `t` might have lost its type in execution, and become a sort of untyped reference (i.e. to null). – sea-rob Mar 13 '14 at 20:31
  • Won't compile any longer. I can't call `t.Something()` anymore, because now `t` is a `maybe`. The compiler forces to me unwrap it and check it is not `null` ... ah, sorry, no - `null` is not allowed anymore for some obscure reason. Of course I have to check against not being a `Nothing`. – JensG Mar 13 '14 at 21:00
  • The compiler can force you to unwrap the `maybe` in the same way it prevents you from assigning an expression of compile-time type `String` to a variable of compile-time type `Int`. Whereas, with the `null` value, the compiler has to know the value of the expression to know you did something illegal, not just the compile-time type of the expression. – Tim Goodman Mar 13 '14 at 21:02
  • 1
    @JensG in your latest example, does the compiler make you do the null check before *every* call to `t.Something()`? Or does it have some way of knowing you already did the check, and not making you do it again? What if you did the check before you passed `t` in to this method? With the Option type, the compiler knows whether you've done the check yet or not by looking at the type of `t`. If it's an Option, you haven't checked it, whereas if it's the unwrapped value then you have. – Tim Goodman Mar 13 '14 at 21:04
  • While I agree with the wrapping, I still don't see the difference of a pointer/reference pointing to an "invalid valid proxy" object and pointing to, well, just plain nothing - which techically is an address area that is protected by the OS against read/write accesses (at least with WinNT), so in fact even `null` *is* pointing to something - the invalid valid Nothing, the untouchable taboo memory. – JensG Mar 13 '14 at 21:06
  • Agree that null object is better. Ultimately, though, pointers/references need to go into a context that separates an "actual" pointer/reference from nullity. That involves "lifting" the pointer into a context that differentiates *known valid*, or *known no*. An old-school approach would be to carry two variables, pointer and pointer-is-set. But emerging techniques (and language mechanisms) formalize that context as a Type of its own. The benefit is that the compiler can work with that "lifted" type and provide nice compile-time guarantees. – sea-rob Mar 14 '14 at 05:45
  • 1
    What does you null object return when asked for a value? – JensG Mar 14 '14 at 09:17
  • @JensG You're missing the point. A variable of type `Foo` should only contain `Foo`s. The language shouldn't be allowing you to write uninitialized variables in the first place. From a computational perspective, *it makes no sense*. – Doval Mar 14 '14 at 13:13
  • `Maybe drink; if( IsSunday()) drink = GiveMeADrink();` - damn, doesn't compile. How can I solve this? – JensG Mar 14 '14 at 14:46
  • @JensG Making up a C#-like syntax for Maybes, something like `Maybe drink = IsSunday() ? Maybe.Just(GiveMeADrink()) : Maybe.None();` would be about right. – Jules Apr 10 '15 at 22:34
  • Couldn't agree more with your arguments @JensG, this all seems like syntactic sugar for toast as opposed to cooked bread. Another article pointed me to https://en.wikipedia.org/wiki/Monad_%28functional_programming%29#Maybe_monad though, where I can see more use when doing pure functional programming. – Paul Carroll Jun 02 '16 at 02:14
15

(Throwing my hat in the ring for an old question ;) )

The specific problem with null is that it breaks static typing.

If I have a Thing t, then the compiler can guarantee I can call t.doSomething(). Well, UNLESS t is null at runtime. Now all bets are off. The compiler said it was OK, but I find out much later that t does NOT in fact doSomething(). So rather than being able to trust the compiler to catch type errors, I have to wait until runtime to catch them. I might as well just use Python!

So, in a sense, null introduces dynamic typing into a statically typed system, with expected results.

The difference between that an divide by zero or log of negative, etc. is that when i = 0, it's still an int. The compiler can still guarantee its type. The problem is that the logic mis-applies that value in a way that isn't permitted by the logic ... but if the logic does that, that's pretty much the definition of a bug.

(The compiler can catch some of those problems, BTW. Like flagging expressions like i = 1 / 0 at compile time. But you can't really expect the compiler to follow into a function and ensure that the parameters are all consistent with the function's logic)

The practical problem is that you do a lot of extra work, and add null checks to protect yourself at runtime, but what if you forget one? The compiler stops you from assigning:

String s = new Integer(1234);

So why should it allow assignment to a value (null) that will break de-references to s?

By mixing "no value" with "typed" references in your code, you're putting an extra burden on the programmers. And when NullPointerExceptions happen, tracking them down can be even more time consuming. Rather than relying on static typing to say "this is a reference to something expected," you're letting the language say "this may well be a reference to something expected."

sea-rob
  • 6,841
  • 1
  • 24
  • 47
  • 4
    In C, a variable of type `*int` either identifies an `int`, or `NULL`. Likewise in C++, a variable of type `*Widget` either identifies a `Widget`, a thing whose type derives from `Widget`, or `NULL`. The problem with Java and derivatives like C# is that they use the storage location `Widget` to mean `*Widget`. The solution is not to pretend that a `*Widget` shouldn't be able to hold `NULL`, but rather to have a proper `Widget` storage-location type. – supercat Aug 28 '14 at 02:25
8

Null references are a mistake because they allow non-sensical code:

foo = null
foo.bar()

There are alternatives, if you leverage the type system:

Maybe<Foo> foo = null
foo.bar() // error{Maybe<Foo> does not have any bar method}

The generally idea is to put the variable in a box, and the only thing you can do is unboxing it, preferably enlisting the compiler help like proposed for Eiffel.

Haskell has it from scratch (Maybe), in C++ you can leverage boost::optional<T> but you can still get undefined behaviour...

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
Matthieu M.
  • 14,567
  • 4
  • 44
  • 65
8

And what's the alternative?

Optional types and pattern matching. Since I don't know C#, here is a piece of code in a fictional language called Scala# :-)

Customer.GetByLastName("Goodman")    // returns Option[Customer]
match
{
    case Some(customer) =>
    Console.WriteLine(customer.FirstName + " " + customer.LastName + " is awesome!");

    case None =>
    Console.WriteLine("There was no customer named Goodman.  How lame!");
}
Deduplicator
  • 8,591
  • 5
  • 31
  • 50
fredoverflow
  • 6,854
  • 8
  • 39
  • 46
7

The problem with nulls is that languages that allow them pretty much force you into programming defensively against it. It takes a lot of effort (far more than trying to use defensive if-blocks) to make sure that

  1. the objects you expect them not to be null are indeed never null, and
  2. that your defensive mechanisms indeed deal with all potential NPEs effectively.

So, indeed, nulls end up being a costly thing to have.

luis.espinal
  • 2,560
  • 1
  • 20
  • 17
  • 1
    It might be helpful if you included an example where it takes a lot more effort to deal with nulls. In my admittedly over-simplified example the effort is about the same either way. I'm not disagreeing with you, I just find specific (pseudo)code examples paint a clearer picture than general statements. – Tim Goodman Oct 18 '10 at 20:10
  • 2
    Ah, I didn't explain myself clearly. It is not that it is more difficult to deal with nulls. It is that it is extremely difficult (if not impossible) to guarantee that **all** access to potentially null references are already safe-guarded. That is, in a language that allows null references, it is just impossible to guarantee that a piece of code of arbitrary size is free of null pointer exceptions, not without through some major difficulties and efforts. That's what make null references an expensive thing. It is extremely expensive to guarantee complete absence of null pointer exceptions. – luis.espinal Oct 19 '10 at 00:52
5

The problem isn't so much null, it's that you can't specify a non-null reference type in a lot of modern languages.

For example, your code might look like

public void MakeCake(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    egg.Crack();
    MixingBowl.Mix(egg, flour, milk);
    // etc
}

// inside Mixing bowl class
public void Mix(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    //... etc
}

When class references get passed around, defensive programming encourages you to check all parameters for null again, even if you just checked them for null right beforehand, especially when building reusable units under test. The same reference could easily be checked for null 10 times across the codebase!

Wouldn't it be better if you could have a normal nullable type when you get the thing, deal with null then and there, then pass it as a non-nullable type to all your little helper functions and classes without checking for null all the time?

Thats the point of the Option solution - not to allow you to switch on error states, but to allow design of functions which implicitly cannot accept null arguments. Whether or not the "default" implementation of types are nullable or non-nullable is less important than the flexibility of having both tools available.

http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake

This is also listed as the #2 most voted feature for C# -> https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
Michael Parker
  • 101
  • 1
  • 2
  • I think another important point about Option is that it's a monad (and therefore also an endofunctor), so you can compose complex computations over value streams containing optional types without explicitly checking for `None` each step of the way. – sara May 23 '16 at 10:35
4

The issue to what degree your programming language attempts to prove your program's correctness before it runs it. In a statically typed language you prove that you have the correct types. By moving to default of non-nullable references (with optional nullable references) you can eliminate many of the cases where null is passed and it shouldn't be. The question is whether the extra effort in handling non-nullable references is worth the benefit in terms of program correctness.

Winston Ewert
  • 24,732
  • 12
  • 72
  • 103
4

Null is evil. However, the lack of a null can be a greater evil.

The problem is that in the real world you often have a situation where you do not have data. Your example of the version without nulls can still blow up--either you made a logic mistake and forgot to check for Goodman or perhaps Goodman got married between when you checked and when you looked her up. (It helps in evaluating the logic if you figure Moriarty is watching every bit of your code and trying to trip you up.)

What does the lookup of Goodman do when she's not there? Without a null you have to return some sort of default customer--and now you're selling stuff to that default.

Fundamentally, it comes down to whether it's more important that the code work no matter what or that it work correctly. If improper behavior is preferable to no behavior then you don't want nulls. (For an example of how this might be the right choice consider the first launch of the Ariane V. An uncaught /0 error caused the rocket to make a hard turn and the self-destruct fired when it came apart due to this. The value it was trying to calculate actually no longer served any purpose the instant the booster was lit--it would have made orbit despite the routine returning garbage.)

At least 99 times out of 100 I would choose to use nulls. I would jump for joy at note to self's version of them, though.

Loren Pechtel
  • 3,371
  • 24
  • 19
  • 1
    This is a good answer. The null construct in programming is not the problem, it's all the instances of null values that are. If you make a default object, eventually all your nulls are going to be replaced with those defaults and your UI, DB and everywhere in between will be filled with those instead, which are probably no more useful. But you'll get to sleep at night because at least your program is not crashing I guess. – Chris McCall Apr 13 '15 at 19:19
  • 2
    You assume that `null` is the only (or even a preferable) way of modeling the absence of a value. – sara May 23 '16 at 11:07
  • So null reference exceptions are a good thing because they let you know there is a problem with the data earlier in the process rather than later after the data problem leads to other problems that make tracking it down very confusing. I think our culture just likes to avoid immediate pain by putting it off and experiencing greater pain. – OCDev Sep 12 '20 at 15:57
2

No.

A language's failure to account for a special value like null is the language's problem. If you look at languages like Kotlin or Swift, which force the writer to explicitly deal with the possibility of a null value at every encounter, then there is no danger.

In languages like the one in question, the language allows null to be a value of any-ish type, but does not provide any constructs for acknowledging that later, which leads to things being null unexpectedly (especially when passed to you from somewhere else), and thus you get crashes. That is the evil: letting you use null without making you deal with it.

Ky -
  • 525
  • 5
  • 17
  • 1
    Very good point. Recent version of C# use ? and ! constructs to handle nullability, and catch possible null error at compile time. – JotaBe May 09 '23 at 17:21
  • Exactly, @JotaBe! And if the compiler mandated them, then you would never be surprised by a `null`-based error again – Ky - May 15 '23 at 17:37
  • Yes, forcing it it's something you can configure at the project level. This allows to migrate old projects to newer C# versions, and "procrastinate" updating them to correctly handle the nulls at compile time. When you enable it, the static analysis allows you to find all the possible null related problems. – JotaBe Jun 14 '23 at 09:57
1

Optimise for the most common case.

Having to check for null all the time is tedious - you want to be able to just get hold of the Customer object and work with it.

In the normal case, this should work just fine - you do the look up, get the object and use it.

In the exceptional case, where you're (randomly) looking up a customer by name, not knowing whether that record/object exists, you'd need some indication that this failed. In this situation, the answer is to throw a RecordNotFound exception (or let the SQL provider beneath do this for you).

If you're in a situation where you don't know whether you can trust the data coming in (the parameter), perhaps because it was entered by a user, then you could also provide the 'TryGetCustomer(name, out customer)' pattern. Cross-reference with int.Parse and int.TryParse.

JBRWilkinson
  • 6,759
  • 29
  • 36
  • I would assert that you never know whether you can trust the data coming in because it's coming in to a function and is a nullable type. Code could be refactored to make the input totally different. So if there's a null possible you always gotta check. So you end up with a system where every variable is checked for null every single time before it's accessed. The cases where it's not checked become the failure percentage for your program. – Chris McCall Apr 10 '15 at 18:52
0

Exceptions are not eval!

They are there for a reason. If your code is running bad, there is a genius pattern, called exception, and it tells you that some thing is wrong.

By avoiding using null objects you are hiding part of those exceptions. I am not spiking about OP example where he convert null pointer exception to well typed exception, this might actually be good thing as it increase readability. How ever when you take the Option type solution as @Jonas pointed out, you are hiding exceptions.

Than in your production application, instead of exception to be thrown when you are clicking on button to select empty option, nothing just happening. Instead of null pointer exception would be thrown and we would probably get a report of that exception(as in many production applications we have such mechanism), and than we could actually fix it.

Making your code bulletproof by avoiding exception is bad idea, making you code bulletproof by fixing exception, well this the path that I would choose.

Ilya Gazman
  • 285
  • 5
  • 14
  • 2
    I know quite a bit more about the Option type now than when I posted the question a few years ago, since I now use them regularly in Scala. The point of Option is that it makes assigning `None` to something that isn't an Option into a compile-time error, and likewise using an `Option` as if it has a value without first checking it is a compile-time error. Compile errors are certainly nicer than run-time exceptions. – Tim Goodman Mar 13 '14 at 19:55
  • For example: You can't say `s: String = None`, you have to say `s: Option[String] = None`. And if `s` is an Option, you can't say `s.length`, however you can check that it has a value and extract the value at the same time, with pattern matching: `s match { case Some(str) => str.length; case None => throw RuntimeException("Where's my value?") }` – Tim Goodman Mar 13 '14 at 19:56
  • Unfortunately in Scala you still *can* say `s: String = null`, but that's the price of interoperability with Java. We generally disallow that sort of thing in our Scala code. – Tim Goodman Mar 13 '14 at 19:57
  • 2
    However I agree with the general comment that exceptions (used properly) are not a bad thing, and "fixing" an exception by swallowing it is generally a terrible idea. But with the Option type, the goal is to turn exceptions into compile-time errors, which are a better thing. – Tim Goodman Mar 13 '14 at 20:01
  • @TimGoodman is it scala only tactic or can it be used in other languages like Java and ActionScript 3? Also it would be nice if you could edit your answer with full example. – Ilya Gazman Mar 15 '14 at 10:55
  • It's common in functional programming languages, like Haskell, Scala, F#, etc. The accepted answer links to an implementation in Java, but in a functional language like Scala, the syntax to use it is easier. If `s` is an `Option[String]` and you want to get its length, you can say `s match { case Some(str) => str.length; case None => /* Handle this case however you want */ }` With a string, `s.length()` risks a `NullPointerException`. With an `Option[String]`, `s.length()` is caught at compile time, because `Option[String]` doesn't have a `length`. – Tim Goodman Mar 16 '14 at 02:49
  • `Option` is all about making clear to the programmer using your code that he or she *must* handle the `null` case. – Jake Toronto Dec 17 '14 at 19:50
  • @JakeToronto this is not about handling, but how you choose to handle it. – Ilya Gazman Dec 17 '14 at 22:52
0

Yes, NULL is a terrible design, in object-oriented world. In a nutshell, NULL usage leads to:

  • ad-hoc error handling (instead of exceptions)
  • ambiguous semantic
  • slow instead of fast failing
  • computer thinking vs. object thinking
  • mutable and incomplete objects

Check this blog post for a detailed explanation: http://www.yegor256.com/2014/05/13/why-null-is-bad.html

yegor256
  • 233
  • 3
  • 8
0

The central problem of NULL is that it makes system unreliable. In 1980 Tony Hoare in the paper dedicated to his Turing Award wrote:

And so, the best of my advice to the originators and designers of ADA has been ignored. …. Do not allow this language in its present state to be used in applications where reliability is critical, i.e., nuclear power stations, cruise missiles, early warning systems, antiballistic missile defense systems. The next rocket to go astray as a result of a programming language error may not be an exploratory space rocket on a harmless trip to Venus: It may be a nuclear warhead exploding over one of our own cities. An unreliable programming language generating unreliable programs constitutes a far greater risk to our environment and to our society than unsafe cars, toxic pesticides, or accidents at nuclear power stations. Be vigilant to reduce the risk, not to increase it.

ADA language has changed a lot since that, however such problems still exist in Java, C# and many other popular languages.

It is developer's duty to create contracts between a client and a supplier. For example, in C#, as in Java, you can use Generics to minimise the impact of Null reference by creating readonly NullableClass<T> (two Options):

class NullableClass<T>
{
     public HasValue {get;}
     public T Value {get;}
}

and then use it as

NullableClass<Customer> customer = dbRepository.GetCustomer('Mr. Smith');
if(customer.HasValue){
  // one logic with customer.Value
}else{
  // another logic
} 

or use two options style with C# extension methods:

customer.Do(
      // code with normal behaviour
      ,
      // what to do in case of null
) 

The difference is significant. As a client of a method you know what to expect. A team can have the rule:

If a class is not of type NullableClass then it's instance must be not null.

The team can strengthen this idea by using Design by Contract and static checking at compilation time, e.g with precondition:

function SaveCustomer([NotNullAttribute]Customer customer){
     // there is no need to check whether customer is null 
     // it is a client problem, not this supplier
}

or for a string

function GetCustomer([NotNullAndNotEmptyAttribute]String customerName){
     // there is no need to check whether customerName is null or empty 
     // it is a client problem, not this supplier
}

These approach can drastically increase application reliability and software quality. Design by Contract is a case of Hoare logic, which was populated by Bertrand Meyer in his famous Object-Oriented Software Construction book and Eiffel language in 1988, but it is not in use invalidly in modern software crafting.

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
Artur A
  • 487
  • 4
  • 4
0

I have one simple objection against null:

It breaks the semantics of your code completely by introducing ambiguity.

Oftentimes you expect the result to be of a certain type. This is semantically sound. Say, you asked the database for a user with a certain id, you expect the resut to be of a certain type (=user). But, what if there is no user of that id? One (bad) solution is: adding a null value. So the result is ambigous: either it is a user or it is null. But null is not the expected type. And this is where the code smell begins.

In avoiding null, you make your code semantically clear.

There are always ways around null: refactoring to collections, Null-objects, optionals etc.

Thomas Junk
  • 9,405
  • 2
  • 22
  • 45
0

Nulls are problematic because they must be explicitly checked for, yet the compiler is unable to warn you that you forgot to check for them. Only time-consuming static analysis can tell you that. Fortunately, there are several good alternatives.

Take the variable out of scope. Way too often, null is used as a place holder when a programmer declares a variable too early or keeps it around too long. The best approach is simply to get rid of the variable. Don't declare it until you have a valid value to put in there. This isn't as difficult a restriction as you might think, and it makes your code a lot cleaner.

Use the null object pattern. Sometimes, a missing value is a valid state for your system. The null object pattern is a good solution here. It avoids the need for constant explicit checks, but still allows you to represent a null state. It is not "papering over an error condition," as some people claim, because in this case a null state is a valid semantic state. That being said, you shouldn't use this pattern when a null state isn't a valid semantic state. You should just take your variable out of scope.

Use a Maybe/Option. First of all, this lets the compiler warn you that you need to check for a missing value, but it does more than replace one type of explicit check with another. Using Options, you can chain your code, and carry on as if your value exists, not needing to actually check until the absolute last minute. In Scala, your example code would look something like:

val customer = customerDb getByLastName "Goodman"
val awesomeMessage =
  customer map (c => s"${c.firstName} ${c.lastName} is awesome!")
val notFoundMessage = "There was no customer named Goodman.  How lame!"
println(awesomeMessage getOrElse notFoundMessage)

On the second line, where we're building the awesome message, we don't make an explicit check if the customer was found, and the beauty is we don't need to. It's not until the very last line, which might be many lines later, or even in a different module, where we explicitly concern ourselves with what happens if the Option is None. Not only that, if we had forgotten to do it, it wouldn't have type checked. Even then, it's done in a very natural way, without an explicit if statement.

Contrast that with a null, where it type checks just fine, but where you have to make an explicit check on every step of the way or your entire application blows up at runtime, if you're lucky during a use case your unit tests exercise. It's just not worth the hassle.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
-1

If it will be semantically possible for a storage location of reference or pointer type to be accessed before code has been run to compute a value for it, there isn't any perfect choice as to what should happen. Having such locations default to null pointer references which may be freely read and copied, but which will fault if dereferenced or indexed, is one possible choice. Other choices include:

  1. Have locations default to a null pointer, but do not allow code to look at them (or even determine that they are null) without crashing. Possibly provide an explicit means of setting a pointer to null.
  2. Have locations default to a null pointer, and crash if an attempt is made to read one, but provide a non-crashing means of testing whether a pointer holds null. Also provide an explict means of setting a pointer to null.
  3. Have locations default to a pointer to some particular compiler-supplied default instance of the indicated type.
  4. Have locations default to a pointer to some particular program-supplied default instance of the indicated type.
  5. Have any null-pointer access (not just dereferencing operations) call some program-supplied routine to supply an instance.
  6. Design the language semantics such that collections of storage locations will not exist, except an an inaccessible compiler temporary, until such time as initialization routines have been run on all members (something like an array constructor would have to be supplied with either a default instance, a function that would return an instance, or possibly a pair of functions--one to return an instance and one that would be called on previously-constructed instances if an exception occurs during the construction of the array).

The last choice could have some appeal, especially if a language included both nullable and non-nullable types (one could call the special array constructors for any types, but one only be required to call them when creating arrays of non-nullable types), but would probably not have been feasible around the time null pointers were invented. Of the other choices, none seem more appealing than allowing null pointers to be copied but not dereferenced or indexed. Approach #4 might be convenient to have as an option, and should be fairly cheap to implement, but it should certainly not be the only option. Requiring that pointers must by default point to some particular valid object is far worse than having pointers default to a null value which can be read or copied but not dereferenced or indexed.

supercat
  • 8,335
  • 22
  • 28
-1

Basically the central idea is that null pointer reference issues should get caught at compile time instead of run time. So if you are writing a function that takes some object and you don't want anyone to call it with null references then type system should allow you to specify that requirement so that compiler can use it to provide guarantee that call to your function would never compile if caller does not satisfy your requirement. This can be done in many ways, for example, decorating your types or using option types (also called nullable types in some languages) but obviously that's lot more work for compiler designers.

I think it is understandable why in 1960s and 70s, compiler designer didn't go all-in to implement this idea because machines were limited in resources, compilers needed to be kept relatively simple and no one really knew how bad this would turn out 40 years down the line.

Shital Shah
  • 181
  • 2
  • 6