Skip to main content

CSE 331: Code Quality

Code Quality

A correct program isn't enough. Code in this course should also be readable, maintainable, and defensively constructed against future bugs. This page collects the conventions we expect on programming assignments. They are only briefly described here; the underlying ideas are introduced more fully in lecture.

Specifications

Public methods should have a Javadoc-style specification. A good specification:

  • Documents every parameter (@param) and the return value (@return), including nullability where relevant.
  • States preconditions (@requires) and any exceptions thrown (@throws).
  • For mutators, names the objects that may be modified (@modifies) and describes the change in terms of the abstract state (@effects).

Private methods do not need specifications, though you may find it useful to document them anyway. Also, test methods do not need specs.

Abstraction function and representation invariant

Implementations of an abstract data type should document, as comments in the source:

  • The abstraction function (AF), describing how to interpret the concrete fields as a value of the abstract state.
  • The representation invariant (RI), describing the conditions on the concrete fields that must hold whenever the object is at rest (i.e., outside a method call).

checkRep()

Define a private checkRep() method that throws an exception if the RI does not hold and does nothing otherwise. Then call it from your code:

  • At the end of the constructor.
  • At the beginning and end of every mutator method.
  • At the beginning of every observer method.

Calling checkRep() at the end of the constructor and the end of every mutator helps catch situations where the code incorrectly leaves the object in an invalid state.

Calling checkRep() at the beginning of a method helps catch representation exposure: if an external caller has reached into your object and broken its invariant, you'll find out the next time you touch the object instead of debugging a mysterious failure later.

Defensive programming

  • Check preconditions when it's cheap. O(1) precondition checks (null, empty, basic type or range constraints) should be performed defensively and signalled with IllegalArgumentException or similar. Skipping these checks turns precondition violations into silent corruption.
    • If something is truly a precondition, then the method specification should not promise to throw the exception.
  • Don't add expensive checks just to be defensive. If a precondition costs O(n) to verify and the method is otherwise O(1), don't check it, since that would change the asymptotic cost of the method. (If the method is already O(n), then it's fine to do the O(n) check as well.)
  • Don't expose your representation. Constructors that take mutable collections should copy them; observers that return mutable collections should return a copy or an unmodifiable view. A client who can mutate your fields can break your RI without you noticing.

In short

A 331 implementation of an ADT should:

  1. Have a Javadoc spec on every public method.
  2. Document its AF and RI.
  3. Define checkRep() and call it at the start and end of mutators, and at the start of observers.
  4. Defensively check cheap preconditions.
  5. Not expose its internal representation to clients.