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.

7
  • Not the "error case" but "in all cases a valid object".
    – user1249
    Commented Jun 8, 2012 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.
    – user53019
    Commented Jun 8, 2012 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. Commented Jun 8, 2012 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.
    – user1249
    Commented Jun 8, 2012 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.
    – user7071
    Commented Jun 9, 2012 at 3:04

8 Answers 8

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.

13
  • 5
    Agree with the use case for null object patterns. But why return null when encountering catastrophic failures? Why not throw exceptions?
    – Apoorv
    Commented Jun 9, 2012 at 0: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
    Commented Jun 9, 2012 at 6: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
    Commented Jun 9, 2012 at 6: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
    Commented Jun 9, 2012 at 7: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
    Commented Jun 9, 2012 at 7:18
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.

6
  • 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
    Commented Jun 9, 2012 at 1: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
    Commented Sep 10, 2012 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. Commented Dec 16, 2015 at 15:27
  • @shawnhcorey - what?
    – Telastyn
    Commented Dec 16, 2015 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. Commented Dec 16, 2015 at 20:38
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.

1
  • 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
    Commented Nov 27, 2013 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.

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).

1
  • 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. Commented May 2, 2014 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

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.

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.