2

I have got a project in C++. Each of my classes has a method void assert_good() const. The usage of that method is solely for debugging purposes: if the object is an inconsistent/impossible internal state, then assert_good(); will make the program abort and leave an error message. Other than that, assert_good(); has no side-effect.

The purpose of these methods is solely for debugging and they will not be compiled in the release version. These methods are called throughout the code, often by other methods of the same object. They are used to verify that certain complicated methods are implemented correctly. I compare them to assertions.

class Foo {

  /* stuff here */

public:
  void assert_good() const {
    /* throw or abort if impossible state is found */
  }

I have been wondering whether the following is a well-known practice or idiom for which some "good practices" are known from experience.

  • 1
    Afaik it’s not a well known practice and imo also not a good practice. It feels like you’re mixing unit testing concerns with production code. I’d focus my effort on making sure that objects can’t go into an inconsistent state by encapsulating it’s state behind methods that verify data before changing state. Write unit tests for these methods and you should get pretty solid code. – Rik D Oct 1 '19 at 17:39
  • Not all methods that methods that change the internal state take arguments. You might have a modifying_member_method such that x.modifying_member_method(); changes the internal state. If that method is complex, you might want to assert that the internal state is good. – shuhalo Oct 1 '19 at 20:48
  • 2
    A better practice is to never allow your classes to get into inconsistent/impossible internal states to begin with, especially those impossible states. – Dunk Oct 1 '19 at 23:23
  • Yes, and checking for that state during development is one means of achieving precisely that, so that bugs are caught earlier. – shuhalo Oct 2 '19 at 12:20
  • Though I agree, I would go farther than @dunk: this is an excellent practice if you are in a situation where objects have state that can be inconsistent. But the better practice is to create objects where all states are orthogonal, and therefore there is no consistency problem to arise in the first place. Can you clarify the question by giving an example of the sorts of states that you're trying to detect inconsistency in? – Eric Lippert Oct 2 '19 at 18:20
4

It looks to be a variant of Design by Contract. DbC was described by Bertrand Meyer in his book Object-Oriented Software Construction. The basic idea of DbC is that there exist Contracts between subroutines, objects, and types, which basically guarantee that IFF the user of an abstraction fulfills their contract, THEN the provider of the abstraction guarantees that it will fulfill its contract.

Pragmatically, this allows to remove a lot of redundant error checking and defensive code, since it is very clear who is responsible for what. For example, in a subroutine, the callee does not have to check that arguments are valid because the contract says the caller is only allowed to pass valid arguments. In turn, the caller does not need to check the return value, since the contract says that if the arguments are valid, so is the return value.

If the contracts are actually recorded in the code proper, instead of just documentation, they can be automatically checked, which actually improves robustness even though the redundant checking has been removed.

Bertrand Meyer implemented DbC in his programming language Eiffel. In Eiffel, every subroutine can state its preconditions and postconditions and the compiler will automatically insert code that checks the preconditions before entering the subroutine, and the postconditions before returning from the subroutine. Likewise, classes can specify invariants about their instances, and the compiler will automatically insert checks after the postconditions checks to make sure an object always satisfies its invariants after a subroutine call. (Invariants may be violated while a subroutine is executing, but as soon as the subroutine is finished, the object must be in a valid state.)

Over time, there have been Contract Frameworks developed for many different programming languages (e.g. Boost.Contract for C++), and a few other languages with specific language-integration of DbC have appeared (e.g. D, Cobra (both also support language-integrated unit tests, and Cobra also language-integrated documentation), Spec♯, Sing♯, M♯).

Traditionally, Contract Systems, whether as libraries or integrated into the language, have worked at runtime. Microsoft's Spec♯ was, to my knowledge, the first system that tried to prove and disprove contracts at compile time. This was later extracted in the CodeContracts.NET library and shipped as part of .NET and Visual Studio.

Native language support for DbC in C++ may actually appear as early as C++2a.

| improve this answer | |
  • Why "iff", not just "if"? – Solomon Ucko Oct 1 '19 at 19:54
  • Yes, in some sense my question describes a possible way of (at least roughly) implementing Desing-by-Contract in a language that does not support it natively. – shuhalo Oct 2 '19 at 12:21
  • @SolomonUcko: Iff means if and only if. In this case, Jörg is making two statements. Statement 1: IF the user of an abstraction fulfills their contract, THEN the provider of the abstraction guarantees that it will fulfill its contract. Statement 2: IF the user of an abstraction does not fulfil their contract, THEN the provider of the abstraction does not guarantee that it will fulfill its contract. I'll note that normally statement 2 is implied (i.e., not stating that something is guaranteed implies means that it is not guaranteed), but Jörg has chosen to be explicit. – Brian Oct 2 '19 at 21:25
  • @Brian Oops, I misread it. I thought it said it guarantees won't fulfill it's part of the contract if the caller didn't fulfill its. – Solomon Ucko Oct 2 '19 at 21:30
  • @SolomonUcko: It's an easy mistake to make. The negation of, "X guarantees Y" is "X does not guarantee Y," but it's easy to accidentally interpret it as "X guarantees not Y." I'll note that I intentionally chose to use the contrapositive of the "proper" choice for iff statement 2 in order to make it easier to read. – Brian Oct 2 '19 at 21:34
1

This looks to me like "invariant checking" from Design By Contract. It can be extended by checking preconditions and postconditions. It is a technique which falls under the term Defensive Programming.

The only question here which arises to me is: why you actually need to check assert_good() in every class, as you wrote. Usually, I try to implement classes in a way they cannot have inconsistent states. An invariant checking like this should normally only be necessary for a few classes of a certain complexity. But your mileage may vary, maybe you are building different types of programs than me.

| improve this answer | |
0

I'm not a C++ guru, but I know there is the <assert.h> header that provides a macro for checking and throwing assertion errors. What you have done may use that internally (I assume), but does not allow the for a purely conditional method call.

In C# and Java, there is a convention for method names that infer certain behaviors:

  • check throws an exception if there is bad state
  • is returns a boolean for whether the state is good or bad

If we were to utilize that convention in your example here, it allows you to have an easily removed assertion:

#include <assert.h>

void MyClass::do_something() {
    // Pre-condition (design by contract)
    assert( is_good() );

    // do some changes and processing

    // Post-condition (design by contract), also Invariant condition
    assert( is_good() );
}

Because the check is is done using the standard assert() macro, when you recompile as a release build, the assertion macro is zeroed out, and all references to the is_good() also are removed allowing the optimizer to remove the dead code.

That said, if object state is so complex that it could easily be in an invalid state, then you might have too much going on in your object. It's hard to say. I personally prefer unit tests, but I can see a place for this in certain circumstances.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.