In the last article we presented the benefits of representation invariants and proposed a syntax to write those. Yet coming up with complete rep invariant requires a bit of practice. Today, we give you 6 tips to help you write good rep invariants.

1 – Identify meaningless rep values

Forbid rep values for which the abstraction function is not defined. The abstraction function is often a partial function, meaning that it isn’t defined for some values in R. This is typically the case when the representation relies on only particular values of the instance variables.

/**
 * Month represents an immutable month of the Gregorian calendar.
 *
 * @specfield month : string // The month, e.g., January, February, etc.
 */
class Month {

    private int monthNumber;

    /*
     * Abstraction Function:
     *   month = M(monthNumber) where M(1) = January, M(2) = February, ...,
     *                                M(11) = November, M(12) = December
     */
}

In the above example, the abstraction function is defined only when the month number is between 1 and 12. The representation invariant must thus prohibit the other values.

/*
 * Representation Invariant:
 *   1 <= monthNumber <= 12
 */

More generally, the rep invariant must exclude all values that do not lead to a licit abstract value according to the abstraction function. On the contrary, make sure you don’t restrict the rep invariant so much that the abstraction function no longer covers the entire abstract value space A. Otherwise some abstract values will not be representable.

2 – Represent the domain constraints

The business domain naturally imposes some constraints. In a bank account, the balance may not be under a given threshold. In chess, one white bishop is always on a white square, while the other white bishop is always on a black square.

These typically constitute constraints over properties of the abstract values (like spec fields). They are in fact abstract invariants and should be documented in the class overview, as we mentioned in an earlier post. Yet they’re also part of the rep invariant, since one must impede the creation of rep values that lead to illicit abstract values.

3 – Synchronize dependent instance variables

Some instance variables have to stay synchronized with each other. In a bank account, for example, the balance and the sum of the transaction amounts should always be in sync. Similarly, in a linked list, the size must always be equal to the number of elements in the list.

4 – Be aware of algorithmic constraints

Pay attention to constraints required by the data structures or algorithms you’ve chosen. For example, an array must be sorted in order to use binary search. A tree cannot have cycles, and two nodes cannot share the same child.

5 – Avoid unexpected exceptions

Some rep values may cause your code to deal with unexpected exceptions. A conventional part of most rep invariants is the forbidding of null values for instance variables. It allows one not to worry about NullPointException when she uses these instance variables in method implementation:

/*
 * Representation Invariant:
 *   name != null && transactions != null
 *   ...
 */

Make sure your methods actually establish rep invariant, though. That means, if any of those instance variables is initialized from parameters, you have to check these are not null before you assign the instance variables.

Some other common exception conditions you should think about are:

  • divide by zero
  • index out of range
  • class cast exception

6 – Simplify your implementation

Some methods may benefit from additional constraints imposed on the representation. For example, consider a class CharSet that represents a set of characters using a mutable string:

/**
 * CharSet represents a mutable set of characters.
 */
class CharSet {

    private StringBuffer chars;

    /*
     * Abstraction Function:
     *   { chars[i] | 0 <= i < chars.size }
     *
     * ...
     */

    /**
     * @modifies this
     * @effects this_post = this - {c}
     */
    void remove(char c) {
        int index = chars.indexOf(String.valueOf(c));

        if (index != -1) {
            chars.deleteCharAt(index);
        }
    }
}

The implementation of the remove method only works if there are no duplicates in the string, because it only deletes the first occurrence of c. Without the “no duplicate” property, it would have to find and delete all occurrences of c.

 

Tell us how you handle inconsistent rep values in your current project. Do you document them? Do you check them?