Adding code comments is important for several reasons:
-
Code readability: Comments help to clarify the intent behind the code and make it easier for other developers to understand what the code does.
-
Maintenance: Comments provide context for the code and make it easier to maintain and update the code in the future.
-
Collaboration: Comments facilitate collaboration between developers by providing context for code changes and helping to avoid misunderstandings.
-
Documentation: Comments serve as documentation for the code and can help to ensure that the code meets industry standards and best practices.
By taking the time to add meaningful and accurate comments to code, developers can improve the overall quality and maintainability of their projects.
Before writing comments around a piece of code, it is important to be clear about the intent of doing so. A code comment is a communication mechanism that should communicate the following aspects of the code:
-
Purpose: Explain the purpose of the code, what problem it solves and why it exists.
-
Input: Specify the inputs required by the code, including their types, expected values and whether they are optional or mandatory.
-
Output: Specify the output produced by the code, including its type and any relevant details.
-
Usage: Provide examples of how to use the code, including relevant code snippets.
-
Linking: Add links to related code and external resources.
-
Best practices: Highlight any best practices or gotchas related to the code.
By following the right mental model, developers can ensure that their code is well-documented, easy to understand, and maintainable over time.
It is crucial to maintain consistency in the formatting and style of comments throughout the codebase. Using a tool such as JS Doc (JavaScript documentation), which is a set of documentation standards and tools for documenting JavaScript code is a good option.
The following are some of JS Doc's features:
-
It uses tags to capture information about the code's structure, function, parameters, return values, and other details to create a human-readable reference for other developers.
-
A JS Doc tags start with an
@
symbol, followed by the tag name and any relevant information. For example, the@param
tag is used to describe a function parameter, while the@returns
tag is used to describe the return value of a function. -
It can generate docuemntation in HTML or other formats from inline comments in the source code.
Let's look at some of the most commonly used JS Doc tags in detail. For reference, we will be adding code comments to the following JS Code:
function factorial(n) {
if (typeof n !== "number") {
throw new TypeError("Input must be a number");
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
function factorialWithCallback(n, callback) {
try {
const result = {
input: n,
output: factorial(n),
};
callback(null, result);
} catch (error) {
callback(error);
}
}
Do not worry if it is unclear what the code accomplishes. The next sections will provide an explanation.
@description
Provides a detailed description of a function, object, or property. Let's see how the code looks after adding this tag.
/**
* @description A function that calculates the factorial of a number using recursion.
*/
function factorial(n) {
if (typeof n !== "number") {
throw new TypeError("Input must be a number");
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
/**
* @description A version of the factorial function that returns the result via a callback.
*/
function factorialWithCallback(n, callback) {
try {
const result = {
input: n,
output: factorial(n),
};
callback(null, result);
} catch (error) {
callback(error);
}
}
@param
It is used to describe the parameters of a function. Adding @param
to the example, makes the code look like this:
/**
* @description A function that calculates the factorial of a number using recursion.
*
* @param {number} n - The number for which to calculate the factorial.
*/
function factorial(n) {
if (typeof n !== "number") {
throw new TypeError("Input must be a number");
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
/**
* @description A version of the factorial function that returns the result via a callback.
*
* @param {number} n - The number for which to calculate the factorial.
* @param {function} callback - The function to call with the result of the calculation.
*/
function factorialWithCallback(n, callback) {
try {
const result = {
input: n,
output: factorial(n),
};
callback(null, result);
} catch (error) {
callback(error);
}
}
@returns
It is used to describe the return value of a function. Adding @return
to the example, makes the code look like this:
/**
* @description A function that calculates the factorial of a number using recursion.
*
* @param {number} n - The number for which to calculate the factorial.
*
* @returns {number} The factorial of `n`.
*/
function factorial(n) {
if (typeof n !== "number") {
throw new TypeError("Input must be a number");
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
/**
* @description A version of the factorial function that returns the result via a callback.
*
* @param {number} n - The number for which to calculate the factorial.
* @param {function} callback - The function to call with the result of the calculation.
*
* @returns {void}
*/
function factorialWithCallback(n, callback) {
try {
const result = {
input: n,
output: factorial(n),
};
callback(null, result);
} catch (error) {
callback(error);
}
}
@throws
It is used to describe any errors that the function can throw. Adding @throws
to the example, makes the code look like this:
/**
* @description A function that calculates the factorial of a number using recursion.
*
* @param {number} n - The number for which to calculate the factorial.
*
* @returns {number} The factorial of `n`.
*
* @throws {TypeError} If the input is not a number.
*/
function factorial(n) {
if (typeof n !== "number") {
throw new TypeError("Input must be a number");
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
/**
* @description A version of the factorial function that returns the result via a callback.
*
* @param {number} n - The number for which to calculate the factorial.
* @param {function} callback - The function to call with the result of the calculation.
*
* @returns {void}
*/
function factorialWithCallback(n, callback) {
try {
const result = {
input: n,
output: factorial(n),
};
callback(null, result);
} catch (error) {
callback(error);
}
}
@example
It is used to provide an example of how to use a function or object. Adding @example
to the example, makes the code look like this:
/**
* @description A function that calculates the factorial of a number using recursion.
*
* @param {number} n - The number for which to calculate the factorial.
*
* @returns {number} The factorial of `n`.
*
* @throws {TypeError} If the input is not a number.
*
* @example
* factorial(5);
* // returns 120
*/
function factorial(n) {
if (typeof n !== "number") {
throw new TypeError("Input must be a number");
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
/**
* @description A version of the factorial function that returns the result via a callback.
*
* @param {number} n - The number for which to calculate the factorial.
* @param {function} callback - The function to call with the result of the calculation.
*
* @returns {void}
*
* @example
* factorialWithCallback(5, (error, result) => {
* if (error) {
* console.error(error);
* } else {
* console.log(result);
* }
* });
* // logs { input: 5, output: 120 }
*/
function factorialWithCallback(n, callback) {
try {
const result = {
input: n,
output: factorial(n),
};
callback(null, result);
} catch (error) {
callback(error);
}
}
@type
, @typedef
and @property
The @type
JSDoc tag is used to specify the type of a variable, function, or expression. It provides additional information about the type of the entity being documented and helps with code readability and maintainability.
The @typedef
JSDoc tag, on the other hand, is used to define a custom type. This can be useful in complex projects where you have custom data structures or types that need to be documented. The custom type defined with @typedef can then be referenced with the @type tag in other parts of the code.
In essence, @typedef
is used to define a custom type, while @type
is used to specify the type of an entity.
The @property
JSDoc tag is used to document properties of an object or class. It is typically used in conjunction with @typedef to document the structure of an object or class. The @property tag specifies the name and type of a property, as well as a description of its purpose.
Adding @type
, @typedef
and @property
to the example, makes the code look like this:
/**
* @typedef {Object} FactorialResult
*
* @property {number} input - The input to the factorial function.
* @property {number} output - The output of the factorial function.
*/
/**
* @description A function that calculates the factorial of a number using recursion.
*
* @param {number} n - The number for which to calculate the factorial.
*
* @returns {number} The factorial of `n`.
*
* @throws {TypeError} If the input is not a number.
*
* @example
* factorial(5);
* // returns 120
*/
function factorial(n) {
if (typeof n !== "number") {
throw new TypeError("Input must be a number");
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
/**
* @description A version of the factorial function that returns the result via a callback.
*
* @param {number} n - The number for which to calculate the factorial.
* @param {function} callback - The function to call with the result of the calculation.
*
* @returns {void}
*
* @example
* factorialWithCallback(5, (error, result) => {
* if (error) {
* console.error(error);
* } else {
* console.log(result);
* }
* });
* // logs { input: 5, output: 120 }
*/
function factorialWithCallback(n, callback) {
/**
* @type {FactorialResult}
*/
const result = {
input: n,
output: factorial(n),
};
try {
callback(null, result);
} catch (error) {
callback(error);
}
}
It is important to note the difference between @param
and @property
tags. The @property
tag is used to describe the properties or attributes of an object or class, whereas the @param
tag is used to describe the parameters of a function or method. Unlike the @property
tag, the @param
tag is used within the JSDocs block of a function or method, not outside it.
@callback
It is used to describe a callback function. The @callback
tag specifies the parameters of the callback function and their types. Adding @callback
to the example, makes the code look like this:
/**
* @typedef {Object} FactorialResult
*
* @property {number} input - The input to the factorial function.
* @property {number} output - The output of the factorial function.
*/
/**
* @callback FactorialCallback
*
* @param {Error} error - The error that occurred during the calculation, if any.
* @param {FactorialResult} result - The result of the calculation.
*/
/**
* @description A function that calculates the factorial of a number using recursion.
*
* @param {number} n - The number for which to calculate the factorial.
*
* @returns {number} The factorial of `n`.
*
* @throws {TypeError} If the input is not a number.
*
* @example
* factorial(5);
* // returns 120
*/
function factorial(n) {
if (typeof n !== "number") {
throw new TypeError("Input must be a number");
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
/**
* @description A version of the factorial function that returns the result via a callback.
*
* @param {number} n - The number for which to calculate the factorial.
* @param {FactorialCallback} callback - The function to call with the result of the calculation.
*
* @returns {void}
*
* @example
* factorialWithCallback(5, (error, result) => {
* if (error) {
* console.error(error);
* } else {
* console.log(result);
* }
* });
* // logs { input: 5, output: 120 }
*/
function factorialWithCallback(n, callback) {
/**
* @type {FactorialResult}
*/
const result = {
input: n,
output: factorial(n),
};
try {
callback(null, result);
} catch (error) {
callback(error);
}
}
In this code, the @callback
tag is used to define a custom type FactorialCallback
, which is a callback function that is passed as a parameter to the factorialWithCallback
function.
The FactorialCallback
type is then referenced in the @param
tag for the callback parameter of the factorialWithCallback
function.
A preview of the above snippet and the documentation that it generates can be checked here.
In conclusion, code comments provide a lot of benefits. From improving code readability to generating automatically generated documentation, tool such as JS Doc are an essential part of any development toolkit. Whether you are a seasoned developer or just starting out, taking the time to document your code in a standardised way will not only benefit the product in the short-term, but it will also help ensure the longevity and maintainability of the codebase for years to come. I'd encourage everyone to adopt this powerful tool and start using it in the projects.