📜 ⬆️ ⬇️

Contract Design

Two years ago I was fortunate enough to attend a lecture by a wonderful person, one of the developers of the Eiffel language, Bertrand Meyer. He gave a lecture at our university (St. Petersburg State University ITMO) about a rather interesting software design concept. It is called “contract design”. The essence of this concept, I will try to describe below.

For example, when you agree with a client to work together, you enter into a contract. Those. You describe the responsibilities of both parties and the possible consequences in the event of unexpected situations. This approach can be applied to software development, where the parties are software modules.
Contract engineering is fairly simple, but, at the same time, a powerful technique based on documenting the rights and obligations of software modules to ensure the correctness of a program. I believe that the correct program is a program that performs no more and no less than what it claims to be.
The central figures of this concept are assertions - these are boolean expressions describing the state of the program. There are three main types of statements: preconditions, postconditions and class invariants.

Preconditions.
Preconditions are subprogram requirements, i.e. what must be true for the execution of the subroutine. If these preconditions are violated, then the subroutine should not be called in any way. All responsibility for sending the “correct” data lies with the calling program.
This approach is contrary to many concepts, which are taught in a huge number of textbooks. There is a constant check made at the forefront. Designing the contract claims the opposite - unnecessary checks can only harm. In general, the design principle under the contract "looks" at design from the perspective of "complexity - the main enemy of quality" (but more on that next time;)).
')
Postconditions
Postconditions express the states of the “surrounding world” at the time of the subprogram execution. Those. these are conditions that are guaranteed by the subroutine itself. In addition, the presence of a postcondition in the subroutine guarantees its completion (that is, there will be no infinite loop, for example).

Class invariants
Invariants are global properties of a class. They define deeper semantic properties and integrity constraints that characterize a class. The class ensures that this condition is always true from the point of view of the caller.

I will try to formulate the main idea that I use:
"If the client calling the subroutine fulfills all the preconditions, then the called subroutine undertakes that after its execution all postconditions and invariants will be true."

As in real life, in case of violation of one of the clauses of the contract, a pre-agreed and coordinated measure occurs. In the software development world, this may be the excitation of an exception or the termination of a program. In any case, you will know for sure that breach of contract is an error.
I would like to make an important remark. It should be understood that the above described is not always the case. Therefore, preconditions should not be used for procedures such as, for example, validation of user input.

Not to be unsubstantiated, I will give an example (hereinafter all the code is written in C #).
Let's consider this situation: the user enters the code zap. parts of the catalog and want to get information about this part. It is known that the code consists of 9 characters. Here is a classic example of the implementation of this function:

private ComponentProvider componentProvider;
…
public ComponentInfo GetComponentInfo( string id)
{
if ( String .IsNullOrEmpty(id) || id.Length != 9)
{
throw new Exception(“Wrong id”);
}
return componentProvider.GetComponent(id);
}


* This source code was highlighted with Source Code Highlighter .
private ComponentProvider componentProvider;
…
public ComponentInfo GetComponentInfo( string id)
{
if ( String .IsNullOrEmpty(id) || id.Length != 9)
{
throw new Exception(“Wrong id”);
}
return componentProvider.GetComponent(id);
}


* This source code was highlighted with Source Code Highlighter .
private ComponentProvider componentProvider;
…
public ComponentInfo GetComponentInfo( string id)
{
if ( String .IsNullOrEmpty(id) || id.Length != 9)
{
throw new Exception(“Wrong id”);
}
return componentProvider.GetComponent(id);
}


* This source code was highlighted with Source Code Highlighter .


In many classic textbooks on creating “high-quality software,” this example would be called excellent. But from the point of view of the principle of design under a contract, this example is erroneous.
To begin with, the caller must check the validity of the value of the id attribute. After all, she (the caller) can take advantage of several options: complete the work, issue a warning and start reading the new number. Or it may be possible to enter only the last 4 digits, and the program will form the first five based on the VIN number of the car. In any case, whichever option is used, it is neither related to the GetComponentInfo () function.
Then we rewrite the original example as follows.

public ComponentInfo GetComponentInfo( string id)
{
return componentProvider.GetComponent(id);
}


* This source code was highlighted with Source Code Highlighter .
public ComponentInfo GetComponentInfo( string id)
{
return componentProvider.GetComponent(id);
}


* This source code was highlighted with Source Code Highlighter .
public ComponentInfo GetComponentInfo( string id)
{
return componentProvider.GetComponent(id);
}


* This source code was highlighted with Source Code Highlighter .


But then the fun begins :). If we have already declared that this function returns an object of type ComponentInfo, then we must provide this. After all, the GetComponent method of the componentProvider object can return null. And then already the calling program will have to do a check for a null-value, otherwise we can “run into” the object reference exception. Those. The example is rewritten like this:

public ComponentInfo GetComponentInfo( string id)
{
ComponentInfo componentInfo = this .componentProvider.GetComponent(id);
if (componentInfo == null )
{
throw new ContractException(“Can't find component”);
}
return componentInfo;
}


* This source code was highlighted with Source Code Highlighter .
public ComponentInfo GetComponentInfo( string id)
{
ComponentInfo componentInfo = this .componentProvider.GetComponent(id);
if (componentInfo == null )
{
throw new ContractException(“Can't find component”);
}
return componentInfo;
}


* This source code was highlighted with Source Code Highlighter .
public ComponentInfo GetComponentInfo( string id)
{
ComponentInfo componentInfo = this .componentProvider.GetComponent(id);
if (componentInfo == null )
{
throw new ContractException(“Can't find component”);
}
return componentInfo;
}


* This source code was highlighted with Source Code Highlighter .


At least, so it is said in many articles and examples. BUT. Let's reason logically. If we use the contracting principle, then, based on my “golden rule”, we can be sure that the GetComponent () method of the componentProvider object will return us the true value (since its parameter is true by definition). Therefore, I see no reason to overload the program with unnecessary code. But on the other hand, an object of type ComponentProvider can be designed by a third-party developer who did not adhere to the principle of designing under a contract. Here comes the dilemma. Here is my advice for this situation - if you call a subroutine that was written by you, then do not write too much code. Trust yourself. But if you call a subprogram written by a third-party developer, and you are not sure about it, then check it out. The most obvious example is the square root extraction function Math.Sqrt (). It is clear that it is impossible to extract the square root of a negative number, but if a negative number is passed to this function, no exception will be generated, but a value of type NaN will be returned.

«»
public ComponentInfo GetComponentInfo( string id)
{
try
{
return this .componentProvider.GetComponent(id);
}
catch (ContractException ex)
{
throw new ContractException(ex.ToString());
}
}


* This source code was highlighted with Source Code Highlighter .
«»
public ComponentInfo GetComponentInfo( string id)
{
try
{
return this .componentProvider.GetComponent(id);
}
catch (ContractException ex)
{
throw new ContractException(ex.ToString());
}
}


* This source code was highlighted with Source Code Highlighter .
«»
public ComponentInfo GetComponentInfo( string id)
{
try
{
return this .componentProvider.GetComponent(id);
}
catch (ContractException ex)
{
throw new ContractException(ex.ToString());
}
}


* This source code was highlighted with Source Code Highlighter .


All these examples are based on some of your (development team) agreements. But there are special extensions for various programming languages. For example, the iContract preprocessor for Java or the eXtensible C # extension.

Most importantly, using the contract design principle will help you ensure that your code is automatically tested.
This article can be called an introduction to the principle of design under the contract. If there is interest from users, then I will continue the series about this principle. After all, all that I have described is just the tip of the iceberg.

Source: https://habr.com/ru/post/38612/


All Articles