Skip to content

Best Practice for Doc Comments

Principles

1. The first sentence

The first sentence of each doc comment should be a summary sentence, containing a concise but complete description of the API item. The summary description should usually not be a grammatically full sentence.

  1. For a class, interface or field the summary description should be a noun phrase describing what’s represented by the field or an instance of the class or interface.
  2. And for a method or constructor, the summary description should be a verb phrase describing the action it performs.

2. Mental model and vocabulary

The documentation defines and explains the mental model and vocabulary.

  1. Good naming of items (classes, functions, fields, parameters, …) is a prerequisite for writing good documentation.
    • Use terminology from the problem domain you are modeling.
    • Use consistent terminology across the application.
    • Avoid using names that represent the absence of a property, such as disableAnnotations.
  2. Describe the purpose/role of classes/records/records. (What they represent)
  3. Describe the purpose/use of functions. (What they are used for, what they modify)
  4. Do not describe the inner workings of items unless it is important for the reader.
  5. For non-obvious items take care to explain the mental model and relationships with other items.

3. Supplement and explain the API

The documentation supplements and explains the API.

  1. Do not describe things that are obvious from the declaration.
    • Do not write “This class represents…”, “A method that…”, “Representation of…”, “Specifies…”, “Getter for…” etc.
    • Do not write “Utility class for…”, “Helper for…”, “A simple…”
    • The description of a class should not list the fields or methods. Similarly, the description of a method should not list the parameters.
  2. Do describe things that are not clear from the declaration itself.
    • Unless the operation is trivial and very clear do not just repeat the name of the item.
    • For functions describe how corner case arguments are handled. (such as null)
    • For fields explain what they are used for.
    • For complex operations use pre- & post-conditions to describe how state is modified.
    • When using standard types describe information that is not expressed by the type itself.
      • For Map explain what the key is.
      • For String explain any restrictions on legal values and whether they are human-readable or only for computers.
      • For List where the index is important, explain why.
      • For long or int : If they hold an ID explain which type of object they identify. If they hold a timestamp or similar explain the encoding (e.g. unix time stamp). If they represent a size document the unit (e.g. timeout in milliseconds).
  3. Write doc comments for developers calling the code, not developers modifying the code.
    • If an algorithm is complex use comments inside the code itself to explain the steps.

Examples: Need improvement

/** Some utils. */
public final class CoverageUtilities {

Obviously not very helpful.

/**
 * Getter.
 *
 * @return the ContractState
 */
 public ContractState getContractState() { return contractState; }

Not very helpful. (Found inside ContractStateProxy)

/** Contract invoker for WASM-based REAL contracts. */
public final class WasmRealContractInvoker implements RealContractInvoker {

Repeating the name will not help the reader understand.

/** Utility class for abstraction over file management. */
public final class FileManager {

Some filler words, but the reader still only knows what the name already revealed.

/// The block time
pub block_time: i64,

How should I understand this numeric value?

/** Proxy for ContractState. */
public final class ContractStateProxy {

What is a contract state? What does proxy mean?

/**
* Loader for getting all the descriptions of TestSuites.
*
* @param pathsToTestSuiteDescriptions paths to all the descriptions.
* @param jarPaths map containing paths to jars
* @param contractLoader loader containing the contracts
*/
public TestSuiteLoader(
  List<Path> pathsToTestSuiteDescriptions,
  Map<String, Path> jarPaths,
  ContractLoader contractLoader) {

What is the key in jarPaths? What is a test suite description?

/**
 * Implementation of the abstract rpc builder. Allows users to build up to the rpc used to make action
 * invocations. Expects that #build is called separately to correctly generate entire rpc.
 */
 public final class FnRpcBuilder extends AbstractRpcBuilder {

The first sentence does not describe the purpose/role.

/**
 * The jar and version interval for a Public Wasm Binder
 *
 * @param bindingJar the binder jar
 * @param versionInterval the interval of the supported binder versions (minimum and maximum)
 */
@Immutable
public record BinderInfo(LargeByteArray bindingJar, SupportedBinderVersionInterval versionInterval)
    implements StateSerializable {

The first sentence only lists the fields, but does not describe the purpose.

/**
 * Contains the total number of rows in the dataset, the merge column to use and all the other
 * columns that can be computed upon.
 */
public final class Dataset {

The first sentence only lists the fields, but does not describe the purpose.

/// Generates the types for the abi given a list of functions that generates the NamedTypeSpecs.
///
/// # Safety
///
/// This should only be run by the ABI generation tool.
pub unsafe fn generate_types(
    iter: Iter<LookupTable<Vec<NamedTypeSpec>>>,
) -> (BTreeMap<String, u8>, Vec<NamedTypeSpec>) {

The input and output is hard to understand. What does the returned BTreeMap contain?

Examples: Pretty Good

/**
 * Binary input for a ZK smart contract. The binary input holds a number of bits, which will be
 * secret shared and encrypted before they are sent.
 */
public final class BinaryInput {


/// The block production time in millis UTC.
pub block_production_time: i64,


/**
 * Encrypted secret shares of binary data. The secret shares have been encrypted for each of the
 * parties (engines) that will receive them. The order of the encrypted shares match the order of
 * the engines.
 */
public final class EncryptedShares {


///
/// A vesting schedule for a transfer. Dictates the duration for when tokens are released and the intervals they are 
/// released in. 
/// See [Partisia Blockchain documentation][https://...] for how these fields are used.
/// The Java code for the type is [here][https://gitlab.com/.../VestingSchedule.java]
///
/// ### Fields
///
/// * `release_duration`: [`i64`], the total time frame the vesting schedule releases tokens in (in milliseconds)
///
/// * `release_interval`: [`i64`], the amount of time between each release (in milliseconds)
///
/// * `token_generation_event`: [`i64`], the point in time from which the vesting schedule starts 
///     running (in milliseconds, since Unix epoch time)
#[derive(ReadWriteRPC, ReadWriteState, CreateTypeSpec, Clone)]
pub struct VestingSchedule {
    release_duration: i64,
    release_interval: i64,
    token_generation_event: i64,
}

Helps the reader understand a complex object and the value of each of the three numeric fields.

/**
 * Produces a map from variable ids to their types, for the given block instructions and inputs.
 *
 * @param instructions Instructions in block.
 * @param inputs Inputs for block.
 * @return Map from ids to types.
 */
private static Map<Ir.VariableId, Ir.Type> typeMapFrom(
    final List<Ir.Instruction> instructions, final List<Ir.Input> inputs) {

/**
 * Replaces values in the given circuit, by lookup in the given map. Variables not in the map are
 * left alone.
 *
 * @param originalCircuit Circuit to replace in.
 * @param variableConstantValues Map from variables to the constant to replace with.
 * @return Newly created circuit with replaced variables.
private static Ir.Circuit replaceCircuit(
    final Ir.Circuit originalCircuit, final Map<Ir.VariableId, Integer> variableConstantValues) {


/// Retrieve a file using http and save it in `save_path`.
///
/// ### Parameters:
///
/// * `url`: [`&str`], the url to make the http request to.
///
/// * `save_path`: &[`Path`], the location for storing the retrieved file jar.
pub(crate) fn get_file_http(url: &str, save_path: &Path) -> Result<(), Box<dyn Error>> {

Good description of functionality and parameters. The return value is an idiom in Rust, so it does not need much explaining.

/**
* Parses a LEB128 unsigned integer from the stream.
*
* @return the byte array representation of the parsed LEB128 unsigned integer
*/
static byte[] parseLeb128(BigEndianByteInput stream) {


/**
* Look up the qualified name of a ABI struct/enum type.
*
* @param namedTypeSpecName the struct/enum name in the ABI
* @return the fully qualified name
*/
public String lookupQualifiedName(String namedTypeSpecName) {

The contents of the string parameter and string return value are described to further understanding.

Sources

These best practices are inspired by