Verifying object invariants and pre- and post-conditions

D supports contract programming, which can help you ensure an object's state is always valid and object methods are passed with correct parameters and returns valid values, including while using class inheritance. Here, we'll see how to use these features and why they work the way they do.

How to do it…

Perform the following steps:

  1. When writing an interface, class, or struct, add in and out blocks to methods, right after the signatures, and also add invariant blocks to the aggregate.
  2. Put assertions in the in blocks to verify your preconditions.
  3. Put assertions in the out blocks to verify your postconditions. The out block may take an argument to access the method's return value.
  4. Add the body keyword before your method's body but after its in and out blocks (if it has them).
  5. In child classes, also use in contracts to reassert your input requirements, even if they are the same as the parent class.
  6. Put assertions in the invariant blocks to verify object conditions that are always true. The invariant must be valid as soon as the constructor returns.
  7. Unless your data members may validly hold any value, make them private and only accessible through getter and setter properties.
  8. Test your application by building without the –release switch and running the program.

The code is as follows:

interface Example {
  int negate(int a)
    in { assert(a >= 0); }
    out(ret) { assert(ret <= 0); }
    // note: no body since this is an interface
}
class ExampleImplementation : Example {
  int negate(int a)
    in { assert(a >= 0); } // same input restriction
    body { // note the body keyword following the contracts
    return –a;
  }
  invariant() {
    // we have no data, so no need for any invariant assertions
  }
}
void main() {
  auto e = new ExampleImplementation();
  e.negate(-1); // throws an assertion failure
}

How it works…

D supports contract programming, inspired by the Eiffel language, with three constructs: the in and out blocks on methods and invariant blocks in objects.

All contracts are run at runtime. Running contracts at compile time is not possible in D today. Like other assert statements, contracts are compiled out when compiling in release mode.

It's preferable to use the in and out blocks instead of writing assertions inside the function itself, because the in and out blocks understand inheritance. When overriding an inherited method in a class, the following points must be considered:

  • Any one of the in blocks in the inheritance tree must pass (it may weaken preconditions by providing alternate checks). If there is no in block on a method, it is assumed to accept any input, and thus always pass. This is why child classes have to reassert their input requirements, even if they are the same as the interface.
  • All of the out blocks in the inheritance tree must pass (it may strengthen postconditions by providing additional checks).
  • All of the object invariants on the inheritance tree must pass (it must honor the parent's invariants).

These rules realize the Liskov substitution principle of object-oriented programming; a subclass should always be substitutable for its superclass or interface without breaking any code. When using an object through its interface, only the interface's in blocks are considered and it must pass (this is because the interface doesn't know which implementation it is forwarding to).

A limitation of contracts on methods in D is the lack of a pre-state in the out contract. There is no easy way to work around this limitation at this time. Instead, focus your out contracts on ensuring that your return value alone meets some requirements, such as not being null.

The current D spec does not permit contracts on abstract class methods as well (though they are permitted on interfaces). This limitation may be removed in the future.

Since contracts are logically part of the interface, when you write a library, you should also write your contracts in your D interface file (a .di file, which includes function prototypes but not the whole implementation bodies), if you choose to use them. This way, the user or compiler may choose to insert them in a debug build of a program, even when using a release build of the library. However, at this time, the compiler does not keep contracts. The common solution is to provide both debug and release builds of your library or to distribute full source code and let the user build it themselves.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.140.204