Zlatko Đurić

Beyond syntax in JavaScript, or why semantics matter

Beyond syntax in JavaScript, or why semantics matter

Created on
Last updated on

I saw someone say on Reddit, “syntax over semantics”. It kind of hit me wrong, and I thought semantics had more value over syntax.

It’s expected, me having so much experience with JavaScript, especially in the early days. But I wanted to write a bit more on it. Sometimes mid-career, I’ve started writing a lot of JavaScript. Mostly it was regular, “web 2.0” client-side JavaScript, but also server-side - and this was even before Node.JS, so really before all the help that EcmaScript standards have brought us.

But unlike, say, Java, JavaScript in those days was a much more wild-west type of ecosystem. People would throw things left and right, mix layers, domains, implementatiuons, helpers, everything would very frequently be a big mess. And even today, any bigger JavaScript codebase is frequently quite messy.

That is why I think that giving things meaning - “name things properly” - is even more important in JavaScript than in general.

Simple example - syntax and structure vs semantics

What do I mean, semantics matter more? Well, if you just focus on doing things very directly, the code seems to be more focused on exactly how something is done. And with a semantics-first approach, the focus is on expressing what is being done. And in the long run, that matters a lot.

Let me give you an example of a naively implemented function validateUser:

function validateUser(user) {
  return (
    !!user &&
    typeof user.name === 'string' &&
    user.name.length > 0 &&
    !!user.email &&
    typeof user.email === 'string' &&
    emailRe.test(user.email)
  );
}

The example may be a bit over the top, but its very explicitness shows my point, with a direct approach we’re strongly focused on exactly how the user checks are done. It’s very hands-on, and very direct - and in my opinion, a bit too direct. The function will exactly check how to validate the user -> check if it’s an object, make sure that the name is a non-zero-length string etc etc.

In a very strongly contrasting example, here’s a more semantic way to do the same:

const isPresent = value => !!value;
const isValidName = name => typeof name === 'string' && name.length > 0;
const isValidEmail = email => typeof email === 'string' && emailRe.test(email);

function validateUser(user) {
  return isPresent(user) &&
    isValidName(user) &&
    isValidEmail(user);
}

The difference is very contrasting, and (probably) a bit over the top. This example gives a lot more “meaning” to the validator, at least in my eyes. The main function defined what it means to be a valid user. And then the implementation does not even matter.

But in my experience, many a junior JavaScript developer will be writing the first version. And even experienced developers will do the same, when they’re writing some new code, for things they’re not even sure what they will look like. And I’ll often write such code myself - at least in the first version.

But I always strive to end up with something that has more meaning in it.


I admit, this “simple example” isn’t very realistic. The truth is, real-world code is frequently a mix of both.

But to me, the second example is a lot more meaningfull. Even if you added a non-trivial specification to the validity requirements, it would still work, while in the first version, you start getting tangled deeply into the data structure. That’s why I think it’s not very prone to future requirements.

For example, let’s say you add an age requirement. A user is valid, if it’s age is 16, unless the user is from Germany, then 18. What now? Let’s see the two diffs first:

The naive approach would now look like this:

function validateUser(user) {
  return (
    !!user &&
    typeof user.name === 'string' &&
    user.name.length > 0 &&
    !!user.email &&
    typeof user.email === 'string' &&
    emailRe.test(user.email) &&
    ((user.country === 'Germany' && user.age >= 18) || user.age >= 16)
  );
}

Of course, this is a silly example, nobody would write the code like that. They would probably go with something like:

function validateUser(user) {
  return !!user &&
  typeof user.name === 'string' &&
  user.name.length > 0 &&
  !!user.email &&
  typeof user.email === 'string' &&
  emailRe.test(user.email) &&
  isValidAge(user);
}
const isValidAge => user.country === 'Germany' && user.age >= 18 || user.age >= 16;

It makes sense, right? And it’s probably the same function in my second example. But the original function is now a bit of a convoluted mess. And it’s exactly something you can see in many a real-world project.

And the second example?

const isValidAge => user.country === 'Germany' && user.age >= 18 || user.age >= 16;
const isPresent = value => !!value;
const isValidName = name => typeof name === 'string' && name.length > 0;
const isValidEmail = email => typeof email === 'string' && emailRe.test(email);

function validateUser(user) {
  return isPresent(user) &&
    isValidName(user) &&
    isValidEmail(user) &&
    isValidAge(user);
}

No big change at all, things still work almost exactly the same.


I got sidetracked - but semantics matter

This kind of went a bit off track, and the examples are not merely talking about semantics. It also demonstrates a bit about function composition, and probably other things.

But it’s also demonstrating my opinion that prioritizing semantics in JavaScript development matters. Focusing on “what” instead of “how” leads to more readable and maintainable code. And semantics is one part of it.