Writing JavaScript code
Some time ago I wrote an article about issues with JavaScript and stated that nobody should write in JavaScript. But in this article, I shared my experience of using JavaScript several years ago. And by a strange twist of fate, I had to write a lot of JavaScript code last 3 weeks and I would like to share my experience with the latest and greatest.
Firstly, I was absolutely sure that the situation would be way better than I described. Those were real problems and developers would put pressure on JavaScript developers to make the language better and easier to use
But I didn’t find the situation any better. There are still no types in the language and as a result, it makes any kind of refactoring full of pain and wastes a lot of time because any change requires testing everything affected. And this happened because due to the lack of types, it is simply not possible to verify the logical structure of the code. As a result, there is pretty much no help from any kind of IDE except a very basic one.
The language is still a whole mess of strange and weird quirks. For example, if you write if (node == null) and the node is undefined, the condition is true. But if you use case null in the switch statement, then it is false. Yes, I know why it happened, but even I make this mistake sometimes.
You can define class and it looks like a normal class definition. But the nested class for some reason defined entirely differently via static. As a result, to use it inside of the outer class, you must specify the outer class name:
class OuterClass {
static InnerClass = class {
}
createInnerInstance(innerName) {
return new OuterClass.InnerClass(innerName);
}
}
Now, if want to change the outer class name, I need to change all usages of the inner class inside the outer class. Why?
There are no constants at the class level. Again, I must use the static keyword and use class name which is less clean code. There are confusing var and let keywords and it is hard for beginners to understand the difference.
There is a huge mess with this and how it passed. For example, if you have any experience with normal languages and you would like to subscribe to some event you then can write in your class something like this: something.addEventListener('focusin', this.handleFocusIn). It looks correct but when the event is called, this will not be your class but the DOM node. Why? Again, I know why, but it looks like every single developer made this mistake and it surely should be possible to improve it.
Most IDEs I tried do not produce any errors but just give you slight hints that something could be wrong. There is no analyzing stage that can produce potential logical issues and in general, it is simply not possible due to the dynamic type of language.
Did you add an argument to the function but forgot to update all places? Not a problem, it is still a valid JS code. In 99% of cases, this function will not work correctly but it will be your problem. Did you call a function and mistyped its name? Not a problem, when you run this code you will see an error. Or not.
Did you write a function but forgot to return the result? Again, not a problem, it will be fine and what's most annoying is that code can work most of the time. So if you do a quick test and most of the time function returns undefined or null, you would assume that everything is fine.
I had a case when I had the following line Something(a, b, c). I accidentally added a plus symbol between the name of the function and the opened parenthesis. After that code looks like Something+(a, b, c). No error during execution at all. Just function is not called.
I had a variable note but typed node. Still fine. It will be undefined and as I wrote before comparing undefined with null returns true, so my code worked fine until the case when that variable wasn’t null.
And now we arrived to the async. Its implementation is simply horrible. You can call it using await or you can call it without await. Both versions are fine. The second version is used to fire and forget and there is no way to detect which one do you want.
I had a code with functions nested to the 3 to 4 levels deep and I missed await in a single one. As a result, the code didn’t wait for that particular place to finish and continued execution introducing very subtle bugs.
In my case, I checked the code around 5 times and I missed it every single time. And only after I placed a lot of console.log in all these functions I was able to find this issue. Effectively I waste half a day only on that single problem.
And keep in mind that I’m a professional developer with tens of years of experience and I know how async works under the hood. Now imagine some junior or middle trying to find and fix this problem.
In a language like C#, it is your choice to do or not to do async. If you don’t understand how it works, you just keep your code synchronous, but in JS some functions (like fetch) have only an async version and you must deal with it.
I can continue a whole day with all the issues I found while writing JavaScript but effectively I came to several conclusions:
- Any non-trivial change will create many runtime errors and bugs in your code
- A new code that I just wrote will have many runtime errors. I found a ratio: every 6 lines of code will create 4 runtime errors
As a result, I spend a lot of my time re-testing everything over and over because I cannot be sure that the change I made will work. The bigger is project the more testing I had to do and after my project grow to a certain size my productivity became very low. In general, I spend around 5-10% of my time on writing code and 70% on making it run. The rest is just debugging.
In comparison, 90-95% of mistakes I made are simply not possible in strongly typed languages like C#. As a result, the chance that the code I write will work is probably 10-20 times higher for C# code.
And when I compare what kind of refactoring I can do in C# and JS I just want to cry. It feels like I went back around 15 – 20 years ago and my productivity considerably reduced. It feels like writing code in Notepad. Well just slightly better.
In conclusion, my previous statement stays true and I would say that it became stronger. It is a horrible language that nobody should use unless the code base is very small or in cases when code has small and independent areas.