Non-nullable Reference Types
A few days ago on InfoQ I saw a link to this blog post offering a solution to "The Billion Dollar Mistake": allowing null references. The proposal, non-nullable reference types, is designed to replace this:
With something like this:
Of course complications arise with arrays of non-nullable reference types and with the methods that feature non-nullable output parameters. There are workarounds for those issues, but the result is a convoluted syntax and unnecessary overhead. But I think the main problem is that we're looking at the problem from the wrong direction.
I think we have to accept that reference types are nullable, period. There will always be a case in which a reference has not yet been or may never be initialized. However, we can stipulate that a caller must provide a properly initialized reference when invoking a function, and we can promise that the results of our function will not be null under normal program flow.
This isn't a new idea. "Design by Contract" has been around since the 80s in the Eiffel programming language. More recently it has reemerged with Spec# and Code Contracts tool set from Microsoft. And it works. For example, this:
... will cause the static analysis tool to verify that all calls to that method within scope have verified that argument reference is not null before making the call. And it doesn't require new keywords, new syntax, compiler enhancements, or breaking changes to existing code.
While I would like to see pre- and post-conditions baked into the language itself (ala Eiffel or Spec#), I think that the "Billion Dollar Problem" is largely solved with the tools we already have.
/// <exception cref="ArgumentNullException"> /// Thrown if <paramref name="argument"/> is null. /// </exception> public void Method(SomeType argument) { if (argument == null) { throw ArgumentNullException("argument"); } // Do stuff... }
With something like this:
public void Method(SomeType! argument) { // Do stuff... }
Of course complications arise with arrays of non-nullable reference types and with the methods that feature non-nullable output parameters. There are workarounds for those issues, but the result is a convoluted syntax and unnecessary overhead. But I think the main problem is that we're looking at the problem from the wrong direction.
I think we have to accept that reference types are nullable, period. There will always be a case in which a reference has not yet been or may never be initialized. However, we can stipulate that a caller must provide a properly initialized reference when invoking a function, and we can promise that the results of our function will not be null under normal program flow.
This isn't a new idea. "Design by Contract" has been around since the 80s in the Eiffel programming language. More recently it has reemerged with Spec# and Code Contracts tool set from Microsoft. And it works. For example, this:
public void Method(SomeType argument) { Contract.Requires(argument != null); // Do something... }
... will cause the static analysis tool to verify that all calls to that method within scope have verified that argument reference is not null before making the call. And it doesn't require new keywords, new syntax, compiler enhancements, or breaking changes to existing code.
While I would like to see pre- and post-conditions baked into the language itself (ala Eiffel or Spec#), I think that the "Billion Dollar Problem" is largely solved with the tools we already have.
Hey Jesse,
ReplyDeleteI had forgotten how great code contracts are. I am trying to incorporate them into our code base now. Have you any tips or caveats you have run into while using them?
p.s. Shouldn't your first code snippet say:
if (argument == null){
throw ArgumentNullException("argument");
}
Good catch! I'm so used to typing Contract.Requires(argument != null) that I let that one slip by. The best tip I can offer is to keep the contracts to null/not null checks. Anything more complex than that should probably be encapsulated in a value objects (in the DDD sense of the word, not necessarily value *types* i.e structs). I have an entry on that here: http://jsweetland.blogspot.com/2011/12/value-objects-and-code-contracts.html.
ReplyDelete