Class Invariants
1 Class Invariants

Class Invariants

CS 3500, Fall 2015

Bad freedoms

What fields does a Connect $N$ model need?

Map<String, Object> properties;

What might be wrong?

  • might not have width, height, goal, columns, etc.

  • those properties might have the wrong types

The language lets us rule these possibilities out

Connect $N$ model fields, take two

int width;
int height;
int goal;
int players;

Status status;
int turn;

List<List<Integer>> columns;

What might be wrong now?

  • width or height or goal or players might change mid-game

  • width or height or goal or players might be zero or negative

  • status or columns might be null

  • the shape of the list-of-lists in columns might not match the dimensions in width and height

  • or it might contain Integers that don’t stand for actual players

  • and the client can look at or change whatever it pleases

The language lets us rule some of these possibilities out

Internalize immutability constraints using final

final int width;
final int height;
final int goal;
final int players;

Status status;
int turn;

final List<List<Integer>> columns;

What might be wrong now?

  • width or height or goal or players might change mid-game

  • width or height or goal or players might be zero or negative

  • status or columns might be null

  • the shape of the list-of-lists in columns might not match the dimensions in width and height

  • or it might contain Integers that don’t stand for actual players

  • and the client can look at or change whatever it pleases

Ensure correct initialization using a constructor

public ConnectNModelImpl(int width, int height,
                         int goal, int players)
{
  if (width < 1 || height < 1 || goal < 2 || players < 1) {
    throw new IllegalArgumentException(…);
  }

  columns = new ArrayList<>(width);
  for (int i = 0; i < width; ++i) {
    columns.add(new ArrayList<>(height));
  }

  ...
}

(If the constructor is private, checking might happen in a factory.)

What might be wrong now?

  • width or height or goal or players might change mid-game

  • width or height or goal or players might be zero or negative

  • status or columns might be null

  • the shape of the list-of-lists in columns might not match the dimensions in width and height

  • or it might contain Integers that don’t stand for actual players

  • and the client can look at or change whatever it pleases

The last point is key!

Gain control by using private

private final int width;
private final int height;
private final int goal;
private final int players;

private Status status;
private int turn;

private List<List<Integer>> columns;

Access levels

Access level modifiers

ModifierScope
privatesame class only
default...and everything else in the package
protected...and subclasses
public...and the rest of the world

Where can it be seen from?

Modifierclasspackagesubclassworld
private
default
protected
public

Who can see members of Base?

package left;                       package right;

public class Base { ... }           public class Derived
                                    extends Base { ... }

public class lefthelper { ... }     public class righthelper { ... }

ModifierBaseLeftHelperDerivedRightHelper
private
default
protected
public

An example

final class Even {
  public Even(int value) {
    if (value % 2 != 0) {
      throw IllegalArgumentException("value must be even");
    }

    this.value = value;
  }

  public int getValue() {
    return value;
  }
  private final int value;
}

How do we know that value is always even?

  • The constructor ensures it

  • It can't change

Another example

final class EvenCounter {
  public EvenCounter(int value) {
    if (value % 2 != 0) {
      throw IllegalArgumentException("value must be even");
    }

    this.value = value;
  }

  public int nextValue() {
    return value += 2;
  }
  private int value;
}

How do we know that value is always even?

  • The constructor ensures it

  • Only methods can change it

  • Method nextValue() preserves evenness

  • There aren't any other (public) methods

Ruling out nonsense

Which methods are okay?

public void reset() {
  value = 0;
}
public int scale(int factor) {
  return value *= factor;
}
public int halve() {
  return value /= 2;
}
public int half() {
  return value / 2;
}

What’s going on here?

A class invariant is a logical statement about the instantaneous state of an object that is ensured by the constructors and preserved by the methods.

public int nextValue() {
  return value += 2;
}
private int value;
// INVARIANT: value is even

Definitions

A class invariant is a logical statement about the instantaneous state of an object that is ensured by the constructors and preserved by the methods.

  • logical statement: a claim that is true or false

  • the instantaneous state of an object: meaning the values in its fields at a point in time

  • ensured by the constructors: when a constructor finishes normally then the claim is true

  • preserved by the methods: if the claim is true before a method is called then it is true afterward

Non-examples

A class invariant is a logical statement about the instantaneous state of an object that is ensured by the constructors and preserved by the methods.

// INVARIANT: value is small

Not a logical statement—too vague

// INVARIANT: value never decreases

Not instantaneous—not even wrong!

// INVARIANT: value is non-negative

Just not true

// INVARIANT: value is an int

True, but vacuous because Java guarantees it

Possible examples from Connect $N$

private final int width;
// INVARIANT (1): width is not null
// INVARIANT (2): width > 0
// INVARIANT (3): width > height
// INVARIANT (4): width never changes
private int turn;
// INVARIANT (5): turn only increases
// INVARIANT (6): turn > 0
// INVARIANT (7): turn < players

(For those reading along at home, only 2 and 7 are invariants.)

More examples from Connect $N$

private List<List<Integer>> columns;
// INVARIANT (1): columns != null
// INVARIANT (2): columns.size() == width
// INVARIANT (3): columns.get(col) != null if 0 <= col < width
// INVARIANT (4): every column in columns has size <= height
// INVARIANT (5): every Integer in columns is a valid player
//                in (0, players]
// NOT AN INVARIANT: columns
// NOT AN INVARIANT: columns agrees with width
// NOT AN INVARIANT: columns always refers to the same list
// INVARIANT BUT VACUOUS: columns is a list

Class invariants enable rely-guarantee reasoning

The class invariant reasoning principle:

  • If the constructor ensures it,

  • and the methods preserve it,

  • then the methods can rely on it.

Rule out representations you don't want (whether invalid or merely inconvenient)

Example: rational numbers

“Merely inconvenient” representation:

public Rational(long numerator, long denominator) {
  if (denominator == 0) {
    throw new IllegalArgumentException(DEN_ZERO_MSG);
  }

  num = numerator;
  den = denominator;
}

private final long num;
private final long den;
// INVARIANT: den != 0

Continue with the code.