This text is a translation of the article 'Stop Being Cute and Clever' by the well-known (at least in the Python-community) Armin Ronher.I spent the last few days in my free time creating a
scheduler . The idea was simple: create a clone of
worldtime buddy using AngularJS and some other JavaScript libraries.
And you know what? It was not fun. I haven’t been so angry for a long time working on something, which means something, because usually I quickly express my discontent (I apologize to my followers on Twitter).
')
I regularly use JavaScript, but I rarely have to deal with other people's code. Usually I'm only tied to jQuery, underscore and sometimes AngularJS. However, this time I went all-in and decided to use different third-party libraries.
For this project, I used jQuery, without which you can’t do without (and why?), And AngularJS with some UI components (angular-ui and jQuery UI bindings). To work with time zones, moment.js was used.
I want to immediately note that I am not going to criticize someone’s specific code. Moreover, if someone looks into my JavaScript source code, their code will be a little better, and sometimes worse, because I didn’t spend much time on it, and in general I don’t have too much experience with this language.
However, I noticed a disturbing tendency for code to appear in terrible quality in JavaScript libraries (at least in those I use), and wondered why this was happening.
I had a lot of problems with js-libraries, and all of them were the result of the fact that everyone seems to spit on the features of the language.
The reason I began to actively explore third-party JavaScript code was because my naive attempt to send 3mb city names to typeahead.js autocompletion library resulted in an incredibly slow UI. Obviously, now no intelligent person will send so much data to the field with autocompletion at once, but will first filter them on the server side. But this problem lies not in slow data loading, but just in slow filtering. What I could not understand, because even if there is a linear search for 26,000 items, it should not be so slow.
Prehistory
So, the interface slowed down - obviously, the error was in my attempt to transfer too much data. But it is interesting that the performance dropped exactly when using the typeahead widget. And sometimes in a very peculiar way. To show how crazy it was, I’ll give a few initial tests:
- We are looking for San Francisco, typing "san". ~ 200ms.
- We are looking for San Francisco, typing “fran”. ~ 200ms.
- We are looking for San Francisco, typing "san fran". Second.
- We are looking for San Francisco, again typing "san". Second.
What is going on? How does a search break if we search for something more than once?
The first thing I did was use the new Firefox profiler to see what wasted so much time. And very quickly found in a typeahead a bunch of things that were too strange.
The bottleneck was found fairly quickly. The problem was an epic blunder when choosing
a data structure and a strange algorithm. The way to search for matches is bizarre and includes such wonderful things as going through a list of strings and further checking each of them for entering other lists, including the original one. When there are 6000 elements in the first list, and for each, a linear search is started to check whether this element is really in the list, it all takes a very long time.
Yes, mistakes happen, and if you run tests with small amounts of data, you won't even notice them. Thousands of cities and time zones I sent were too many. Also, not everyone writes search functions every day, so I don’t blame anyone.
But due to the fact that I had to debug this piece, I stumbled upon the strangest code of all that I had seen before. After further research, it turned out that the same eccentricities are found not only in typeahead.
Based on this, I am now convinced that JS is a kind of Wild West software development. First of all, because it competes with the PHP code of the year 2003 in terms of quality, but it seems that this concerns fewer people, since it works on the client side and not on the server. You do not have to pay for slow javascript.
Smart Code
The first painful point is people to whom JS seems like a sweet and 'smart' language. And it makes me ridiculously paranoid when reviewing the code and searching for bugs. Even if you know the applied idioms, you cannot be sure whether the side effects will be intentional or someone just made a mistake.
For example, I will give a piece of typeahead.js:
_transformDatum: function(datum) {
var value = utils.isString(datum) ? datum : datum[this.valueKey],
tokens = datum.tokens || utils.tokenizeText(value),
item = {
value: value,
tokens: tokens
};
if (utils.isString(datum)) {
item.datum = {};
item.datum[this.valueKey] = datum;
} else {
item.datum = datum;
}
item.tokens = utils.filter(item.tokens, function(token) {
return !utils.isBlankString(token);
});
item.tokens = utils.map(item.tokens, function(token) {
return token.toLowerCase();
});
return item;
}
, . , – c . ? - . , - . value- ( ) . – ( ) . , .
, :
{
"value": "San Francisco",
"tokens": ["san", "francisco"],
"extra": {}
}
:
{
"value": "San Francisco",
"tokens": ["san", "francisco"],
"datum": {
"value": "San Francisco",
"tokens": ["san", "francisco"],
"extra": {}
}
}
, , , , datum- , . : , . , , 10MB.
JavaScript, . , , . ''.
. : datum, ? . , – , - . , JS , - .
, , . , JS-, ,
map
.
map
,
["1", "2", "3"].map(parseInt)
[1, NaN, NaN]
.
. :
_processData: function(data) {
var that = this, itemHash = {}, adjacencyList = {};
utils.each(data, function(i, datum) {
var item = that._transformDatum(datum), id = utils.getUniqueId(item.value);
itemHash[id] = item;
utils.each(item.tokens, function(i, token) {
var character = token.charAt(0), adjacency =
adjacencyList[character] || (adjacencyList[character] = [ id ]);
!~utils.indexOf(adjacency, id) && adjacency.push(id);
});
});
return {
itemHash: itemHash,
adjacencyList: adjacencyList
};
}
:
utils.indexOf
– ,
utils.getUniqueId
.
, -
O(1)
, hashmap. . 100 000 , , .
:
utils.each(item.tokens, function(i, token) {
var character = token.charAt(0), adjacency =
adjacencyList[character] || (adjacencyList[character] = [ id ]);
!~utils.indexOf(adjacency, id) && adjacency.push(id);
});
, . , ?
!~utils.indexOf(...) &&
if (utils.indexOf(...) >= 0)
? , hashmap
adjacencyList
… , ID , . - ‘’ .
–
+
( ,
noop
) .
+value
– ,
parseInt(value, 10)
.
, Ruby. Ruby ,
''
:
false
nil
. –
''
. . JS . – .
,
""
false
. , . . jQuery
each
this
. ,
this
, , .
:
> !'';
true
> !new String('');
false
> '' == new String('');
true
Ruby, JavaScript. . , , . - , , .
~
indexOf
,
-1
, . , , « ».
«»
, , JS . , Python – , runtime . JavaScript . AngularJS.
, JS , . . . , , .
, Angular , , , , . , – , . , , , ' ' – .
, , . - . , , .
, Angular DOM . , , firing’ . .
, . JavaScript. API stateful-. , , . moment-. , ,
foo.add('minutes', 1)
. , , API . , , , .
, JS API, ‘’ . , Python. Python , immutable- . , , first-, -.
« »
Angular, . UI JavaScript, . . , .
fooBar
, DOM
foo-bar
. ? ,
style
DOM API, . , , . . Angular-, .
Angular JS- . AnguarJS, , . , JS: . . . Angular, .
?
Python JavaScript . , Angular URL . , . ? , - , .
Angular. JS HTML. DOM, , . - HTML :
function escapeHTML(string) {
var el = document.createElement('span');
el.appendChild(document.createTextNode(string));
return el.innerHTML;
}
URL:
function getQueryString(url) {
var el = document.createElement('a');
el.href = url;
return el.search;
}
, .
- , , , , . JS-.
« »
PHP , , . . , . : , , . , , PHP-
mod-. - , . , register_globals, SQL, .
JS . , , . , , .
: , . PHP , JS « » , .
?
, JavaScript. , , , PHP: , , , , . , , , , .
Python-. , , , . Django , , .
, JavaScript- , .
Armin Ronacher,
09/12/2013