Pros/cons of different methods for testing preconditions?
Asked Answered
S

2

17

Off the top of my head, I can think of 4 ways to check for null arguments:

Debug.Assert(context != null);
Contract.Assert(context != null);
Contract.Requires(context != null);
if (context == null) throw new ArgumentNullException("context");

I've always used the last method, but I just saw a code snippet that used Contract.Requires, which I'm unfamiliar with. What are the advantages/disadvantages of each method? Are there other ways?


In VS2010 w/ Resharper,

  • Contract.Assert warns me that the expression is always true (how it knows, I'm not quite sure... can't HttpContext be null?),
  • Contract.Requires gets faded out and it tells me the compiler won't invoke the method (I assume because of the former reason, it will never be null), and
  • if I change the last method to context != null all the code following gets faded out and it tells me the code is heuristically unreachable.

So, it seems the last 3 methods have some kind of intelligence built into the VS static checker, and Debug.Assert is just dumb.

Semitrailer answered 15/12, 2010 at 1:19 Comment(6)
The question isn't clear. Are you actually trying to use them all at once?Kunzite
@Matthew: Not sure how that isn't clear... no, I'm not trying to use them all at once. I did up a code snippet like that just to illustrate the different ways you can check for null.Semitrailer
That's what I thought from the rest of the question, but the syntax presentation you used was odd.Kunzite
@Matthew: Used it to give context. So that people would know what context is and where it's being used, although it's not overly relevant to the question. Guess I could have put the function signature in 4 times, but that was too much typing, and wasting too much space :PSemitrailer
Contract.Requires is grayed out because it's conditional on the symbol CONTRACTS_FULL, which is added to the compilation by Code Contracts, so Resharper doesn't see it. If you add CONTRACTS_FULL to your project Resharper won't gray it out.Brian
Also: the first message is from Code Contracts, the second from Resharper, and I'm not sure about the third -- possibly Resharper. If you're using Resharper with Code Contracts you should install the file from this question: #930359Brian
H
12

My guess is that there is a contract applied to the interface IHttpHandler.ProcessRequest which requires that context != null. Interface contracts are inherited by their implementers, so you don't need to repeat the Requires. In fact, you are not allowed to add additional Requires statements, as you are limited to the requirements associated with the interface contract.

I think it's important to make a distinction between specifying a contractual obligation vs. simply performing a null check. You can implement a null check and throw an exception at runtime, as a way to inform the developer that they are using your API correctly. A Contract expression, on the other hand, is really a form of metadata, which can be interpreted by the contract rewriter (to introduce the runtime exceptions that were previously implemented manually), but also by the static analyzer, which can use them to reason about the static correctness of your application.

That said, if you're working in an environment where you're actively using Code Contracts and static analysis, then it's definitely preferable to put the assertions in Contract form, to take advantage of the static analysis. Even if you're not using the static analysis, you can still leave the door open for later benefits by using contracts. The main thing to watch out for is whether you've configured your projects to perform the rewriting, as otherwise the contracts will not result in runtime exceptions as you might expect.


To elaborate on what the commenters have said, the difference between Assert, Assume and Requires is:

  • A Contract.Assert expression is transformed into an assertion by the contract rewriter and the static analyzer attempts to prove the expression based on its existing evidence. If it can't be proven, you'll get a static analysis warning.
  • A Contract.Assume expression is ignored by the contract rewriter (as far as I know), but is interpreted by the static analyzer as a new piece of evidence it can take into account in its static analysis. Contract.Assume is used to 'fill the gaps' in the static analysis, either where it lacks the sophistication to make the necessary inferences or when inter-operating with code that has not been decorated with Contracts, so that you can Assume, for instance, that a particular function call returns a non-null result.
  • Contract.Requires are conditions that must always be true when your method is called. They can be constraints on parameters to the method (which are the most typical) and they may also be constraints on publicly visible states of the object (For instance, you might only allow the method to be called if Initialized is True.) These kinds of constraints push the users of your class to either check Initialized when using the object (and presumably handle the error appropriately if it's not) or create their own constraints and/or class invariants to clarify that Initialization has, indeed, happened.
Hithermost answered 15/12, 2010 at 1:24 Comment(4)
So...what exactly is the difference between Contract.Assert and Contract.Requires then?Semitrailer
@Ralph: Contract.Requires indicates things that should be true when the method is called, Contract.Assert should go in the middle of your method to check intermediate states. Adding Contract.Asserts in the right places can help the static analyzer prove your code correct if it can't manage it on its own.Solution
Actually, the static checker would only benefit from Contract.Assume. This method behaves like Contract.Assert at runtime, but tells the static checker to not try and statistically prove this.Phylogeny
Contract.Assert is basically just documentation for other programmers :)Brian
V
2

The first method is appropriate for testing for a null condition that should never exist. That is, use it during development to ensure it doesn't unexpectedly get set to null. Since it doesn't do any error handling, this is not appropriate for handling null conditions in your released product.

I would say the 2nd and 3rd versions are similar in that they don't handle the issue in any way.

In general, if there's a possibility that the variable could actually be null in the final product, the last version is the one to use. You could do special handling there, or just raise an exception as you've done.

Veranda answered 15/12, 2010 at 1:24 Comment(3)
Actually, the 2nd and 3rd methods use static analysis in order to try and prove them true at compile-time.Solution
@Anon: But the static analyzer won't be able to catch all null exceptions at compile-time... what happens if one slips by? Does it raise an exception, crash, do nothing or what?Semitrailer
@Ralph: If the static analyzer can't prove something either way (it can't show that the contract will fail in some cases, but it can't prove it always correct either) it will show a compile warning (not an error) and replace it with an assertion.Solution

© 2022 - 2024 — McMap. All rights reserved.