Software Construction and Practical Logic
Clean Code Principles
Code Is for Humans First
You've learned the syntax. You can make a program run, which is a huge step. But there's a big difference between code that works and code that's good. The secret is realising who you're actually writing for.
It isn't the computer. It's the next human who has to read your code. That human might be a teammate, or it might be you, six months from now, long after you've forgotten why you made certain decisions.
Clean code is about reducing the cognitive load on that future reader. It’s professional, empathetic, and ultimately saves enormous amounts of time and frustration. The goal is to make your code readable, understandable, and easy to change without breaking things.
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
What's in a Name?
Everything starts with naming. Variables, functions, and classes should reveal their intent. A name shouldn't just be a label; it should be a tiny piece of documentation. If you have to read the entire body of a function to understand what it does, its name has failed.
Avoid single-letter variables (unless they're simple loop counters like i), abbreviations, or vague terms like data or temp. Be explicit, even if it makes the name longer. A clear, descriptive name is always better than a short, cryptic one. The person most responsible for popularising these ideas is Robert C. Martin, often known as 'Uncle Bob'.
Consider this simple example. Which is easier to understand at a glance?
| Bad Names | Good Names |
|---|---|
let d; | let elapsedTimeInDays; |
function proc(data) { ... } | function calculateSalesTax(orderData) { ... } |
const list1 = [ ... ]; | const activeUsers = [ ... ]; |
One Job, Done Well
Great code is like a set of specialised tools, not a clumsy multi-tool. Each function and class should have one, and only one, reason to change. This is the Single Responsibility Principle (SRP), and it's a cornerstone of clean, modular design.
When a function tries to do too many things—fetch data, process it, format it, and save it—it becomes brittle and hard to understand. If you need to change how the data is saved, you risk breaking the fetching logic. Instead, you should break that monster function into smaller ones, each with a single, clear purpose.
A good sign a function is doing too much is the number of arguments it takes. A function with zero or one argument is ideal. Two is acceptable. Three or more is a that suggests the function is trying to coordinate too many different things. It might be time to refactor.
Let's look at some code that violates this principle.
// Bad: This function does three things
function handleUserData(userId) {
// 1. Fetches data
const userData = api.fetchUser(userId);
// 2. Formats data into a report string
const report = `User: ${userData.name}, Email: ${userData.email}`;
// 3. Saves the report to a file
fileSystem.save('user_report.txt', report);
}
Now, let's refactor it into clean, single-responsibility functions.
// Good: Each function has one job
function getUserData(userId) {
return api.fetchUser(userId);
}
function formatUserReport(userData) {
return `User: ${userData.name}, Email: ${userData.email}`;
}
function saveReport(reportData, filename) {
fileSystem.save(filename, reportData);
}
// We can now compose them to achieve the same result
const user = getUserData(123);
const report = formatUserReport(user);
saveReport(report, 'user_report.txt');
Notice how each new function is easy to understand, test, and reuse. This approach naturally leads us to another key idea: the DRY principle – Don't Repeat Yourself. When you have small, focused functions, you can reuse them instead of copying and pasting logic, which is a major source of bugs.
Explain Yourself in Code
A common misconception is that good code is full of comments. While comments have their place, they should be a last resort. Your code should strive to be self-documenting.
If you find yourself writing a comment to explain what a complex line of code does, first ask yourself: can I rewrite the code to be clearer? Often, this means extracting a piece of logic into a well-named function.
For example, instead of this:
// Check if the user is an admin and has been active in the last 30 days
if (user.type === 'admin' && (Date.now() - user.lastLogin) < 2592000000) {
// ...
}
You could write this:
function isRecentlyActiveAdmin(user) {
const thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000;
const isActive = (Date.now() - user.lastLogin) < thirtyDaysInMs;
return user.type === 'admin' && isActive;
}
if (isRecentlyActiveAdmin(user)) {
// ...
}
The second version needs no comment. The function name explains everything. The code itself becomes the documentation.
So when are comments useful? When you need to explain the why, not the what. For example, commenting on a business rule or the reason for a specific, non-obvious technical choice is valuable. Explaining what i++ does is not.
Break large blocks of code into focused, reusable modules. Name functions and variables clearly for instant understanding. Remove redundant code and enforce consistent styles.
Ready to test your understanding of these principles?
According to the principles of clean code, who is the primary audience you are writing for?
Which of the following variable names best reveals its intent for storing the elapsed time in seconds for a user's session?
Writing clean code is a habit. It takes discipline and practice, but the payoff is immense. It transforms you from a coder into a professional software engineer who builds robust, maintainable systems.
