William Lim Software Developer.

Discussing the over-engineering trap in TypeScript

6 min read 1906

Introduction

Before I start the discussion of over-engineering in TypeScript, I want to give a basic overview of how JavaScript and more strongly-typed languages currently deal with types. All types in JavaScript (with the exception of the object type) are called primitive values. Primitive values that are used a lot in JavaScript, include values with the type of string, number, and boolean. All primitive values are immutable. For example, for strings, this means that once you have done the assignment let testString = ‘test’, you cannot mutate it by doing something like testString[0] = ‘r’ to get rest . You have to re-assign it, testString = ‘rest’.

JavaScript is a loosely typed language. A variable in JavaScript can be reassigned to a value of any type such as number, string, or object. For example, you can do testString = 4. Notice how the string is now a number and the name of the variable is still testString.

In contrast, C# can be an example of a more strongly-typed language. To explain this, let me give you an example. This is how you assign a string to a variable in C# string testString = “test”;. Instead of using the var or let keyword, we declare the type of the variable, (string), right when we assign it. Then, if we try to reassign it to a value of a different type, for example, by doing it like this testString = 4;, you get the error error CS0029: Cannot implicitly convert type ‘int’ to ‘string’:

C# type error in Visual Studio 2019
C# type error in Visual Studio 2019

testString will always be a string. I want to note here that you should try to avoid adding types to the names of your variables, because, as you have seen in the loosely-typed language case, it gives the wrong type (testString became a number), and in more strongly-typed languages, sometimes the type is already shown very clearly.

TypeScript is a language that allows JavaScript applications to have features that more strongly-typed languages often have. You can also choose how strict you want type checking to be and how much of your existing JavaScript code you want to migrate to TypeScript. You can make type checking very strict and do a full migration over to TypeScript, or you can even add TypeScript to your JavaScript projects to prepare for the future and not use it at all. In this article, I will go through the advantages and disadvantages of using TypeScript. This will give us a better idea of when to avoid over-engineering in TypeScript.

Applications built with more JavaScript

Before I start explaining the advantages and disadvantages of using TypeScript, I feel that I should start with an explanation of the advantages of not using it. For the purpose of keeping the length of this article not too long, I am only going to go through the primary way client-side scripting is done on the web in general — which is by using JavaScript. Discussion of related languages such as CoffeeScript or Elm, will not be included in this article. I will also explain the use of more JavaScript than TypeScript in server-side code.

One advantage of using more JavaScript is the speed that it brings. JavaScript does not have to be compiled and you can see results immediately. It does not have to be configured — you don’t have to create a configuration file (tsconfig.json) and then add and alter its configuration. No extra configuration and no compile time means that you can prototype applications easily to see how your designs, data-fetching, and other features will turn out.

Another advantage of using more JavaScript is its maturity. It has been, at least in the last few years, the primary way to do scripting on the client-side/frontend. Sometimes, it is even used in the backend (Node.js). Because it has been so mature, it already includes ways of handling types depending on the library used. In Mongoose, for example, you can define schemas using code that looks like this: const userSchema = new Schema({ firstName: String }). firstName here is defined as having a String type. Also because of its maturity, JavaScript already includes ways of handling types in documentation — JSDoc being the first one that comes to mind. If you already use JSDoc, adding TypeScript might seem excessive.

TypeScript is often useful, even for the smallest applications or applications in a Microservices architecture. However, if you have smaller applications it means that there is a higher chance that you can easily infer what the type is. Also, you lose out when you do additional configuration for TypeScript and compile every time to do a few type checks. If your codebase is small, type checking can be more easily done in unit tests, using libraries such as Chai, without the need for TypeScript. Which brings me to another point — you can already check for types without using TypeScript by using test libraries, but TypeScript offers feedback sooner. Before you run tests, and it can sometimes offer feedback automatically whilst you are coding (Visual Studio Code). If you have been given a new application to take care of, have a look at what the existing tests do.

Applications built with more TypeScript

I’m not going to go through all of the advantages or disadvantages of using TypeScript here because there are so many. Please bear that in mind when I start to discuss using TypeScript in this section.

We made a custom demo for .
No really. Click here to check it out.

One of the first advantages of using TypeScript that I want to describe is its tooling. If you already use tools like Visual Studio Code (Version 1.43.2) or Visual Studio 2019, there is a lot of support for the language right now specifically in these tools. I will refer to Visual Studio Code as VS Code in the rest of this article.

One feature that is not often available in other tools is, hover information in VS Code. This allows you to quickly see information about types and other information for methods, parameters, variables, or other features of JavaScript, as you hover your mouse over code. You can also hold the alt key when you hover your mouse over the function name of a function call, to see the function that you are calling:

Hover information in VS Code

This is helpful when the function you are calling is outside of your view or many lines away.

Signature help (a tooltip shown when you type in ( after a function name to call that function) is another helpful feature that you sometimes don’t find in other good tools that are available right now. The example below shows this happening when you type in errorMessage( to call the errorMessage function:

Signature help in VS Code
Signature help in VS Code

The ability to quickly and automatically remove unused imports is another particularly useful feature that you don’t find in other tools. When you hover over grayed-out imports, a blue indicator that allows you to do this is shown:

Quickly remove unused imports in VS Code
Quickly remove unused imports in VS Code

A disadvantage of using TypeScript that I haven’t discussed yet, is the change to your project’s file structure and build tools. You would have to rename all your JavaScript files to use the .ts file extension. If you use JSX, you need to rename extensions to .tsx. The amount of work that you need to do to migrate would then depend on how many files your project contains, but you could always gradually migrate your files over time.

Also, dealing with declaration and source map files can be a hindrance. For example, when you install declaration files for React and react-dom, you need to add more packages to your project, npm install --save-dev @types/react @types/react-dom. Integration with frameworks is also a hassle and you would also need to adjust your build tools (Babel and webpack are some examples of these) to support TypeScript. However, depending on which frameworks and build tools you use, good official resources and documentation probably already exist to support this.

One argument against using TypeScript for server-side code is that you could just switch to other type-safe languages on the backend. However, that would depend on what skill set other contributors have. For instance, if your other team members all already know how to write in another type-safe language, then this option is a good use of everyone’s skills. Try to pick the best language based on the task that is required, if you want to deal with data, concurrency, or even types, TypeScript might not be the best choice for each of those topics. The advantage of using TypeScript over other languages is that, well… it is basically just JavaScript… but with types!

Sequentially added properties and strictness of checks

Another issue that might cause problems when migrating away from JavaScript is what the official TypeScript documentation refers to as ‘sequentially added properties’. This is when you create an empty object: let person = {} and then assign new keys and values to it person.name = ‘John’. This is an often used and documented pattern in JavaScript. However, TypeScript, by default, does not allow this. The fastest way of getting around this would be to just give person the any type, but this comes at the cost of a weaker type check. You can also move properties into the object literal or use a TypeScript interface.

The last feature of TypeScript that I will discuss is the ability to control the strictness of checks on your code. The strictNullChecks option is great at preventing bugs because the null reserved keyword and the undefined value are common sources of errors in JavaScript. An example I can give is let a:number = null. In this case, the value of a can be null, undefined or NaN (you can get NaN when you set a to 0/0 ). With the strictNullChecks option set to false, you might wonder which of the three values a is set to if all three are allowed. One extra thing to note about this option is that I found that strictNullChecks seems to already be set to true by default when you don’t use the strictNullChecks option in the configuration file.

Another option I tested, noImplicitAny did not work in the way that was expected. We would expect it to raise errors when there is an implied any type, but this was not the case. You can try this in Visual Studio Code and see that let b: any is shown when you hover your mouse over b, let b; console.log(b);:

noImplicitAny allows implicit any sometimes
noImplicitAny allows implicit any sometimes

However, in this example:

function example (c) {
  return c;
}
console.log(example(5));

The noImplicitAny option works and throws the error: Parameter ‘c’ implicitly has an ‘any’ type. TS7006:

noImplicitAny works in this example

It is still an excellent option and works as intended, but it does not cover all the cases we would expect it to.

Conclusion

The cost or overhead of adding types to your code is far outweighed by its positives in the long term or for large projects. Remember to always use TypeScript correctly. Learn more about its features and about your own project first, before you consider using it. This will help you to decide if you should migrate to TypeScript, how many of its features you want in your projects, and if your project already has similar features.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    : Full visibility into your web apps

    LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

    In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

    .
    William Lim Software Developer.

    6 Replies to “Discussing the over-engineering trap in TypeScript”

    1. I’d just like to add that at least IntelliJ and Atom both provide features like intellisense / unused import removal etc on es6 modules. You don’t need TS for just those features.

    2. The disadvantage is configuring typescript? I can do that in less than an hour, including webpacj and babel. The self-documenting nature of types makes your code so much readable and avoids stupid errors when you don’t know the structures you’re using, even if you wrote them 10 minutes ago. The sheer speed of typed development in excellent editors like VS code is already an extremely useful advantage, and of you take into account that your whole program can become type safe, it’s a no brainier to go for Typescript. JavaScript really becomes a scripting language, or a begginers language. Any serious project I’ve ever seen ends up migrating to Typescript unless it has robust testing set up, seasoned developers with a lot of knowledge of the project, and few changes or features over time. On top of all of this, it exposes you to typing (although structural, not nominal) which lowers the learning curve for statically typed languages. Embrace Typescript as soon as you can, or be forced into it later.

    3. “If your codebase is small, type checking can be more easily done in unit tests, using libraries such as Chai, without the need for TypeScript.”

      I know this isn’t what you are promoting here, but I have heard this before as an argument against moving to Typescript. However I don’t think unit tests can replace Typescript. It is not a good idea to expect the programmer who wrote the bugs to know exactly which unit tests to write in order to catch them. It is also much more difficult to write unit tests that would catch every type safety bug than to just use a language that makes it near impossible. And then write unit tests as well. You can focus your unit tests on behaviour rather than something the language and static analysis tooling could do for you instead.

      There are several entire classes of errors commonly seen in JavaScript code that will never be seen in Typescript code if configured well. For example, trying to call a method on an undefined variable. Yes, migrating to Typescript is a lot of work, but like the article says, you can do it a bit at a time and you still get some benefits along the way. 🙂

    4. Disclaimer: I work with that “even backend” thing called node.js and I find it fascinating compared to strongly typed languages like Java. MUCH less code does the exact same thing. Easier to read and write.

      Have you ever used a combination of eslint, JSDoc and webstorm? I had zero type errors thanks to that.

      Oh, and I sometimes write “string” or “number” in the name of the variable because I know I won’t be reassinging it (because I mostly use const). When type is important and comes from outside of a function, I do type checks.

      I follow KISS principle. Keep it simple, stupid (no offence meant).
      Small functions, small files, one function aims to do one thing at a time. One module does what it’s supposed to do, and nothing more. Types are defined via JSDoc.

      So far every time I thought about using TS I caught myself wondering what benefits it would bring. Found none for me personally so far. But it does make it harder to work – compile before execution, change file names, code becomes more verbose. It has its benefits but so far I’ve managed just fine with just js. And linter. And JSDoc. And unit tests. And open api docs. And type checks. And TS won’t remove the need of any of those.

      This could be different in front end. This could be different for your project and your contributors. This is just my experience.

      If the next project i work with uses TS, I’ll use it without an issue. If not, I won’t push for TS ASAP. I’ll aim to use the best tool for the job.

      The job being implementing business logic in an optimal way that scales well and doesn’t take a very long time to realize. Oh, and make sure you have unit, implementation and end to end testing. Don’t skip on tests, regardless of TS.

    5. The article doesn’t have enough experience I suppose. I started with Javascript and after moving to typescript there’s no turning back. Commercial projects are just too big to be maintained with Javascript. Typescript makes the development faster because your team members don’t have to know the code before hand and can safely write code that works as expected. In the high-tech industry everybody works with typescript. In react everybody works with typescript. That is the future and you better embrace it sooner than later

    Leave a Reply