Design by Contract

up:: Programming best practices ⚐

Learned about in The Pragmatic Programmer.

  1. Invariants
    • Statements that should be true throughout the lifetime of the function.
    • No matter how often this is called, it should always be true.
  2. Preconditions
    • Check in the function itself if requirements are met.
    • Evaluated before the main function body
  3. Postconditions
    • Ensure the output meets conditions before returning it. Otherwise throw error.
    • Evaluated after the main function body

Pseudo-ish code to illustrate:

function divide (a, b) {
  pre:
    typeof a === 'number';
    typeof b === 'number';
    b !== 0, 'May not divide by zero';
  main:
    return a / b;
  post:
    __result < a;
}
 
alert(divide(10, 0));

Languages

Difference to typing

DBC conditions should be used for logic that is hard to type check. For example the requirement “number must be even” for a function. Type checking cannot perform that logic.

Defensive programming?

I’m not sure how this connect to the idea that a method should handle bad data gracefully. Isn’t it better, especially in iOS Development, if the application doesn’t crash but only behaves a bit unexpectedly?

Maybe the middle road is a behaviour like Swift assert: Invalid asserts crash on debug build, but are removed from the source code in production build. (Although The Pragmatic Programmer recommends to leave it in Production)

Sometimes crashing the application is the right thing to do. Sometimes handling the bad data gracefully is the right thing to do. The tradeoff is Robustness (always keep the software running) versus Correctness (Never return an inaccurate result).

When choosing robustness, it is important to log odd data. The error should not go unnoticed.

Tension: Programs should crash early versus Bad data should be handled gracefully.

Resources