Chapter 17. Contract programming

Description

Contract programming, also known as Design By Contract (DBC) (TM) is used to ensure that specific conditions, which are defined in the source code, are always fulfilled at runtime. Whenever a given condition is not fulfilled then a program error is raised immediately.

There are several kinds of conditions that can be specified. They are used to:

  • protect an attribute, input argument or output argument against invalid values

    While static typing ensures that only an object of a defined type (or any compatible type) can be assigned to an object reference, contract programming goes a step further by limiting the set of allowed values that can be assigned. For example, static typing ensures that only a positive32 object can be assigned to an attribute of type positive32, and contract programming can then be used to restrict the allowed values to, let's say, values between 10 and 20.

  • ensure that the state (i.e. the set of all attribute values) of an object or service fulfills one or more conditions

    Suppose type address has attributes postal_code, city, and country. Contract programming could then be used to ensure that the city actually exists in the country, and that the postal_code exists for that city.

  • ensure that one or more conditions are fulfilled before executing a command

    An example would be a file that must exist before executing a command.

  • ensure that one or more conditions are fulfilled after executing a command

    Example: a network connection must be available after executing a command that establishes this connection, or else an error must be returned.

  • ensure that a specific condition is fulfilled somewhere in a script

    This can be used to ensure a loop invariant, ensure that a variable is not void at some point of execution, check that the sum of two integer constants is less than 100, etc.

The next section describes the rules for using Contract programming in Obix.

Rules

  1. There are two ways to define contract programming conditions:

    • Simple conditions are defined through an expression of type yes_no. If the expression evaluates to yes, the condition is fulfilled. If it evaluates to no or void it is not fulfilled.

    • More complex and multiple conditions are defined through a check script that returns void if all conditions are fulfilled. As soon as a condition is unfulfilled, the script returns an error object describing the problem. A condition in the script is specified with the check instruction (see the section called “check instruction”). The check instruction evaluates an expression of type yes_no. If the expression evaluates to yes, the condition is fulfilled. If it evaluates to no or void it is not fulfilled, and the script immediately returns an error object.

    Please refer to the links in the following table for details about how to code the different kinds of conditions:

    Table 17.1. Contract programming links

    Relates toKind of conditionHow to codeDefinition of check script
    Attributescheck the value of a single attributethe section called “Attribute property checkthe section called “Attribute check script”
    check the state of an object or service (multiple attributes)the section called “Property attribute_checkthe section called “attribute_check script”
    Commandscheck the value of a single input argumentthe section called “Argument property checkthe section called “Input argument check script”
    check the value of a single output argumentthe section called “Argument property checkthe section called “Output argument check script”
    check input conditions before executing a commandExample 6.7, “in_check (using an expression)”the section called “Command in_check script”
    check output conditions after executing a commandExample 6.8, “Multiple output arguments”the section called “Command out_check script”
    Scriptsensure a condition is fulfilled in a scriptthe section called “check script instruction”not applicable

  2. Contract programming conditions defined in a type are implicitly inherited in every child type

    This rule is a logical consequence of the type compatibility rule. Because a child type is always compatible to all its parent types, all contract programming conditions defined in a type must also be enforced in any child type.

    For example, if a type's command returns an integer value that is guaranteed to be greater than 10, a child type must also ensure this condition. If it was allowed to return the value 7, for example, type compatibility would be violated.

  3. Contract programming conditions defined in a type can be loosen or strengthened in a child type, as long as type compatibility is preserved.

    Please refer to Chapter 18, Feature redefinition for more information.

  4. Contract programming conditions defined in a type are automatically enforced in every factory that implements this type.

    There is no need to explicitly (re)check conditions in factories. For example, if a type specifies that a command's input argument of type string must contain at least 10 characters, then the factory can rely on this condition and doesn't need to add code such as if input_value.item_count < 10 then ....

Tips

Here are some tips to consider when using contract programming.

  1. To ensure that an object reference cannot be void at runtime, property voidable should be used, rather than contract programming.

    Although contract programming could be used to check for void, using property voidable is easier to use, and allows the compiler to make some optimizations. However, if the check for void depends on circumstances at runtime, then contract programming must be used (e.g. an attribute's voidability depends on a parameter stored in an XML configuration file)

    Please refer to the following links for more information:

  2. If the same condition is defined for more than one object reference (attribute, input argument or output argument), then consider defining a new type that contains this condition and use this type for each object reference.

    Suppose types meeting and asset both have attribute remark which is a string limited to 1024 characters. The types could be defined as follows:

    type meeting 
    
       // some attributes not shown here
    
       attribute remark type:string check: i_remark.item_count <= 1024 end
    
    end
    type asset 
    
       // some attributes not shown here
    
       attribute remark type:string check: i_remark.item_count <= 1024 end
    
    end

    However, a much better solution is to define a new type remark as:

    type remark
    
       inherit string
          attribute value and_check: i_value.item_count <= 1024 end
       end
    
    end
    [Note]Note

    This code uses type inheritance and feature redefinition. Please refer to Chapter 15, Type inheritance and Chapter 18, Feature redefinition for more information about these techniques.

    Now types meeting and asset can both use type remark like this:

    type meeting_2
    
       // some attributes not shown here
    
       attribute remark type:remark end
    
    end
    type asset_2
    
       // some attributes not shown here
    
       attribute remark type:remark end
    
    end

    Besides the obvious advantage that the code gets easier to write, the design has been improved for the following reasons:

    • Code duplication, one of the biggest enemies of software maintainability, has been eliminated.

      If somewhere in the future, remarks will be limited to 2048, instead of 1024 characters, there is only one place to change in the code (i.e. type remark). This saves time and eliminates the risk of forgetting to make the same change in other types that have a remark attribute.

      [Note]Note

      Source code duplication could also be eliminated by using source code templates (see Chapter 20, Source code templates), but this method lacks the advantage of type safety (see below); therefore this solution is not shown here.

    • The same type remark can now easily be used in other types with a remark attribute.

    • Type safety is increased, because a remark is now semantically different from a standard string.

      For example, an error like assigning the name of an elephant (defined as string) to the remark attribute of a meeting object would be detected by the compiler.

Rationale

Contract programming is undoubtedly one of the most effective means to support the fail fast principle. It helps to detect programming errors in an early stage and therefore greatly contributes to more reliable and maintainable code.

This is due to the fact that in a typical application there are hundreds or thousands of contract programming conditions scattered throughout the code. These conditions are verified continuously at runtime, so that any violation immediately generates a runtime error.

One of the most strategically important conditions are the command input conditions, because these are the conditions that risk to be violated mostly. Why? Because a client script that calls a supplier script is often written by different programmers who might make different assumptions, for example in case of using Obix's standard library, or a third party library. Or, the same programmer might have forgotten the input conditions of a supplier script he wrote some time ago. In such cases the risk of bad input data transmitted from the client to the supplier script is higher, and contract programming effectively helps to detect these problems quickly.

Another advantage is that contract programming conditions in the source code greatly document program interfaces in an infallible way. Without contract programming, a condition expressed as a simple comment such as value should not contain more than 1024 characters cannot be used by the compiler to actually check the condition at runtime. Hence the comment might be in discordance with the code. Moreover it can become outdated if the code is changed without changing the code's comment.

Examples

Examples of contract programming can be found in the links listed in the following section.

See also

attributes:

input/output arguments:

related topics:

instructions: