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.
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; 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.
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; Contract failed: *Denominator must not be zero
Contracts are inherited and follows the "Liskov Substitution Principle":
require in an overridden method is ORed with the base class requirement.ensure in an overridden method is ANDed with the base class guarantee.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.
Design by Contract is a philosophy of "Fail Fast". By explicitly stating assumptions, you make your code self-documenting and much easier to debug.