Write Code Ugly, Please

Lukas Bares
5 min readAug 11, 2023

--

Photo by Pranav Kumar Jain on Unsplash

Social media, articles and blogs are filled with advice on how to make code more readable, to appear as senior programmer. They seem to offer pure wisdom ready to be implemented in your production code.

And here I' am with a message: “Please, don't. At least not blindly”.

Yes, this might be controversial, but there needs to be someone providing an alternative perspective. Mentioned tips are often based on the idea: “What looks good is good”. The problem is, it's not. Not always.

Before you start implementing syntax from posts and reels, take a moment to consider the code itself. How the programming language works? What problem needs to be solved? Does the advice align with my data?

Let's demonstrate a few examples of “senior” JavaScript code, that isn't necessarily a silver bullet.

Example one: Array methods only


// very simplified example of CSV content
const csvRows = [
{ id: 1, value: 10 },
{ id: 2, value: 20 },
{ id: 3, value: 30 },
{ id: 4, value: 40 },
{ id: 5, value: 50 }
];

// "junior" syntax
let sum = 0;

for (const row of csvRows) {
if (row.value > 20) {
sum += row.value * 1.21;
}
}

// "senior" code
const result = csvRows
.filter(row => row.value > 20)
.map(row => row.value * 1.21)
.reduce((acc, value) => acc + value, 0);

Principle: Replace for / while loops for array methods

The “senior” approach appears more readable, but it can introduce performance issues. Every time you call array method, the entire array input (output from previous array method) is iterated over again.

If you chain three array methods, it’s similar to writing three “for” loop code blocks under. Would you do it without elegant syntax outcome?

With short collections these array methods might be fine solution. But try a CSV file, that has a few millions lines and you implement multistep transformation process.

In addition, array methods employ callback syntax. This can potentially lead to execution issues when the code is mixed together with asynchronous functions. Using old-school “for” loop simplify control over code timing, because required asynchronous code is executed within the iteration step.

Example two: Object as a switch

// ugly code, that is unpopular
switch (name) {
case 'Alice':
return 'Hello, Alice!';
case 'Bob':
return 'Hi, Bob!';
case 'Eve':
return 'Hey, Eve!';
default:
throw new Error(`Unsupported name with value ${name}`);
}

//... or this version also
if (name === 'Alice') {
return 'Hello, Alice!';
} else if (name === 'Bob') {
return 'Hi, Bob!';
} else if (name === 'Eve') {
return 'Hey, Eve!';
} else {
throw new Error(`Unsupported name with value ${name}`)
}

// and "senior" alternative
function getGreetingUsingObject(name) {
const greetingMessages = {
Alice: 'Hello, Alice!',
Bob: 'Hi, Bob!',
Eve: 'Hey, Eve!'
};

return greetingMessages[name] || throw new Error(`Unsupported name with value ${name}`);
}

Principle: Use JavaScript object as Switch / If-Else alternative.

No, please don't.

The problem lies in nature of objects, which are primarily intended as static containers for data.

The object require own memory that needs to be managed. It isn't designed to handle dynamic keys swiftly, despite the possibility to set keys from properties.

For simple cases, like days in the week, the object disadvantages doesn’t matter. However, there may be systems with nested structures of decisions and logic.

Moreover, objects don't ensure the presence of a value under a given key. There's no guarantee, that the key contain anything. Handling this problem, you have to be careful and implement extra logic to prevent unexpected exceptions or usage of default values, where they shouldn't be.

Contrastingly, the ugly switch statement doesn’t require any extra memory of your application. It’s logical element of the code designed to handle a decision based on dynamic values and cover all possibilities. The same if-else approach.

If you can’t accept switch / if-else in your code, try JavaScript Map.

const greetingsMap = new Map([
['Alice', 'Hello, Alice!'],
['Bob', 'Hi, Bob!'],
['Eve', 'Hey, Eve!']
]);

function greetPerson(name) {
const greeting = greetingsMap.get(name)

if (!greeting)
throw new Error(`Unsupported name with value ${name}`)

return greeting
}

The JavaScript Map is one of the most underrated elements of the language. This built-in collection is explicitly designed for managing key-value dynamic structures and be fast. The key even don't have to be a string, the type is up to you.

Maps consume application memory. But represent complex tool for dynamic key-value relationships.

Whenever you retrieve data from a Map, it accounts with possibility of the absence of a value under the key. For this reason “Map<T>.get()” returns a type “T | undefined”. This design encourages you to cover possibility of missing value every time you retrieve data from the Map.

Example three: Ternary operators everywhere

// ugly version
function checkPaymentMethodIfElse(paymentMethod) {
if (paymentMethod === "cash") {
return "Payment method is cash.";
} else if(paymentMethod === "card") {
return "Payment method is card.";
} else {
throw new Error("Unknown payment method")
}
}

// ternary version
function checkPaymentMethodTernary(paymentMethod) {
return paymentMethod === "cash" ? "Payment method is cash." : "Payment method is card.";
}

Principle: Replace if-else block by ternary operator.

Using ternary is not a bad choice. But it's important to understand, that ternary is dedicated to situations, when output always will be true / false and never anything else. Like a question “Is database connected?”

Payment method illustration represents poor application of ternary operator in JavaScript code. It describes a situation where payment methods are currently limited to just “cash” or “card”. Similar to the circumstances of launching an online store with limited payment options.

However, this approach can lead to complications when additional payment methods are introduced. It will be needed to modify entire code block, because ternary is unable to work with more than two possibilities.

Furthermore, example code open space for a serious issue. If payment method from function argument is invalid, the ternary version would select “not cash” option no matter what value is passed. In other words, ternary operator can't work properly using defaults in the case of invalid values.

These reasons demonstrates why if-else or switch or Map version can be considered as a better choice.

Summary

The internet is full of universal advice, but not all it's perfect. My recommendation is to be careful. Code influencing is popular, but not always cover all possibilities.

Don't overestimate the effort to write code pretty for other people. Your application repository isn't akin to a bestseller book. While readability is valuable, it isn't the primary objective of the code, nor an absolute measure to distinguish between “junior” or “senior” programmer.

You U-S-E the code to instruct computer in solving problem. Syntax of the code must primary serve the purpose of achieving the most effective solution. When application demands high performance, aesthetic code syntax takes a back seat. This is the reason why programmers have code reviews, not previews.

--

--