The notion of invariant is strongly linked with 'side effects'. I believe it was promoted by Bertrand Meyer's 'Design by Contract (DbC)' approach for software design.
DbC enriches Abstract Data Types (backbone of classes) with 3 important notions, preconditions, postconditions, invariants. It is easily explained when referring to procedures, so I'll try to explain in reference with it:
A precondition represents the condition input data for a procedure must respect in order to call that procedure. This precondition must be respected and enforced by the client of that particular procedure. The procedure designer might however defend from clients that do not respect the precondition by asserting that condition as first lines in the procedure. For example having a method double divide(double dividend, double divisor)
a precondition might be divisor != 0
.
A postcondition represents the a condition on the output data after the procedure returns; it is entirely the job of the procedure designer to respect this postcondition provided the precondition was respected; in a defense programming style before returning, the postcondition can be asserted.
An invariant can be regarded as a both a precondition and a postcondition, but with different understanding for precondition and postcondition from above concepts. An invariant basically says that the if the input has a particular condition met before the procedure was called, then that particular condition is valid after the procedure is called. For example a valid invariant for a procedure boolean search(int term, int array[])
might say that the state of array
before the call is the same as it is after the call.
Enforcing invariants on procedures (and not only procedures) is a great thing since it reduces side effects; this is useful since side effects are a great evil in programming. A particular procedure might change the state of the input arguments, or change the state of some global variables, or depend on some global variables; this might lead to nasty situations where two identical calls on the same procedure (with the same input) might yield different outputs. This leads to knowing the history of the calls and is very hard to debug especially in a multithreading context.