When trying to use the
Code Contracts library in a real project, a small complexity may arise: although the
Contract class itself with methods for testing preconditions and postconditions, is located in mscorlib starting from version 4 of the .NET Framework, but without installing the Code Contracts library itself, they do not fall into resulting assembly.
This can cause certain difficulties in large distributed teams, since for normal use of contracts all developers will have to install an additional extension. And since the key people of the project may not have a clear confidence in whether it is necessary for us to do this kindly, such a transition may be difficult.
However, Code Contracts support an additional “compatibility mode”, which allows you to “hard-fix” precondition checks in the resulting code, so that they will be visible to everyone, regardless of whether contracts are installed on the developer’s machine or not.
Formulation of the problem
Let's first look at an example that more clearly shows what the problem is.
')
class SimpleClass { public int Foo(string s) { Contract.Requires(s != null); return s.Length; } }
With this code, everything is fine and if you try to call the
Foo method with
null , we get a violation of the contract, which, if the Code Contracts library is installed and the preconditioning check is on, will throw an exception 'System.Diagnostics.Contracts .__ ContractsRuntime.ContractException'.
Yes, this is exactly what we are waiting for, but the peculiarity is that the
exception generation code is not generated by the compiler, but by a separate process that starts immediately after compilation . And this means that without the Code Contracts library, the execution of this code will lead to the generation of a
NullReferenceExcpetion , since no additional validation of the arguments will remain in the shadow. I have repeatedly come across the fact that this behavior caused something like this: “WTF? Where did my check go! ”
Since we don’t want to hear such “WTF?!?” From our colleagues, whose contracts have not been established, we would like to have a way to fix the precondition check more thoroughly.
Manual precondition check
The Code Contracts library allows you to use preconditions in the old format. This means that if the existing method already contains a check of input parameters (ie, a check of preconditions), then to convert them into full-fledged preconditions after them, it suffices to add the call to
Contract . EndContractBlock :
public class SimpleClass { public int Foo(string s) { if (s == null) throw new ArgumentNullException("s"); Contract.EndContractBlock(); return s.Length; } }
Add Call
Contract . EndContractBlock turns one (or several) checks of input parameters into full-fledged preconditions. Now, for any developer whose contracts are
not installed , this code will look like before. At the same time, contract holders will be able to enjoy all their benefits, such as checking the validity of a program with the help of Static Checker, automatic generation of documentation, the possibility of catching all breaches of contracts (more on this later). The difference between this method of verification and the fact that they cannot be turned off and cut out of the code completely.
This approach can be combined with more advanced contract management techniques. For example, it is possible to combine the
old - style precondition check in conjunction with the check of post-conditions and invariants. But since the postconditions and invariants relate more to the class itself, and not to its clients, this does not affect all those developers whose contracts have not been established.
NOTEThe Code Contracts library allows you to customize which checks should remain in the resulting code. If developers are confident enough in their code, then they can remove all checks from the code and save a few processor cycles on each of them. In more detail about the levels of monitoring and opportunities for their management can be found in the article:
“Monitoring of statements during the execution period” .
Using existing validation methods
Another standard method for validating arguments is to use special classes (guard) with a set of different methods, such as
NotNull ,
NotNullOrEmpty , etc. The Code Contracts library supports the ability to turn such methods into full-fledged contracts: for this, the methods of the validator class must be marked with the
ContractArgumentValidatorAttribute attribute.
NOTEUnfortunately, the
ContractArgumentValidatorAttribute attribute is not included in the .NET Framework version 4.0, it will only appear in version 4.5. This situation is being tackled by adding the ContractExtensions.cs file to your project, which will appear in% ProgramFiles% \ Microsoft \ Contracts \ Language \ CSharp after installing the Code Contracts library.
public static class Guard { [ContractArgumentValidatorAttribute] public static void IsNotNull<T>(T t) where T : class { if (t == null) throw new ArgumentNullException("t"); Contract.EndContractBlock(); } }
Now we can use the good old method
IsNotNull to check the preconditions:
public int Foo(string s) { Guard.IsNotNull(s); return s.Length; }
Retreat from the topic. Contract.ContractFailed
You may have noticed that there are two versions of the
Contract method
. Requires , one of which is generic and can be used to generate the desired type of exception, but a violation of the non-generic version leads to the generation of an internal exception of the type
ContractException .
The reason why the internal exception is generated by default is that contract violation cannot be repaired programmatically. This is a bug in the code and for its elimination it is necessary to change this code. However, when using any approach to the verification of preconditions (
Contract . Requires + 2 of the approaches considered today), the user can “grab” the exception by intercepting the basic type of exception.
In the
Contract class, there is a
ContractFailed event that will be triggered if the preconditions / postconditions / invariants are violated. For example, before running an integration or unit test, you can subscribe to this event, and if the precondition falls, but the test remains green, then you can roll up your sleeves and go look for that careless programmer who catches exceptions that are not intended to be processed.
Conclusion
Using one of the approaches described here for the transition to contract programming allows you to smoothly migrate code to contracts without breaking the life of the rest of the team. At the same time, you can use old means of validation with all the advantages of contracts (including the ability to learn about the violation of preconditions described in the previous section), without forcing everyone to install additional utilities on their machines.