Item 30: Don’t Repeat Type Information in Documentation

Chapter 4 of Effective TypeScript covers type design: the process of crafting your types to accurately model your domain. This item has always been a favorite of mine because of how immediately actionable it is. When you review code, be on the lookout for violations!

What’s wrong with this code?

/**
* Returns a string with the foreground color.
* Takes zero or one arguments. With no arguments, returns the
* standard foreground color. With one argument, returns the foreground color
* for a particular page.
*/
function getForegroundColor(page?: string) {
return page === 'login' ? {r: 127, g: 127, b: 127} : {r: 0, g: 0, b: 0};
}

The code and the comment disagree! Without more context it’s hard to say which is right, but something is clearly amiss. As a professor of mine used to say, "when your code and your comments disagree, they’re both wrong!"

Let’s assume that the code represents the desired behavior. There are a few issues with this comment:

  • It says that the function returns the color as a string when it actually returns an {r, g, b} object.
  • It explains that the function takes zero or one arguments, which is already clear from the type signature.
  • It’s needlessly wordy: the comment is longer than the function declaration and implementation!

TypeScript’s type annotation system is designed to be compact, descriptive, and readable. Its developers are language experts with decades of experience. It’s almost certainly a better way to express the types of your function’s inputs and outputs than your prose!

And because your type annotations are checked by the TypeScript compiler, they'll never get out of sync with the implementation. Perhaps getForegroundColor used to return a string but was later changed to return an object. The person who made the change might have forgotten to update the long comment.

Nothing stays in sync unless it's forced to. With type annotations, TypeScript's type checker is that force! If you put type information in annotations and not in documentation, you greatly increase your confidence that it will remain correct as the code evolves.

A better declaration and comment might look like this:

interface Color {
r: number;
g: number;
b: number;
}

/** Get the foreground color for the application or a specific page. */
function getForegroundColor(page?: string): Color {
// ...
}

If you want to describe a particular parameter, use an @param JSDoc annotation. For more on this, see Item 48: Use TSDoc for API Comments.

Comments about a lack of mutation are also suspect. Don't just say that you don't modify a parameter:

/** Does not modify nums */
function sort(nums: number[]) { /* ... */ }

Instead, declare it readonly (See Item 17: Use readonly to Avoid Errors Associated with Mutation) and let TypeScript enforce the contract:

function sort(nums: readonly number[]) { /* ... */ }

What's true for comments is also true for variable names. Avoid putting types in them: rather than naming a variable ageNum, name it age and make sure it's really a number.

An exception to this is for numbers with units. If it's not clear what the units are, you may want to include them in a variable or property name. For instance, timeMs is a much clearer name than just time, and temperatureC is a much clearer name than temperature. Item 37 describes "brands," which provide a more type-safe approach to modeling units.

Things to Remember

  • Avoid repeating type information in comments and variable names. In the best case it is duplicative of type declarations, and in the worst it will lead to conflicting information.
  • Consider including units in variable names if they aren't clear from the type (e.g., timeMs or temperatureC).
Like this post? Consider subscribing to my newsletter, the RSS feed, or following me on Twitter.
Effective TypeScript Book Cover

Effective TypeScript shows you not just how to use TypeScript but how to use it well. The book's 62 items help you build mental models of how TypeScript and its ecosystem work, make you aware of pitfalls and traps to avoid, and guide you toward using TypeScript’s many capabilities in the most effective ways possible. Regardless of your level of TypeScript experience, you can learn something from this book.

After reading Effective TypeScript, your relationship with the type system will be the most productive it's ever been! Learn more »