Interface Contract Deference Principle
Postel’s law states that you should be strict with how you communicate to others, and permissive with how others communicate to you.
Hyrum’s law states that all behaviors of an interface will be depended upon. Implementation details1 will be used, exploited or worked around in unpredictable ways.
I propose a corollary of these two that I call interface contract deference principle:
When using an interface you should defer strictly on what its contract explicitly specifies.
Consider that any implementation detail that is not specified in the contract is fair game to be changed. This will make your code safer, since you are not exposing it to unwanted changes. This makes your code more decoupled and allow to switch from an implementation to another (provided that interfaces actually respect the contract). This makes the interface implementer’s job easier, since they won’t have to deal with migration for things that are not entirely relevant, since they’re not in the contract.
You might come to know certain things on how an interface behaves, that applying certain conditions result in certain behaviors, that a certain field gets a meaningul value. Be it that it’s directly observable, or known from implementation. If you decide to transgress that principle, you are at the mercy of the implementer removing the unspecified feature you were using. Arguing against the change will become harder2.
For example (from real life)
An interface returns an
id that is semantically meaningful, while being specified as id: unique identifier - string. This meaningfulness should be ignored, so that the id can be replaced by anything else. If you exploited the field for its semantic value, the day this id is replaced with a random id, your code will break.
You know for a fact that some data store library is implemented to validate some data that you should be and throw an error, but it’s not specified. Keep a validation, or be at the mercy of the implementer to remove this validation to make their library more flexible.
An interface returns a field that looks like a GUID, specified as a string. Load this into a string. Or be at the mercy of the implementer to change that id to a mashup of several semantic values.
Also see Joel Spolsky / The law of leaky abstractions on topic - basically, limitations of low level dependencies eventually appear on higher level abstractions under certain conditions. ↩
See Wired / Locked Out of ‘God Mode,’ Runners Are Hacking Their Treadmills for an example of consequences of relying on an undocumented feature. ↩