Contents:


Introduction

This handout describes how to document the specifications of classes and methods. This document focuses on practical issues.

This document uses a Line class as an example. We do not provide fields or method bodies in our example. This document covers specifying the behavior of classes and methods (what they should do), not their implementation (what they actually do and how they do it).

Abstraction Functions and Representation Invariants covers how to document a class's implementation.


/**
 * This class represents the mathematical concept of a line segment.  A typical
 * line segment can be represented by {(x1, y1), (x2, y2)} where (x1, y1) and
 * (x2, y2) are the integer coordinates of the starting and ending end points of
 * the line segment in the cartesian plane, respectively.
 */
public class Line {

  ... // Fields not shown.

  /**
   * Let {(x1, y1), (x2, y2)} be this.
   *
   * @requires x != x1 || y != y1
   * @modifies this
   * @effects Sets this to {(x1, y1), (x, y)}.
   */
  public void setEndPoint(int x, int y) {
    ...
  }

  ...

}

Because several concepts discussed here are interrelated, let's starts with a short list of definitions before diving into the details.

Abstract Value
What an instance of a class is supposed to represent. For example, each instance of Line represents some line segment, which can be represented by the concise notation {(x1, y1), (x2, y2)}.
Method Specifications
Describe a method's behaviors in terms of abstract state. For example, Line's setEndPoint method updates the end-point portion of the abstract notation.

The above concepts are included in a class's external specification (in Javadoc). They help document for clients how to use the class.

The rest of this document is organized as follows. First, it explains how to document what a class abstractly represents using abstract state, Then, it explains how to specify method behavior, in terms of abstract state.

Abstract Values and Abstract State

The abstract value of an object is the information that clients use when reasoning about an object. For example, a line segment is defined in terms of a start point and an end point. This does not necessarily imply that the concrete reprsentation of the object has two point fields. That is one representation, but there are others, such as a start point, an angle, and a length. Abstract values are typically at a less detailed level than an implementation because this helps clients reason about only what matters to them. For example, clients of some representation of strings just need to know that a string is a sequence of characters, not whether that sequence is implemented with an array, a linked list, a combination of the two, or some completely different way. The notion of sequence is more abstract than particular ways to reprsent sequences.

Mathematical Abstract Values

For some ADTs, the abstract values are well-described by concepts and notation that are common in mathematics and well-understood by software developers. Examples include:

If you are specifying such a class, then you're in luck. You can use conventional notation for specifyng the class's abstract values and methods. Such notation includes:

You aren't obliged to use this syntax. Some of it is more standard than the rest: set-comprehension syntax is standard in just about all of mathematics, but sequence concatenation isn't particularly standardized. You may find it clearer to write sequence concatenation as a function like concat(x, y). What really matters is clarity and lack of ambiguity, so if you have any doubt whether your reader will understand you, just define it: “...where concat(x,y) is the concatenation of two sequences x and y.”

Method Specifications

Method specifications describe the behavior of a method in terms of its preconditions and postconditions. Note that method specifications may only refer to the abstract value, method arguments, and global variables (also known as public static fields), never to the concrete fields of the implementation.

Preconditions

Preconditions are properties that must be true when the method is called. It is the responsibility of the caller to guarantee that these properties hold. If the preconditions do not hold, the method is allowed to behave in absolutely any fashion, including crashing the program, continuing with incorrect results, informing the user of the problem, or gracefully recovering from the problem. Callers should always assume that preconditions are not checked by a method. However, it is good practice — though not required — for an implementation to check its preconditions (if the check can be performed efficiently) and throw an exception if they are not satisfied. Note, however, that if you do throw this exception, you should either not specify that the method will throw an exception, since this would contradict the @requires which specifically says the behavior is undefined on these inputs, or you should remove the precondition and specify that the method will throw an exception in this case. In other words, "@requires p" and "@throws SomeException if not p" are mutually exclusive.

Preconditions are indicated by the "requires" clause in a method specification. If a "requires" clause is omitted from the method specification, it is assumed that the method does not have any preconditions.

requires (default: no constraints)
The preconditions that must be met by the method's caller

Postconditions

Postconditions are properties that a method guarantees will hold when the method exits. However, if the precondition did not hold when the method was called, then nothin else is relevant, and the method may behave in any fashion whatsoever. In particular, if the precondition does not hold upon method entry, then the postcondition need not hold on method exit.

A postcondition can be written as a single complex logical formula, but it is convenient to separate it into logically distinct parts. CSE 331 uses "return", "effects", "throws", and "modifies". (In the descriptions below, "default" indicates what is assumed if that clause is omitted from the method specification.)

return (default: no constraint on what is returned)
The value returned by the method, if any
throws (default: none, which means that no exceptions are ever thrown)
The exceptions that may be raised, and under which conditions. The specification should not make guarantees about the behavior when the preconditions are not satisfied.
modifies (default: nothing, which means that there are no side effects)
Variables whose value may be modified by the procedure: They are not guaranteed to be modified, unless otherwise indicated by the effects clause. If object x has abstract value {f, g, h}, then "modifies x" means that any combination of f, g, and h might be modified. "modifies g and h where this = {_, g, h}" would be more restrictive. Often, programmers are more interested in quantities that are not listed in the modifies clause, since those are guaranteed not to be changed by the method.
effects (default: true, which means "can have any effect" on the references listed under modifies)
The side effects of the method, such as changes to the state of the current object, to parameters, or to objects held in global variables: If a part of the abstract value is listed in the modifies clause but not in the effects clause, then it may take on any value that is sensible for that part of the abstract value to represent. The difference between the modifies and effects causes is that modifies lists everything that may change and effects indicates what changes occur.

Subclasses and overridden methods

A subclass often has a different (stronger) specification than its superclass, and often has a larger abstract state than its superclass. When the specification and abstract state are identical to those of the parent (for instance, an implementation of an abstract class), then there is no need to repeat them in the subclass. However, it is helpful to include a brief note indicating that the superclass documentation should be used instead. That note helps readers to distinguish whether the specification is the same, or the author simply didn't document the class.

When the specifications differ, then you have two options. The first option is to repeat, in the subclass, the full superclass documentation. The advantage is that everything is in one place, which may improve understanding. The second option is to augment the existing specification -- for example, to add a few new parts of the abstract value and constraints on them. Whichever you do, make sure that you clearly indicate your approach.

Similar rules hold for a method that overrides another method. It is acceptable to leave the Javadoc blank if the specification is identical. (The generated HTML will use the overridden method's Javadoc documentation, but a normal Java comment is a good hint to someone who is reading the source code.) Otherwise, it is usually better to give the complete specification. If you merely augment the overridden method's specification, be sure to refer to it in the documentation.