Home Web Front-end JS Tutorial Washing your code: don't make me think

Washing your code: don't make me think

Dec 06, 2024 pm 02:54 PM

Washing your code: don’t make me think

You’re reading an excerpt from my book on clean code, “Washing your code.” Available as PDF, EPUB, and as a paperback and Kindle edition. Get your copy now.


Clever code is something we may see in job interview questions or language quizzes, when they expect us to know how a language feature, which we probably have never seen before, works. My answer to all these questions is: “it won’t pass code review”.

Some folks confuse brevity with clarity. Short code (brevity) isn’t always the clearest code (clarity), often it’s the opposite. Striving to make your code shorter is a noble goal, but it should never come at the expense of readability.

There are many ways to express the same idea in code, and some are easier to understand than others. We should always aim to reduce the cognitive load of the next developer who reads our code. Every time we stumble on something that isn’t immediately obvious, we waste our brain’s resources.

Info: I “stole” the name of this chapter from Steve Krug’s book on web usability with the same name.

Dark patterns of JavaScript

Let’s look at some examples. Try to cover the answers and guess what these code snippets do. Then, count how many you guessed correctly.

Example 1:

const percent = 5;
const percentString = percent.toString().concat('%');
Copy after login
Copy after login
Copy after login
Copy after login

This code only adds the % sign to a number and should be rewritten as:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'
Copy after login
Copy after login
Copy after login
Copy after login

Example 2:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

The ~ symbol is called the bitwise NOT operator. Its useful effect here is that it returns a falsy value only when indexOf() returns -1. This code should be rewritten as:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

Example 3:

const value = ~~3.14;
Copy after login
Copy after login
Copy after login
Copy after login

Another obscure use of the bitwise NOT operator is to discard the fractional portion of a number. Use Math.floor() instead:

const value = Math.floor(3.14);
// → 3
Copy after login
Copy after login
Copy after login
Copy after login

Example 4:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

This one is understandable after a moment: it checks if either of the two arrays has any elements. However, it’s better to make it clearer:

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

Example 5:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);
Copy after login
Copy after login
Copy after login
Copy after login

This one took me a while to understand. Imagine we have a portion of a URL, such as filename="pizza". First, we split the string by = and take the second part, "pizza". Then, we slice the first and the last characters to get pizza.

I’d likely use a regular expression here:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'
Copy after login
Copy after login
Copy after login
Copy after login

Or, even better, the URLSearchParams API:

const header = 'filename="pizza.rar"';
const filename = new URLSearchParams(header)
  .get('filename')
  .replaceAll(/^"|"$/g, '');
// → 'pizza'
Copy after login
Copy after login

These quotes are weird, though. Normally we don’t need quotes around URL parameters, so talking to the backend developer could be a good idea.

Example 6:

const percent = 5;
const percentString = percent.toString().concat('%');
Copy after login
Copy after login
Copy after login
Copy after login

In the code above, we add a property to an object when the condition is true, otherwise we do nothing. The intention is more obvious when we explicitly define objects to destructure rather than relying on destructuring of falsy values:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'
Copy after login
Copy after login
Copy after login
Copy after login

I usually prefer when objects don’t change shape, so I’d move the condition inside the value field:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

Example 7:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

This wonderful one-liner creates an array filled with numbers from 0 to 9. Array(10) creates an array with 10 empty elements, then the keys() method returns the keys (numbers from 0 to 9) as an iterator, which we then convert into a plain array using the spread syntax. Exploding head emoji…

We can rewrite it using a for loop:

const value = ~~3.14;
Copy after login
Copy after login
Copy after login
Copy after login

As much as I like to avoid loops in my code, the loop version is more readable for me.

Somewhere in the middle would be using the Array.from() method:

const value = Math.floor(3.14);
// → 3
Copy after login
Copy after login
Copy after login
Copy after login

The Array.from({length: 10}) creates an array with 10 undefined elements, then using the map() method, we fill the array with numbers from 0 to 9.

We can write it shorter by using Array.from()’s map callback:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

Explicit map() is slightly more readable, and we don’t need to remember what the second argument of Array.from() does. Additionally, Array.from({length: 10}) is slightly more readable than Array(10). Though only slightly.

So, what’s your score? I think mine would be around 3/7.

Gray areas

Some patterns tread the line between cleverness and readability.

For example, using Boolean to filter out falsy array elements (null and 0 in this example):

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

I find this pattern acceptable; though it requires learning, it’s better than the alternative:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);
Copy after login
Copy after login
Copy after login
Copy after login

However, keep in mind that both variations filter out falsy values, so if zeros or empty strings are important, we need to explicitly filter for undefined or null:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'
Copy after login
Copy after login
Copy after login
Copy after login

Make differences in code obvious

When I see two lines of tricky code that appear identical, I assume they differ in some way, but I don’t see the difference yet. Otherwise, a programmer would likely create a variable or a function for the repeated code instead of copypasting it.

For example, we have a code that generates test IDs for two different tools we use on a project, Enzyme and Codeception:

const header = 'filename="pizza.rar"';
const filename = new URLSearchParams(header)
  .get('filename')
  .replaceAll(/^"|"$/g, '');
// → 'pizza'
Copy after login
Copy after login

It’s difficult to immediately spot any differences between these two lines of code. Remember those pairs of pictures where you had to find ten differences? That’s what this code does to the reader.

While I’m generally skeptical about extreme code DRYing, this is a good case for it.

Info: We talk more about the Don’t repeat yourself principle in the Divide and conquer, or merge and relax chapter.

const percent = 5;
const percentString = percent.toString().concat('%');
Copy after login
Copy after login
Copy after login
Copy after login

Now, there’s no doubt that the code for both test IDs is exactly the same.

Let’s look at a trickier example. Suppose we use different naming conventions for each testing tool:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'
Copy after login
Copy after login
Copy after login
Copy after login

The difference between these two lines of code is hard to notice, and we can never be sure that the name separator (- or _) is the only difference here.

In a project with such a requirement, this pattern will likely appear in many places. One way to improve it is to create functions that generate test IDs for each tool:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

This is already much better, but it’s not perfect yet — the repeated code is still too large. Let’s fix this too:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

This is an extreme case of using small functions, and I generally try to avoid splitting code this much. However, in this case, it works well, especially if there are already many places in the project where we can use the new getTestIdProps() function.

Sometimes, code that looks nearly identical has subtle differences:

const value = ~~3.14;
Copy after login
Copy after login
Copy after login
Copy after login

The only difference here is the parameter we pass to the function with a very long name. We can move the condition inside the function call:

const value = Math.floor(3.14);
// → 3
Copy after login
Copy after login
Copy after login
Copy after login

This eliminates the similar code, making the entire snippet shorter and easier to understand.

Whenever we encounter a condition that makes code slightly different, we should ask ourselves: is this condition truly necessary? If the answer is “yes”, we should ask ourselves again. Often, there’s no real need for a particular condition. For example, why do we even need to add test IDs for different tools separately? Can’t we configure one of the tools to use test IDs of the other? If we dig deep enough, we might find that no one knows the answer, or that the original reason is no longer relevant.

Consider this example:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

This code handles two edge cases: when assetsDir doesn’t exist, and when assetsDir isn’t an array. Also, the object generation code is duplicated. (And let’s not talk about nested ternaries…) We can get rid of the duplication and at least one condition:

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

I don’t like that Lodash’s castArray() method wraps undefined in an array, which isn’t what I’d expect, but still, the result is simpler.

Avoid shortcuts

CSS has shorthand properties, and developers often overuse them. The idea is that a single property can define multiple properties at the same time. Here’s a good example:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);
Copy after login
Copy after login
Copy after login
Copy after login

Which is the same as:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'
Copy after login
Copy after login
Copy after login
Copy after login

One line of code instead of four, and it’s still clear what’s happening: we set the same margin on all four sides of an element.

Now, look at this example:

const percent = 5;
const percentString = percent.toString().concat('%');
Copy after login
Copy after login
Copy after login
Copy after login

To understand what they do, we need to know that:

  • when the margin property has four values, the order is top, right, bottom, left;
  • when it has three values, the order is top, left/right, bottom;
  • and when it has two values, the order is top/bottom, left/right.

This creates an unnecessary cognitive load, and makes code harder to read, edit, and review. I avoid such shorthands.

Another issue with shorthand properties is that they can set values for properties we didn’t intend to change. Consider this example:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'
Copy after login
Copy after login
Copy after login
Copy after login

This declaration sets the Helvetica font family, the font size of 2rem, and makes the text italic and bold. What we don’t see here is that it also changes the line height to the default value of normal.

My rule of thumb is to use shorthand properties only when setting a single value; otherwise, I prefer longhand properties.

Here are some good examples:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

And here are some examples to avoid:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

While shorthand properties indeed make the code shorter, they often make it significantly harder to read, so use them with caution.

Write parallel code

Eliminating conditions isn’t always possible. However, there are ways to make differences in code branches easier to spot. One of my favorite approaches is what I call parallel coding.

Consider this example:

const value = ~~3.14;
Copy after login
Copy after login
Copy after login
Copy after login

It might be a personal pet peeve, but I dislike when the return statements are on different levels, making them harder to compare. Let’s add an else statement to fix this:

const value = Math.floor(3.14);
// → 3
Copy after login
Copy after login
Copy after login
Copy after login

Now, both return values are at the same indentation level, making them easier to compare. This pattern works when none of the condition branches are handling errors, in which case an early return would be a better approach.

Info: We talk about early returns in the Avoid conditions chapter.

Here’s another example:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

In this example, we have a button that behaves like a link in the browser and shows a confirmation modal in an app. The reversed condition for the onPress prop makes this logic hard to see.

Let’s make both conditions positive:

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}
Copy after login
Copy after login
Copy after login
Copy after login

Now, it’s clear that we either set onPress or link props depending on the platform.

We can stop here or take it a step further, depending on the number of Platform.OS === 'web' conditions in the component or how many props we need to set conditionally

We can extract the conditional props into a separate variable:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);
Copy after login
Copy after login
Copy after login
Copy after login

Then, use it instead of hardcoding the entire condition every time:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'
Copy after login
Copy after login
Copy after login
Copy after login

I also moved the target prop to the web branch because it’s not used by the app anyway.


When I was in my twenties, remembering things wasn’t much of a problem for me. I could recall books I’d read and all the functions in a project I was working on. Now that I’m in my forties, that’s no longer the case. I now value simple code that doesn’t use any tricks; I value search engines, quick access to the documentation, and tooling that help me to reason about the code and navigate the project without keeping everything in my head.

We shouldn’t write code for our present selves but for who we’ll be a few years from now. Thinking is hard, and programming demands a lot of it, even without having to decipher tricky or unclear code.

Start thinking about:

  • When you feel smart and write some short, clever code, think if there’s a simpler, more readable way to write it.
  • Whether a condition that makes code slightly different is truly necessary.
  • Whether a shortcut makes the code shorter but still readable, or just shorter.

If you have any feedback, mastodon me, tweet me, open an issue on GitHub, or email me at artem@sapegin.ru. Get your copy.

The above is the detailed content of Washing your code: don't make me think. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

What should I do if I encounter garbled code printing for front-end thermal paper receipts? What should I do if I encounter garbled code printing for front-end thermal paper receipts? Apr 04, 2025 pm 02:42 PM

Frequently Asked Questions and Solutions for Front-end Thermal Paper Ticket Printing In Front-end Development, Ticket Printing is a common requirement. However, many developers are implementing...

Demystifying JavaScript: What It Does and Why It Matters Demystifying JavaScript: What It Does and Why It Matters Apr 09, 2025 am 12:07 AM

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

Who gets paid more Python or JavaScript? Who gets paid more Python or JavaScript? Apr 04, 2025 am 12:09 AM

There is no absolute salary for Python and JavaScript developers, depending on skills and industry needs. 1. Python may be paid more in data science and machine learning. 2. JavaScript has great demand in front-end and full-stack development, and its salary is also considerable. 3. Influencing factors include experience, geographical location, company size and specific skills.

How to achieve parallax scrolling and element animation effects, like Shiseido's official website?
or:
How can we achieve the animation effect accompanied by page scrolling like Shiseido's official website? How to achieve parallax scrolling and element animation effects, like Shiseido's official website? or: How can we achieve the animation effect accompanied by page scrolling like Shiseido's official website? Apr 04, 2025 pm 05:36 PM

Discussion on the realization of parallax scrolling and element animation effects in this article will explore how to achieve similar to Shiseido official website (https://www.shiseido.co.jp/sb/wonderland/)...

Is JavaScript hard to learn? Is JavaScript hard to learn? Apr 03, 2025 am 12:20 AM

Learning JavaScript is not difficult, but it is challenging. 1) Understand basic concepts such as variables, data types, functions, etc. 2) Master asynchronous programming and implement it through event loops. 3) Use DOM operations and Promise to handle asynchronous requests. 4) Avoid common mistakes and use debugging techniques. 5) Optimize performance and follow best practices.

The Evolution of JavaScript: Current Trends and Future Prospects The Evolution of JavaScript: Current Trends and Future Prospects Apr 10, 2025 am 09:33 AM

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

How to merge array elements with the same ID into one object using JavaScript? How to merge array elements with the same ID into one object using JavaScript? Apr 04, 2025 pm 05:09 PM

How to merge array elements with the same ID into one object in JavaScript? When processing data, we often encounter the need to have the same ID...

Zustand asynchronous operation: How to ensure the latest state obtained by useStore? Zustand asynchronous operation: How to ensure the latest state obtained by useStore? Apr 04, 2025 pm 02:09 PM

Data update problems in zustand asynchronous operations. When using the zustand state management library, you often encounter the problem of data updates that cause asynchronous operations to be untimely. �...

See all articles