30

Attribution: This grew out of a related P.SE question

My background is in C / C++, but I have worked a fair amount in Java and am currently coding C#. Because of my C background, checking passed and returned pointers is second-hand, but I acknowledge it biases my point of view.

I recently saw mention of the Null Object Pattern where the idea is that an object is always returned. Normal case returns the expected, populated object and the error case returns empty object instead of a null pointer. The premise being that the calling function will always have some sort of object to access and therefore avoid null access memory violations.

  • So what are the pros / cons of a null check versus using the Null Object Pattern?

I can see cleaner calling code with the NOP, but I can also see where it would create hidden failures that don't otherwise get raised. I would rather have my application fail hard (aka an exception) while I'm developing it than have a silent mistake escape into the wild.

  • Can't the Null Object Pattern have similar problems as not performing a null check?

Many of the objects I have worked with hold objects or containers of their own. It seems like I would have to have a special case to guarantee all of the main object's containers had empty objects of their own. Seems like this could get ugly with multiple layers of nesting.

  • Not the "error case" but "in all cases a valid object". –  Jun 08 '12 at 18:20
  • @ThorbjørnRavnAndersen - good point, and I edited the question to reflect that. Please let me know if I am still misstating NOP's premise. –  Jun 08 '12 at 18:44
  • 7
    The short answer is that "null object pattern" is a misnomer. It's more accurately called the "null object anti-pattern". You're usually better off with an "error object" -- a valid object that implements the class' entire interface, but any use of it causes loud, sudden death. – Jerry Coffin Jun 08 '12 at 18:52
  • 1
    @JerryCoffin that case should be indicated by an exception. The idea is that you _never_ can get a null and hence do not need to check for null. –  Jun 08 '12 at 19:55
  • 1
    I dont like this "pattern". But possibly it is rooted in overconstrained relational databases, where it is easier to refer to special "null rows" in foreign keys during staging of editable data, before final update when all foreign keys are perfectly not null. –  Jun 09 '12 at 03:04
  • It sometimes makes sense to put error handling code into your Null Object such that yes it is valid to call methods on a Null Object, but doing so will log the erroneous behavior or throw a known exception type which consolidates your error handling as opposed to having separate duplicated code at every null check. – YoungJohn May 02 '14 at 19:28
  • @JerryCoffin What, why would you do that instead of throwing an exception right off? there is a valid use of nop, when it actually is ok to have a do nothing object. for example we have customer logic provided by customer specific implementations. But its not required to have a plugin so if one isn't found for the customer the null plugin is returned. This prevents someone from forgetting a null check somewhere and blowing up the app. – Andy May 03 '14 at 00:16

8 Answers8

28

You wouldn't use a Null Object Pattern in places where null (or Null Object) is returned because there was a catastrophic failure. In those places I would continue to return null. In some cases, if there's no recovery, you might as well crash because at least the crash dump will indicate exactly where the problem occurred. In such cases when you add your own error handling, you are still going to kill the process (again, I said for cases where there's no recovery) but your error handling will mask very important information that a crash dump would've provided.

Null Object Pattern is more for places where there's a default behavior that could be taken in a case where object isn't found. For example consider the following:

User* pUser = GetUser( "Bob" );

if( pUser )
{
    pUser->SetAddress( "123 Fake St." );
}

If you use NOP, you would write:

GetUser( "Bob" )->SetAddress( "123 Fake St." );

Note that this code's behavior is "if Bob exists, I want to update his address". Obviously if your application requires Bob to be present, you don't want to silently succeed. But there are cases where this type of behavior would be appropriate. And in those cases, doesn't NOP produce a much cleaner and concise code?

In places where you really can't live without Bob, I would have GetUser() throw an application exception (i.e. not access violation or anything like that) that would be handled at a higher level and would report general operation failure. In this case, there's no need for NOP but there's also no need to explicitly check for NULL. IMO, those checks for NULL, only make the code bigger and take away from readability. Check for NULL is still the right design choice for some interfaces, but not nearly as many as some people tend to think.

DXM
  • 19,932
  • 4
  • 55
  • 85
  • 5
    Agree with the use case for null object patterns. But why return null when encountering catastrophic failures? Why not throw exceptions? – Apoorv Jun 09 '12 at 00:58
  • 3
    @MonsterTruck: reverse that. When I write code, I make sure to validate majority of input received from external components. However, between internal classes if I write code such that a function of one class will never return NULL, then on the calling side I will not add null check just to "be more safe". The only way that value could possibly be NULL is if some catastrophic logic error occurred in my application. In that case, I want my program to crash exactly on that spot because then I get a nice crash dump file and reverse back the stack and object state to identify the logic error. – DXM Jun 09 '12 at 06:05
  • 2
    @MonsterTruck: by "reverse that" I meant, don't explicitly return NULL when you identify catastrophic error, but expect NULL to only occur because of some unforeseen catastrophic error. – DXM Jun 09 '12 at 06:09
  • I now understand what you meant by catastrophic error --something that causes the object allocation itself to fail. Right? Edit: Cannot do @ DXM because your nickname is just 3 chars long. – Apoorv Jun 09 '12 at 07:01
  • @MonsterTruck: weird about "@" thing. I know other people have done it before. I wonder if they tweaked the website recently. It doesn't have to be allocation. If I write a class and the intention of the API is to always return a valid object, then I wouldn't have the caller do a null check. But catastrophic error could be anything like programmer error (a typo that caused NULL to be returned), or maybe some race condition that erased an object before its time, or maybe some stack corruption. Essentially, any unexpected code path that led to NULL being returned. When that happens you want... – DXM Jun 09 '12 at 07:18
  • ... to crash. Pragmatic Programmer has a more extended explanation: http://books.google.com/books?id=5wBQEp6ruIAC&pg=PA120&dq=%22crash+early%22+pragmatic+programmer&hl=en&sa=X&ei=BfnST6aACaaO6gHYzZiqAw&ved=0CDkQ6AEwAA#v=onepage&q=%22crash%20early%22%20pragmatic%20programmer&f=false – DXM Jun 09 '12 at 07:20
  • Code Contracts, if your language has something similar allow you to specify your method won't return null and if it does, regardless of exit point, a contract exception is thrown. – Andy May 03 '14 at 00:20
  • See my answer here for [when throwing exceptions is appropriate](http://stackoverflow.com/questions/410558/why-are-exceptions-said-to-be-so-bad-for-input-validation/24677533#24677533) – Frederik Krautwald Mar 27 '15 at 13:03
  • In 99.99999% of the cases, a null reference is returned because of bad implementation. Null references are supposed to be addressed at design time and not handled at runtime. If you code for cases where a catastrophic failure can occur, you BETTER return something more meaningful than a Null Pointer, and let your application either shutdown gracefully on its own, or allow a human operator to implement proper shutdown procedure. Will you be OK being a pilot and have your avionics throw an NPE because a catastrophic hydraulics failure? No. That would be absurd. – hfontanez May 01 '20 at 01:20
  • @hfontanez - you just replied to a question from 8 years ago. A part of me is telling me to let this question go back to obscurity of time, yet... here I am. I know what you said, you believe to be very true. Would you allow that a part of what you said could be considered by other engineers to simply be your opinion? I could have misread your comment, but reading it, qq for you... is it possible you are mixing what is an opinion of one engineer (possibly even many, but I feel it is very far from *all*) with something you might believe to be a universal truth? – DXM May 02 '20 at 21:17
  • @DXM Except this "opinion" is backed by many experts on the field. It has been written about in many books, none of which indicate that null pointers are a good thing. The idea that having pointers in your program pointing to an area outside your program's address space being a good thing is actually pretty absurd. – hfontanez May 04 '20 at 06:00
  • @hfontanez - you are not wrong. In fact you are right. And I've read many of the books as well. And I've done this for 20+ years and spent my share of aggressively defending my points of view as truth. And I've worked with and written my share of less than perfect code. And I've also learned when to admit no matter how strong I feel on particular opinion or point of view, to always communicate with other people with acceptance that they will often have views/opinions different from "my local truth" – DXM May 04 '20 at 12:26
  • I don't think GetUser should return a null object to indicate the user was not found. You could return an Optional. But a null user is not something that makes a lot of sense. It makes more sense for a logger for example - if logging is not enabled, instead of returning null from GetLogger, you return a logger that doesn't do anything. – user253751 Sep 29 '20 at 11:16
10

So what are the pros / cons of a null check versus using the Null Object Pattern?

Pros

  • A null check is better since it solves more cases. Not all objects have a sane default or no-op behavior.
  • A null check is more solid. Even objects with sane defaults are used in places where the sane default isn't valid. Code should fail close to the root cause if it's going to fail. Code should fail obviously if it is going to fail.

Cons

  • Sane defaults usually result in cleaner code.
  • Sane defaults usually result in less catastrophic errors if they manage to get into the wild.

This last "pro" is the main differentiator (in my experience) as to when each should be applied. "Should the failure be noisy?". In some cases, you want a failure to be hard and immediate; if some scenario that should never happen somehow does. If a vital resource wasn't found... etc. In some cases, you're okay with a sane default since it's not really an error: getting a value from a dictionary, but the key is missing for example.

Like any other design decision, there are upsides and downsides depending on your needs.

Telastyn
  • 108,850
  • 29
  • 239
  • 365
  • 1
    In Pros section: Agree with the first point. The second point is the designers fault because even null can be used where it shouldn't be. Does not say anything bad about the pattern just reflects on the developer who used the pattern incorrectly. – Apoorv Jun 09 '12 at 01:01
  • What is more catstrophic failure, not being able to send money or sending (and thus no longer having) money without recipient receiving them and not known it before explicit check? – user470365 Sep 10 '12 at 15:27
  • Cons: Sane defaults can be used to build new objects. If a non-null object is returned, some of its attributes can be modified when creating another object. If a null object is returned, it can be used to create a new object. In both cases, the same code works. No need for separate code for copy-modify and new. This is part of the philosophy that every line of code is another place for bugs; reducing the lines reduces bugs. – shawnhcorey Dec 16 '15 at 15:27
  • @shawnhcorey - what? – Telastyn Dec 16 '15 at 15:35
  • A null object should be usable as though it was a real object. For example, consider IEEE's NaN (not a number). If an equation goes wrong, it is returned. And it can be used for further calculations since any operation on it will return NaN. It simplifies the code since you don't have to check for NaN after every arithmetic operation. – shawnhcorey Dec 16 '15 at 20:38
  • To say that a null check is better and more solid is absolutely false. Null checks forces you to branch off your code which adds more complexity. If you implement the Null Object pattern correctly, you eliminate the need for logic evaluations and simply just use the object. What can be more solid than that? – hfontanez May 01 '20 at 01:15
7

I'm not a good source of objective information, but subjectively:

  • I don't want my system to die, ever; to me it's a sign of badly designed or incomplete system; complete system should handle all possible cases and never get into unexpected state; to achieve this I need to be explicit in modeling all the flows and situations; using nulls is not anyhow explicit and groups a lot of different cases under one umrella; I prefer NonExistingUser object to null, I prefer NaN to null, ... such approach allows me to be explicit about those situations and their handling in as much details as I want, while null leaves me with only null option; in such environment like Java any object can be null so why would you prefer to hide in that generic pool of cases also your specific case?
  • null implementations have drastically different behavior than object and are mostly glued to the set of all objects (why?why?why?); to me such drastical difference seems like a result of design of nulls to be indication of error in which case system most likely will die (I'm supprised so many people actually prefer their system to die in the above posts) unless explicitly handled; to me that's a flawed approach in it's root - it allows the system to die by default unless you explicitly have taken care about it, that's not very safe right? but in any case it makes impossible to write a code that treats the value null and object in same manner - only few basic built in operators (assign, equal, param, etc.) will work, all other code will just fail and you need two completely different paths;
  • using Null Object scales better - you can put as much information and structure into it as you want; NonExistingUser is a very good example - it can contain an email of the user you tried to recieve and suggest to create new user based on that information, while with the null solution I would need to think how to keep the email that people attempted to access close to the null result handling;

In environment like Java or C# it won't give you the main benefit of explicitness - safety of solution being complete, cause you can still recieve null in the place of any object in the system and Java has no means (except custom annotations for Beans, etc.) to guard you from that except explicit ifs. But in the system free of null implementaitons, approaching problem solution it in such a way (with objects representing exceptional cases) gives all the benefits mentioned. So try to read about something other than mainstream languages, and you'll find yourself changing your ways of coding.

In general Null/Error objects/return types is considered to be very solid error handling strategy and quite mathematically sound, while exceptions handling strategy of Java or C goes like only one step above "die ASAP" strategy - so basically leave all the burden of unstability and unpredictability of the system on developer or maintainer.

There are also monads that can be considered superior to this strategy (also superior in complexity of understanding at first), but those are more about the cases when you need to model external aspects of type, while Null Object models internal aspects. So NonExistingUser is probably better modeled as monad maybe_existing.

And lastly I don't think that there is any connection between Null Object pattern and silently handling error situations. It should be other way around - it should force you to model each and every error case explicitly and handle it accordingly. Now of course you might decide to be sloppy (for what ever reason) and skip handling the error case explicitly but handle it generically - well that was your explicit choice.

  • 11
    The reason I like my application to fail when something unexpected happens is - that something unexpected happened. How did it happen? Why? I get a crash dump and I can investigate and fix a potentially dangerous issue, rather than let it get hidden in the code. In C#, I can of course catch all unexpected exceptions to try and recover the application, but even then I want to log the place where the problem happened and find out if it's something that needs separate handling (even if it is "don't log this error, it's actually expected and recoverable"). – Luaan Nov 27 '13 at 11:19
3

I think it's interesting to note that the viability of a Null Object seems to be a little different depending on whether or not your language is statically typed. Ruby is a language that implements an object for Null (or Nil in the language's terminology).

Instead of getting back a pointer to uninitialized memory, the language will return the object Nil. This is facilitated by the fact that the language is dynamically typed. A function isn't guaranteed to always return an int. It can return Nil if that's what it needs to do. It becomes more convoluted and difficult to do this in a statically typed language, because you naturally expect null to be applicable to any object that can be called by reference. You'd have to start implementing null versions of any object that could be null (or go the Scala/Haskell route and have some kind of "Maybe" wrapper).

MrLister brought up the fact that in C++ you're not guaranteed to have an initialize reference/pointer when you first create it. By having a dedicated null object instead of a raw null pointer, you can get some nice additional features. Ruby's Nil includes a to_s (toString) function that results in blank text. This is great if you don't mind getting a null return on a string, and want to skip reporting it without crashing. Open Classes also allow you to manually override the output of Nil if need be.

Granted this can all be taken with a grain of salt. I believe that in a dynamic language, the null object can be a great convenience when used correctly. Unfortunately, getting the most out of it might require you to edit it's base functionality, which could make your program hard to understand and maintain for people used to working with its more standard forms.

KChaloux
  • 5,773
  • 4
  • 35
  • 34
1

For some additional viewpoint, have a look at Objective-C, where instead of having a Null object the value nil kind-of works like an object. Any message sent to a nil object has no effect and returns a value of 0 / 0.0 / NO (false) / NULL / nil, whatever the return value type of the method is. This is used as a very pervasive pattern in MacOS X and iOS programming, and usually methods will be designed so that a nil object returning 0 is a reasonable result.

For example, if you want to check whether a string has one or more characters, you could write

aString.length > 0

and get a reasonable result if aString == nil, because a non-existing string doesn't contain any characters. People tend to take a while to get used to it, but it would probably take me a while to get used to checking for nil values everywhere in other languages.

(There's also a singleton object of class NSNull which behaves quite different and isn't used very much in practice).

gnasher729
  • 42,090
  • 4
  • 59
  • 119
  • Objective-C made the Null Object/Null Pointer thing really blurry. `nil` get `#define`'d to NULL and behaves like a null object. That is a runtime feature while in C#/Java null pointers emits errors. – Maxthon Chan May 02 '14 at 17:18
1

I think that I am one of the few, if not the only one, who has proposed that Null Object implementation examples provided on the web are incomplete. My solution makes the case that null objects need to be immutable. Otherwise, your solution is poor. You should not allow your null objects to mutate. The main reason is that, since the pattern calls for the Null Object to be a subtype of the object being evaluated, it would pass the instanceof check, and therefore, the potential exists that the user will not be able to tell the difference between a "real" object and a null object. To avoid this, you will make the class immutable setting the internal components of the class to empty values. There are a few other caveats, like setting numeric (primitive) values. As a designer, you must make conscious decisions as to what constitutes good default values for those null objects.

I posted a video here: https://youtu.be/cqVqOpdPMHA

And I wrote about it on my blog here: https://www.professorfontanez.com/2020/04/the-beauty-of-null-object-pattern.html

hfontanez
  • 211
  • 2
  • 6
0

Don't use either if you can avoid it. Use a factory function returning an option type, a tuple, or a dedicated sum type. In other words, represent a potentially-defaulted value with a different type from a guaranteed-not-to-be-defaulted value.

First, let's list the desiderata then think about how we can express this in a few languages (C++, OCaml, Python)

  1. catch unwanted use of a default object at compile time.
  2. make it obvious whether a given value is potentially-defaulted or not while reading the code.
  3. choose our sensible default value, if applicable, once per type. Definitely don't pick a potentially different default at every call site.
  4. make it easy for static analysis tools or a human with grep to hunt for potential mistakes.
  5. For some applications, the program should continue normally if unexpectedly given a default value. For other applications, the program should halt immediately if given a default value, ideally in an informative way.

I think the tension between the Null Object Pattern and the null pointers comes from (5). If we can detect errors early enough, though, (5) becomes moot.

Let's consider this language by language:

C++

In my opinion, a C++ class should generally be default-constructible since it eases interactions with libraries and makes it easier to use the class in containers. It also simplifies inheritance since you don't need to think about which superclass constructor to call.

However, this means that you can't know for sure whether a value of type MyClass is in the "default state" or not. Besides putting in a bool nonempty or similar field to surface default-ness at runtime, the best you can do is produce new instances of MyClass in a way that compels the user to check it.

I'd recommend using a factory function that returns a std::optional<MyClass>, std::pair<bool, MyClass> or an r-value reference to a std::unique_ptr<MyClass> if possible.

If you want your factory function to return some kind of "placeholder" state that's distinct from a default-constructed MyClass, use a std::pair and be sure to document that your function does this.

If the factory function has a unique name, it's easy to grep for the name and look for sloppiness. However, it is hard to grep for cases for where the programmer should have used the factory function but didn't.

OCaml

If you are using a language like OCaml, then you can just use an option type (in the OCaml standard library) or an either type (not in the standard library, but easy to roll your own). Or a defaultable type (I'm making up the term defaultable).

type 'a option =
    | None
    | Some of 'a

type ('a, 'b) either =
    | Left of 'a
    | Right of 'b

type 'a defaultable =
    | Default of 'a
    | Real of 'a

A defaultable as shown above is better than a pair because the user must pattern match to extract the 'a and can't simply ignore the first element of the pair.

The equivalent of the defaultable type as shown above can be used in C++ using a std::variant with two instances of the same type, but parts of the std::variant API are unusable if both types are the same. Also it's a weird use of std::variant since its type constructors are unnamed.

Python

You don't get any compile-time checking anyway for Python. But, being dynamically typed, there generally aren't circumstances where you need a placeholder instance of some type to placate the compiler.

I would recommend just throwing an exception when you would be forced to create a default instance.

If that's not acceptable, I would recommend creating a DefaultedMyClass that inherits from MyClass and returning that from your factory function. That buys you flexibility in terms of "stubbing out" functionality in defaulted instances if you need to.

Greg Nisbet
  • 288
  • 1
  • 9
0

If you are using Typescript with --strictNullChecks set, and a variable may contain null, then you have to declare this in its type. e.g. let foo: MyType | null;If you use an null object instead, the variable can just have the normal type, which is a little nicer, I think. I personally think null objects are pretty handy in front end development where you're dealing with a lot of data from external sources which you have little or no control over and you probably don't want to error out and break the UI.