Everywhere You look, there's TypeScript!
In JetBrains' "State of Developer Ecosystem 2022" report, TypeScript is recognized as the fastest-growing language in recent years. Undoubtedly, when working with technologies related to JavaScript, it's impossible not to notice that TypeScript is becoming increasingly prevalent and dominating tech stacks. TypeScript can be found in the development process of frontend applications in modern frameworks and libraries like React.js, Vue.js, and Angular. It's also present in backend projects using Node.js and its frameworks, mobile solutions like React Native, and can even be used in older solutions like jQuery. In other words, anywhere JavaScript is used, TypeScript can follow suit. But why do we need TypeScript in the first place? What is it used for?
What's wrong with JavaScript?
It's worth noting that TypeScript emerged as an extension of the JavaScript language. This means that any valid program implemented in JavaScript is also a valid TypeScript program. Furthermore, code implemented in TypeScript is transpiled into JavaScript. In other words, what's implemented in one programming language (TypeScript) is expressed in another language, JavaScript in this case. Ultimately, the program that is executed is always implemented in JavaScript.
One might wonder what's wrong with JavaScript that led people to decide to improve it. Moreover, why didn't they create TypeScript as an independent language, given that so many people see value in it and invest their time and quite often money to learn and implement it in commercial projects? In my opinion, the answer to this question is both the greatest advantage and disadvantage of the JavaScript language:
JavaScript is a popular, simple language that offers tremendous flexibility in implementing solutions.
In the aforementioned survey, JavaScript has been the most widely used programming language for web and mobile applications for several years. Interestingly, the same report lists JavaScript as the least liked language among developers. This creates an intriguing dissonance that speaks volumes about the language itself.
So, why didn't the creators of TypeScript decide to release it as an independent language? If Microsoft, the company that developed TypeScript, had done so, it's likely that few people would be using the language today. This move reflects the lessons Microsoft learned from its past. Remember that in the early days of the internet, Microsoft proposed its own technology, ActiveX, to compete with JavaScript, but today it's virtually unheard of.
JavaScript is everywhere!
One of the key reasons for JavaScript's popularity is its cross-platform nature. JavaScript began as a scripting language meant to enhance interactivity on websites. Over time, it expanded its use beyond websites to server-side and native device applications, including mobile apps and desktop applications. Learning one programming language, with relatively low effort, enables developers to implement programs for various devices. Such a developer is more flexible in the job market and is not limited to a specific type of application. There's a slim chance that TypeScript could surpass the popularity of JavaScript. Despite being considered a separate language, TypeScript is closely tied to JavaScript.
JavaScript is simple!
JavaScript is simple, but some context is necessary. It's simple compared to other programming languages like C/C++, Java, C#, and even Python or PHP. If you look at the history of programming languages, you'll notice a trend. Later languages have simpler syntax, automate many operations, or encapsulate them in ready-made functions that you can use. Initially, we had only bitwise operations on processors. Assembly language was introduced as a way to operate on memory more easily. Assembly, however, is still a low-level language. Later, languages like C provided a friendlier environment for developers. Despite this, they still required direct memory management, including memory allocation and deallocation. Programs had to be compiled separately for different hardware architectures. To solve this problem, more layers of abstraction were introduced, such as virtual machines for Java or C#, and script interpreters for Perl, PHP, or Python.
As the internet and websites grew in complexity, there was a need for greater interactivity. Websites are applications that run within a runtime environment – the browser. The browser, in turn, is a program executed natively by the operating system. Originally, JavaScript was confined within a limited environment and lacked the capability to interact with the operating system or file system. In this context, a complicated language wasn't necessary. To simplify things, JavaScript introduced dynamic typing. Unlike "classical" programming languages, where you had to specify whether a variable would hold text, numbers, or boolean values, JavaScript can infer this based on the value's type. Its straightforward syntax, limited capabilities, dynamic typing, and the reduction of everything to basic types (text, numbers, boolean values) and data structures (arrays and objects) make JavaScript easier for beginners compared to other programming languages.
JavaScript is flexible!
JavaScript's flexibility is both its greatest advantage and disadvantage. JavaScript allows you to write code in the way you find most convenient. If a programmer thinks it's most convenient to change the behavior of a function that works in a specific way, there's nothing stopping them. JavaScript is a prototype-based scripting language. When we refer to an "object," we mean a combination of data and behaviors within a shared context, representing what we can do with that data. In traditional object-oriented programming, objects are created based on "classes" that describe the behaviors and data within an object. However, JavaScript doesn't have typical "classes"; instead, it employs the concept of objects that share data and behaviors via prototypes. A prototype is an object that serves as a base from which other objects can inherit. All objects ultimately inherit from the "object" prototype.
“object” ←”Person” ←”Employee”
In the example above, "Employee" is also a "Person," and "Person" is also an "object". This means that both "Employee" and "Person" can use the same behaviors as "object," and additionally, "Employee" can use behaviors available for "Person."
The problem arises when we overwrite the behavior of an object in the prototype chain while the program is running. This change affects all objects that inherit from it. Other object-oriented languages, due to the way classes are structured, don't have this issue. Even if you change the behavior of the code in the base class, it won't affect existing objects. Furthermore, the possibility to change the behavior of prototypes extends to built-in JavaScript objects as well.
Things can get very messy…
Imagine a scenario where you use a library created by someone else for a particular purpose. However, you're unaware that the library's creator has changed the behavior of base prototypes. In such a situation, your program might start behaving in ways you can't understand. It's extremely challenging to identify and eliminate these types of errors.
There's a popular joke that in languages like C/C++, C#, or Java, errors are mostly reported by the compiler during software development, while in JavaScript, errors are reported by the client. Unfortunately, this joke has a basis in reality. JavaScript makes it easy to write unreadable code. As a result, it's challenging to predict the consequences of even the simplest changes in the future. In compiled languages, the static determination of expected types offers a significant advantage because the compiler can quickly identify potential errors. In JavaScript, dynamic typing is performed during program execution. During code execution, in a specific use case, only one code path is executed. If the use case relies on input data, testing it becomes even more complicated, and a single execution may not suffice. As a result, such testing is often omitted or limited to critical paths.
We need to take a step back!
In my opinion, TypeScript represents a step backwards in the context of the evolution of programming languages. However, it's a step in the right direction. JavaScript has proven that too much freedom and simplicity in a programming language place too much responsibility on humans, in this case, on the programmer and the tester. When implementing software, we need quick feedback on whether what we've created works or not. We can distinguish two types of feedback here:
-
01
Technical feedback, which is about whether our program executes correctly.
-
02
Business feedback, which pertains to whether the program delivers the expected added value and meets functional requirements.
TypeScript can help us to some extent with both of these aspects.
Business feedback
Although not directly, TypeScript can also help us when delivering software that needs to meet specific functional requirements. One of the fundamental principles of object-oriented programming is modelling the real world. When building software, we attempt to reflect real-world concepts in our code, while keeping it to a necessary minimum. For example, in an employee management system, we need information about employees, but only the data required for managing them within the organization, while other data can be omitted.
As I mentioned at the beginning, JavaScript, besides simple types, provides an "object" type, allowing us to create arbitrary data structures and behaviors. However, it doesn't enable the creation of new types describing other objects based on it; it's quite generic. Although newer versions of JavaScript introduced the concept of "classes," it's essentially syntactic sugar for "objects." In TypeScript, we can speak of classes that create new complex types. This allows us to create a type like "Employee," which enforces specific data and enables specific behaviors, such as "change hourly rate" or "set vacation days." Furthermore, just like with simple types, the correctness of using these classes is controlled by the language itself.
So, we gain a powerful mechanism for control, fast feedback, and data modelling, making the code more readable and understandable. This not only reduces technical errors but also functional errors, which often result from a lack of understanding of a given solution within the team.
A Solution to All Evils?
TypeScript solves many of the problems that JavaScript developers face on a daily basis. However, it can't solve them all. JavaScript is everywhere, especially on almost every website. It's difficult to introduce new versions that break away from the old ones due to the multitude of browsers and the millions of places where it's used, requiring backward compatibility. Therefore, it's easier to improve the software development process itself and then transpile nice and modern code into JavaScript that's backwards compatible. In the resulting code, we don't need to worry too much about errors related to incorrect data types because they have been verified in an earlier stage.
Help for the Programmer or Art for Art's Sake?
When trying to answer the question posed in the title of this article, we need to consider our motivations for using such solutions. TypeScript restricts the programmer and controls the code, making it harder to make simple mistakes. At the same time, one can go to extremes. For example, imagine building a front-end application that uses a technique to control CSS styles (which describe how a webpage should look) on the JavaScript side (typically these are two separate technologies). With a very rigid approach, a programmer might conclude that they need to describe, using types in TypeScript, the capabilities for managing CSS styles. Anyone who has ever implemented CSS styles knows that it would be cumbersome, time-consuming, and ultimately unnecessary. In this case, it could become art for art's sake.
It's essential to remember that TypeScript isn't a language used during program execution. It was created to enhance the work of developers during the software development process and to shorten the feedback loop. With this approach, a programmer can create more robust, maintainable, less error-prone, and ultimately more cost-effective code in the long term.
Furthermore, TypeScript helps us describe code, providing priceless context. This context is invaluable when using supporting tools such as Github Copilot. With this context, the tool can offer code suggestions that are more precise, speeding up work.
In my opinion, with the right approach, TypeScript is a wonderful aid for developers that eliminates many of the problems we face daily in pure JavaScript. However, it's also essential to recognize that it's not always worth using it in every possible aspect and to apply appropriate compromises in cases where its use would be too time-consuming, and potential benefits would be minimal.
Do you think TypeScript is a step forward or backwards in the development of programming languages? What are your personal experiences with TypeScript?
Leave your opinion on our social media!
Sources: JetBrains, The State of Developer Ecosystem 2022 JavaScript, Prototypal Inheritance JavaScript TypeScript