TypeScript has become a hugely popular alternative to JavaScript in the frontend web development community. Many engineering teams are convinced it is a critical tool that allows them to write better code faster. From a business perspective, however, things are not so straightforward. Many engineering teams love to chase new technology trends and rarely look back to consider if these new tools truly make them more productive or not. In this article, I want to look at TypeScript from the business side. Since it is an engineering tool, we will have to consider its technical traits, but we’ll view them through a business-conscious lens. In other words, we’ll try to figure out if TypeScript lives up to its reputation. I want to look at the language from three angles:
- Does it improve code quality?
- Does it reduce the total cost of the software development process?
- Does it help engineering become better developers over time?
Before we start to get into that, however, let’s step back and review what TypeScript is, where it came from, and what the project’s goals are.
What is TypeScript?
Before we talk about what TypeScript is, we need to talk about JavaScript. JavaScript is the primary language that web browsers use for programs that run in web browsers. There are many other places that JavaScript is running nowadays, but we’re going to limit ourselves to the browser for now. JavaScript is, at its core, a pretty simple language. Much of this simplicity centers around the flexibility built into its type system – JavaScript doesn’t require variables to be assigned a specific type. The type assigned to a variable can change at any time.
Furthermore, the types themselves can be dynamically altered at runtime, allowing programmers to add and remove data fields and methods as needed. This extremely flexible data model allows development teams to quickly develop and evolve their codebases to meet each project’s needs. However, JavaScript’s flexibility does not come without costs. The lack of strong types puts the onus for type-checking on the programs themselves. A substantial amount of JavaScript code must be written to guard against using invalid types. Additionally, the dynamic nature of JavaScript makes it challenging to write high-quality tooling to support development efforts. A type that can change its fields and methods at runtime introduces a level of ambiguity that tools, such as code completion, simply can’t accommodate.
Many solutions have been developed to address JavaScript’s weaknesses over the years. Projects like Dart and Elm are independent languages that are compiled into JavaScript before being delivered to client browsers. These languages have shown that this can be a successful approach, but it is not without its flaws. The fact that they are distinct languages means that there is a lag between new features being introduced to JavaScript and their availability to developers in those languages.
TypeScript takes a different approach. Instead of being a completely independent language, it is a “superset” of JavaScript. In other words, everything that you can do in JavaScript can be done in TypeScript. As such, developers can immediately adopt new JavaScript features as they become available. On top of the basic JavaScript language, TypeScript introduces, well … types.
While the reality is a bit more complex, the primary feature that TypeScript introduces is a strong type system. This type-system is entirely optional, allowing untyped JavaScript to be considered valid TypeScript. It is also transparent at runtime – the types are removed during the compilation process that yields idiomatic JavaScript code. This approach is unique because it tames JavaScript’s primary weaknesses while still granting access to its highly dynamic nature when needed. TypeScript has other features, but we’re going to focus on the type system and its ramifications on development projects from a business perspective.
Code Quality
TypeScript’s type system has a strong, positive effect on a project’s code quality. There are three characteristics of the type system, and TypeScript’s implementation of it, that influence it:
- Elimination of most type-based coding errors
- Type-based language features
- Tooling support
JavaScript’s dynamic nature makes it vulnerable to several errors involving variables and their data types. One of the most common errors is the humble typo. A single mistyped character can radically change the behavior of a program and can be exceedingly difficult to find. TypeScript requires that all variables be declared before their first use. In addition to enforcing best practices, this rule makes it trivial to identify misspelled variable names since they are undeclared and, therefore, invalid.
Another common source of JavaScript errors involves passing invalid parameters to functions. Since the language doesn’t encode the parameter types, extensive documentation is often required to let engineers know how to call a function. Functions in TypeScript include type information with their parameters, making it clear how they are to be called. There are advantages to JavaScript’s dynamic approach to function signatures, however. Many projects use this language characteristic to allow functions to accept several permutations of parameters, simulating function overloading. One of TypeScript’s goals is to preserve JavaScript’s flexibility, so it supports this by allowing a single function to be defined with multiple parameter lists. As a result, the flexibility of JavaScript is preserved and is, in fact, enhanced by TypeScript’s automatic documentation of all the permissible ways to call a function.
The inclusion of a type system allows TypeScript to include language features that can increase decoupling and encourage code reuse. Generics and interfaces are common constructs in typed languages that define a set of behaviors, or methods, that several concrete data types can implement. They introduce a level of abstraction into the program that eliminates the tight coupling between the concrete types and the functions that are working with them. In general, increasing the level of decoupling in a program leads to better code organization and easier testability. TypeScript allows generics and interfaces to be created and ensures that they are correctly used at compile time. It even extends the concept of an interface to either identify a set of methods, as is typical, or a group of data fields.
The final code quality advantage that TypeScript provides lies in the realm of tooling support. JavaScript’s highly dynamic nature makes it very difficult to create tools that automate many everyday software maintenance tasks, such as code refactoring. Since types can be changed at any time, tools to provide these capabilities are typically complex and unreliable. The addition of a strong type system makes it much easier for tools to analyze codebases and automate these everyday tasks flawlessly. This improves code quality in two ways. First, it provides a much higher chance that any refactoring effort will be correct and nothing is missed. Second, and perhaps more importantly, it makes refactoring much less daunting, so developers are more likely to perform these critical tasks to create and maintain healthy source code.
Total Cost of Ownership
Many development teams are initially drawn to JavaScript by its low barrier of entry. Its flexible type system provides a shallow learning curve that allows developers to ramp up quickly and build sophisticated business applications. However, as they say, “there’s no such thing as a free lunch”; eventually, this flexibility starts to cause problems. Software projects are constantly evolving, both during the initial development period and throughout their production life. Without strong guidance, developers often take the most expedient way to implement their current task. Unfortunately, the most expedient way often involves using the existing types in ways that differ from their original intent. This gradual drift makes applications more difficult to maintain and enhance as they age and, eventually, they must be replaced.
Applications written in TypeScript are much easier to evolve and maintain. By utilizing strongly-typed APIs, developers don’t waste time figuring out how to use existing code simplifying the work of maintenance engineers. Additionally, the code can evolve more quickly since the compiler will immediately inform developers of the changes they need to make when a data type is updated. This rapid and detailed feedback ensures that data continues to flow through the application predictably.
TypeScript programs tend to lack most of the guard code that is common in JavaScript applications. Since the compiler can enforce that functions are being called with the correct number and type of parameters, developers can stay focused on getting the function’s logic right. In this way, TypeScript’s compiler provides the benefits of reducing boilerplate code while simultaneously virtually eliminating the errors associated with calling functions incorrectly.
Effect on Engineering Maturity
Developers go through an odd learning process as they work with JavaScript. In the beginning, many engineers find that JavaScript’s flexible type system makes it easy for them to get started, and they rapidly make progress in the size and sophistication of applications that they can create. Eventually, however, JavaScript developers hit a wall. As their skills mature, they discover that while it is easy to write programs with JavaScript, it is tough to write them well. High-quality JavaScript programs should protect themselves from invalid function calls, be verified by meaningful automated tests, and be well-organized with minimal repeated code. Unfortunately, JavaScript doesn’t make any of those tasks easy, and, as a result, many JavaScript developers find it challenging to mature their skills fully.
When a developer decides to learn TypeScript, their skill growth is quite different. TypeScript is a more challenging language to learn since engineers must become familiar with its strong type system in addition to all of JavaScript’s language mechanics. Once they have a basic grasp, however, they have a much smoother learning curve. As they start to expand their skills, TypeScript provides tools and techniques to help them. For example, creating comprehensive automated tests can be difficult in JavaScript – there are no guarantees about how a function will be invoked. TypeScript’s strongly-typed function signatures, interfaces, and generics ensure that functions can only be called in a finite number of ways. This makes it much easier for engineers to develop test scenarios that verify that the function works as intended.
TypeScript is an engineering tool and, as such, its adoption is often left to the engineering organization. Ultimately, though, the engineering team is part of a larger company that must concern itself with productivity and return on investment. TypeScript’s flexible and powerful type system has been proven to reduce the short- and long-term costs associated with development projects while simultaneously facilitating the personal growth of each engineer’s skills and abilities.