Self-Documenting Code and When to Comment

5 minArchitecture

Code should be self-documenting. This means that the code is so clear and easy to read that you don't need comments to understand what it does. This is the ideal scenario. However, we don't always live in a perfect world. Sometimes, we need to write comments to explain why we did something.

Making Code More Readable

If you find yourself writing comments to explain what a piece of code does, you should consider refactoring the code to make it more readable.

  • Principles: If you have principles from Separation of Concerns, Single Responsibility Principle, Functional Programming, "Don't repeat yourself" (DRY), and so on, in the back of your mind, you can make the code clearer without comments. We don't expect you to know all there is to know about these principles, and you shouldn't follow them blindly, but you should know that they exist and be able to look them up when needed.
  • Naming: If you have good naming conventions, you can make the code clearer without comments. Most projects don't have strict naming conventions, but we should strive to have clear and consistent naming.
  • Tests: Writing tests can be used as documentation. If you have good test coverage, you can get a good understanding of what the code does by looking at the tests.

When to Comment

We are not always living in a perfect world. Sometimes you work with legacy code, or you are stuck with a third-party integration that is not well-documented or behaves in an unwanted way. In these cases, you might need to write comments to explain why you did something.

And that is the keyword: why.

If you have tried your best to make the code clearer, and you decide to write a comment, you should always strive to comment on the why and not the what. The what should be clear from the code itself, and if it is not, tools like GitHub Copilot can help you with the what, but it can't help you with the why. This is of course not an excuse to write unreadable code, but it is a way to think about comments. The why is the most important part of the comment.

Types of Code Comments

  • Inline Comments: These types of comments should be avoided if possible. They are often placed within the code and are a sign that the code is not clear enough.
  • Block Comments: These are typically placed above the code, which clearly separates the explanation from the code itself. This makes it easier for readers to distinguish between the code and the comments.
/*
 * The example function was created to demonstrate 
 * block comments. The input parameter is returned 
 * as the result, which is pretty useless.
 */
function example(input) {
  // Look how useless this function is
  return input;
}
javascript

Documentation Comments

In general, we should strive to write functions that are so clear and concise that they don't need comments or documentation. If we are writing a function called convertStringToTitleCase, which only takes one parameter, input, and returns a string, it is hopefully clear to the reader what the function does. However, in some cases, it can be useful to write information above the function that is not given from the function itself or the editor. Relevant block tags can be @param, @throws, @example, @see, and so on. You could look up JSDoc for more information.

/**
 * Converts a string to title case.
 * @throws {Error} If the input is not a string
 * @example
 * const title: string = convertStringToTitleCase("hello world");
 * console.log(title); // "Hello World"
 */
function convertStringToTitleCase(input: string): string {
  // Function implementation
}
typescript
  • @param: When using IntelliSense, we are able to see the parameters of a function, but maybe you want to tell the reader more than the name and type. As an example, you could write where they get the input if it is not clear from the name and type.
  • @throws: If the function throws an error, you could write what kind of error it throws and add additional information about the error if needed. This can be useful to know without having to look at the implementation of the function.
  • @example: If the function is used in a special way - for example, if it is used with a third-party library - you could write an example of how to use the function.
  • @see: When working with third-party libraries, it can especially be useful to write useful information or provide links to the documentation.

Again, we don't need to add these comments or tags to every function, but it can be useful in some cases.

General Documentation

  • README.md: We should explain how to set up and run the project. If we get hit by a bus, someone else should be able to take over the project fairly easily. This is of course an extreme example, but it is a good way to think about it. We should also provide links to useful resources.
  • Diagrams and Flowcharts: We shouldn't overdo it and explain everything with diagrams and flowcharts, but sometimes it can be useful to provide a high-level overview of the project or a specific part of the project. This could be done in collaboration platforms like Miro if explained to non-developers, or with diagramming tools like Mermaid using code if it is for developers.

To Sum Up

Writing self-documenting code significantly enhances the readability and maintainability of your projects. Ideally, code should require minimal comments. When comments are necessary, focus on the reasoning behind your code rather than its functionality, as this provides valuable context for future developers.

By following naming best practices, coding principles, and writing comprehensive tests, you can reduce the need for comments and make your code more intuitive.

Do you have any tips for writing self-documenting code?

Thank you for your time!