Damien has the “pleasure” of supporting JavaScript code which runs, not in a browser or Node, but inside of a proprietary runtime specialized in handling high-performance collection datatypes. You can write extremely fast code, so long as you’re using the collection types correctly. This is good, because a lot of those JavaScript blocks have to be executed for every single request. Milliseconds of execution time add up faster than you think.
One of Damien’s senior peers needed to add some code that would filter fields out of a response. Data fetched from the database would be compared against a blacklist of fields to exclude- those fields should be removed from the collection.
This is a relatively simple task, and one that has, in other variations, already been implemented in the codebase. Some of those proprietary enhancements are faster implementations of types like Set
, so the basic approach would be:
- Load the blacklist into a Set
- Iterate over the items in reverse order
- Check if each item is in the set, and if so, remove it from the list using
removeAt
The Set
is fast. By iterating in reverse order, you can change the length of the list without disrupting the for loop. removeAt
is a direct access, which is also fast. Since performance is a concern, this is a pretty good solution.
The senior developer came up with this:
function getFilteredItems() {
var items = getItems();
var blacklist = config.getBlacklist();
Object.keys(blacklist).forEach(function (blacklistElement) {
if (blacklist[blacklistElement]) {
items.toArray().forEach(function (item) {
if (item.tag === blacklistElement) {
items.remove(item);
}
});
}
});
return items;
}
getBlacklist
was implemented by this developer, and thus they could have done anything they wanted. What they chose to do was return an object where keys were the field names, and values were booleans. The blacklist could contain hundreds of items.
So we iterate across each key, keeping in mind that some of these keys may potentially be false
, and thus should be skipped anyway. Then, for each key in the blacklist, we iterate across each item in our items
array, turning something that should have been a linear operation into a quadratic operation. The items
array could have hundreds or hundreds of thousands of items, depending on the operation.
But we don’t just iterate across the items
array. We iterate across items.toArray()
, a copy of the items
array. So that bloats the runtime and the memory footprint, especially since we need to do that for each blacklist item. But then that’s necessary, since we use items.remove
to remove the item in place- something that we couldn’t do from inside of a forEach
because changing the size of a collection while iterating across the collection can be tricky (if you don’t use the solution we mentioned above).
But items.remove
is what makes this even worse. Because items.remove
does a linear search through the array to find the target reference. So, we iterate across the blacklist. For each item in the blacklist, we iterate across the entire list of items, then for each item we need to remove, we iterate across the items again. And all this assumes that the toArray
cast is cleverly implemented using a memcopy
approach and doesn’t have to actually iterate the array, which is probably true.
Even if performance doesn’t matter, that’s a pretty WTF way to implement that, even if you’re a “senior developer”.