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.
- 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.
- 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.
- 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
.
- Describe the purpose/role of classes/records/records. (What they represent)
- Describe the purpose/use of functions. (What they are used for, what they modify)
- Do not describe the inner workings of items unless it is important for the reader.
- 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.
- 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.
- 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
orint
: 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).
- For
- 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
- https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html
- https://blog.joda.org/2012/11/javadoc-coding-standards.html?ref=steven-benitez
- https://google.github.io/styleguide/javaguide.html
- https://dzone.com/articles/the-value-javadoc
- https://www.pascal-man.com/navigation/faq-java-browser/java-concurrent/Effective_Java.pdf Item 28 (page 103)
- https://web.archive.org/web/20210506033010/https://exocortex.anothernode.com/2016/04/20/writing-good-javadoc-according-to-joshua-bloch.html