Card 03/ 09

ConceptDifficulty: Intermediate2 min

Preconditions, Postconditions and Invariants in Liskov Substitution

Ask for no more, promise no less, and keep true what the parent keeps true.

"Behaves like its parent" is too vague to check in a code review, so Barbara Liskov and Jeannette Wing made it precise in 1994 with three rules. Each rule is about one part of the contract a method offers its callers. This card states the three rules and what each allows a subtype to change.

Three terms come first. A precondition is what a method requires before it is called, such as "the amount is positive". A postcondition is what it promises afterwards, such as "the balance has gone down by the amount". An invariant is something that is always true of the object between calls, such as "the balance is never negative".

The three Liskov rules, what a subtype may do, and a breach of each
RuleA subtype mayA breach
Preconditions cannot be strengthenedAccept the same inputs as the parent, or moreThe parent accepts any positive amount; the subtype rejects amounts over 100.
Postconditions cannot be weakenedPromise the same result as the parent, or moreThe parent always reduces the balance by the amount; the subtype sometimes withdraws less.
Invariants must be preservedKeep every rule the parent always keepsThe parent's width and height change independently; the subtype ties them together.

The shorthand is: ask for no more, promise no less, and keep true what the parent keeps true.

The direction of each rule surprises people at first. A stricter subtype feels safer, so rejecting large amounts seems harmless. But a caller that was allowed to pass 150 to the parent will pass 150 to the subtype too, and it has no reason to expect a refusal. A subtype that accepts more inputs is fine, because no caller of the parent was relying on those inputs being rejected.

Two more rules come from the same paper and are enforced by most typed languages: a subtype's method may not throw new kinds of exception the parent never threw, and it may return a more specific type than the parent, never a less specific one. The ostrich breaks the first. Its fly throws an exception that Bird.fly never threw, so it weakens the postcondition "the bird flies" to "the bird flies, or something goes wrong".

These rules let you test substitutability directly: run the parent's tests against the subtype. If a test written for the parent's contract fails for the subtype, the subtype breaks one of the three rules.