DEV Community

Cover image for Why do we write JavaScript like this?
Anders
Anders

Posted on

Why do we write JavaScript like this?

Before we start, I do realize that this may potentially come off as a little harsh or negative, but that is not my intent, I am just curious as to why we’ve ended up writing code this way? All the samples in this article are from this article: https://dev.to/papabearcodes/four-common-javascript-interview-coding-problems-1gdk.

Over the past several years I’ve been reading a lot of articles here on Dev.to and I just keep coming across JavaScript that looks like this:

const isPalindrome = string => {
  let revString = [...string].reverse().join('')

  const palindromeCheck = () => string === revString ?  true : false

  return palindromeCheck()
}

And I can’t help but wonder, why do people prefer writing code like this. It makes use of a lot of built in functions, lambdas and what not, but it's also perhaps a little more difficult to read than you’d like, and if you think about what it's actually doing, it's not very efficient.

Let’s break this one down:

We use the spread [...string] function to make the string into an array of characters (memory allocation), we then reverse that array (hopefully this at least is done in place by the compiler, but I doubt it), lastly we join that back up into a new string (memory allocation).

Second we declare a sub function that basically just performs a single comparison, this literally seems to have absolutely no purpose whatsoever.

I’d personally write the thing something like this:

function isPalindrome(s) {

  for (var i = 0; i < s.length / 2; ++i) {

    if (s[i] != s[s.length - 1 - i]) {

      return false;
    }
  }

  return true;
}

Certainly a few more lines, and very “old looking” perhaps, but I am old ; ). Thing is though, this runs faster (albeit slightly), and in my mind at least is also easier to read and understand. But maybe I’m just crazy.

This example also sticks out, from the same article:

const sum = (...args) => [...args].reduce((acc, num) => acc + num, 0)

Granted, it is actually preceded by a more readable alternative first, that again avoids the things that I just think you should always avoid, poor readability.

function sum() {
  let total = 0

  for (let num of arguments) {
    total += num
  }
  return total
}

There is no way you’d ask yourself how that works.

However that example uses the “of” operator on the arguments array, a “new” fun feature. Sadly, performance is affected, using jsbench.me to compare the two the one line version is massively faster, the second version is reported as 92% slower than the first. This probably doesn’t matter for a function that runs once or twice but if we keep following this kind of pattern of blatant disregard for performance we will get into a lot of trouble eventually (Some may even say we are already, I may be one of those people ; ).

I did my own take as well, old school javascript again:

function sum() {

  var sum = 0;

  for (var i = 0; i < arguments.length; ++i) {        

    sum += arguments[i];
  }

  return sum;
}

Super simple, also as it turns out, super fast, our old one line champ is reported as being 74% slower than this above function.

Rounding up, let’s take a third function:

const captilizeAllWords = sentence => {
  if (typeof sentence !== "string") return sentence

  return sentence.split(' ')
    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
    .join(' ')
}

Split, Map, Slice, Join.. so many array operations in action here. Let’s try a more basic approach:

function captilizeAllWords(s) {

  var capitalize = true;
  var returnValue = '';

  for (var i = 0; i < s.length; ++i) {

    var c = s[i];

    if (c == ' ') { 

      capitalize = true; 

    } else if (capitalize) {

      c = c.toUpperCase();
      capitalize = false;
    }

    returnValue += c;
  }

  return returnValue;
}

This is a much more explicit take. Again it’s slightly longer. And again it's faster. JSBench reports the first variant as 33% slower here. An additional benefit of this solution, if you actually wanted to do this for things after a tab or new line, that doesn’t really take much thinking of code to achieve.

That does it for this old developer ranting about new stuff style article, hopefully this can lead to some healthy discussion if nothing else =).

Eat your vegetables, make your code readable, and don't forget to BENCHMARK!

Latest comments (65)

Collapse
 
spazmodius profile image
rajiv • Edited

Why do we right code like this?

if (s[i] != s[s.length - 1 - i]) {
Enter fullscreen mode Exit fullscreen mode

This allocates 2 1-character strings, and performs a string comparison (not a character comparison, there is no character datatype in Javascript).

If efficiency is our criteria, then we would write

if (s.charCodeAt(i) !== s.charCodeAt(s.length - 1 - i)) {
Enter fullscreen mode Exit fullscreen mode
Collapse
 
anders profile image
Anders

That is great, how does that perform compared to what I wrote?

Collapse
 
spazmodius profile image
rajiv • Edited

That is, of course, the right question.

In Node 12, it seems to make no difference at all. Maybe they optimize to the same thing.

Results may vary in other runtimes.

 
pentacular profile image
pentacular

Well, bad poetry isn't to everyone's taste, so it's understandable.

Mostly I wanted to convey that I think that I understood their position, and that it was a fine personal choice, but might lead to a situation that they would eventually come to regret.

Without framing it as an argumentative position, which is the tricky bit.

Collapse
 
pj profile image
Paul Johnson

I think we're at this weird time in programming where functional concepts are becoming wider spread, but still not ubiquitous enough that you can necessarily assume people will be familiar with them. It's possible that over the next 10 years FP will become the dominant programming paradigm and OOP will become legacy or restricted to certain use cases.

In that world I would expect the FP style will become familiar to a majority of programmers and so won't be strange to see it in examples.

I also I think it represents a gap between enthusiasts who produce blog posts and tend to be up on the latest code style trends and the majority of programmers who mostly work in an OOP/imperative style.

Collapse
 
tominflux profile image
Tom

I have never ever actually had a problem with JavaScript running too slowly. Only ever issues with performance of complex graphical operations stemming from low-level. The readability that this functional, immutable style of JS provides has, however, been incredibly beneficial to me in terms of being able to wrap my head around large codebases.

Collapse
 
johnkazer profile image
John Kazer

If concerned about performance over functional clarity should we be even using JavaScript? Rather than web assembly?

Collapse
 
anders profile image
Anders

That may certainly be an option if you do bot need to support older browsers. My focus here though was specifically JavaScript patterns. I think we can all agree that JS isn't exactly the perfect language.

Collapse
 
_hhandoko profile image
Herdy Handoko • Edited

I'm not sure I understand, if this is a satire piece then I missed it completely.

All the examples provided, are far more readable than the author's own examples. Perhaps, it is because those operations are inspired by Functional Programming concepts, and has commonality across many different languages.

Putting performance aside (for now), these examples shows intent. It tells the 'what' rather than the 'how'. In one glance, I can tell what the first code is supposed to be doing, not so much with the for loop of the second one where I have to inspect and step-through the algorithm (in my head).

In my personal experience as well, for loop implementations need to be accompanied with comprehensive tests as it is easy to make a mistake in them.

Performance matters, but I do believe readability and correctness matters more.

I would have been fine if the focus of the article was simply on performance of conventional, imperative JS code. However, it should be acknowledged that readability and comprehension is way lower.

Collapse
 
anders profile image
Anders

The article posts a question "why", and I think there has been quite a few potential answers to that question.

Not sure where you see many issues with loops though, care to elaborate with examples? =)

Collapse
 
_hhandoko profile image
Herdy Handoko

Missed the reply notification 😬

Not so much of a problem, but rather that for loops carry higher cognitive overload, whereas map or filter is composable and easier to comprehend (e.g. treating it as data pipeline).

So there is definitely a tradeoff, readability vs performance.

Collapse
 
xowap profile image
Rémy 🤖

I draw the line at: if I have to Google how to make this code a one-liner then maybe it's not right to do.

Collapse
 
anders profile image
Anders

Hehe, that sounds like the ultimate sanity check =)

Collapse
 
eelstork profile image
Tea

Anders, I think you need to see this:
dev.to/eelstork/introducing-howl-a...
: ))
Concision is valuable on its own. When concision works against clarity, there is a problem. How do you balance these two?
Not that simple. I'm not a huge fan of lambdas, but then again, as a programmer I predate (the widespread adoption of) lambdas. So there's an issue here of:

  • "not clear" (because we are not familiar with a notation or coding style x)
  • "not clear" (after getting enough familiarity with said coding style or notation)

You are showing "not much longer" code which ends up 3x 4x longer than the alternative. Programmers spend most time reading code and navigating code bases they know.
3x shorter, I think that's valuable when scanning through thousands of lines of code.

There isn't a standard of clarity per se (well; linters, good practices and so forth, but that is definitely not the end of it). What matters is what works for you, and your team.

Now, talking performance, the answer is really simple. BEFORE benchmarking your code there should be a perceived need for the code to run faster. Optimise for readability, concision and productivity. These concerns duly override worrying about how fast your code is running.

Collapse
 
tominflux profile image
Tom

Does this count as an esoteric language?

Collapse
 
anders profile image
Anders

∘ ㅇ IsModifier(ㄹ x) {
⤴ (howlTemplate ☰ null) ⤬
⤵ ∀ (∙ k ∈ howlTemplate) ⤴ (k ☰ x) ㆑ ⤬
}

Ohh my =)

Collapse
 
sargalias profile image
Spyros Argalias • Edited

It's about readability and understandability.

I never aim to write functional code. I aim to write what I consider to be the best code I can write.

First example

In the palindrome example, the function body is 2 lines long:

function isPalindrome(str) {
    const reversedStr = str.split('').reverse().join('');
    return reversedStr === str;
}

The function body of the loop version has 4 lines of code and a few more lines with braces. The volume of code to read is much higher, and you must read all of it before you understand what it's doing. Furthermore, you have to actually parse the logic in your head and see how it works, which I find much more difficult than the simpler logic in the functional example.

"reverse the string and check if it's equal to the original", compared to "loop through the string, for each index, grab the current character, check if the current character is equal to the character at the index from the opposite end of the string and return false if it isn't".

It just takes longer to read and understand for someone who is well-versed in both versions.

About performance: In many apps, it brings no benefit to optimise performance for simple things (premature optimisation). It's usually better to optimise for readability and only optimise for performance if needed.

Second example

I would have written the second example like this:

import {add} from 'ramda';

const sum = (...args) => args.reduce(add, 0);

Using a library, or coding your own versions of these utilities, is fine, since you'll reuse those functions in many places throughout your codebase (DRY).

Again, if you understand what this is doing (which just takes a bit of practice), this is instant to read and understand. It says, "for every item in the list, add it to the total, with the total starting at 0".

The for-loop version is fine, but again, you have to read every word and parse it to understand it.

And so on.

Edit: Fixed the code.

Collapse
 
rsa profile image
Ranieri Althoff • Edited

That is because Javascript, even though it has the concept of iterables and generators for ages, still does not use them enough.

Take Python, for instance: reversed(x) does not allocate memory if x is randomly indexable. Same for map and filter, which don't execute until you require them. That means:

const [x] = arr(x => x ** 2)

Should only execute the callback ONCE, as we are extracting just the first element. In plain JS, however, it executes for everything and returns a new array.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.