0

I am studying the 'Design by Contract' development methodology. However, I am having a hard time seeing its advantages.

For example, it is said that one of the characteristics of this methodology is that the preconditions of a function can be expressed in its header. Thus, we could have this example, in which we divide the number 10 by any number whose precondition is that it must be greater than 0:

 get_result(value:INTEGER): REAL is
 require 
 valid_value: value > 0
 do
    Result := 10 / value
 ensure
    Result = 10 / value
 end

However, what benefit does it give me compared to this other more traditional way of doing it?

get_result(value:INTEGER): REAL is
do
    if value>0 
        Result := 10 / value
    else
        --throw an exception: 
        throw valid_value_exception
    end 
end

What I'm seeing is that the only thing that changes between the two ways of doing it is the site where the preconditions are validated. In the first form, the precondition is validated in the header of the function, while in the second form, the precondition is evaluated in the body of the function, but, in both cases, you have to continue doing the validations.

I'm sure I'm missing something important. I would appreciate if someone could help me on this.

Mike
  • 1
  • 2

1 Answers1

1

The key property of the Design by Contract methodology is that a correct program behaves exactly the same when assertions are monitored and when they are not. In other words, in a correct program assertion monitoring can be turned off without affecting the program behavior.

Assertion monitoring can be turned off after making sure all required tests pass (an unsound approach) or by using a verification tool that checks assertions at compile time rather than at run time (the example is written in Eiffel, the corresponding verification tool is AutoProof; other languages with design-by-contract support have other similar tools).

Whereas the requirement that the argument should be above zero could be documented in the description of the feature, comments (usually) are not consumable by programming language tools like compilers, code analyzers, etc., and could not be used for program verification, automatic test generation and other automated tasks.

Preconditions and postconditions also play a role in responsibility separation. Preconditions allow the supplier to rely on the guarantees provided by the client. Postconditions allow the client to rely on the guarantees provided by the supplier. Note that in both cases there is no need to repeat the checks and no need to catch exceptions or to check the result. All the knowledge is accumulated in the associated assertions. Were the checks done inside the method body, in the implementation part, there would be no glue what was guaranteed, what result was expected, etc.

Use of both preconditions and postconditions enables chaining the calls. Let's consider an expression

get_result ((get_result (n * n + 1).ceiling)

The precondition of the first feature call is satisfied by construction. Its postcondition guarantees that the result is a positive value. The call to ceiling makes sure the argument to the outer feature satisfies its precondition. So we can prove that the expression never throws an exception without ever looking into the implementation of get_result (provided that it is correct). In fact, several implementations are possible, but we do not care which implementation is used.

To summarize, preconditions and postconditions

  • can be turned off at run-time for correct programs
  • are consumable by program analysis tools
  • are used to verify programs at compile time (and make this verification modular)
  • separate responsibilities between client and supplier (who is guilty of a bug?)
  • remove the need for additional checks at the client side thus simplifying feature call chaining
Alexander Kogtenkov
  • 5,770
  • 1
  • 27
  • 35
  • Thanks for the elaborate response and for your time, @alexander-kogtenkov. More doubts have arisen in the wake of what you have stated. Would you be so kind as to be able to solve them? – Mike May 13 '21 at 16:35
  • 1. Says the following: "The key property of the Design by Contract methodology is that a correct program behaves exactly the same when assertions are monitored and when they are not." When you mention 'assertions', are you referring to preconditions and postconditions? – Mike May 13 '21 at 16:36
  • 3. When you say: "Assertion monitoring can be turned off after making sure all required tests pass (an unsound approach) or by using a verification tool that checks assertions at compile time rather than at run time." – Mike May 13 '21 at 16:37
  • You say that checking assertions at compile time using verification tools is a better approach than testing. What I don't understand at this point is how, at compile time, you can check that the preconditions and postconditions are met. Imagine, for example, the case of a client who calls the supplier's method (whose precondition is that the number that the client passes to him is greater than zero). In case the number passed by the client is dynamically generated (for example, by keyboard input by the user), how can you know at compile time that this condition is met? – Mike May 13 '21 at 16:37
  • 4. Is this methodology appropriate for all types of projects or is it intended only for a specific type of project? For example, if my project does not use any external API and is only based on the methods implemented in my project, would it be appropriate to specify in all the methods the preconditions and the postconditions? – Mike May 13 '21 at 16:38
  • 2. When you say: "In other words, in a correct program assertion monitoring can be turned off without affecting the program behavior." Do you mean that assertion compliance checking can be disabled? What do you mean when you say it wouldn't affect the program behavior? – Mike May 13 '21 at 16:47
  • 1. Yes, preconditions and postconditions are assertions. – Alexander Kogtenkov May 13 '21 at 18:05
  • 3. Among other things, correct programs validate input, i.e. they would test if the input is an integer, if the value of the integer is in the expected range, etc. Calls to the `get_result` would happen only in the branches that ensure that the value is expected. This can be checked at compile time. (I'm not saying that it is trivial or that the tools can check any program, so far it is a research field, but the tools do rely on assertions.) – Alexander Kogtenkov May 13 '21 at 18:09
  • 4. Yes, it would be appropriate to specify preconditions and postconditions for all methods. If the project lives for a long time, having them in the code would simplify refactoring and would help to find bugs when some parts of the project change. – Alexander Kogtenkov May 13 '21 at 18:11
  • 2. Assertions serve as means to detect bugs in the program. If an assertion is violated, there is a bug. If this is a precondition, the bug is on the client side. If it is a postcondition, the bug is in the supplier side. As soon as there are no bugs, assertion violation does not happen, and, they have no effect on the program behavior - it runs the same with or without assertion monitoring. – Alexander Kogtenkov May 13 '21 at 18:15
  • Thanks Alexander. Could you answer any more questions? – Mike May 14 '21 at 18:58
  • 5.Can preconditions only be built on the basis of the arguments that come to the supplier or are they also allowed on other elements? For example, can a precondition be a complex function that returned a boolean and was not related to any of the arguments? – Mike May 14 '21 at 18:58
  • 6.Why is it more appropriate to do compile-time checks? – Mike May 14 '21 at 18:58
  • 7.How are cases in which an error is not properly a bug treated with this method? Let me explain with an example. Let's imagine that we are in an application based on user events (a web page, for example). The user has to enter a number greater than zero and then press a button.When you click on the button, the method associated with said event will be triggered (it would be our client method) and this method will call the supplier method. In case the user enters a number less than or equal to zero, the supplier precondition will not be met and an exception will be thrown. – Mike May 14 '21 at 19:00
  • However, this is not properly a bug, but an error on the part of the user who entered the number, to which it would be convenient to notify of this circumstance. – Mike May 14 '21 at 19:00
  • 8.Let's imagine that I have debugged my application until, based on the tests that I have carried out in my test environment, no assertions are violated. Therefore, since I consider that my tests are finished, I disable the monitoring of the assertions and put my software in production. However, it turns out that I have made the mistake of not performing all the necessary tests and a case occurs in production that causes a precondition not to be met. What would happen? Would an exception continue to be thrown, or would having the monitoring disabled make it no longer thrown? – Mike May 14 '21 at 19:01
  • 9.It has become clear to me that once the software is bug-free, the provider fully trusts what the client sends them and vice versa. However, software development is not an exact science and it is difficult to be 100% sure that all cases are controlled. If it were the case that we have not tested all of them and some of them resulted in the non-compliance of any precondition or postcondition, an exception would be thrown to the client. – Mike May 14 '21 at 19:03
  • In the event that this exception is not caught by any method in the call chain and, furthermore, the client or provider (any client or provider in the call chain) is blocking some resource (files, database, ... .), these resources might not be released correctly. This gives me the following doubt: What exception handling philosophy is used in this methodology? Wouldn't it be convenient for the client to always be prepared for any possible exception thrown by the supplier? – Mike May 14 '21 at 19:03
  • 10.My previous question in turn leads me to a dilemma: In case the client had to be prepared for any possible exceptions thrown by the provider (methods that could belong to different classes within the project), that would lead to bad practice in the design of applications that would be the coupling between components. In other words, in the event that an exception thrown by the supplier was changed, the client would also have to modify it to be prepared for said modifications. – Mike May 14 '21 at 19:04
  • In fact, if the client had to have knowledge of the supplier's preconditions, wouldn't it also be a type of coupling, breaking the concept of class encapsulation? Should any supplier precondition change, the client would have to be aware of this in order to adapt to the change. In other words, a change in a method of one class implies changes in a method that could be of another class. – Mike May 14 '21 at 19:04
  • I understand that the change in the API of any class (a method that happens to be called in another way or that accepts another type of parameters) also entails a coupling with the other classes that call said API (since the sites where This method was called will have to be modified to call the new method name or pass the new parameters), but this coupling is unavoidable. – Mike May 14 '21 at 19:05
  • It seems as if the preconditions and postconditions established in a method with this methodology became part of the specifications of the method and that, therefore, they were like an extension of the API. Can the one relating to preconditions and postconditions be considered an inevitable coupling? – Mike May 14 '21 at 19:06
  • Sorry if I have not fully understood any concept. – Mike May 14 '21 at 19:06