Skip to main content

CSE 331: Code Quality

Code Quality

In addition to being correct, code should also be readable, maintainable, and constructed to aid future debugging. This page collects the coding conventions we expect on programming assignments in CSE 331.

Specifications

Public methods should have a 331 Javadoc specification.

  • Each parameter has a @param tag with a description and any preconditions about that parameter in isolation. If the type of the parameter is a reference type, then the specification should say whether it is allowed to be null or not.
  • The return value (if any) is described with a @return tag, which also mentions any postconditions about the return value. If the return type is a reference type, then the specification should say whether it is possible for the return value to be null or not.
  • Use the @requires tag for preconditions that relate multiple parameters or relate parameters to other pieces of state.
  • For an ADT, anywhere in the specification that needs to refer to the current object can use obj.
  • Use the @throws tag to describe the conditions under which the method is specified to throw an exception, and to describe which exception is thrown.
  • If the method mutates any objects visible to the client, use the @modifies tag to describe which objectes are mutated. In that case, use the @effects clause to describe what modifications are done.
  • Private methods do not need specifications, though you may find it useful to document them anyway. Similarly, methods that are part of the test suite do not need specs.

Abstraction function and representation invariant

Implementations of an abstract data type (ADT) 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).

Avoid representation exposure

If an ADT's fields include mutable objects, then the ADT instance should be the sole owner and reference to those objects. This means:

  • If you initialize the field from an object passed in as an argument to the constructor, you must make a copy of the passed in object. This is called "copy in".
  • If you return the field, you must make a copy and return that copy instead. This is called "copy out".

checkRep()

An ADT implementation should define a private checkRep() method that throws an exception if the RI does not hold and does nothing otherwise. The surrounding ADT implementation should call the checkRep() method in the following places:

  • At the end of the constructor.
  • At the beginning and end of every mutator method.
  • At the beginning of every non-mutator 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's fields and broken the representation invariant, then the next time a method on the object is called, the checkRep() call at the beginning will detect this and fail. This is much better than the alternative of debugging a mysterious downstream failure.

See the following section for an additional note about how to handle parts of the representation invariant that are slow to check.

Defensive programming

  • Check preconditions when it is cheap to do so.
    • If checking a precondition does not change the asymptotic running time of a method, then it is worth checking.
    • If something is truly precondition, then the method specification should not guarantee to throw the exception.
    • If checking a precondition would change the asymptotic running time of a method, then that precondition is typically not checked. It can be useful to have a flag to check even these expensive preconditions, which is enabled during debugging and testing but disabled in production.
  • checkRep() is another form of defensive programming.
    • Similar to the case of checking preconditions, we typically only check the "fast" parts of the representation invariant in production. A flag can be used to enable slow checks during debugging and testing.