Skip to main content
Programming

Clean Code Principles Explained

Learn to write self-documenting code using DRY, SOLID, and KISS principles.

G

gs_admin

Author & Reviewer

Published

May 05, 2026

Read Time

15 min read

main.go
💻
Programming

# Clean Code Principles: Writing Maintainable, Self-Documenting Software

SEO Meta Description

Learn how to write clean, maintainable, self-documenting code. Deep dive into naming conventions, function design, SOLID design principles, DRY, and KISS, with clear bad-to-good refactoring examples.

---

Introduction

Writing code is easy. Writing code that can be read, understood, and modified by another developer (or yourself six months from now) is hard. Software engineering is a team sport, and code is read far more often than it is written.

Over time, codebases can accumulate "technical debt." Features take longer to build, bugs reappear, and developer velocity drops. The antidote is writing Clean Code from day one.

In this guide, we will study the foundational principles of clean programming. We will dissect SOLID design principles, discuss naming conventions, review function sizes, and refactor a complex, messy code module into clean, self-documenting software.

---

Table of Contents

  1. 1. The High Cost of Bad Code
  1. 2. Meaningful Naming Conventions
  1. 3. Function Design: Keep it Small and Pure
  1. 4. The Single Responsibility Principle (SRP)
  1. 5. Open/Closed Principle (OCP)
  1. 6. Liskov Substitution Principle (LSP)
  1. 7. Interface Segregation Principle (ISP)
  1. 8. Dependency Inversion Principle (DIP)
  1. 9. The DRY and KISS Guidelines
  1. 10. Step-by-Step Refactoring Case Study
  1. 11. Common Mistakes and Clean Code Violations
  1. 12. Performance Trade-offs in Clean Architectures
  1. 13. Frequently Asked Questions (FAQs)
  1. 14. Key Takeaways
  1. 15. Related Resources

---

The High Cost of Bad Code

Bad code is sometimes written to ship features faster. However, this is a trap. In a messy codebase, adding a new feature requires reading hundreds of lines of confusing, tangled code. This slowing down is known as the Technical Debt Curve:

Clean code is not about formatting preferences or aesthetic layouts. It is about reducing the cognitive load required to read, understand, and modify software.

---

Meaningful Naming Conventions

Variable, function, and class names should tell you exactly *why* they exist, *what* they do, and *how* they are used. If a name requires a comment to explain it, the name is not clean.

Bad Naming Conventions

php
1234
// What do these mean?
$d = 45; // elapsed time in days
$list1 = []; // array of user items
function get_u($id) { ... }

Good Naming Conventions

php
123
$elapsedTimeInDays = 45;
$registeredUsers = [];
function getUserById(int $userId): ?User { ... }

Clean Naming Guidelines

  • Use Pronounceable, Searchable Names: Avoid abbreviations like $authUsrLst when you can use $authenticatedUserList.
  • Class Names: Should be nouns or noun phrases (e.g., User, PaymentProcessor). Avoid generic suffixes like Helper or Manager when you can be more specific.
  • Method Names: Should be verbs or verb phrases (e.g., saveUser, calculateTotal).
  • Boolean Names: Should ask clear questions (e.g., isEmailVerified, hasActiveSubscription).

---

Function Design: Keep it Small and Pure

Functions should do one thing, do it well, and do it only.

1. Small Size

Functions should rarely exceed 20 lines of code. If a function is too long, it is likely doing multiple things and should be split into smaller, helper functions.

2. Single Level of Abstraction

Do not mix high-level business rules with low-level details (e.g., string concatenation or database connection queries) in the same function.

3. Fewer Arguments

The ideal number of arguments for a function is zero (niladic), followed by one (monadic), and two (dyadic). Avoid passing three or more parameters. If a function requires many inputs, encapsulate them in a parameters object:
php
123456789101112131415
// Bad: Hard to read, ordering is error-prone
function registerUser($username, $email, $password, $role, $firstName, $lastName) { ... }

// Good: Clean and readable
class UserRegistrationData {
    public function __construct(
        public string $username,
        public string $email,
        public string $password,
        public string $role = 'user',
        public ?string $firstName = null,
        public ?string $lastName = null
    ) {}
}
function registerUser(UserRegistrationData $data) { ... }

---

The Single Responsibility Principle (SRP)

> *A class should have one, and only one, reason to change.*

When a class is responsible for multiple tasks, it becomes tightly coupled. Changes to one feature can inadvertently break another.

Bad Example (Violates SRP)

php
123456789101112131415161718
class User {
    public function __construct(public string $email) {}

    // Responsibility 1: User Properties
    public function getEmail(): string { return $this->email; }

    // Responsibility 2: Database Operations
    public function saveToDatabase() {
        $db = new PDO("mysql:host=localhost;dbname=db", "root", "");
        $stmt = $db->prepare("INSERT INTO users...");
        $stmt->execute();
    }

    // Responsibility 3: Mailing
    public function sendWelcomeEmail() {
        mail($this->email, "Welcome", "Thank you!");
    }
}

Good Example (Respects SRP)

php
123456789101112131415161718
// Class 1: Simple Domain Model
class User {
    public function __construct(public string $email) {}
}

// Class 2: Database Operations (Repository)
class UserRepository {
    public function save(User $user): void {
        // DB writing logic
    }
}

// Class 3: Mailing Service
class WelcomeEmailMailer {
    public function send(User $user): void {
        // Mail sending logic
    }
}

---

Open/Closed Principle (OCP)

> *Software entities should be open for extension, but closed for modification.*

You should be able to extend a class's behavior without modifying its existing source code, preventing regression bugs.

Bad Example (Violates OCP)

php
1234567891011121314
class AreaCalculator {
    public function calculate($shapes): float {
        $area = 0;
        foreach ($shapes as $shape) {
            if ($shape instanceof Rectangle) {
                $area += $shape->width * $shape->height;
            } elseif ($shape instanceof Circle) {
                $area += pi() * ($shape->radius ** 2);
            }
            // If we add Triangle, we must modify calculate() logic!
        }
        return $area;
    }
}

Good Example (Respects OCP)

php
12345678910111213141516171819202122232425
interface ShapeInterface {
    public function area(): float;
}

class Rectangle implements ShapeInterface {
    public function __construct(public float $width, public float $height) {}
    public function area(): float { return $this->width * $this->height; }
}

class Circle implements ShapeInterface {
    public function __construct(public float $radius) {}
    public function area(): float { return pi() * ($this->radius ** 2); }
}

class AreaCalculator {
    public function calculate(array $shapes): float {
        $area = 0;
        foreach ($shapes as $shape) {
            if ($shape instanceof ShapeInterface) {
                $area += $shape->area();
            }
        }
        return $area;
    }
}

---

Liskov Substitution Principle (LSP)

> *Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.*

Subclasses must respect the contract defined by the parent class. They should not throw unexpected exceptions or change the meaning of inherited methods.

Bad Example (Violates LSP)

php
123456789
class Bird {
    public function fly() { return "I can fly!"; }
}

class Ostrich extends Bird {
    public function fly() {
        throw new Exception("Ostriches cannot fly!"); // Violates LSP!
    }
}

Good Example (Respects LSP)

php
123456789
class Bird {}

class FlyingBird extends Bird {
    public function fly() { return "I can fly!"; }
}

class Ostrich extends Bird {
    // Ostriches do not inherit fly() behavior
}

---

Interface Segregation Principle (ISP)

> *Clients should not be forced to depend on interfaces they do not use.*

Avoid creating bloated interfaces with methods that implementing classes don't need. Instead, create smaller, focused interfaces.

Bad Example (Violates ISP)

php
12345678910111213
interface WorkerInterface {
    public function work();
    public function code();
    public function design();
}

class GraphicDesigner implements WorkerInterface {
    public function work() { /* design UI */ }
    public function code() {
        return null; // Violates ISP: designers don't code!
    }
    public function design() { /* design vector */ }
}

Good Example (Respects ISP)

php
12345678910111213141516
interface Workable {
    public function work();
}

interface Codable {
    public function code();
}

interface Designable {
    public function design();
}

class SoftwareEngineer implements Workable, Codable {
    public function work() { /* build system */ }
    public function code() { /* write tests */ }
}

---

Dependency Inversion Principle (DIP)

> *Depend upon abstractions, not concretions.*

High-level modules should not depend on low-level modules. Both should depend on interfaces/abstractions.

Bad Example (Violates DIP)

php
123456789101112
class MySQLConnection {
    public function connect() { return "Connecting to MySQL..."; }
}

class PasswordReset {
    private MySQLConnection $db;

    public function __construct() {
        // Tightly coupled to MySQL! Cannot easily switch to MongoDB or Mock classes for tests.
        $this->db = new MySQLConnection();
    }
}

Good Example (Respects DIP)

php
1234567891011121314151617181920
interface DbConnectionInterface {
    public function connect(): string;
}

class MySQLConnection implements DbConnectionInterface {
    public function connect(): string { return "Connecting to MySQL..."; }
}

class MongoConnection implements DbConnectionInterface {
    public function connect(): string { return "Connecting to MongoDB..."; }
}

class PasswordReset {
    private DbConnectionInterface $db;

    // Inject interface dependency (polymorphic)
    public function __construct(DbConnectionInterface $db) {
        $this->db = $db;
    }
}

---

The DRY and KISS Guidelines

DRY (Don't Repeat Yourself)

Every piece of knowledge or business rule must have a single, unambiguous representation within a system. Avoid copy-pasting code blocks. If you need to make changes to a duplicated block, you have to update it in multiple places, which increases the risk of bugs.

KISS (Keep It Simple, Stupid)

Systems work best when they are kept simple. Avoid over-engineering solutions. Do not build abstract factory patterns for features that can be solved with a simple conditional block. Write code that is simple and easy to read.

---

Step-by-Step Refactoring Case Study

Let's refactor a messy, hard-to-read order processing script into clean code.

The Messy Original

messy_order.php
1234567891011121314151617181920212223242526272829303132
function processOrder($orderId) {
    $db = new PDO("mysql:host=localhost;dbname=store", "root", "");
    $stmt = $db->prepare("SELECT * FROM orders WHERE id = ?");
    $stmt->execute([$orderId]);
    $order = $stmt->fetch();

    if ($order) {
        if ($order['status'] == 'Pending') {
            // Update balance
            $stmt2 = $db->prepare("SELECT * FROM users WHERE id = ?");
            $stmt2->execute([$order['user_id']]);
            $user = $stmt2->fetch();

            if ($user['balance'] >= $order['total']) {
                $db->beginTransaction();
                $stmt3 = $db->prepare("UPDATE users SET balance = balance - ? WHERE id = ?");
                $stmt3->execute([$order['total'], $order['user_id']]);

                $stmt4 = $db->prepare("UPDATE orders SET status = 'Completed' WHERE id = ?");
                $stmt4->execute([$orderId]);
                $db->commit();

                // Send email
                mail($user['email'], "Order Completed", "Your order has been completed.");
                return true;
            } else {
                return false;
            }
        }
    }
    return false;
}

The Clean Refactored Design

Let's split this script into small classes, each with a single responsibility, and inject dependencies to make it clean and easy to test:
clean_order.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
namespace App\Services;

interface OrderRepositoryInterface {
    public function findById(int $orderId): ?array;
    public function updateStatus(int $orderId, string $status): bool;
}

interface UserRepositoryInterface {
    public function findById(int $userId): ?array;
    public function deductBalance(int $userId, float $amount): bool;
}

interface MailerInterface {
    public function send(string $to, string $subject, string $body): bool;
}

class OrderProcessor {
    public function __construct(
        private OrderRepositoryInterface $orderRepo,
        private UserRepositoryInterface $userRepo,
        private MailerInterface $mailer
    ) {}

    public function process(int $orderId): bool {
        $order = $this->orderRepo->findById($orderId);
        if (!$order || $order['status'] !== 'Pending') {
            return false;
        }

        $user = $this->userRepo->findById((int) $order['user_id']);
        if (!$user || (float)$user[&#039;balance'] < (float)$order['total']) {
            return false;
        }

        // Deduct balance and update status (repositories handle transactions internally)
        $deducted = $this->userRepo->deductBalance((int)$user[&#039;id'], (float)$order['total']);
        if (!$deducted) {
            return false;
        }

        $this->orderRepo->updateStatus($orderId, &#039;Completed');
        
        $this->mailer->send(
            $user[&#039;email'], 
            "Order Completed", 
            "Your order #{$orderId} was processed successfully."
        );

        return true;
    }
}

---

Common Mistakes and Clean Code Violations

  • Magic Numbers: Hardcoding numbers without explanations.
``php if ($user['status'] == 4) // What is 4? Use constants instead: User::STATUS_ACTIVE `
  • Deep Nesting: Writing deeply nested conditionals (if inside if inside if). Use Guard Clauses to return early instead.
`php // Bad if ($user) { if ($user->isActive()) { // logic } } // Good (Guard Clause) if (!$user || !$user->isActive()) { return; } // logic ``
  • Comment Overuse: Comments should explain *why* code does something, not *what* it does. If your code is hard to read, refactor it to make it self-documenting instead of writing comments.

---

Performance Trade-offs in Clean Architectures

Some developers worry that splitting classes and injecting interfaces introduces memory and latency overhead.
  • The Reality: The runtime cost of class instantiation in PHP 8+ is negligible. The bottleneck is almost always slow database queries or network connections.
  • The Trade-off: The time saved by writing clean, maintainable code far outweighs minor microsecond performance differences. Maintainability is key.

---

Frequently Asked Questions (FAQs)

What is the "boy scout rule" in programming?

"Leave the campground cleaner than you found it." When you edit a legacy file, refactor a small variable name or split a long function. Over time, this keeps the codebase clean.

Should I write tests before refactoring?

Yes. Do not refactor code without a test suite. Without tests, you cannot verify that your changes didn't break existing features.

---

Key Takeaways

  1. 1. Readable First: Write code for humans to read, and computers to compile.
  1. 2. SRP is Key: Give every class and function a single responsibility.
  1. 3. Guard Clauses: Use early returns to simplify conditional checks.
  1. 4. Depend on Abstractions: Use interfaces to keep modules decoupled and easy to test.

---

  • *Clean Code: A Handbook of Agile Software Craftsmanship* by Robert C. Martin (Uncle Bob)
  • *Refactoring: Improving the Design of Existing Code* by Martin Fowler
G

About the Author: gs_admin

A senior technical contributor specializing in architectural designs, software optimization, database structures, and developer education. Passionate about writing clean code and sharing engineering knowledge.