Should We Return Null From Our Methods?
I read a interesting article recently about the potential perils of Null Check Hell. The author’s suggestion? Stop allowing any of your methods to return null, ever. No nulls returned, no null checks necessary. Problem solved, right?
Abby Fitchner is a self-described “hacker chick, software engineer, and ScrumMaster Extraordinare” from New Hampshire. Being a born-and-bred New Englander myself (Portland, Maine) who sometimes feels out of place here in the Midwest, I have a soft spot for the Red Sox, whoopie pies, Moxie, and gerdy bloggers from back East.
That graphic to the right? I kifed it right off her blog (being from New England, she’ll know exactly what that means).
Anyway, this post if not really about Abby, but about an interesting idea she proposes in a recent blog post of hers. To paraphrase:
I can't help but feel my code is getting harder and harder to read as I wade through an ever increasing number of if != null checks before finding my way to the real logic that I actually care about...
Doesn't it feel like there has to be a better way?!
Well, actually yes, there is.
Stop allowing any of your methods to return nulls.
EVER.
No nulls returned, no null checks necessary. Thank you for reading.
Wow, what a revolutionary idea. What would you give to never have to look at one of these cursed things again?
Then I thought about this a little further. I’m wondering, is this possible? Or necessary? Or even… desirable?
Let’s step back for a minute and consider the case in which a method is expected to return something, yet for some reason it cannot return a valid result (assume we’re talking about a reference type here). As I see it, unless I’m missing the boat, there are three distinct possibilities. The method can:
- Throw an exception.
- Return an empty “default” instance of the object.
- Return null.
In her blog post, Abby proposes the second option:
Of course, even after all of this cleaning, we'll still have some methods that just want to return null. But, even so, a lot we can handle by simply returning an empty instance of the object or collection where we previously would have returned null.
Let’s nail this down to earth with a concrete example. Consider the .NET Membership.GetUser(string userName) method. If a user with the name of userName is not found in the data store, what should this method do? What behavior should a programmer using this framework reasonably expect? In other words, what is the most meaningful outcome?
Option 1: Throw an exception
Okay, for the purposes of this little thought-experiment, pretend you are Scott Guthrie (I do this all the time) and that you are responsible for writing the code to implement Membership.GetUser. Perhaps your first inclination is to throw an exception. You really want to call it ComeOnThatUserIsSoNotHereException, but wisely opt instead to call it MembershipUserNotFoundException.
But then you think, “Hmmm… is throwing an exception really the best way to go here?” When considering throwing an exception from a method, there is one question you need to ask yourself:
Is the condition that causes the proposed exception truly exceptional?
Throwing an exception is acceptable when a method cannot provide its defined functionality. However, if a given condition could happen naturally in the course of a function’s normal operation, then you shouldn’t throw an exception in response to it.
In our example case, you asked the method to find a user with a given user name. It looked, and it didn’t find that user. That the method failed to find the user is due to the fact that the user does not exist, not because the method couldn’t provide its normal function. In fact, the method performed perfectly well! Therefore, throwing an exception simply doesn’t make sense in this case.
Option 2: Return an empty instance
What exactly would an “empty” instance of MembershipUser look like? Most of the properties of this class are simple types like dates, booleans, and strings. So, an empty instance of MembershipUser would presumably provide string.Empty for the string properties, DateTime.MinValue for date properties, false for booleans, and Guid.Empty for the ProviderUserKey. (How the method would “guess” which of your membership providers you intended to use would be a pretty sticky problem, but let’s ignore that issue for now.)
Now, let’s use this functionality in very simple administrative function – changing a user’s email address.
protected void btnChangeEmail_Click(object sender, EventArgs e) { MembershipUser user = Membership.GetUser(txtUsername.Text); // if the user was not found, user would be an "empty" instance user.Email = txtEmail.Text; Membership.UpdateUser(user); }
I probably don’t have to tell you what’s wrong with this picture. The user’s intent in invoking this method is to work with the user who’s name is the value contained in txtUsername.Text. If there is no such user, the method simple provides us with an “empty” user, which is clearly not the caller’s intent at all. We’re not interested in working with some meaningless empty user, but of course, the method has no way of knowing this, and proceeds merrily along its way. Naturally this user doesn’t exist in the data store, so when the UpdateUser method is called, the whole thing flops like a pancake. Whoops! Looks like we’re going to have to check for that.
protected void btnChangeEmail_Click(object sender, EventArgs e) { MembershipUser user = Membership.GetUser(txtUsername.Text); // if the user was not found, user would be an "empty" instance if (user.UserName != string.Empty) { user.Email = txtEmail.Text; Membership.UpdateUser(user); } }
As you can see, we certainly haven’t saved ourselves any work here. Sure, we’re not checking for null, but in this case we still have to check for empty. This example points out the problem with the “empty instance” approach: in many cases, working with an empty instance of an object is just as logically invalid as attempting to use a null reference. This means you still have to perform the requisite checks, which is what you were trying to avoid in the first place.
Option 3: Return null
At this point, perhaps it would be helpful to stop and think about exactly what we mean by the concept of null.
Take a look at the NullReferenceException message in that all-too-familiar screenshot above: Object reference not set to an instance of an object. The means, quite literally, that you have attempted to refer to an object for which a reference does not exist. Move along folks, nothing to see here. There is no object to refer to. So, to say that a variable is “null” is to say that it does not refer to any object.
This is exactly why the Membership.GetUser method returns null when the user is not found in the data store. The method is telling us, “I tried to do as you asked, but I came up empty-handed. That user does not exist. There is no user object for me to return.”
When the object that is meant to be returned by a method does not exist, returning null is the only semantically meaningful thing you can do.
We’re not talking about cases where the argument supplied to the GetUser method is null, or isn’t a valid username, or the provider can’t connect to the database. All of those conditions throw exceptions, because in these cases the GetUser function cannot complete. But in the case where the function does complete, yet still cannot return the intended value, returning null is exactly the right thing to do.
protected void btnChangeEmail_Click(object sender, EventArgs e) { MembershipUser user = Membership.GetUser(txtUsername.Text); if (user != null) { user.Email = txtEmail.Text; Membership.UpdateUser(user); } }
Summary
Never, ever returning nulls from your methods will definitely alleviate the need for null checking; in much the same way that toe fungus can be prevented by removing your toes. But both cases represent an extreme solution that probably isn’t the wisest course of action.
If you find yourself writing a seemingly endless stream of null checks in a given method, you can use refactoring to abstract them away, which can make your methods easier to read. Amy shows some clever examples of this on her blog.
In my opinion, the best thing you can do to mitigate the pain of null checking is to properly and copiously document your methods. The .NET Framework uses XML documentation to clearly indicate if and when a method can return null, and you should do the same. This allows the documentation to show up directly in Intellisense (if you’re using the Visual Studio IDE), which informs the developers on your team exactly when and where null checks are necessary, and when they aren’t. This goes a long way toward alleviating “null check paranoia”, and keeps the dreaded NullReferenceException from biting you in the keyster at runtime.
Subscribe to this blog for more cool content like this!
You've been kicked (a good thing) - Trackback from DotNetKicks.com
Thank you for submitting this cool story - Trackback from DotNetShoutout
Pingback from Arjan`s World » LINKBLOG for June 7, 2009