Leveraging Contract Technology for Simpler Applications
If you haven’t used the .NET Framework Contract tools to help guarantee code quality, it’s worthwhile to consider integrating them into your work. If you figured that Contracts were all hype, it may be time to see how they could be genuinely useful.
In an earlier column, I made the case that your life would be easier if you incorporated the technology that Microsoft developed to support Coding by Contract (CbC). I didn’t suggest that you wanted to adopt CbC, but I did suggest that using the related tools would let you improve application quality without writing a great deal of extra code. These contract-related tools will also simplify (or even eliminate) some testing code while ensuring consistency in your UI. There’s nothing wrong with any of those things.
While Code Contracts provides support for tying constraints to interfaces, in practice I haven’t found the additional complexity worth the effort. Using Contracts in conjunction with interfaces often generated a lot of extra work: You needed to define an interface, a "contract class" and (often) a base class that implemented the interface. I find Code Contracts easiest to implement by inserting contract constraints directly into classes, especially into base classes (more on that later).
With Code Contracts you specify constraints that ensure that a class is always used correctly. The constraints that you can specify fall into three categories:
- Pre-Conditions: Things that must be true before code in a method can run. This category includes, as an example, constraints on parameters passed to a method.
- Post-Conditions: Things that must be true if the method completed successfully. This category includes constraints on a method’s return value.
- Invariants: Things that must be true throughout the class after any public method completes. These typically include constraints on class fields that are manipulated in several places in the class.
As an example, let’s look at an AccountingNotification method that accepts and returns an Invoice. I’d assume that I want the input Invoice parameter to be not to be null (a pre-condition). Similarly, if the method behaves correctly, I’d assume the method will return a non-null value (a post-condition).
To implement a pre-condition, I call the Requires method on the Contract object, passing a predicate expression that specifies the test I want to perform (a predicate is an expression that boils down to a True or False value -- essentially, a test). For post-conditions, I call the Ensures method on the Contract object, also passing a predicate expression. When calling the Ensures method I frequently use the Contract’s Result property, which gives me access to the value being returned by the method. For both methods, I can supply a message that will be used when the constraint is violated.
Putting that all together, this code ensures that my code will raise an error message if I’m passed a null Invoice or if the method fails to return an invoice:
Public Overridable Function AccountingNotification(Bill As Invoice)
Contract.Requires(Bill IsNot Nothing, "Method must be passed an Invoice object.")
Contract.Ensures(Contract.Result(Of Invoice) IsNot Nothing, "Invoice not created.")
Notice that all the code for the method must follow the calls to the Contract class’s methods, even though the Ensures call is actually applied at the end of the method. If you intermix code and constraints, then you’ll get the error message "Malformed contract."
As another example, after calling a CancelOrder method, the invoice in the Shipping property should have its IsValid property set to False. I can apply that constraint by testing against the property itself, like this:
Public Sub CancelOrder()
Contract.Ensures(Me.Shipping.IsValid = True, "Invoice not marked as invalid after canceling.")
Invariant constraints are handled differently than pre- and post-constraints because they apply to the class as a whole. While an invariant constraint is applied using the Contract object’s Invariant method, those method calls must be wrapped in a private method that’s decorated with the ContractInvariantMethod (you can give the method any name you want). This method, added to my base class, checks that two of the class’s fields are never set to Nothing:
Private Sub CheckInvariants()
Contract.Invariant(cust IsNot Nothing, "Customer object must not be set to null")
Contract.Invariant(ord IsNot Nothing, "Order object must not be set to null")
Invariants aren’t, of course, checked all the time. They are, however, checked as a post-condition every time a public method that might affect the constraint is called. This means that if, in a method, I set the ord field to Nothing but put a new object in ord before the method ends, I won’t have violated the constraint.
So far, this isn’t much different than using Debug.Assert/Trace.Assert to declaratively apply validation to your classes. There is, at least, one major difference with Code Contracts: Debug.Assert or Trace.Assert only apply in the methods where you place them. Code Contract call constraints apply in the classes where you apply them and any class that inherits from them, even if you don’t call the base methods. This means that a Contract.Requires applied in a base class is automatically enforced in all derived classes.
That’s certainly a good thing. However, I think the real benefits of Code Contracts kick in when you integrate static checking. The static checking tool isn’t included with Visual Studio so you’ll need to download it and install it.
To control what static checking will do for you, after installing it (and restarting Visual Studio) go to your project’s Properties page. You’ll find a new tab called Code Contracts with several dozen options. To get the maximum benefit from static checking I recommend these settings, working from the top:
- Set Assembly Mode to "Standard Contract Requires"
- Check "Perform Runtime Contract Checking," set its option to "Full" and check "Call-site requires checking"
- Set Configuration to the custom configuration you set up to trigger static checking
- Check all the options under static checking, except for "Baseline"
- Set SQL Server to a local instance of SQL Server Express (I use "(LocalDb)\MSSQLLocalDB"). This speeds up your static checking
- Set the warning level slide to "hi"
- Check "Be optimistic on external API"
- Set Contract Reference Assembly to "Build"
With static checking enabled, you’ll find that you get a ton of feedback in the Output Window from static checking (depending on your version of Visual Studio, you might also get this feedback in your Errors List). Static checking will, for example, recommend both additional requirements or validation tests.
With the following code, for example, static checking will recommend that you add either a constraint or some validation code to ensure that Inv isn’t set to Nothing:
Public Sub MyMethod(Inv As Invoice)
Dim Typ As String
Type = Inv.InvoiceType
Static checking also takes advantage of the way that constraints propagate to derived classes. For example, this code accepts an Invoice object and passes it to AccountingNotification:
Public Sub MyMethod(Inv As Invoice)
If the AccountingNotification method has a constraint that says Inv can’t be Nothing, then static checking will recommend that you add either a validation check or another constraint to MyMethod. You could, therefore, solve the problem with either of these sets of code:
Public Sub MyMethod(Inv As Invoice)
Contract.Requires(Inv IsNot Nothing)
Public Sub MyMethod(Inv As Invoice)
If Inv IsNot Nothing Then
Static checking doesn’t make these recommendations blindly, either. Static checking will analyze your logic and, if it can assure itself no constraint is required, it won’t suggest one.
Static checking doesn’t limit itself to the Output Window, however. Static checking will also, like IntelliSense, flag your code with wavy blue lines where static checking finds errors. Code that’s guaranteed to fail a constraint will be flagged with a wavy blue line. In addition, static checking will also flag the method containing the code as "guaranteed to raise an error." It’s this feature that allows you to omit tests: With the right settings in the Code Contracts tab, your code won’t compile if it’s known to be violating constraints set in the base class.
The only problem with static checking is that it’s time consuming. In the heyday of CbC, the usual recommendation was to perform static checking on every build -- I can’t recommend that.
Instead, I suggest that you set up a new configuration in Build | Configuration Manager, tie Code Contracts static checking to that configuration, and (occasionally) compile in that configuration. If your version of Visual Studio already has a configuration choice called Code Analysis, you might want to assign static checking to that configuration rather than create a custom configuration. After you’ve done that, in static checking configurations tied to the Debug and Release modes, just check off "Perform Runtime Checking."
When you do build in your Static Checking configuration, static checking will still run for a long time, but it will run in the background, so you can carry on writing code. When static checking is finished you can review the results and start making the appropriate changes. Once you’ve addressed all the issues raised by static checking, you can switch back to your standard configuration.
There’s more to be said here. You can also use constraints to check for conditions after a specific exception is thrown (this can help you prevent bugs tied to exceptions). There are ForAll and Exist methods that make it easy for you to create constraints against collections. You can use Contract.OldValue to check original values against final values. You can use Contract.Assume to assure static checking that the outputs of a library you don’t have access to will do the right thing.
Let me be clear: You won’t be able to do everything you want with contracts and, sometimes, using Code Contracts will be more trouble than it’s worth. In those cases, don’t use it: This is a tool, not a religion. But, while automated testing won’t tell you what you’ve missed, Code Contracts and static checking will … and they'll tell you before you run your code.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.