Design by Contract

DWScript supports Design by Contract (DbC) natively, allowing you to define preconditions and postconditions for your subroutines. This helps ensure code correctness and robust error handling.

Preconditions (require)

Use require to specify conditions that must be true before the subroutine executes. If a condition fails, an EAssertionFailed exception is raised.

You can provide a custom error message using the : syntax.

function Divide(a, b : Integer) : Float;
require
  b <> 0 : 'Denominator must not be zero';
begin
  Result := a / b;
end;

Postconditions (ensure)

Use ensure to specify conditions that must be true after the subroutine executes. You can access the result using Result and the original value of parameters using the old keyword.

procedure Deposit(var balance : Float; amount : Float);
require
  amount > 0;
begin
  balance += amount;
ensure
  balance = old balance + amount;
end;

The old keyword captures the value of the expression at the moment the subroutine was entered.

Contract Failures

When a contract is violated, the script engine raises an EAssertionFailed exception. This allows you to catch and handle contract violations during development or in specific error-handling blocks.

function Divide(a, b : Integer) : Float;
require
  b <> 0 : 'Denominator must not be zero';
begin
  Result := a / b;
end;

try
  Divide(10, 0);
except
  on E: EAssertionFailed do
    PrintLn('Contract failed: ' + E.Message);
end;
Result
Contract failed: *Denominator must not be zero

Contracts & Inheritance

Contracts are inherited and follows the "Liskov Substitution Principle":

  • require: A subclass can only weaken preconditions (i.e., accept more inputs). In practice, this means require in an overridden method is ORed with the base class requirement.
  • ensure: A subclass can only strengthen postconditions (i.e., guarantee more outcomes). In practice, this means ensure in an overridden method is ANDed with the base class guarantee.

Production Builds

Contracts are powerful for development and debugging, but they do incur a small performance overhead. In high-performance production environments, contract checking can be disabled via compiler options, effectively turning them into no-ops.

Summary

Design by Contract is a philosophy of "Fail Fast". By explicitly stating assumptions, you make your code self-documenting and much easier to debug.

On this page