Alternative precondition checking in Code Contracts

    When trying to use the Code Contracts library in a real project, a little complexity may arise: although the Contract class itself with methods for checking preconditions and postconditions is located in mscorlib since the 4th version of the .NET Framework, but without installing the Code Contracts library itself, they do not fall into resulting assembly.

    This can cause some 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 certainty about whether we really need this good, such a transition can be difficult.

    However, Code Contracts supports an additional "compatibility mode", which allows you to "hard-wired" pre-condition checks into the resulting code, so that they will be visible to everyone, regardless of whether the contracts are installed on the developer's machine or not.

    Formulation of the problem

    Let's start by looking at an example that shows more clearly what the problem is.

    classSimpleClass
    {
        publicintFoo(string s)
        {
            Contract.Requires(s != null);
            return s.Length;
        }
    }
    


    Everything is fine with this code, and when we try to call the Foo method with null , we get a contract violation that, when the Code Contracts library is installed and the precondition checking is turned on, it will throw an exception 'System.Diagnostics.Contracts .__ ContractsRuntime.ContractException'.

    Yes, this is exactly what we are expecting, but the peculiarity is that the exception generation code is generated not 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 NullReferenceExcpetion, since no additional validation of the arguments will remain. I have repeatedly come across the fact that this behavior caused about the following reaction: “WTF? Where did my check go! ”

    Since we don’t want to hear such“ WTF?!? ”From our colleagues who have no contracts, we would like to have a way to sew up the precondition check in a more thorough way.

    Manual precondition checking

    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 the input parameters (i.e., checking preconditions), then to convert them into full-fledged preconditions, it is enough to add a Contract call after them . EndContractBlock :

    publicclassSimpleClass
    {
        publicintFoo(string s)
        {
            if (s == null)
                thrownew ArgumentNullException("s");
            Contract.EndContractBlock();
            return s.Length;
        }
    }
    


    Adding a Contract call . EndContractBlock turns one (or several) checks of input parameters into full-fledged preconditions. Now, for any developer whose contracts have not been established , the code will look like before. At the same time, the owners of the contracts will be able to take advantage of all their advantages, such as checking the validity of the program using the Static Checker, automatic generation of documentation, the possibility of catching all contract violations (more on this below). The difference between this method of verification is only that they cannot be turned off and cut out of the code completely.

    This approach can be combined with more advanced contracting techniques. So, for example, you can combine old- style verification of preconditions together with verification of postconditions and invariants. But since postconditions and invariants are more relevant to the class itself, and not to its customers, this will not affect all those developers who have no contracts.

    NOTE
    The Code Contracts library allows you to configure which checks should remain in the resulting code. If the developers are confident enough in their code, then they can remove all checks from the code and save several processor cycles on each of them. You can read more about the monitoring levels and the possibilities for their management in the article: “Monitoring claims during the execution period” .

    Using existing validation methods

    Another standard way to validate arguments is to use special classes (guards) 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 attribute ContractArgumentValidatorAttribute .

    NOTE
    Unfortunately, the ContractArgumentValidatorAttribute attribute is not part of the .NET Framework version 4.0; it will appear only in version 4.5. This situation is resolved by adding the ContractExtensions.cs file to your project, which will appear in% ProgramFiles% \ Microsoft \ Contracts \ Language \ CSharp after installing the Code Contracts library.

    publicstaticclassGuard
    {
        [ContractArgumentValidatorAttribute]
        publicstaticvoid IsNotNull<T>(T t) where T : class
        {
            if (t == null)
                thrownew ArgumentNullException("t");
            Contract.EndContractBlock();
        }
    }
    


    Now we can use the good old IsNotNull method to check preconditions:

    publicintFoo(string s)
    {
        Guard.IsNotNull(s);
        return s.Length;
    }
    


    Deviation 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, violation of the non-generalized version leads to the generation of an internal exception of type ContractException .

    The reason that an internal exception is thrown by default is because a contract violation cannot be repaired programmatically. This is a bug in the code and to fix it you need to change this code. However, using any approach to checking preconditions ( Contract . Requires+ 2 approaches considered today), the user can “catch” the exception by intercepting the basic type of exception.

    In the class Contract is an event ContractFailed , which will be called in case of violation of preconditions / postconditions / invariants. For example, before starting 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 to switch to contract programming allows you to smoothly migrate code to contracts without breaking the rest of the team. At the same time, you can use the old validation tools with all the virtues of contracts (including the ability to learn about violation of preconditions, described in the previous section), without forcing everyone to install additional utilities on their machines.

    Also popular now: