User Tools

Site Tools


principles:invariant_avoidance_principle

This is an old revision of the document!


Invariant Avoidance Principle

Variants and Alternative Names

Context

Principle Statement

Avoid Invariants and Preconditions.

Description

Methods typically have preconditions. Something that has to be true prior to invoking the method so it can work properly. Typical cases are parameters that may not be null or have to be in a certain range. A solution is better the fewer preconditions there are.

Furthermore there are (class) invariants, i.e. conditions that have to be true in all observable states during the whole lifetime of an object. Typical invariants are attributes that may not be null or have to be in a certain range, lists that have to contain certain objects with certain properties, etc. A solution s better the fewer invariants there are.

While preconditions and invariants are absolutely necessary, introducing further ones comes at a certain cost.

Not that this principle does not apply to loop invariants, control-flow invariants, etc. as there is normally no chance to avoid them. But there can be fewer or more class invariants depending on the solution.

Rationale

A typical kind of defect is the violation of an invariant or a precondition. The more preconditions and invariants there are, the more possibilities there are to introduce defects. And according to Murphy's Law these possibilities will sooner or later result in defects. So it is better to avoid preconditions and invariants as this reduces the number of potential faults in the software.

Strategies

  • If the language supports that, use references which cannot be null
    • In C++ use references instead of pointers (see example 3: C++ references)
    • In Java use primitive types instead of their object wrappers (int instead of Integer but not int instead of Customer)
  • Use value objects instead of primitive types (see example 2: string preconditions)
  • Avoid duplication of information. If the same information is stored in different places (maybe in different formats), the values may get out of sync (see also DRY). This also applies to caching.

Caveats

Keep in mind that preconditions and invariants are absolutely necessary for every software. So this principle is constantly violated. Introducing preconditions and invariants is often also done deliberately in order to simplify the code (see KISS). So the purpose of this principle is mainly to point out that there are drawbacks. By no means invariants are problematic themselves or should be entirely avoided. They just also have disadvantages.

See also section contrary principles.

Origin

This principle is newly introduced here.

Evidence

Relations to Other Principles

Generalizations

  • Murphy's Law (ML): ML states that an invariant will eventually be broken. So IAP is the application of ML to invariants.

Specializations

Contrary Principles

  • Keep It Simple Stupid (KISS): Adding an invariant typically makes the code easier, as it can be assumed that the invariant holds. In fact that is often the very purpose if introducing invariants: Either they make the design easier or they are inevitable. Otherwise they should be avoided.

Complementary Principles

  • Information Hiding/Encapsulation (IH/E): When an invariant cannot be avoided, it should at least be encapsulated.
  • Liskov Substitution Principle (LSP): Invariants can also be broken by subtypes. LSP tells that invariants may only be strengthened by subtypes, so they are not broken. FIXME
  • Fail Fast (FF): Breaking an invariant is a defect. And in such a case the software should fail fast.
  • Don't Repeat Yourself (DRY): Duplication of information, like having the same data in different representations or like caching values, creates invariants. So an invariant sometimes is a hidden DRY violation.

Principle Collections

OOD Principle Language
General Principles
ML KISS MIMC DRY GP RoE
Modularization Principles
MP HC ECV
Module Communication Principles
TdA/IE LC DIP
Interface Design Principles
EUHM PLS UP
Internal Module Design Principles
IH/E IAP LSP PSU

Examples

Example 1: Index Preconditions

public void prettyPrintItem(List<Item> items, int index)
{
  ...
}

This method has the following preconditions:

  • items may not be null
  • index must be greater or equal 0
  • index must be lesser than items.size()

Compare the following solution:

public void prettyPrintItem(Item item)
{
  ...
}

This is better as it just has one precondition: item may not be null

Example 2: String Preconditions

public void downloadFile(String url)
{
  ...
}

This method has the following preconditions:

  • url may not be null
  • url must contain a valid URL (which is even a quite complicated precondition)

Compare the following method:

public void downloadFile(URL url)
{
  ...
}

This is better since there is only one precondition: url may not be null

Example 3: C++ References

Sompare the following two methods:

void prettyPrint(SomeClass * obj)
{
  ...
}
void prettyPrint(SomeClass& obj)
{
  ...
}

In the second version obj cannot be NULL as it is a reference and not a pointer. So there is one precondition less.

Example 4: DRY

A class for complex numbers should either store the real and the imaginary part or absolute value and argument but not both. If both are stored, there is the invariant that both representations result in the same complex number.

So it is better to store just one representation (e.g. the real and imaginary values) and if the other representation is needed (in this case the polar form), it can be computed. This can also be done transparently in the getter method.

Description Status

Further Reading

principles/invariant_avoidance_principle.1362675450.txt.gz · Last modified: 2013-05-19 22:10 (external edit)