Clean Code: Tips for Writing Code that is Easy to Read, Understand, and Maintain
Clean code is code that is easy to read, understand, and maintain. It follows certain rules and principles to ensure that it is of high quality.
Clean Code is a set of rules and principles designed to help you write code that is easy to read, understand, and maintain. Here is a list of things you should avoid or improve in your code.
Bad Naming
How often have you seen poorly named variables, functions, and classes? I’ve seen them quite a lot. Good naming makes code easier to read and understand, reducing the need for comments. Here’s a general rule of thumb for naming variables, methods/functions, and classes:
- Variable: noun, adjective (“user”)
- Method/Function: verb (“GetUser”)
- Class: noun (“User”)
Apply these rules to any programming language, and if in doubt, follow the language conventions.
// Bad naming
var d = 5;
var temp = GetData();
void Process() { }
class Manager { }
// Good naming - following the rules
var user = GetActiveUser(); // Variable: noun
void GetUser() { } // Method: verb
class User { } // Class: noun
Comments
Some love them; others hate them. They can be helpful or misleading. Good code doesn’t need comments to explain how something works. Use comments to explain why you did something or highlight something that isn’t obvious.
Code Repetition
Repeated code is often a mistake made by beginners or those on tight deadlines. While some duplication might be acceptable, generally, you should aim to have code in a single place to make changes easier. However, don’t follow this rule blindly!
// Bad - repeated validation logic
public bool ValidateAdminUser(User user)
{
if (user == null) return false;
if (string.IsNullOrEmpty(user.Email)) return false;
if (user.Age < 18) return false;
if (!user.IsAdmin) return false;
return true;
}
public bool ValidateRegularUser(User user)
{
if (user == null) return false;
if (string.IsNullOrEmpty(user.Email)) return false;
if (user.Age < 18) return false;
if (!user.IsActive) return false;
return true;
}
// Good - DRY (Don't Repeat Yourself)
public bool ValidateUser(User user, Func<User, bool> additionalCheck)
{
if (user == null) return false;
if (string.IsNullOrEmpty(user.Email)) return false;
if (user.Age < 18) return false;
return additionalCheck(user);
}
// Usage
var isValidAdmin = ValidateUser(user, u => u.IsAdmin);
var isValidRegular = ValidateUser(user, u => u.IsActive);
Zombie Code
There’s nothing worse than commented-out code. Why was it commented out? Should it do something? It creates too many questions and makes you wonder about its purpose. Similarly, unreachable or unused code should be deleted. You can always find it in source control later.
Long Methods
What makes a method too long? A general rule of thumb is that it’s too long if it doesn’t fit on the screen. Even if your 100-line method fits on a high-resolution screen, it’s usually doing too many things at once.
Why are long methods bad?
- They often do too many things at once
- They are hard to read and understand
- They are harder to test
You can often split one big method into smaller methods focusing on specific tasks. Long methods also tend to have many arguments in their signatures.
// Bad - long method doing too much
public void ProcessOrder(Order order)
{
// Validate order
if (order == null) throw new ArgumentNullException();
if (order.Items.Count == 0) throw new InvalidOperationException();
// Calculate total
decimal total = 0;
foreach (var item in order.Items)
{
total += item.Price * item.Quantity;
}
// Apply discount
if (order.Customer.IsVip)
{
total *= 0.9m;
}
// Process payment
var payment = new Payment { Amount = total };
paymentGateway.Process(payment);
// Send email
emailService.Send(order.Customer.Email, "Order confirmed");
}
// Good - split into smaller, focused methods
public void ProcessOrder(Order order)
{
ValidateOrder(order);
var total = CalculateTotal(order);
ProcessPayment(total);
SendConfirmationEmail(order.Customer);
}
Methods with Too Many Arguments
Some say a method shouldn’t have more than 3-4 arguments. Too many arguments often indicate that a method is doing more than it should. You can group arguments and introduce a new type (class or record). For instance, instead of passing “first name,” “last name,” “date of birth,” and “email,” create a new Person type with those properties.
// Bad - too many arguments
public void CreateUser(string firstName, string lastName,
string email, DateTime dateOfBirth, string address,
string city, string zipCode, string country)
{
// Implementation
}
// Good - use a parameter object
public record UserDetails(
string FirstName,
string LastName,
string Email,
DateTime DateOfBirth,
Address Address);
public void CreateUser(UserDetails details)
{
// Implementation
}
Nested (Arrow) Code
Nested code is hard to read and often performs poorly. It’s called arrow code because the indentation often resembles an arrow’s tip.
What can you do about it?
- Simplify the code
- Use guard clauses and early returns
- For long loop bodies, extract them into separate methods
Nested loops are the worst offenders, often with long bodies that make the code hard to read and understand. Keep in mind the previous tips when refactoring long loops.
// Bad - deeply nested code
public decimal CalculateDiscount(User user, Order order)
{
if (user != null)
{
if (user.IsActive)
{
if (order != null)
{
if (order.Total > 100)
{
if (user.IsVip)
{
return order.Total * 0.2m;
}
return order.Total * 0.1m;
}
}
}
}
return 0;
}
// Good - using guard clauses and early returns
public decimal CalculateDiscount(User user, Order order)
{
if (user == null || !user.IsActive) return 0;
if (order == null || order.Total <= 100) return 0;
return user.IsVip ? order.Total * 0.2m : order.Total * 0.1m;
}
God Classes
Classes with very low cohesion try to do too many things at once. The solution is to split the class up. Don’t try to cheat with partial classes.
// Bad - God class doing everything
public class UserManager
{
public void CreateUser(User user) { }
public void DeleteUser(int id) { }
public void SendEmail(string email, string message) { }
public void ProcessPayment(Payment payment) { }
public void GenerateReport() { }
public void LogActivity(string activity) { }
public void ValidateInput(string input) { }
public void ConnectToDatabase() { }
}
// Good - split into focused classes
public class UserService
{
public void CreateUser(User user) { }
public void DeleteUser(int id) { }
}
public class EmailService
{
public void SendEmail(string email, string message) { }
}
public class PaymentService
{
public void ProcessPayment(Payment payment) { }
}
How Do We End Up with Bad Code in the First Place?
People don’t write bad code intentionally. Here are some common reasons:
- Lack of mentorship
- No code reviews
- Lack of skills
- “Just get it done” culture
- Deadlines
Mentoring is effective but time-consuming, especially for junior developers. If your company or team doesn’t use code reviews (PRs, pair programming), consider starting. Trusting people to write good code is nice, but having more than one pair of eyes on the code is even better.
Some companies prioritize profits over quality. Tight deadlines and small budgets might force developers to make unpopular choices.
Every developer should be a responsible individual who cares about their craft. As a developer, you should:
- Apply engineering principles
- Invest in knowledge
- Not be afraid to ask for help
- Learn from mistakes
- Think critically
Becoming proficient in any craft takes time, and software development is no different. Don’t worry – with persistence, you can do it.