MacMusic  |  PcMusic  |  440 Software  |  440 Forums  |  440TV  |  Zicos
code
Search

Seven little habits for writing better code

Wednesday September 3, 2025. 10:30 AM , from InfoWorld
Last week, I talked about the relationship between polishing forks and writing good code, and how deep attention to detail is the key to true greatness as a developer. Writing that column set me to thinking about how, exactly, one “polishes” code to make it worthy of a Michelin star. 

So, here are seven little habits you can adopt that will set your code apart from the merely good code and make it truly great.

Prefer enumerations over constants

Make meaning explicit, not implied.

You should always prefer enumerations over constant values. Not only are enumerations compiler-friendly, but they are more human-readable as well. For instance, get a load of this code:

function calculateDiscount(customerId: number): number {
// Look up loyalty tier somehow...
if (customerId === 100) {
return 1;
} else if (customerId === 200) {
return 2;
} else {
return 3; }
}

It’s not at all clear from the above what 1, 2, and 3 mean. So you think, “Okay, I’ll give them some values,” and you do something like this:

const DISCOUNT_GOLD = 1;
const DISCOUNT_SILVER = 2;
const DISCOUNT_BRONZE = 3;

function calculateDiscount(customerId: number): number {
if (customerId === 100) {
return DISCOUNT_GOLD;
} else if (customerId === 200) {
return DISCOUNT_SILVER;
} else {
return DISCOUNT_BRONZE;
}
}

Now, I’ll grant that this is better. However, this code still can lead to confusion because the function could be altered to return 45 without defining what 45 means. 

But if you define an enumeration, then the function must return an enumeration, and thus it becomes impossible to be ambiguous. This code leaves no doubt:

enum DiscountTier {
Gold,
Silver,
Bronze,
}

function calculateDiscount(customerId: number): DiscountTier {
if (customerId === 100) {
return DiscountTier.Gold;
} else if (customerId === 200) {
return DiscountTier.Silver;
} else {
return DiscountTier.Bronze;
}
}

Always use explaining variables. Always.

If a name can save the reader a step, give it.

This particular technique is a pet peeve of mine:

function logPurchase(userId: number, itemId: number): void {
console.log(`User ${userId} purchased item ${itemId}`);
}

logPurchase(getUserId(), getItemId());

Notice how the logPurchase call is made with two function calls as parameters. Argh. What are the values of the parameters? You have to step into each function to know before logPurchase is even called. This is very unfriendly to the developer who has to read this code a year later. 

Always use an explaining variable. Always make it easy for the future maintainer to step through the code line by line:

const userId = getUserId();
const itemId = getItemId();

logPurchase(userId, itemId);

This is vastly more maintainer and debugger-friendly. Remember, that maintainer might very well be you.

Write complete and verbose error messages

An error that explains itself is half fixed.

Nothing is more frustrating than getting an error message like this:

List index out of bounds

That’s it. That’s the message. Which list? What index value? Who knows?

Given how thorough and reflective code can be these days, how about returning this error message instead:

The list named 'menuItemModifiers' attempted to retrieve index 43, but there are only 42 items in the list

That’s vastly more helpful, and not that much more work to write in code.

Don’t be afraid to over-log

Logs are cheap; log insights are priceless.

Along the same lines, don’t be afraid to “overdo” your log entries. A complete and detailed log file is vastly more valuable than one that is thin and sparse. Log reading tools are designed to slice, dice, and filter, and so don’t be afraid to use logging levels, complete and data-rich log entries, and verbose explanations of what is going on.

If you open it, close it.

Build the habit before you build the logic.

If you create or open something, immediately write the code to destroy or close that thing. Sometimes, if you are in a hurry, you might forget. For example:

function readFirstLine(filePath: string): string {
const file = fs.openSync(filePath, 'r'); // File handle opened

const buffer = Buffer.alloc(100);
fs.readSync(file, buffer, 0, 100, 0);

// Oops: no close!
// The file handle stays open until the process ends.
return buffer.toString().split('n')[0];
}

Yeah, that’s not good. Instead, when you write the openSync call, immediately write the try… finally clause. Immediately.

function readFirstLine(filePath: string): string {
const file = fs.openSync(filePath, 'r');
try {
// don't put anything here until the finally clause is written
} finally {
// Always close what you open
fs.closeSync(file);
}
}

It’s a great habit to get into. If you allocate it, always de-allocate it right away. If you write a while loop, make sure there is a way to break out of it before you write any logic. Build up the muscle memory for this one.

Don’t store what you can calculate

Derived and stored data ages badly.

Only store raw data, and don’t store data that is calculated. For instance, the TotalPrice field below is totally redundant:

CREATE TABLE Orders (
OrderID INT PRIMARY KEY,
Quantity INT NOT NULL,
UnitPrice DECIMAL(10,2) NOT NULL,
TotalPrice DECIMAL(10,2) NOT NULL -- ❌ Calculated field stored
);

Nothing makes me crazier than a stored field in a database called TotalLessFeesAndTax. Store the items, fees, and tax values, and then calculate the rest. That way, if one value changes, the computed values never get stale.

Minimize mutability

What can’t be changed won’t lie to you later.

If something can be made immutable, then make it immutable. Few things make for more tedious debugging journeys than a change to something that no one expects to change and that never should be changed in the first place. Making that thing impossible to change will nip the problem in the bud. 

Consider this code:

function moveRight(p: Point, distance: number): void {
// Mutates the original object
p.x += distance;
}

const start: Point = { x: 0, y: 0 };
newPoint = moveRight(start, 5);

The caller of moveRight probably didn’t expect start to change, nor should they. If you code defensively:

interface Point {
readonly x: number;
readonly y: number;
}

Now the compiler will find what would otherwise be a very subtle, hard-to-find bug that could drive you crazy later.

Writing good code takes a lot of attention to detail and a lot of will power and discipline. Since those traits are in short supply in all of us, a good developer builds good habits and personal coding systems that lead to great code. 

Build these seven habits, and future you will thank you.
https://www.infoworld.com/article/4050251/seven-little-habits-for-writing-better-code.html

Related News

News copyright owned by their original publishers | Copyright © 2004 - 2025 Zicos / 440Network
Current Date
Sep, Fri 5 - 13:23 CEST