Really Good JavaScript: Part 1/3 - Cogent

Really Good JavaScript: Part 1/3

Craig Ambrose

Craig Ambrose

Crafter of code, and idealistic change-maker.

What separates unskilled hacking from the high quality JavaScript you’d expect from a senior developer? 

In Part 1, we examine the basics of writing good JavaScript on a line-by-line basis.

My hands positioned artistically on the workbench in some sort of metaphor for quality code

What paradigm do we use to evaluate JavaScript?

It’s not uncommon to need to review someone’s code to make a decision like “should I hire this person”, or “is this project going well”. If you’re reviewing Java or C#, you’d certainly expect to see good object-oriented design, and there are known principles that you can use to judge that, such as SOLID. JavaScript code can be object-oriented, but it often isn’t and there’s some debate as to whether it should be. Functional programming principles are also very popular in the JS community and need to be judged with totally different criteria. Many other popular examples of JS code use neither Functional nor Object-Oriented practices, so how do we judge them?

This isn’t just an academic question, or an issue for recruiters and team leads only. If we don’t know what the best way to write JavaScript code is, how do we write it ourselves? Most things are easier when there’s to guide us, but the only guiding principle that JavaScript seems to have is that there should be a diversity of ideas and approaches.

Before we get overwhelmed, lets look at the basics.

Procedural JavaScript

While JavaScript can be used in other ways, it’s first and foremost a Procedural Language. JavaScript code tends to be made up of functions which do all the work. The prevalence of the word “function” in any discussion of JavaScript makes us think of Functional Programming (FP), but functional programming relies on specific constraints such as the inability to mutate data, restricted side effects, and specific optimisations that assist with recursion and lazy evaluation. These characteristics allow code written in functional language to look declarative, meaning that it reads like a set of rules declared to be true.

Without jumping through specific hoops and using libraries to make JavaScript appear more declarative, our code is typically going to be be imperative, meaning it’ll read like a set of instructions to be followed in order. This puts JavaScript in the same family as languages like C, and Go.

While we can use other techniques to enrich our JavaScript development, any JavaScript developer needs to take steps to ensure that where they are writing procedural code, that it is really good procedural code.

What makes good procedural code?

At the function level

When dealing with inexperienced developers, I always encourage them to first focus on their code at the level of a single function. My go-to book recommendation for getting these basics down is Uncle Bob’s “Clean Code”

If you look at in this book, you’ll see the sorts of sensible recommendations you’d expect when learning to write well formed functions. They should:

  • Be small
  • Do one thing
  • Have one level of abstraction per function
  • Use descriptive names
  • Not have too many arguments

Of all of those, the most important to teach new developers is often . This takes experience to get right, but tends to drive many improvements to the code.

One level of abstraction

Here’s an example of a function that mixes levels of abstraction:

isValidOrder mixes levels of abstraction

We’d expect a function like this to check multiple things on the order to ensure that it’s valid. Each of these checks should be similar in that they likely require understanding of the order. Object-oriented developers will of course notice that in OO code, this would probably be a method on an Order class called “isValid”, but even if so, the same criticisms I’m about to make would apply.

I want to be able to read this function as a list of clearly understandable checks performed on the order. At the moment there are three checks, but I’d also be aware that more could be added later. The current three are:

  • The order is has more than one unit
  • The order is marked as complete
  • The total cost of the order (including tax) does not exceed the customer’s available funds

These three checks as I’ve expressed them in words are on the same level of abstraction, but in code it’s a different story. The third check performs some mathematical operations to figure out the price including tax. When I’m reading a list of checks I don’t want to have to look at the multiplication of tax percentages and check that it is doing what I expect. Hide that code somewhere in a function that is all on that lower level and knows about tax percentages.

Even without using any Object-Oriented techniques at all (such as adding methods to order compute the with-tax amount, or to customer to determine if it can afford it) in the very least lets extract that math into a function.

The sales tax example is easy to spot, because numerical calculations are almost always a lower level of abstraction to other business rules. A similar rule of thumb could be applied to string operations, file management, HTTP calls, and so on.

Good Naming

Keeping functions at the right level of abstraction, and giving them a single and clear thing to do, also ties in to naming them well.

In their article “Name That Thing”, experienced developer Charlie Ablett writes:

 

Within a function, variables also need to be well named. Variables are often used to explain part of something more complex. Don’t worry about saving yourself typing time, while a long name is not always the best either, too long is definitely better than a one-letter abbreviation.

At the module level

If someone can write high quality functions again and again, then the next milestone on their journey to producing a high quality code-base is how they organise their functions. Even procedural code that does not contain object-oriented structures needs to organise related functions into concepts that can help the developer understand and decompose the overall problem.

Units of code which are not instantiate-able classes are typically called modules. Modules don’t connect data and behaviour the way that classes do, but they do encapsulate a set of related behaviours into a cohesive unit of code, and can even be used polymorphically (ie, you might chose to use one module or another, where both expose the same functions).

Here’s a great article on how modules work in JavaScript.

The short version, however, is that almost all modern JavaScript programs now use the CommonJS ecosystem, which originally comes from NodeJS and is now supported in the browser via Browserify, Webpack or similar system. This means that we automatically have one module per file in our JavaScript programs, and so it’s up to us to arrange our code into sensible files that form cohesive modules, and to choose what sort of public functions they expose to other files.

This means that in most standard procedural JavaScript programs each file is a module, and is the “Unit” of code that you think of in terms of unit testing. The good JavaScript code that we’re looking for has well chosen and well named modules.

At the Directory Level

If a file is a module, and thus a unit of code, then our directory structure represents some sort of taxonomy of those files. The tree structure of directories is not an ideal taxonomy for all purposes, and shouldn’t be overused. Sometimes, with well named files it’s worth considering a fairly flat directory structure, particularly for things like React component libraries.

When we do use our directory structure to organise our modules, we only get to use it to break down our application according to a single set of criteria, because a file can only be in one directory. Here’s an idea to consider:

If you find yourself splitting your code up into directories like “models, views, controllers”, then I think you’ll find that this will make it harder for new developers to understand the key parts of your application, and will also reinforce the tendency of your application to become a single monolithic code-base which you can’t later split up.

If you want to think about that idea in more detail, here’s a fairly entertaining hour of Uncle Bob rambling on about it: Beyond Procedural JavaScript

If it seems like the above points are a bit basic, that’s because they are — and these basics are incredibly important and can take years of practice to get right. It’s unlikely they they will be enough tools to write a large maintainable JavaScript application, so next up we’ll look at the two main directions that JavaScript code can go from here.

In part 2, we’ll go over writing good Object-Oriented JavaScript, and in part 3 we’ll talk about Functional Programming, and how FP techniques can be (and often are) applied to JavaScript event though it isn’t strictly speaking a functional language.

 That’s all from me, catch you again for part 2.

 

Related posts