Writing Readable and Maintainable Code in TypeScript

How to make it easier for other people to understand your code

Photo by Carles Rabada on Unsplash

My least favorite part of programming is having to read code that is not immediately understandable. If you have worked in a team you will probably know what I mean. For example:

This code is not easy to read — you need to focus and you may have to read it multiple times to fully understand it. There is also zero typing, so it is not clear what is supposed to happen when you use the function. Now take this code:

Immediately you can see that this is a function for splitting arrays into chunks. If you were to use this function, your IDE would show exactly which parameters are requested and what you should expect as output. In terms of the implementation, logically named variables and whitespace make the code easy to understand.


You might point out that it would take longer to write the code in the second example than the first. That is probably true, but the second example will save time by making the function easier to use. You know what the function will do without looking it up. The typings make it easier to use the function because your IDE’s Intellisense will tell you how to use it. You can also more easily locate bugs because it is much easier to read. You understand it more quickly and more deeply.

The chunk array function might be a trivial example, but for more complex code the same logic will apply.

5 Tips to improve you and your team’s code

I am going to share some of the things I have learned working on TypeScript projects as part of a team. Following these tips made it easier to read and maintain our code, and reduced the time it took for new team members to become productive.

Don’t write code for yourself, write code for others.

In other words, remember: other people will read your code. It might not be today or even next week, but it will happen.

1. Make you tsconfig as strict as possible

This is probably the most important tip I am going to give you in this article. Let TypeScript do what it is made to do: prevent human errors. The best way to do this is by enabling TypeScript options that force you to follow certain rules.

Don’t just enable all TypeScript features though. Carefully consider what options would be an improvement for your team. In some situations, it could be counterproductive to enable certain features.

Strict options

There are a couple of strict options in TypeScript that I highly recommend you to use:

--noImplicitAny
Raise error on expressions and declarations with an implied any type.

--noImplicitThis
Raise error on “this” expressions with an implied any type.

--alwaysStrict
Parse in strict mode and emit “use strict” for each source file.

--strictNullChecks
In strict null checking mode, the null and undefined values are not in the domain of every type and are only assignable to themselves and any (the one exception being that undefined is also assignable to void).

--strictFunctionTypes
Disable bivariant parameter checking for function types.

I call these strict options because they are all set when you use the --strict flag. There are a few more options that would be set, but they can be bothersome to use depending on the framework you are using.

For example, using --strictPropertyInitialization with Angular clashes a bit with the @Input, @Output and @ViewChild(ren) decorators.

Non-strict options

There are many more options in Typescript that improve code quality, and I highly suggest you explore them. You can find all the available options in the TypeScript handbook.

2. Prefer readability over performance

Variable and function names

Don’t be that guy who always uses single letter variables. You might save a few milliseconds — a few lines even — but only at the cost of readability.

Always ask: “Would someone understand this code the first time they read it?” If the answer is “yes”, give yourself a pat on the back. If the answer is “no”, think of a good name that makes the purpose of the variable clear — it makes your code easier to understand.

The same goes for your function names. If you see the name of a function, its parameters and the class it belongs to, you should know what the function does. If you do not know what the function does, it needs a better name.

Performance

It depends on the type of project you’re working on, but in most cases, it is not worth making your code harder to understand in order to save a millisecond. Code that is hard to understand is more likely to cause bugs.

First focus on correctness, then clarity, and finally, if needed, performance.

If you do need the performance, make sure you use proper naming and add clarifying comments. This way you save your teammates from wasting their time trying to understand complex code.

3. Linting

If you do not enforce a code style, everyone will code the way they prefer. Some will use trailing commas, some will use 4 space indents, some will prefix their private variables with an underscore. In short, every file will look different.

With tools like TSLint and/or Prettier, you can create rules on how you want the code to look. Try to make your linting as strict as possible — sure it can annoying if you miss a trailing comma or a semi-colon, but it will be easier to read other people’s code because you will know what to expect.

4. Project structure

A project structure that does not make sense is time-consuming. It is annoying when, trying to solve a bug, you can’t find the file that handles that specific logic.

I cannot suggest a project structure for you, but I can suggest that you think before randomly creating folders and files. Maybe even sit down with your team to create a project structure in which everyone will be able to find what they are looking for. Otherwise, who knows what you may end up with?

5. Pair programming

Sometimes when you are solving complex problems, it can be useful to do this in pairs. Working in pairs has several advantages over working alone: two people will better understand the complex problem, and four eyes will detect bugs faster than two eyes.

The second person might also come to a different solution. Sometimes, combining two solutions can give a better result than either person would have achieved if they were working on the problem alone.

However, do not always work in pairs. You also have to able to program independently and cannot always rely on someone else.


TL;DR

  • Make your tsconfig as strict as possible.
  • First focus on correctness, then clarity, and finally, if needed, performance.
  • Enforce a code style using TSLint and/or Prettier.
  • Make it easy to find relevant files in your project structure.
  • It can be useful to program in pair for complex problems.

Final note

These are all things I have learned from my own experience. Do not underestimate the benefit of a high-quality code base. It will make your life easier.