Handbook
Code Comments

Effective code comments

Last updated by Noel Varanda (opens in a new tab),
Code comments
TypeScript
JSDoc blocks

Effective code comments play a vital role in enhancing code readability, facilitating collaboration, and providing valuable insights for engineers. While it's important to strive for self-documenting code, there are situations where well-placed comments can improve code understanding effectively.

Principle: Self-documenting code

One of the fundamental principles of writing clean and maintainable code is to make it self-documenting. By using descriptive and meaningful variable and function names, you can reduce the need for explicit comments. Well-designed code should convey its purpose and functionality through clear and expressive syntax. Aim to make your code as readable and understandable as possible, ensuring that future developers can easily comprehend its intent without relying heavily on comments.

When to comment

Complex logic and algorithms

Comment on sections of code that involve complex logic, algorithms, or optimizations. Explain the thought process behind the code to help future developers understand the reasoning.

/**
 * Calculate the factorial of a number.
 * @param {number} n - The number to calculate the factorial for.
 * @returns {number} The factorial of the given number.
 * @throws {Error} Throws an error if the input is a negative number.
 *
 * @example
 * // Calculate the factorial of 5
 * const result = factorial(5);
 * console.log(result); // Output: 120
 */
function factorial(n: number): number {
  // Base case: factorial of 0 or 1 is 1
  if (n === 0 || n === 1) {
    return 1;
  }
 
  let result = 1;
 
  // Calculate factorial iteratively
  for (let i = 2; i <= n; i++) {
    result *= i;
  }
 
  return result;
}

Assumptions and constraints

When code relies on assumptions or has specific constraints, it's important to document them. This helps other developers understand the context and limitations of the code.

function calculateRectangleArea(width: number, height: number): number {
  // Assumption: Both width and height are positive numbers
  // Constraints: The width and height should be within reasonable limits
 
  if (width <= 0 || height <= 0) {
    return 0;
  }
 
  const area = width * height;
 
  return area;
}

Cross-browser or compatibility issues

If the code includes specific workarounds or considerations for different browsers or compatibility issues, comments can provide insights into the reasons behind those choices.

// IE11 specific workaround: Polyfill for missing Array.includes() method
if (!Array.prototype.includes) {
  Array.prototype.includes = function (value) {
    // Implementation details for IE11 support
  };
}

Business rules and requirements

When the code implements specific business rules or requirements, comments can provide additional context and explanations.

// Discount calculation based on customer loyalty level
if (loyaltyLevel === 'gold') {
  // Apply a 10% discount for gold-level customers
  discount = totalAmount * 0.1;
}

Workarounds and temporary solutions

You may encounter situations where a permanent solution is not immediately feasible or a workaround is required to address a specific issue. When implementing workarounds or temporary solutions, it's essential to document them properly to avoid confusion and ensure they are not mistaken as permanent solutions.

/**
 * Workaround for issue #123
 * Description: This is a temporary fix to prevent a race condition
 * in the asynchronous data fetching process.
 *
 * TODO: [JIRA-1234] Monitor the issue and remove this workaround once a permanent solution is implemented.
 */
async function fetchData(): Promise<Data> {
  // Temporary workaround for race condition
  await sleep(500); // Delay added to mitigate the issue
 
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
 
  return data;
}

When to avoid commenting

While comments can be helpful, it's important to recognize situations where commenting may not be necessary or even detrimental to the codebase. Here are some scenarios where commenting should be avoided:

Self-explanatory code

If the code is already self-explanatory and easy to understand, additional comments may only clutter the code and add unnecessary noise. Always prioritize writing clean and expressive code that is easy to comprehend without relying heavily on comments.

// Calculate the sum
const sum = a + b;

Redundant or obvious comments

Avoid comments that merely repeat what the code already expresses. Redundant comments add no value and can make the code harder to read and maintain.

// Perform the operation
const result = performOperation();

Outdated comments

Code evolves over time, and comments can quickly become outdated or misleading if not properly maintained. If a comment no longer accurately reflects the code's behavior, it's better to remove or update it accordingly.

// This code returns an object
const returnNumber = () => 12;

IDE-friendly comment formats

When you do need to include comments in your code, it's important to make them IDE-friendly. Utilizing appropriate comment formats ensures that tools and IDEs can parse and present them effectively. One widely adopted convention is the use of documentation blocks, also known as JSDoc comments. These comments follow a specific format and can provide valuable information for both developers and IDEs.

/**
 * Calculate the sum of two numbers.
 * @param {number} a - The first number.
 * @param {number} b - The second number.
 * @returns {number} The sum of the two numbers.
 */
function sum(a, b) {
  return a + b;
}

By following the JSDoc format, you can document function parameters, return types, and provide additional contextual information. IDEs can parse these comments and provide autocompletion, type information, and inline documentation, improving the developer experience and code understanding.

When using the function, IDEs can guide the user to proper usage. Take the factorial example above:

IDE-friendly comment formats

Using JSDoc blocks

JSDoc blocks can benefit developers in several ways. Let's explore some examples:

Type annotations and documentation

JSDoc comments allow you to document the types of function parameters, return values, and variables. This is particularly useful in TypeScript to provide explicit type information and ensure type safety within your codebase.

/**
 * Get the user's name.
 * @param {User} user - The user object.
 * @returns {string} The user's name.
 */
function getUserName(user) {
  return user.name;
}

By documenting the types, you enable the IDE to perform type checking, offer auto-completion, and provide better code suggestions, leading to fewer runtime errors.

Function signatures and usage examples

JSDoc comments allow you to provide detailed explanations of function behavior and usage. You can include information about input constraints, expected outputs, and any additional side effects.

/**
 * Calculate the factorial of a number.
 * @param {number} n - The number to calculate the factorial for.
 * @returns {number} The factorial of the given number.
 * @throws {Error} Throws an error if the input is a negative number.
 *
 * @example
 * // Calculate the factorial of 5
 * const result = factorial(5);
 * console.log(result); // Output: 120
 */
function factorial(n) {
  if (n < 0) {
    throw new Error('Input must be a non-negative number');
  }
 
  if (n === 0 || n === 1) {
    return 1;
  }
 
  let result = 1;
  for (let i = 2; i <= n; i++) {
    result *= i;
  }
 
  return result;
}

Including usage examples in your comments helps developers understand how to use the function correctly and provides clarity on expected results and potential exceptions.

Commenting practices in different code contexts

React code

  • Focus on explaining the purpose and behavior of components, especially for complex or reusable components.
  • Comment on component lifecycle methods and their usage.
  • Provide insights into the overall structure and flow of the component hierarchy.

node.js code

  • Comment on the purpose and functionality of modules, classes, or functions.
  • Explain the expected input and output of functions or API endpoints.
  • Clarify any important business logic, algorithms, or data transformations.
  • Document external dependencies or integration points, such as database connections or third-party APIs.

Keep up to date with any latest changes or announcements by subscribing to the newsletter below.