May 18, 2024
10 Essential Clean Code Principles With TS Examples
Clean code is the foundation of efficient, maintainable, and scalable software. It is not just about making code work, but about making it understandable and easy to manage over time. Robert Cecil Martin, also known as Uncle Bob, has been a significant influence on me and in the world of software development with his book “Clean Code: A Handbook of Agile Software Craftsmanship.” The principles he outlines in this book have become essential guidelines for developers aiming to produce high-quality code.
In this blog post, we will explore the top 10 clean code principles, complemented by practical TypeScript examples. We will demonstrate both bad and good coding practices to illustrate how each principle can be applied effectively in a TypeScript context. By adhering to these principles, you can write code that is not only functional but also clean, readable, and maintainable.
1. Meaningful Names
Bad Example
function cn(p: number, q: number): number {
return p * q;
}
Good Example
function calculateNetPrice(price: number, quantity: number): number {
return price * quantity;
}
Meaningful names make code easier to understand. In the good example, calculateNetPrice
, price
, and quantity
are clear and descriptive.
2. Functions Should Do One Thing
Bad Example
function processOrder(order: Order) {
validateOrder(order);
calculateOrderTotal(order);
saveOrder(order);
}
Good Example
function validateOrder(order: Order) {
// validation logic
}
function calculateOrderTotal(order: Order) {
// calculation logic
}
function saveOrder(order: Order) {
// saving logic
}
Each function in the good example is responsible for a single task, making the code more modular and maintainable.
3. Avoid Side Effects
Bad Example
let total = 0;
function addToTotal(amount: number) {
total += amount;
}
Good Example
function calculateNewTotal(currentTotal: number, amount: number): number {
return currentTotal + amount;
}
Avoiding side effects leads to more predictable and testable code. The good example avoids modifying external state.
4. Use Descriptive Function Names
Bad Example
function handle(data: Data) {
// handling logic
}
Good Example
function handleUserRegistration(data: Data) {
// registration logic
}
Descriptive function names clarify the function’s purpose. In the good example, handleUserRegistration
specifies what is being handled.
5. Use Objects and Data Structures
Bad Example
function calculateArea(width: number, height: number): number {
return width * height;
}
Good Example
class Rectangle {
constructor(public width: number, public height: number) {}
calculateArea(): number {
return this.width * this.height;
}
}
const rectangle = new Rectangle(10, 5);
console.log(rectangle.calculateArea());
Using objects and data structures, as shown in the good example, encapsulates related data and behaviors, improving code organization.
6. Prefer Exceptions Over Returning Error Codes
Bad Example
function readFile(path: string): number {
if (!fileExists(path)) {
return -1;
}
// read file logic
return 0;
}
Good Example
function readFile(path: string) {
if (!fileExists(path)) {
throw new Error("File not found");
}
// read file logic
}
Using exceptions makes error handling more explicit and avoids the pitfalls of checking error codes.
7. Keep Functions Small
Bad Example
function manageOrder(order: Order) {
validateOrder(order);
let total = calculateOrderTotal(order);
saveOrder(order);
sendOrderConfirmation(order);
}
Good Example
function manageOrder(order: Order) {
validateOrder(order);
processPayment(order);
sendConfirmation(order);
}
function processPayment(order: Order) {
let total = calculateOrderTotal(order);
saveOrder(order);
}
function sendConfirmation(order: Order) {
sendOrderConfirmation(order);
}
Small functions are easier to understand, test, and maintain. Breaking down manageOrder
into smaller functions improves clarity.
8. Avoid Magic Numbers
Bad Example
function calculateDiscount(price: number): number {
return price * 0.1;
}
Good Example
const DISCOUNT_RATE = 0.1;
function calculateDiscount(price: number): number {
return price * DISCOUNT_RATE;
}
Replacing magic numbers with named constants improves code readability and maintainability.
9. Use Clear and Consistent Formatting
Bad Example
function doSomething(){if(condition){return true;}else{return false;}}
Good Example
function doSomething(): boolean {
if (condition) {
return true;
} else {
return false;
}
}
Clear and consistent formatting enhances code readability. The good example follows a consistent style with proper indentation.
10. Write Tests
Bad Example
// No tests
function add(a: number, b: number): number {
return a + b;
}
Good Example
function add(a: number, b: number): number {
return a + b;
}
// Tests
describe("add", () => {
it("should return the sum of two positive numbers", () => {
expect(add(2, 3)).toBe(5);
});
it("should return the sum of a positive and a negative number", () => {
expect(add(5, -3)).toBe(2);
});
it("should return the sum of two negative numbers", () => {
expect(add(-2, -3)).toBe(-5);
});
it("should return 0 when adding 0 and 0", () => {
expect(add(0, 0)).toBe(0);
});
});
Writing tests has been personally quite helpful and useful for me. it ensures that your code works as expected and helps prevent bugs. In the good example, tests are written using a testing framework (like Jest, my favorite testing framework), covering various scenarios for the add
function.
In conclusion, Writing clean code is more than a best practice; it’s a professional standard that ensures your software is robust, maintainable, and scalable. with tips like the ones that were mentioned in this post, developers can create code that is not only functional but also elegant and efficient and good for teams!