Class invariant

Last updated

In computer programming, specifically object-oriented programming, a class invariant (or type invariant) is an invariant used for constraining objects of a class. Methods of the class should preserve the invariant. The class invariant constrains the state stored in the object.

Contents

Class invariants are established during construction and constantly maintained between calls to public methods. Code within functions may break invariants as long as the invariants are restored before a public function ends. With concurrency, maintaining the invariant in methods typically requires a critical section to be established by locking the state using a mutex.

An object invariant, or representation invariant, is a computer programming construct consisting of a set of invariant properties that remain uncompromised regardless of the state of the object. This ensures that the object will always meet predefined conditions, and that methods may, therefore, always reference the object without the risk of making inaccurate presumptions. Defining class invariants can help programmers and testers to catch more bugs during software testing.

Class invariants and inheritance

The useful effect of class invariants in object-oriented software is enhanced in the presence of inheritance. Class invariants are inherited, that is, "the invariants of all the parents of a class apply to the class itself." [1]

Inheritance can allow descendant classes to alter implementation data of parent classes, so it would be possible for a descendant class to change the state of instances in a way that made them invalid from the viewpoint of the parent class. The concern for this type of misbehaving descendant is one reason object-oriented software designers give for favoring composition over inheritance (i.e., inheritance breaks encapsulation). [2]

However, because class invariants are inherited, the class invariant for any particular class consists of any invariant assertions coded immediately on that class in conjunction with all the invariant clauses inherited from the class's parents. This means that even though descendant classes may have access to the implementation data of their parents, the class invariant can prevent them from manipulating those data in any way that produces an invalid instance at runtime.

Programming language support

Assertions

Common programming languages like Python, [3] PHP, [4] JavaScript,[ citation needed ] C++ and Java support assertions by default, which can be used to define class invariants. A common pattern to implement invariants in classes is for the constructor of the class to throw an exception if the invariant is not satisfied. Since methods preserve the invariants, they can assume the validity of the invariant and need not explicitly check for it.

Native support

The class invariant is an essential component of design by contract. So, programming languages that provide full native support for design by contract, such as Eiffel, Ada, and D, will also provide full support for class invariants.

Non-native support

For C++, the Loki Library provides a framework for checking class invariants, static data invariants, and exception safety.

For Java, there is a more powerful tool called Java Modeling Language that provides a more robust way of defining class invariants.

Examples

Native support

Ada

The Ada programming language has native support for type invariants (as well as pre- and postconditions, subtype predicates, etc.). A type invariant may be given on a private type (for example to define a relationship between its abstract properties), or on its full definition (typically to help in verifying the correctness of the implementation of the type). [5] Here is an example of a type invariant given on the full definition of a private type used to represent a logical stack. The implementation uses an array, and the type invariant specifies certain properties of the implementation that enable proofs of safety. In this case, the invariant ensures that, for a stack of logical depth N, the first N elements of the array are valid values. The Default_Initial_Condition of the Stack type, by specifying an empty stack, ensures the initial truth of the invariant, and Push preserves the invariant. The truth of the invariant then enables Pop to rely on the fact that the top of the stack is a valid value, which is needed to prove Pop's postcondition. A more complex type invariant would enable the proof of full functional correctness, such as that Pop returns the value passed into a corresponding Push, but in this case we are merely trying to prove that Pop does not return an Invalid_Value.

generictypeItemisprivate;Invalid_Value:inItem;packageStacksistypeStack(Max_Depth: Positive)isprivatewithDefault_Initial_Condition=>Is_Empty(Stack);functionIs_Empty(S: inStack)returnBoolean;functionIs_Full(S: inStack)returnBoolean;procedurePush(S: inoutStack;I: inItem)withPre=>notIs_Full(S)and thenI/=Invalid_Value,Post=>notIs_Empty(S);procedurePop(S: inoutStack;I: outItem)withPre=>notIs_Empty(S),Post=>notIs_Full(S)and thenI/=Invalid_Value;privatetypeItem_Arrayisarray(Positiverange<>)ofItem;typeStack(Max_Depth: Positive)isrecordLength:Natural:=0;Data:Item_Array(1..Max_Depth):=(others=>Invalid_Value);end recordwithType_Invariant=>Length<=Max_Depthandthen(forallJin1..Length=>Data(J)/=Invalid_Value);functionIs_Empty(S: inStack)returnBooleanis(S.Length=0);functionIs_Full(S: inStack)returnBooleanis(S.Length=S.Max_Depth);endStacks;

D

D programming language has native support of class invariants, as well as other contract programming techniques. Here is an example from the official documentation. [6]

classDate{intday;inthour;invariant(){assert(day>=1&&day<=31);assert(hour>=0&&hour<=23);}}

Eiffel

In Eiffel, the class invariant appears at the end of the class following the keyword invariant.

classDATEcreatemakefeature{NONE}-- Initializationmake(a_day:INTEGER;a_hour:INTEGER)-- Initialize `Current' with `a_day' and `a_hour'.requirevalid_day:a_day>=1anda_day<=31valid_hour:a_hour>=0anda_hour<=23doday:=a_dayhour:=a_hourensureday_set:day=a_dayhour_set:hour=a_hourendfeature-- Accessday:INTEGER-- Day of month for `Current'hour:INTEGER-- Hour of day for `Current'feature-- Element changeset_day(a_day:INTEGER)-- Set `day' to `a_day'requirevalid_argument:a_day>=1anda_day<=31doday:=a_dayensureday_set:day=a_dayendset_hour(a_hour:INTEGER)-- Set `hour' to `a_hour'requirevalid_argument:a_hour>=0anda_hour<=23dohour:=a_hourensurehour_set:hour=a_hourendinvariantvalid_day:day>=1andday<=31valid_hour:hour>=0andhour<=23end

Non-native support

C++

The Loki (C++) library provides a framework written by Richard Sposato for checking class invariants, static data invariants, and exception safety level.

This is an example of how class can use Loki::Checker to verify invariants remain true after an object changes. The example uses a geopoint object to store a location on Earth as a coordinate of latitude and longitude.

The geopoint invariants are:

  • latitude may not be more than 90° north.
  • latitude may not be less than -90° south.
  • longitude may not be more than 180° east.
  • longitude may not be less than -180° west.
#include<loki/Checker.h>  // Needed to check class invariants.#include<Degrees.hpp>classGeoPoint{public:GeoPoint(Degreeslatitude,Degreeslongitude);/// Move function will move location of GeoPoint.voidMove(Degreeslatitude_change,Degreeslongitude_change){// The checker object calls IsValid at function entry and exit to prove this// GeoPoint object is valid. The checker also guarantees GeoPoint::Move// function will never throw.CheckFor::CheckForNoThrowchecker(this,&IsValid);latitude_+=latitude_change;if(latitude_>=90.0)latitude_=90.0;if(latitude_<=-90.0)latitude_=-90.0;longitude_+=longitude_change;while(longitude_>=180.0)longitude_-=360.0;while(longitude_<=-180.0)longitude_+=360.0;}private:/** @note CheckFor performs validity checking in many functions to determine   if the code violated any invariants, if any content changed, or if the   function threw an exception.   */usingCheckFor=::Loki::CheckFor<constGeoPoint>;/// This function checks all object invariants.boolIsValid()const{assert(this!=nullptr);assert(latitude_>=-90.0);assert(latitude_<=90.0);assert(longitude_>=-180.0);assert(longitude_<=180.0);returntrue;}Degreeslatitude_;///< Degrees from equator. Positive is north, negative is///< south.Degreeslongitude_;///< Degrees from Prime Meridian. Positive is east,///< negative is west.}

Java

This is an example of a class invariant in the Java programming language with Java Modeling Language. The invariant must hold to be true after the constructor is finished and at the entry and exit of all public member functions. Public member functions should define precondition and postcondition to help ensure the class invariant.

publicclassDate{int/*@spec_public@*/day;int/*@spec_public@*/hour;/*@invariant day >= 1 && day <= 31; @*///class invariant/*@invariant hour >= 0 && hour <= 23; @*///class invariant/*@    @requires d >= 1 && d <= 31;    @requires h >= 0 && h <= 23;    @*/publicDate(intd,inth){// constructorday=d;hour=h;}/*@    @requires d >= 1 && d <= 31;    @ensures day == d;    @*/publicvoidsetDay(intd){day=d;}/*@    @requires h >= 0 && h <= 23;    @ensures hour == h;    @*/publicvoidsetHour(inth){hour=h;}}

Related Research Articles

Eiffel is an object-oriented programming language designed by Bertrand Meyer and Eiffel Software. Meyer conceived the language in 1985 with the goal of increasing the reliability of commercial software development; the first version becoming available in 1986. In 2005, Eiffel became an ISO-standardized language.

In computer programming, a precondition is a condition or predicate that must always be true just prior to the execution of some section of code or before an operation in a formal specification.

In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment. The environment is a mapping associating each free variable of the function with the value or reference to which the name was bound when the closure was created. Unlike a plain function, a closure allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.

Standard ML (SML) is a general-purpose, modular, functional programming language with compile-time type checking and type inference. It is popular for writing compilers, for programming language research, and for developing theorem provers.

Generic programming is a style of computer programming in which algorithms are written in terms of data types to-be-specified-later that are then instantiated when needed for specific types provided as parameters. This approach, pioneered by the ML programming language in 1973, permits writing common functions or types that differ only in the set of types on which they operate when used, thus reducing duplicate code.

In computer programming, a type system is a logical system comprising a set of rules that assigns a property called a type to every term. Usually the terms are various language constructs of a computer program, such as variables, expressions, functions, or modules. A type system dictates the operations that can be performed on a term. For variables, the type system determines the allowed values of that term. Type systems formalize and enforce the otherwise implicit categories the programmer uses for algebraic data types, data structures, or other components.

In object-oriented programming (OOP), the object lifetime of an object is the time between an object's creation and its destruction. Rules for object lifetime vary significantly between languages, in some cases between implementations of a given language, and lifetime of a particular object may vary from one run of the program to another.

<span class="mw-page-title-main">D (programming language)</span> Multi-paradigm system programming language

D, also known as dlang, is a multi-paradigm system programming language created by Walter Bright at Digital Mars and released in 2001. Andrei Alexandrescu joined the design and development effort in 2007. Though it originated as a re-engineering of C++, D is a profoundly different language — features of D can be considered streamlined and expanded-upon ideas from C++, however D also draws inspiration from other high-level programming languages, notably Java, Python, Ruby, C#, and Eiffel.

In computer science, a tagged union, also called a variant, variant record, choice type, discriminated union, disjoint union, sum type or coproduct, is a data structure used to hold a value that could take on several different, but fixed, types. Only one of the types can be in use at any one time, and a tag field explicitly indicates which one is in use. It can be thought of as a type that has several "cases", each of which should be handled correctly when that type is manipulated. This is critical in defining recursive datatypes, in which some component of a value may have the same type as that value, for example in defining a type for representing trees, where it is necessary to distinguish multi-node subtrees and leaves. Like ordinary unions, tagged unions can save storage by overlapping storage areas for each type, since only one is in use at a time.

<span class="mw-page-title-main">Pointer (computer programming)</span> Object which stores memory addresses in a computer program

In computer science, a pointer is an object in many programming languages that stores a memory address. This can be that of another value located in computer memory, or in some cases, that of memory-mapped computer hardware. A pointer references a location in memory, and obtaining the value stored at that location is known as dereferencing the pointer. As an analogy, a page number in a book's index could be considered a pointer to the corresponding page; dereferencing such a pointer would be done by flipping to the page with the given page number and reading the text found on that page. The actual format and content of a pointer variable is dependent on the underlying computer architecture.

In computer science, type safety and type soundness are the extent to which a programming language discourages or prevents type errors. Type safety is sometimes alternatively considered to be a property of facilities of a computer language; that is, some facilities are type-safe and their usage will not result in type errors, while other facilities in the same language may be type-unsafe and a program using them may encounter type errors. The behaviors classified as type errors by a given programming language are usually those that result from attempts to perform operations on values that are not of the appropriate data type, e.g., adding a string to an integer when there's no definition on how to handle this case. This classification is partly based on opinion.

In class-based, object-oriented programming, a constructor is a special type of function called to create an object. It prepares the new object for use, often accepting arguments that the constructor uses to set required member variables.

Many programming language type systems support subtyping. For instance, if the type Cat is a subtype of Animal, then an expression of type Cat should be substitutable wherever an expression of type Animal is used.

<span class="mw-page-title-main">Dangling pointer</span> Pointer that does not point to a valid object

Dangling pointers and wild pointers in computer programming are pointers that do not point to a valid object of the appropriate type. These are special cases of memory safety violations. More generally, dangling references and wild references are references that do not resolve to a valid destination.

In computer programming, thread-local storage (TLS) is a memory management method that uses static or global memory local to a thread.

In computer science, a literal is a textual representation (notation) of a value as it is written in source code. Almost all programming languages have notations for atomic values such as integers, floating-point numbers, and strings, and usually for booleans and characters; some also have notations for elements of enumerated types and compound values such as arrays, records, and objects. An anonymous function is a literal for the function type.

This article compares a large number of programming languages by tabulating their data types, their expression, statement, and declaration syntax, and some common operating-system interfaces.

In computer programming, a constant is a value that is not altered by the program during normal execution, i.e., the value is constant. When associated with an identifier, a constant is said to be "named," although the terms "constant" and "named constant" are often used interchangeably. This is contrasted with a variable, which is an identifier with a value that can be changed during normal execution, i.e., the value is variable.

<span class="mw-page-title-main">Nim (programming language)</span> Programming language

Nim is a general-purpose, multi-paradigm, statically typed, compiled high-level systems programming language, designed and developed by a team around Andreas Rumpf. Nim is designed to be "efficient, expressive, and elegant", supporting metaprogramming, functional, message passing, procedural, and object-oriented programming styles by providing several features such as compile time code generation, algebraic data types, a foreign function interface (FFI) with C, C++, Objective-C, and JavaScript, and supporting compiling to those same languages as intermediate representations.

Whiley is an experimental programming language that combines features from the functional and imperative paradigms, and supports formal specification through function preconditions, postconditions and loop invariants. The language uses flow-sensitive typing also known as "flow typing."

References

  1. Meyer, Bertrand. Object-Oriented Software Construction, second edition, Prentice Hall, 1997, p. 570.
  2. E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading, Massachusetts, 1995., p. 20.
  3. Official Python Docs, assert statement
  4. "PHP assert function". Archived from the original on 2001-03-21.
  5. "Ada Reference Manual 7.3.2 Type Invariants". ada-auth.org. Retrieved 2022-11-27.
  6. "Contract Programming - D Programming Language". dlang.org. Retrieved 2020-10-29.