Take a good look at the following function and try to understand what it’s doing.
function do_magic(a, b) {
return a + b;
}
Is it summing two numbers? Or maybe it’s doing string concatenation? The answer is: you can’t know. And this beats the entire purpose of programming languages – to be easily understandable by humans.
A brief history of dynamically typed languages
There is a long list of dynamically typed languages, but for simplicity, I’ll focus on a few of the most popular.
PHP
PHP first appeared in 1995, and is officially categorized as a scripting language. It was created by Rasmus Lerdorf to maintain his personal homepage, which Rasmus wrote as CGI programs in C. It was never intended to be a programming language and Rasmus even said: “I don’t know how to stop it, there was never any intent to write a programming language […] I have absolutely no idea how to write a programming language, I just kept adding the next logical step on the way.”
It is now powering 78.1% of all websites, according to W3Techs report from January 2022.
Python
First appeared in 1991. Its development started 2 years before that, in 1989, by Guido van Rossum. He was working with Amoeba operating system and needed a better language to do system administration, rather than writing C programs.
Python consistently ranks as one of the most popular programming languages.
Lua
Created by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, and Waldemar Celes in 1992, Lua was primarily designed to be embedded in other applications.
Today, Lua is the leading scripting language for games.
JavaScript
Designed by Brendan Eich and appeared in 1995, JavaScript was created for one sole purpose – add dynamic capabilities to boringly static HTML pages.
Today, JavaScript is one of the core technologies of the World Wide Web and over 97% of websites use JavaScript.
As you can see, none of these languages were designed to be robust and replace the old C or Pascal. They all managed to find their niches, but they were all languages designed to either be used for personal needs, or with the main purpose of extending other applications which were written in statically compiled languages.
None of these languages should be used, in their pure state, as the primary languages for application development. Let me elaborate on that.
The human factor
If you’ve ever worked on a serious project, you know that extending and maintaining it – is a challenging task.
How often you’ve looked at your code, or code you peer reviewed, and you said to yourself, “What a nice, clean solution”? But after a few months, when you’ve revisited the code, you thought to yourself “How this piece of crap ended up in our code base?”.
When we write code, we are unable to keep the entire context of “why” that we have in our head at the moment of coding; hence we end up looking at old code and crying. And the fact that we take away types – only makes things worse.
One little bug
I remember hunting a bug in one of the system I was working on. The system had entities that were versioned, and the version was universal across the entire entity domain. Since the number of entities was huge, and with each entity modification, we needed to increase the version, we could easily overflow the 64bit integer. So we’ve decided to use BigInt column in the database to track the version.
For each create or update operation on an entity, we would call the following function:
async function getCurrentVersion(howMany = 1) {
const currentVersion = await magically_get_current_version_from_db();
const nextVersion = currentVersion + howMany;
await magically_set_current_version_in_db(nextVersion);
return currentVersion;
}
Note: I’m reconstructing the code from memory.
The entire function was wrapped in a database transaction (to avoid race condition), and the “*magically_**” functions were actually calls to our ORM (please don’t use ORMs) which would fetch the version from the DB, then reserve 1 or more (we could have batch updates) versions, and save it as next.
What we saw in our development environment was that entities were out of order, and we saw a considerable increase in version numbers. We’ve reached massive numbers very fast.
Just for an exercise, try to understand what was the problem. Continue reading once you’ve shed some tears.
It turned out that our ORM returned BigInt as strings. So if our current version in the DB was 123, it would come back from the ORM call as "123"
, and adding an integer (let’s say 1) to it, turned out to be… poopity whoopity… "1231"
.
I’ll pause for a minute, so you could enjoy your moment of pseudo-superiority and express your nasty comments about my mediocre programming skills, because we all know that you write perfect code, and compile it in your head before V8’s runtime even finishes interpreting it.
My point is – it would not have happened in a statically typed language. No compiler in the world would just let you sum a string with an integer, or would at least warn you that you are about to do something stupid.
And yet, when I proposed people that we need to switch to Typescript, I’ve encountered comments such as: “but I want fast iteration”, “I had enough of Java headaches”, “But then I have to define every type” and my favorite “But we don’t have errors related to dynamic types”.
This bug is only one of many I’ve dealt with in my life. I don’t have enough bits to count how many times I had to print objects just to see their contents. Is it obj.foo_bar
or obj.fooBar
? Not having types is like not having street names. You need to memorize the entire city and knock on each door and ask what street and what number is it. You have to keep the entire project and all of its dependencies in your head (and we all know that node_modules
is like a black hole, so it can’t be kept in your head). Otherwise, you risk forgetting that some library decided not to use native BigInt
, but instead return strings 🤷♂️.
The cost of bugs
With the shift from desktop application towards SaaS software, and because of humans losing patience, developing software became a 24/7 task. You no longer have engineers who write code from 9to5, but you also have 24/7 on-call engineers (which nobody really wants to be) – hence the cost of such bugs is colossal.
Not only it harms your customers who cannot access your products; it harms your company, which loses money. And it harms you – because Murphy hates you, and it’s your turn to be on-call. So, suddenly, you find yourself half drunk in a bar, on Friday evening, trying to understand why your BigInt is overflowing, while your friends take Vodka shots off your MacBook Pro. But hey, at least you don’t need to define EVERY TYPE, like a bizarre monster who enjoys writing interfaces in Java.
They aren’t made for it
None of the dynamically typed languages we are using in our production today are made to be used in such way. You keep hearing about how PayPal or Uber are switching to NodeJS, while forgetting about the fact, that their adoption of NodeJS is for a tiny API Gateway that proxies requests to their huge Java monoliths (probably; I’ve never worked in those companies, but I have 10+ years of software development experience, and know a thing or two about maintaining legacy code).
You keep embracing dynamic languages, while the world keeps deprecating them. Facebook developed Hack to introduce types into PHP. Even PHP now has support for types. Microsoft came up with TypeScript to ease the pain while writing JavaScript applications; Python 3 has support for type hinting; and surprisingly, but in Lua "123" + 1
produces 124, and "123" + nil
throws an error. Want to guess what’s the output of "123" + null
in JavaScript?
It’s better to be type safe than sorry
A world of fast iterations, race to market and other marketing terms – has no place for dynamically typed languages for business critical applications.
Don’t get me wrong, dynamic languages are great at what they were designed to do. Python excels at AI and ML applications; Lua is perfect as embeddable scripting language for a type safe application; and JavaScript… it’s still terrible, so please, at least, use TypeScript.
Nah. I don’t make stupid type related errors – Well, go to your logs and search for how many cannot read property x of undefined
you have. Chances are you have a few of those logs, and your code is scattered with const bar = foo || {}
- because computers are made to be deterministic, but you are a free spirit and your foo
might or might not be an actual object.
I have tests to make sure I never sum integers with strings – Well, guess what. If you had let compilers do their job, you wouldn’t have to spend developer’s time on writing guard tests for type related problems. Java developers don’t spend time writing tests to make sure int x
is an integer.
Type safety is not some esoteric obsession of programming geeks in basements. It’s business critical for every company that want to stay in business. And I’d even radically say to you, and ask you to politely tell your manager, that making your code type safe should be treated the same way you treat customer bugs, and not buried in a technical debt pile. Use type hinting, switch to TypeScript or a real statically typed language – I don’t really care, as long as you do it.
You owe it to yourself and your sanity. And if not yourself, then for the well-being of your company. And if not your company, then, at least, for the satisfaction of your customers.