0

I know that DbC mandates that the caller is responsible for the precondition (parameters or maybe values of member variables) and I have just read, in one of the books, that actually few people are bold enough to really leave all the responsibility up to the calling code and do not check the input in the called routine.
But I am thinking, doesn't it also lead to duplication? What if I need to call a method from several places.. in all those places I would need to make sure the preconditions are met..

bool AddEmployee(Employee e)
{
  //precondition: List of employees is not full, employee is not empty...
  EmployeeList.Add(e);
}

I could call it from several modules (Employee management, HR module..) so I do not get whether I truly should check for preconditions in all those places.

John V
  • 4,855
  • 15
  • 39
  • 63

1 Answers1

0

The contract states that it is the callers responsibility to ensure that the pre-conditions are met.

The contract states clearly who is responsible for a bug. If you fail to meet a pre-condition its the caller. If you fail to meet a post-condition its the callee. That is useful enough alone that its worth documenting the contract.

Sometimes you can write your code so that the pre-conditions don't need to be checked. For example:

Foo() 
{
  int x = 1;
  Bar(x);
}

Bar(int x)  [[expects: x>0]]
{
}

You set x so you know it can't be less than zero.

At other times you do need to check them. It does sometimes create duplication . I have not found this to be a significant problem often but you may sometimes see patterns like:

SafeBar(int x)
{
  if (x <= 0) throw SomeException();
  else Bar(x);
}

This assumes of course that errors can be handled in the same way for each use, which is not always the case.

Removing pre-condition checks is a performance optimisation. As we know premature optimisation is the root of all evil, so it should only be done when necessary.

Now another factor is implementation. Few languages support checking contracts at compile time. It was recently voted into C++20 but at the time of writing there is only an experimental implementation. C++20 uses attributes as above. Attributes are not supposed to change runtime behaviour.

If you don't have compile time support you will typically find implementations using some sort of assertion macro. Personally I use one that throws an exception. You are then using standard exception handling mechanism for handling bugs (some consider this inappropraite) but you don't necessarily need to check the contract at the call site.

Why might it be inappropriate? It is worth remembering that a contract violation is a bug. If you run the function without meeting the pre-condition you are invoking undefined behaviour. In principle anything could happen. It could even format your hard-drive (though that is unlikely). Checking the pre-condition at runtime is like defensive coding. If the assertion causes an exception then undefined behaviour never occurs. That is safer and makes it easier to debug. But from one point of view you've modified the contract.

In general checking contracts at compile time is undecidable. Quoting the linked answer:

If the Theorem Prover can prove that a contract will always be violated, that's a compile error. If the Theorem Prover can prove that a contract will never be violated, that's an optimization.

Proving contracts in general is equivalent to solving the Halting Problem and thus not possible. So, there will be a lot of cases, where the Theorem Prover can neither prove nor disprove the contract. In that case, a runtime check is emitted


A slight aside as the question is marked language agnostic but an issue I have with the C++20 proposal is that it seems to omit the runtime check for the other cases. It also says explicitly that it should not be possible to set the violation handler at run time:

There should be no programmatic way of setting or modifying the violation handler

It also mandates default choice of calling std::terminate() on contract violation to end the whole process. This would be a bad thing(tm) for something like a multithreaded fault tolerant task scheduler. A bug in one task should not kill the whole process.

I think the reasoning is that C++20 contracts are intended as a compile time feature only. That includes evaluating them in compile time meta-pograms using constexpr and consteval. The feature allows compiler venders to start adding theorem provers to check contracts which was not possible before. This is important and opens up many new opportunities.

Hopefully a pragmatic modification considering the runtime possibilities will follow. The downside is that in the short term you will need to retain your assertions. If, like me, you use Doxygen for documentation (which does not yet understand contracts) you have triple redundancy. For example:

///
/// @brief
/// Bar does stuff with x
///
/// @pre
/// @code x > 0 @endcode
///
void Bar(int x) [[expects: x > 0]]
{
   { //pre-conditions
      assertion(x>0);
   }
   ...do stuff
}

Note that the C assert() macro doesn't throw. Hence we use our own assertion() macro that does. The CppCoreGuidelines support library includes Expects() and Ensures(). I'm not sure if they throw or call std::terminate().

Bruce Adams
  • 4,953
  • 4
  • 48
  • 111
  • So basically, as the author of Eiffel mentioned, not many people actually dare to that and rely on caller to provide correct input. – John V Dec 20 '18 at 19:53
  • That misses another point (which I've added to the answer). The contract states clearly who is responsible for a bug. If you fail to meet a pre-condition its the caller. If you fail to meet a post-condition its the callee. That is useful enough alone that its worth documenting the contract. – Bruce Adams Dec 20 '18 at 23:41
  • I do not follow. What misses a point? I mentioned that even the author of Eiffel claimed that very few people do DbC "fully" and do not implement any validation/defensive mechanism on provided inputs, i.e. they fully trust that the caller provides the right input as prescribed in the contract. – John V Dec 21 '18 at 12:20