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?
|
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:
|
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:
|
Instead, declare it readonly
(See Item 17: Use readonly to Avoid Errors Associated with Mutation) and let TypeScript enforce the contract:
|
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
ortemperatureC
).