In programming world we are working with logic. Everything relies on it, it's a fundamental part of computers.
If we do 3+4
we always expect to get 7
. Call to createDatabase
shall not destroy database. As experience grows developer grasps more and more concepts and approaches because of the past experience and logic. It's very important part of programming ecosystem which helps to grow skill set without getting another Masters degree or attending classes/courses
People ended up with very common concepts and gave them names - algorithms, design patterns, data types, naming conventions.
I've used dozens of different languages for different platforms such as (android, web, desktops, symbian, j2me, embedded AVR & ARM, etc..) and I see quite the same patterns and approaches pretty much everywhere, almost. But JavaScript is always stands out!
Add mayo to the socks
I won't mention about hieracy system in JS - it's a mess, nonsense and you probably know about that. I want to point out on another amusing parts of the language.
> {}+[]
0
> []+{}
'[object Object]'
:thinking_face: Generally in the real life and normal languages you won't sum up chairs and color because they are too different. I'd except to get error, ideally compile time error because this code is nonsense. But no, JS actually executes it and returns result (different result, you woulnd't expect that, whould you?)
It's not true, because of "soda"
Boolean math is the foundation of the computers. Literally.
As a type Boolean is one of the most important way to express conditions and flags, binary state(means it has two states)
disclaimer: it's not only JS problem, most of weak-typed languages have that nonsense
> true == 0
false
> true == 1
true
> true == 10
false
> true <= 0
false
> true >= 0
true
So wait, true is not 0 but 1. My common sense says me that false
should only correspond to 0 and true
- for any other value, because you know, it's boolean. But JS engine is cheating here as well. For two state value we have an integer range lying in the range of true 𝈨 (-Min, 0) 𝉅 (0, +Max)
. Is it only me who founds it weird?
Obviously, my initial understanding of how to compare apples to sheeps might be wrong. But how can we use programming language which allows us to refer to the gut feelings?
Nono no, it's 'true'
There is an binary operator which makes sense only in context of boolean or group of booleans - not
. What it does it simply inverts boolean value or bits in the number
But wait, in JS there are more usages!
> !""
true
> !"rabbit"
false
According to JS architects it's okay to perform negation on the string. If they could really try to use their imagination they could come up with real negation/inversion operation which makes sense to given datatype, for example by performing !"manDARIN"
-> !"MANdarin"
.
Boolean not
in JS inverts string and produces boolean. If string is empty - it's true, otherwise it's false. That's strange but okay, may be it's supposed to be a shortcut to check if there is something? If so why not to call string.isEmpty()
, for example?
> !nonExistingString
ReferenceError: nonExistingString is not defined
at repl:1:2
at ContextifyScript.Script.runInThisContext (vm.js:23:33)
at REPLServer.defaultEval (repl.js:339:29)
Oops no, I mean, !"oops"
Who said that JS is easy language?
Strings are easy dangerous
Typical JS application operates with massive number of strings, so I'd assume toolkit for this data type should be perfect
Strings are almost arrays
Just recent discover. You would think you can access chars of the string the same way as you do in the vast majority of the languages using indexed access, but..
> var s = 'abcd'
undefined
> s[0]
'a'
> s[0] = 'X'
'X'
> s
'abcd'
Basically, it is possible to extract character at given position AND assign something to the specific position BUT it DOESN'T WORK. How do you feel about code which doesn't throw any error AND doesn't work, by design. Difficult to believe that it's in language spec
String literals aren't really strings, sort of
Let me impress some professional developers - there are string literals and string object, which behave, almost the same way. Something tells me some developers even didn't know there is a difference between.
Consider example from SO:
var x = "hello" ;
x.y = "world";
console.log(x.y); // this will print undefined
var x = new String("hello");
x.y = "world";
console.log(x.y); // this will print world
One can say, it's completely fine as they're different datatypes, even it's dynamically typed language. Generally speaking I hate inconsistent behaviour but if it significantly improve performance sometimes we have to go with this kind of tradeoff. My main issue here with error swallowing. Lovely javascript doesn't indicate that property wasn't set, even in strict mode.
Map/Set are useless
If you like to use existing data structures you also might use Set and Map, which finally were introduced in JS6. I really was waiting for that and had to use lodash
before
But don't celebrate it yet.
Let me tell you story about Iterator
s. There is a design pattern which allows consumer to walk through collection(basically list of items organised in some way) and get every next element, only one element an iteration. In functional languages and pretty much everywhere nowadays it's common to use Iterator
interface to perform functional-like operations on collections, such as map
, filter
, reduce
.
I'm happy to confirm that these operators were around for a while for Arrays. But, think about it, in 2017 after ten years of working on new version architects didn't bother to add these operations on all-new Map
and Set
. Iterators there are half-backed and don't behave as you might expect.
You simple can't transform or filter key-value pairs on it
var m = new Map()
m.set("a", 11)
m.set("b", 22)
> m.filter
undefined
> m.map
undefined
You might think I've forgotten about entries()
. Well, look at this nonsense:
> m.entries()
MapIterator { [ 'a', 11 ], [ 'b', 22 ] }
> m.entries().map
undefined
> m.entries().filter
undefined
> m.entries().reduce
undefined
So what is a javascript way?
If you understand roughly what is happening here you might be terrified:
> new Map(Array.from( m ).filter(([key, value]) => value > 20 ))
Map { 'b' => 22 }
Same thing is happening with Set
:
var s = new Set([1,2,2,3])
> s.map
undefined
> s.filter
undefined
And the recommended way to perform operation on collection is using Array
:
> new Set(Array.from(s).filter(v => v >= 3))
Set { 3 }
When I see those unnecessary allocations due transformations to array and back to map I'm shivering. It immediately became known anti-pattern and no one uses this approach. Apparently because of those issues developers consider neither Set
nor Map
to use. :sad_face:
The good news both Set
and Map
do have forEach
function which returns iterator with pairs of key-value wrapped with array, if you still care after all:
> m.forEach((k, v) => console.log(<code>${k}=${v}
))
11=a
22=b
As I love to say those data types are implemented in JavaScript style.
But wait, how do people write so many apps and they seem to be working?
The answer is simple:
1) Luck. Yes, sometimes we're lucky enough to make app which doesn't fail for years because corner case never comes. It doesn't mean it won't happen. The more system in use the more likely hidden bug will be discovered in the next moment, as per Bayes' theorem. Also, having one failure of thousand requests can be considered "okay".
2) Unit tests. The biggest unit tests and TDD adopters are found in javascript community. I believe unit tests are important, unfortunately. Many of them simply cover what compiler can't infer itself - practically we complement compiler's stupidity/simplicity paying high cost of time of our human lives. On another extreme in Haskell world you won't find much unit tests since compiler guarantees really a lot.
3) Good practices. They exists in any ecosystem and they guide developer to write good, readable code, solve well-defined problems in common way. In javascript world good practice is more of "must use practice" otherwise system explodes. Even you can you should never assign property to string object, because it could be literal. Instead pollute namespace via use helper functions, because, you know, js is not prototype
fully classic OOP language ¯_(ツ)_/¯
Instead of conclusion
I always say if technology works for someone I don't mind what is being used until business is happy. But looking on javascript ecosystem I feel it drains a lot of human energy, which could be used in more conscious way in another worlds. Developers often fight windmills instead of bringing more value to business.