5

One of the problems I have with null references is that they may not be exceptional. In my current position, there are few requirements and you are lucky if conventions are followed. This means being unable to handle a request is a frequent occurrence and throwing an exception is too slow (think millions of requests). I am in charge of writing an api for handling these requests and I am trying to decide how to handle failures.

Our code is written in C# and I am a huge fan of LINQ. I know that LINQ handles this problem by providing two methods "Function()" and "FunctionOrDefault()". By returning the default value (usually null), we make it very easy for the developer to shoot himself in the foot. On the other hand, throwing an exception is unacceptable for us. There is also the "TryParse" pattern, but that doesn't lend its self well to lambda expressions.

I recently discovered the Option type in F# and how it requires explicit handling of both the Some and None cases or your assembly will not compile. This seems like a much better solution to the null value problem than leaving it up to the developer to remember to implement error handling.

I could implement an option type that will not both compile and return the desired value unless both Some and None cases are handled. The contract for my implementation would look like this:

Option<SomeClass> option = new SomeClass { Value = 5 }.ToOption(); // Extension method
int value = option.Some(x => x.Value).None(x => x.Default(25)); // Other helper methods provided as well for None cases.

So my question is, if I have the ability to require explicit null reference handling, are there any downsides to this approach? Would it be better to stick with conventions rather than try to solve this problem upfront to prevent future hidden bugs?

4
  • I'm confused about your example. What is x in those lambdas? Is it SomeClass or Option<SomeClass>. And why isn't the Node() lambda simply () => 25 (or maybe even just 25, without the lambda)?
    – svick
    Mar 23, 2013 at 12:06
  • @svick I could implement None in those ways and everything would be fine. My first instinct was to provide an object with helper methods that made it very explicit to the reader what was happening. It also makes it easier to throw exceptions since you can't one line those in lambdas. Mar 23, 2013 at 14:34
  • Sure you can, it just requires three more characters: () => { throw new Exception(); }.
    – svick
    Mar 23, 2013 at 15:25
  • @svick Your right, "can't" was too strong a word. I meant inconvenient. I like not having to type brackets. Mar 23, 2013 at 16:00

2 Answers 2

4

You don't need it. Here's my reasoning:

  1. You may be underestimating the consumers of your API. Every textbook I ever read on programming languages contained a section on how to check for null values. Many languages include null coalescing or null checking features + syntactic sugar.

  2. You need to prove to me (another programmer) why you need this convention.
    If you can't prove that it provides a significant workflow or debugging benefit, then you need to use a simpler approach. Especially if the simpler approach is already taught by the community at large.

    var value = proxy.FirstOrDefault();
    if (value != null) { ...
    
  3. Consider is the meaningfulness of the idioms you create. Is the code above more or less meaningful than your ToOption() method? If they are about the same, don't do it. Only do work that provides a significant benefit. This is the basic idea behind YAGNI.

  4. Consider a similar debate in the nodejs community about the usefulness of enforcing the Promise/A pattern for libraries. While not equivalent, you can see that there is some discontinuity on the issue.

  5. Lastly, I would err on the side of allowing your consumers to choose the level of complexity they want. If your ToOption approach is clearly superior, consumers will implement it anyway.

2
  • DocBrown provides good advice, but this is the kind of answer I was looking for. I suppose I will just use my nifty Option type in my own internal code :). Mar 26, 2013 at 2:10
  • +1 for giving a really good explanation about this code which just "smelled" overengineered to me, but I could not tell all those reasons why.
    – Doc Brown
    Mar 26, 2013 at 7:14
3

By returning the default value (usually null), we make it very easy for the developer to shoot himself in the foot.

I disagree, and I guess you are on the road to over-engineering. If you have a function returning null and the user of that function handles that properly, everything is fine. If the user does not (which means he tries to use the returned object), he will get an exception, so no error will be masked.

Only problem here is that the user might not get a clear error message. But that would be also the case when you use any other kind of error signaling mechanics and the user of your functions forgets "translating" technical error messages to clear-text messages which show the root cause of a problem.

Better invest the time to teach your team mates to write tests for revealing missing error handling.

6
  • I don't understand why this would be over-engineering. Mar 23, 2013 at 13:10
  • I completely agree that writing tests is a much better solution. However, I can't force consumers of an api to write tests at compile time. By requiring null handling before returning the desired value, it is an explicit reminder to write a test for handling null references. I've been bitten by "FunctionOrDefault" a few too many times. Mar 23, 2013 at 14:48
  • @mortalapeman: you did not get my point - missing error handling for null refs leads to an uncaught exception, missing error handling for exceptions obviously, too. You cannot have been "bitten by FirstOrDefault" more than by First without Default, that is technically impossible in C#.
    – Doc Brown
    Mar 23, 2013 at 15:34
  • I understand your point and it is perfectly valid. It is idiomatic C# to do as you are suggesting. I mean that one can write FirstOrDefault knowing that null is not an exceptional case, but then fail to provide null handling at the time for one reason or another. Granted that is the developers own fault, but why not make it harder to make that mistake? Mar 23, 2013 at 16:07
  • One of the advantages of having an exception mechanism is that it can free one from cluttering up the main-line code with logic to check errors which it wouldn't be able to handle except by aborting the current operation. While an NRE is often annoyingly uninformative, trying to turn NREs into ANEs can cause issues of its own. For example, if the IEqualityComparer associated with a Dictionary wouldn't have trouble with a null key, there's no reason the Dictionary should either, but since the Dictionary doesn't know whether its IEqualityComparer will work with null keys...
    – supercat
    Feb 24, 2014 at 17:16

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.