DEV Community

The Eagle 🦅
The Eagle 🦅

Posted on

Clean Code vs Best Practices: What’s the Difference?

In software development, "Clean Code" and "Best Practices" are two important concepts. Sometimes you might hear comments like "Good job buddy, your code looks clean" or "Apply best practices to avoid technical debt next time bro." Although they overlap, they each play unique roles in creating high-quality, maintainable software. Let’s break down what each term means, explore some illustrative examples, and see how they differ.

What is "Clean Code"? 📚

"Clean Code" is a book by Robert C. Martin (Uncle Bob) that provides guidelines for writing code that's easy to read, understand, and maintain. Here are eight key principles that you should know:

1. Meaningful Names: Names should clearly describe what a variable, function, or class does.

  • Example: Instead of x, use userAge if it represents a user’s age, and instead of calculate, write what the function calculates.
   // Bad
   function calculate(x: number): number {
       return x * 12;
   }

   // Good
   function calculateUserAgeInMonths(userAge: number): number {
       return userAge * 12;
   }
Enter fullscreen mode Exit fullscreen mode

2. Boy Scout Rule: Leave the code better than you found it. Refactor and improve as you go.

  • Example: If you fix a bug and see messy code, clean it up before committing changes.
   // Before
   function fetchUserData(userId: string): any {
       const data = getDataFromDatabase(userId);
       return data;
   }

   // After
   function fetchUserData(userId: string): User {
       return getDataFromDatabase(userId).user;
   }
Enter fullscreen mode Exit fullscreen mode

3. Reduce Coupling: Minimize dependencies between classes to make your codebase more modular.

  • Example: Use dependency injection instead of hard-coding dependencies.
   // Bad
   class UserService {
       private database: Database;

       constructor() {
           this.database = new Database();
       }
   }

   // Good
   class UserService {
       private database: Database;

       constructor(database: Database) {
           this.database = database;
       }
   }
Enter fullscreen mode Exit fullscreen mode

4. Be the Author: Write code as if telling a story. Functions should be small and focused.

  • Example: Think of it like making a smoothie. Instead of one giant function that picks fruit, blends, and pours, split it into smaller, focused functions.
   // Before
   function makeSmoothie(fruit: string, liquid: string): void {
       console.log(`Choosing ${fruit}`);
       console.log(`Blending ${fruit} with ${liquid}`);
       console.log(`Pouring the smoothie into a glass`);
   }

   // After
   function chooseFruit(fruit: string): void {
       console.log(`Choosing ${fruit}`);
   }

   function blend(fruit: string, liquid: string): void {
       console.log(`Blending ${fruit} with ${liquid}`);
   }

   function pourIntoGlass(): void {
       console.log(`Pouring the smoothie into a glass`);
   }

   function makeSmoothie(fruit: string, liquid: string): void {
       chooseFruit(fruit);
       blend(fruit, liquid);
       pourIntoGlass();
   }
Enter fullscreen mode Exit fullscreen mode

5. DRY (Don’t Repeat Yourself): Avoid duplicating code by creating reusable components.

  • Example: Create a helper function instead of copying and pasting code blocks.
   // Before
   function makeDogSound(): string {
       return "The dog says: Woof!";
   }

   function makeCatSound(): string {
       return "The cat says: Meow!";
   }

    // After
    function makeAnimalSound(animal: string, sound: string): string {
       return `The ${animal} says: ${sound}!`;
    }

    function makeDogSound(): string {
        return makeAnimalSound("dog", "Woof");
    }

    function makeCatSound(): string {
       return makeAnimalSound("cat", "Meow");
    }
Enter fullscreen mode Exit fullscreen mode

6. Commenting: Comment only when necessary and avoid obvious explanations.

  • Example: Instead of // Increase salary by 10%, name your function increaseSalaryByPercentage(10).
   // Before
   function increaseSalary(employee: Employee): void {
       // Increase salary by 10%
       employee.salary *= 1.10;
   }

   // After
   function increaseSalaryByPercentage(employee: Employee, percentage: number): void {
       employee.salary *= (1 + percentage / 100);
   }
Enter fullscreen mode Exit fullscreen mode

7. Error Handling: Handle errors clearly and immediately, with specific messages.

  • Example: If a file can’t be opened, throw an exception with a message about the file path and error.
   // Before
   function readFile(filePath: string): string {
       const file = fs.readFileSync(filePath, 'utf8');
       return file;
   }

   // After
   function readFile(filePath: string): string {
       try {
           return fs.readFileSync(filePath, 'utf8');
       } catch (e) {
           throw new Error(`Failed to open file ${filePath}: ${e.message}`);
       }
   }
Enter fullscreen mode Exit fullscreen mode

8. Clean Testing: Ensure your tests are fast, independent, repeatable, self-validating, and timely.

  • Example: Write unit tests that focus on one aspect of functionality at a time.
   // Test example for a function
   describe('calculateCircleArea', () => {
       it('should correctly calculate the area of a circle', () => {
           expect(calculateCircleArea(5)).toBeCloseTo(78.54);
       });
   });

   describe('calculateCylinderArea', () => {
       it('should correctly calculate the area of a cylinder', () => {
           expect(calculateCylinderArea(5, 10)).toBeCloseTo(471.23);
       });
   });
Enter fullscreen mode Exit fullscreen mode

What are "Best Practices"? 🛠️

"Best Practices" are proven methods and techniques to improve various aspects of software development. Here are six key examples that you should also know:

1. Code Testing: Regularly test your code to catch bugs early.

  • Example: Use tools like Jest for JavaScript or JUnit for Java to automate your tests.

2. Readability: Write code that is easy to read and understand.

  • Example: Use consistent naming conventions and clear indentation.

3. Architecture: Design your software with a clear structure.

  • Example: Apply patterns like MVC (Model-View-Controller) to separate concerns.

4. Design Patterns: Use established patterns to solve common problems.

  • Example: The Singleton pattern ensures a class has only one instance, making it ideal for maintaining a single, efficient database connection throughout the application. I'll leave an example in case you're curious about this pattern.
   import { Client } from 'pg'; // Example with PostgreSQL

   class DatabaseConnection {
       private static instance: DatabaseConnection;
       private client: Client;

       // Private constructor to prevent direct instantiation
       private constructor() {
           this.client = new Client({
               connectionString: process.env.DATABASE_URL,
           });
           this.client.connect();
       }

       // Static method to get the single instance of the class
       public static getInstance(): DatabaseConnection {
           if (!DatabaseConnection.instance) {
               // Create instance if it does not exist
               DatabaseConnection.instance = new DatabaseConnection();
           }
           // Return the single instance if it does exist
           return DatabaseConnection.instance;
       }

       public async query(text: string, params?: any[]): Promise<any> {
           return this.client.query(text, params);
       }
   }

   // Usage example of the singleton database connection
   const db = DatabaseConnection.getInstance(); // Get the single instance of the database connection
   db.query('SELECT * FROM users WHERE id = $1', [1])
       .then(res => console.log(res.rows))
       .catch(err => console.error('Database query error:', err));
Enter fullscreen mode Exit fullscreen mode

5. Version Control Systems (VCS): Manage code changes with tools like Git.

  • Example: Follow ACID principles for commits: Atomicity, Consistency, Isolation, and Durability.

6. DRY and KISS: Apply Don’t Repeat Yourself (DRY) and Keep It Simple and Stupid (KISS) principles.

  • Example: Simplify complex functions and avoid redundant code.

Key Differences 🔍

  • Scope: "Clean Code" focuses on writing clean, maintainable code. In contrast, "Best Practices" encompass a broader range of techniques and strategies for various aspects of software development.
  • Application: "Clean Code" provides specific guidelines for improving code quality, while "Best Practices" offers a more comprehensive approach to enhancing overall project effectiveness.

Conclusion 🎯

"Clean Code" is a crucial part of Best Practices, focusing on creating readable and maintainable code. Best Practices, on the other hand, encompass a wide range of techniques for improving different aspects of software development. Using both can significantly enhance the quality and efficiency of your projects.

What are your favorite best practices for writing clean code and managing projects? Share your tips and experiences below—let’s help each other avoid those "painful headaches" and "WTFs" in our development journeys! 💬

Top comments (0)