![]() | Note |
|---|---|
The following synonyms are used in the programming world to denote the same concept:
|
The basic idea of Contract Programming is to add data validation checks in the source code. These checks ensure that invalid data exchanged between software components is rejected immediately and automatically. As soon as data doesn't pass a validation check at runtime a program error is raised.
As a result a good number of program errors are detected quickly and automatically.
You can also think of Contract Programming as many little firewalls installed in your application and protecting software components from illegal or malicious attacks. To illustrate what can happen in case of an application without Contract Programming just imagine what can happen in case of a web server without firewall protection.
Type string in Obix's standard library has command (function) extract_from_to. This command extracts a substring, starting at position from of the string and ending at position to.
Without Contract Programming the code for type string would be as follows:
type string
command extract_from_to
in from type:positive32 end
in to type:positive32 end
out result type:string end
end
// other 'string' commands omitted
end type
It is obvious that calling extract_from_to only makes sense if the following conditions are fulfilled:
from is less than or equal to the length of the stringto is less than or equal to the length of the stringfrom is less than or equal to the value of to
Moreover we can state that the length of the result string must be equal to to - from + 1
These conditions can be expressed with Contract Programming. The above code rewritten with data validation checks becomes:
type string
command extract_from_to
in from type:positive32 check: from <= object_.item_count end
in to type:positive32 check: to <= object_.item_count end
in_check check: from <= to end
out result type:string check: result.item_count =v to - from + 1 end
end
end type
![]() | Note |
|---|---|
The check for input argument from could be omitted because the other two input conditions (from <= to and to <= object_.item_count imply that from <= object_.item_count is also fulfilled. |
Adding Contract Programming to your source code has many benefits, as explained in the next section.
Contract Programming:
leads to more reliable and more maintainable software.
Contract Programming is a very effective application of the important Fail fast! principle. It supports defensive programming and software bug prevention. As a result it leads to more reliable and more maintainable software.
helps to detect many program errors quickly and automatically
A typical application contains hundreds or thousands of Contract Programming conditions scattered everywhere throughout the source code. These conditions are continuously verified at runtime, so that any violation immediately generates a runtime error. This helps debugging, especially when client and supplier code is written by different programmers. In many cases it also reduces the time needed to correct an error because the source of the error is immediately revealed.
documents the code in a reliable manner
Contract Programming documents program interfaces in a reliable way. By just looking at a command's input checks, for example, you can quickly understand the conditions that must be fulfilled before you can call the command. This avoids misunderstandings between client and supplier code (possibly written by different programmers).
Without Contract Programming, conditions could be expressed through a comment in the source code. But comments cannot be understood and used by the compiler to actually check the condition at runtime. Moreover comments can be in discordance with the source code and they can be outdated after changing the code without changing the comment.
makes implementation code simpler and more consistent
Have a look at the above example code of type string.
Without Contract Programming conditions any factory that implements command extract_from_to has to deal in some way with each case of invalid input data and decide what to do. If from is greater than to, for example, there are at least 3 ways to deal with this case. The factory could (1) throw an error or (2) return a void value or (3) return an empty string. Different factories implementing type string could apply different ways to handle invalid input data. It is easy to see that this can easily lead to unforeseeable and inconsistent behavior between objects of the same type. The programmer would have to look at each factory's implementation code in order to understand what happens in a given case of invalid data. Contract Programming eliminates these problems. Every factory can reliably assume that input is in a valid state. This makes the implementation code to write much simpler. And there is no inconsistent behavior between different factories implementing the same type.
Another benefit is that the factories' implementation code is implicitly checked (to a certain degree) for erroneous results, because the output condition result.item_count =v to - from + 1 is implicitly checked for any factory and for any case of input values.
ensures that conditions are automatically inherited in child types, but allows for redefining them if necessary
A fundamental rule of object oriented programming is that a child type must always be compatible to its parent type. Therefore Contract Programming conditions are implicitly inherited in child types. You don't have to restate the checks in child types - the compiler takes care of this.
![]() | Note |
|---|---|
| Sometimes, it is necessary to weaken input conditions or to strengthen output conditions in child types. This is called Contravariance and Covariance in computer science. Obix supports these techniques in a type-safe way, as explained below. |
There are no general drawbacks of Contract Programming. However the following two points must be considered:
The programmer has to "write a little bit more"
The compiler cannot deduce Contract Programming conditions to be inserted in the code. They have to be written manually. Some programmers might therefore refrain from using this powerful technique because "writing more makes me less productive". This is of course a silly argument because not using Contract Programming simply means more bugs in the software and more time (and frustration and money) spent to find and repair the bugs. The serious programmer who wants to write high quality code will quickly be convinced of all the benefits and use Contract Programming consistently.
Contract Programming might create performance problems in specific cases
Constantly checking all conditions at runtime takes time. Therefore an application using Contract Programming will run slower. Two questions arise:
There is no general answer to these questions. It depends. Practice shows that in most cases performance is not a concern. It is typically assumed that an application with Contract Programming enabled runs about 30% slower.
If you encounter performance problems then you can proceed as follows:
As said already, Contract Programming consists of adding conditions at specific places in the source code in order to protect software components against invalid data. A condition is a yes_no (boolean) expression which must evaluate to yes (true) in the case of valid data. A condition would be used, for example, to specify that the length of a name cannot exceed 70 characters. During execution of the program conditions are constantly checked and a runtime error occurs immediately whenever a condition is violated. Thus, in our example, a runtime error would occur immediately if a name is set to a value consisting of more than 70 characters.
![]() | Note |
|---|---|
It is interesting to note that 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 would ensure that only an object of type Another point to note is that static typing violations are detected at compile-time, whereas Contract programming violations are detected at run-time. |
The rules for Contract programming in Obix are as follows:
There are four kinds of Contract Programming conditions:
Attribute conditions
Attribute conditions are used to protect attributes of objects and services against invalid data. They ensure that the state (i.e. the set of all attribute values) of an object or service is valid.
Attribute conditions can be defined:
for a single attribute
example: attribute name of type customer cannot exceed 70 characters
for multiple attributes of the same type or service
example: 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.
![]() | Note |
|---|---|
| In computer science the term class invariant is used to denote the concept of attribute conditions used in Obix. |
Command input conditions
Command input conditions are conditions that must be fulfilled before a command is executed. They protect a command against invalid input.
Command input conditions can be defined:
for a single input argument
an example was given above: the value of input argument from of command extract_from_to must be less than or equal to the length of the string
for multiple input arguments of the command
an example was given above: the value of input argument from of command extract_from_to must be less than or equal to the value of to
![]() | Note |
|---|---|
| In computer science the term precondition is used to denote the concept of command input conditions used in Obix. |
Command output conditions
Command output conditions are conditions that must be fulfilled after a command is executed. They ensure that invalid output from a command is rejected.
Command output conditions can be defined:
for a single output argument
an example was given above: the length of output argument result must be equal to to - from + 1
for multiple output arguments of the command
example: a network connection must be available after executing a command that establishes this connection, or else an error must be returned.
![]() | Note |
|---|---|
| In computer science the term postcondition is used to denote the concept of command output conditions used in Obix. |
Script conditions
Script conditions are conditions that can be inserted anywhere in a script. They are used to ensure a valid state of one or more objects at a certain point in the script.
examples: ensure a loop invariant; ensure the sum of two integer variables to be less than 100; etc.
![]() | Note |
|---|---|
| In computer science the term Assertion is used to denote the concept of script conditions used in Obix. |
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 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 the condition 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 4.1. Links for how to code Contract Programming
| Kind of condition | Sub-kind | How to code | Definition of check script |
|---|---|---|---|
| Attribute conditions | check a single attribute | the section called “Attribute property check” | the section called “Attribute check script” |
| check multiple attributes (i.e. the state of an object or service) | the section called “Property attribute_check” | the section called “attribute_check script” | |
| Command input conditions | check a single input argument | the section called “Argument property check” | the section called “Input argument check script” |
| check multiple input arguments | Example 18.7, “in_check (using an expression)” | the section called “Command in_check script” | |
| Command output conditions | check a single output argument | the section called “Argument property check” | the section called “Output argument check script” |
| check multiple output arguments | Example 18.8, “Multiple output arguments” | the section called “Command out_check script” | |
| Script conditions | the section called “check script instruction” | N/A |
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 are also enforced in all child types.
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. Returning the value 7, for example, would be rejected.
Contract Programming conditions defined in a type can be weakened or strengthened in a child type, as long as type compatibility is preserved.
Please refer to Chapter 10, Feature redefinition for more information.
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 any factory implementing this type (or a child-type) can rely on this condition and doesn't need to add code such as if input_value.item_count < 10 then ....
Here are two tips to consider when using Contract Programming.
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:
voidable” (attribute)voidable” (input/output argument)If the same condition is defined for more than one 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
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 |
|---|---|
This code uses type inheritance and feature redefinition. Please refer to Chapter 12, Type inheritance and Chapter 10, 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 the limit of remark is later changed to 2048 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.
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.
attributes:
input/output arguments:
related topics:
instructions:
Wikipedia links: