Code Like a Pro: A Guide to Modern Clean Code

Search for a command to run...

No comments yet. Be the first to comment.
There's a question that most people feel but almost nobody says out loud. Not because it's complicated. Because saying it threatens everything built on top of not saying it. The question is simple. Wh

Public discourse around personal finance in India increasingly relies on lifestyle narratives, absolute numbers, and loosely imported benchmarks. Concepts such as middle class, financial security, high income, or wealthy are often used without refere...

By Ahmad W Khan (interactive at https://ahmadwkhan.com/india-work-landscape) Summary (TL;DR): India’s labour market is vast, diversified, and uneven. Government roles remain tenure‑rich but hard to enter; healthcare and licensed professional practice...

Audience: Intermediate PHP devs (comfortable with OOP, Composer, basic MVC) who are new/rusty with SymfonyOS Assumptions: macOS/Linux primary; Windows notes included (PowerShell + WSL2)Target PHP & Symfony: PHP 8.2+ and Symfony 7.3.x (current stable ...

Money, unlike geography, is invisible.We know where a country starts and ends on a map. But wealth and income? They’re like air currents, everywhere, yet hard to see. Percentiles help make them visible: where do you sit compared to your neighbors, yo...

Hey there, code wrangler. I see you’re not here to just slap together a few lines of code and call it a day. You’re here to craft something timeless, something you can be proud of. You’ve probably wrestled with spaghetti code at 3 AM, cursed at cryptic variable names like data123, and debugged a function called doThing() that does... everything. We’ve all been there.
This guide is your ultimate companion to modern clean code. It’s packed with wisdom and hard-earned lessons. Whether you write Python, PHP, or JavaScript—or are cursed with all three—you’ll find yourself nodding along as we cover:
Writing code that’s as readable as a novel (and not a Kafka one).
Building systems that scale without screaming in pain.
Making your future self and your team say, “Wow, this is beautiful,” instead of, “What in the actual heck happened here?”
Let’s dive in.
Conclusion: From Developer to Craftsman
Clean code isn’t just a luxury—it’s a weapon. It’s the difference between shipping features in hours versus days, between loving your job and secretly planning an exit strategy from a codebase you hate.
Here’s the deal:
Clean code is readable: Imagine coming back to your own code six months later and understanding it immediately.
Clean code is maintainable: Bugs are easier to fix, features easier to add.
Clean code is collaborative: Your teammates won’t mutter curses every time they touch your code.
Why does this matter in 2024? Because today’s software is fast, distributed, and complex. You’re juggling cloud services, APIs, and microservices while dealing with ever-changing requirements. Writing clean code isn’t optional—it’s survival.
Before we dive in, let’s establish the sacred laws of clean code. These are the principles you’ll see echoed throughout this guide:
KISS (Keep It Simple, Stupid): If your code makes you feel like a genius, it’s probably terrible. Simplicity wins.
DRY (Don’t Repeat Yourself): Copy-pasting code is like planting landmines in your project. Consolidate repeated logic.
SRP (Single Responsibility Principle): One function/class, one reason to change. Period.
YAGNI (You Aren’t Gonna Need It): Don’t build features you “might need later.” You won’t. Trust me.
The Boy Scout Rule: Leave the code cleaner than you found it. Always.
"Naming things is hard" isn’t just a meme—it’s gospel. Names are the first thing anyone sees in your code. If they suck, people will assume your logic does too. Don’t be that developer.
Names Should Reveal Intent
Bad:
let n = 2; // Number of users?
Good:
let userCount = 2;
Avoid Noise Words
Bad:
function get_user_data($user) { ... }
Good:
function fetchUser($user) { ... }
Consistent Conventions
Use camelCase for JavaScript and PHP.
Use snake_case for Python.
Python
# Bad: Ambiguous and lazy
def calc(d):
return d * 0.8
# Good: Clear and expressive
def calculate_discount(price):
return price * 0.8
PHP
// Bad: Misleading
function prc($itm) {
return $itm['qty'] * $itm['price'];
}
// Good: Intentional
function calculateTotalPrice(array $item): float {
return $item['quantity'] * $item['price'];
}
JavaScript
// Bad: Obscure
const f = (a, b) => a + b;
// Good: Descriptive
const addTwoNumbers = (num1, num2) => num1 + num2;
A function should be like a well-trained dog: it does one thing and does it well. If your function spans 50 lines, congratulations—you’ve created a monster.
Do One Thing
Keep It Short
Limit Arguments
Python
# Bad: Does too much
def process_order(order, db, email_service):
db.save(order)
email_service.send(order.email, "Order confirmed")
# Good: Each function has one responsibility
def process_order(order):
save_order(order)
send_order_confirmation(order)
def save_order(order):
db.save(order)
def send_order_confirmation(order):
email_service.send(order.email, "Order confirmed")
PHP
// Bad: All-in-one function
function handleOrder($order) {
validateOrder($order);
saveToDatabase($order);
sendConfirmationEmail($order);
}
// Good: Modular and clean
function handleOrder($order) {
validateOrder($order);
saveOrder($order);
sendEmail($order['email']);
}
function saveOrder($order) {
// Save logic
}
JavaScript
// Bad: Kitchen sink function
function handleUser(user) {
validate(user);
saveToDB(user);
sendWelcomeEmail(user);
}
// Good: Each task is isolated
function handleUser(user) {
validate(user);
saveUser(user);
sendWelcome(user.email);
}
"Comments are a failure." That’s a direct quote from Uncle Bob, and he’s not wrong. If you need a comment to explain your code, it probably means your code isn’t clear enough.
Explaining Why (not What)
Bad:
// Add 1 to i
i += 1;
Good:
// Compensating for zero-based index
i += 1;
Clarifying Complex Logic
Example:
# Using binary search for faster lookups
def find_user(users, target):
...
TODOs
Good comments include actionable TODOs:
// TODO: Add error handling for database connection
Let’s get one thing straight—messy code is a crime against humanity (and your future self). Formatting is like good typography: when done right, nobody notices, but when done wrong, it’s all anyone can see.
Readability is King: Cleanly formatted code reads like a book. Poorly formatted code reads like an encrypted message.
Consistency Over Style Wars: Tabs or spaces? Snake_case or camelCase? Doesn’t matter—as long as you’re consistent.
Vertical Spacing:
Group related code and separate blocks with blank lines.
Example:
// Bad: Cramped
function fetchData(){const data=fetch('url');return data;}
// Good: Spaced out for clarity
function fetchData() {
const data = fetch('url');
return data;
}
Horizontal Spacing:
Use spaces around operators, after commas, and between blocks.
Example (Python):
# Bad
total=price*quantity+tax
# Good
total = price * quantity + tax
Line Length:
Keep lines under 80–120 characters for readability.
Split long statements:
// Bad
$longStatement = "This is an unnecessarily long statement that makes your brain hurt while reading it.";
// Good
$longStatement = "This is an unnecessarily long statement " .
"that makes your brain hurt while reading it.";
Indentation:
Python: Use 4 spaces (PEP 8 standard).
PHP/JavaScript: Use 2 or 4 spaces—just be consistent.
Python:
# Bad: Indentation chaos
def process_order(order):
if order.is_valid():
process(order)
# Good: Consistent indentation
def process_order(order):
if order.is_valid():
process(order)
PHP:
// Bad: Inconsistent spacing
function calculatePrice($items){
$total=0;foreach($items as $item){$total+=$item['price'];}return$total;
}
// Good: Proper spacing and indentation
function calculatePrice($items) {
$total = 0;
foreach ($items as $item) {
$total += $item['price'];
}
return $total;
}
JavaScript:
// Bad: Cramped
const add=(a,b)=>{return a+b;}
// Good: Clean and spaced
const add = (a, b) => {
return a + b;
};
Errors are inevitable. How you handle them determines whether your app feels like a Rolls Royce or a rusted tricycle.
Use Exceptions, Not Error Codes
Error codes lead to messy conditionals.
Example:
// Bad: Error codes
function divide(a, b) {
if (b === 0) return -1;
return a / b;
}
// Good: Throw exceptions
function divide(a, b) {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
}
Fail Fast
Detect errors early to avoid cascading failures.
Example:
def process_order(order):
if not order.is_valid:
raise ValueError("Invalid order")
Centralized Error Handling
Centralize error-handling logic to avoid duplication.
Example:
try {
$data = fetchData();
} catch (Exception $e) {
logError($e);
respondWithError("Something went wrong.");
}
Graceful Degradation
Python:
# Bad: Silent failure
try:
value = int(input_data)
except:
pass # Silent failure
# Good: Specific exception handling
try:
value = int(input_data)
except ValueError as e:
log_error(f"Invalid input: {e}")
raise
PHP:
// Bad: Generic catch-all
try {
$db->save($data);
} catch (Exception $e) {
echo "Error!";
}
// Good: Handle specific exceptions
try {
$db->save($data);
} catch (DatabaseException $e) {
logError($e->getMessage());
respondWithError("Database error occurred.");
}
JavaScript:
// Bad: Catch and do nothing
try {
processUser(user);
} catch (e) {}
// Good: Catch and log meaningful error
try {
processUser(user);
} catch (e) {
console.error("Failed to process user:", e.message);
}
If your classes are doing everything, they’re doing nothing well. A clean class is focused, lean, and obeys the Single Responsibility Principle (SRP).
One Responsibility, One Class
A class should have only one reason to change.
Example:
# Bad: Mixing responsibilities
class UserManager:
def validate_user(self, user): ...
def save_user(self, user): ...
def send_email(self, user): ...
# Good: Separate concerns
class UserValidator: ...
class UserRepository: ...
class EmailService: ...
Encapsulation
Keep data private and expose behavior through methods.
Example:
class User {
private $password;
public function setPassword($password) {
$this->password = hash('sha256', $password);
}
public function getPasswordHash() {
return $this->password;
}
}
Favor Composition Over Inheritance
Inheritance creates tight coupling. Prefer composition:
// Composition
class Engine {
start() { console.log("Engine started"); }
}
class Car {
constructor() {
this.engine = new Engine();
}
start() {
this.engine.start();
}
}
Python:
# Bad: Fat class
class OrderProcessor:
def validate_order(self, order): ...
def calculate_shipping(self, order): ...
def save_to_database(self, order): ...
# Good: Lean and focused classes
class OrderValidator: ...
class ShippingCalculator: ...
class OrderRepository: ...
PHP:
// Bad: All-in-one
class PaymentProcessor {
public function validateCard($card) { ... }
public function processPayment($card, $amount) { ... }
}
// Good: Separate responsibilities
class CardValidator: ...
class PaymentGateway: ...
JavaScript:
// Bad: Does too much
class Order {
validate() { ... }
save() { ... }
sendNotification() { ... }
}
// Good: Modular
class OrderValidator { ... }
class OrderRepository { ... }
class NotificationService { ... }
You’ve probably heard this one before: “You can’t write clean code without tests.” It’s not just true—it’s gospel. Without tests, your codebase is a house of cards waiting for a breeze. With tests, you’ve got a fortress. But here’s the catch: bad tests can be worse than no tests at all.
In this chapter, we’ll dive deep into unit tests, integration tests, mocking, and how to write tests that are a joy (or at least not a chore) to run.
Catches Bugs Early: Tests act as an automated safety net.
Enables Refactoring: With tests in place, you can refactor with confidence.
Documents Behavior: Tests show what your code should do.
Future-Proofs Your Code: Keeps regressions from sneaking in.
Flaky Tests: Tests that fail randomly are the bane of your CI pipeline.
Overtesting: Don’t test things that are already tested by the language/framework.
Hard-to-Maintain Tests: Tests should evolve with the codebase, not hold it hostage.
Unit Tests: Test individual functions or classes in isolation.
Integration Tests: Ensure that components work together.
End-to-End Tests: Mimic real-world usage scenarios to test the full stack.
Follow the AAA Pattern
Arrange: Set up your data and environment.
Act: Perform the operation being tested.
Assert: Verify the outcome.
Example (Python):
def test_add_item_to_cart():
# Arrange
cart = ShoppingCart()
item = Item("Laptop", 1500)
# Act
cart.add_item(item)
# Assert
assert len(cart.items) == 1
assert cart.items[0].name == "Laptop"
One Assertion Per Test
Tests should focus on a single outcome.
Example:
public function testCalculateDiscount() {
$result = calculateDiscount(100, 20);
$this->assertEquals(80, $result);
}
Mock External Dependencies
Avoid testing APIs or databases directly in unit tests.
Example:
const fetchData = jest.fn().mockResolvedValue({ data: "mockData" });
test("fetchData returns mock data", async () => {
const result = await fetchData();
expect(result.data).toBe("mockData");
});
Python with Pytest:
import pytest
def calculate_tax(income, tax_rate):
return income * tax_rate
def test_calculate_tax():
assert calculate_tax(1000, 0.2) == 200
PHP with PHPUnit:
use PHPUnit\Framework\TestCase;
class TaxCalculatorTest extends TestCase {
public function testCalculateTax() {
$result = calculateTax(1000, 0.2);
$this->assertEquals(200, $result);
}
}
JavaScript with Jest:
function calculateTax(income, taxRate) {
return income * taxRate;
}
test("calculateTax should return correct tax", () => {
expect(calculateTax(1000, 0.2)).toBe(200);
});
Name Tests Clearly:
Bad: test1()
Good: test_add_item_to_cart_increases_item_count()
Use Factories or Builders:
Test Edge Cases:
Automate Your Tests:
Refactoring is like tidying your room—it’s annoying but necessary. The good news? Once you get started, it’s oddly satisfying. In this chapter, we’ll turn your legacy code nightmares into sleek, maintainable dreams.
Improve Readability: Code that’s easy to read is easier to debug.
Reduce Complexity: Simplify convoluted logic.
Increase Reusability: Extract reusable components.
Code Smells: Repeated code, bloated classes, or convoluted logic.
Before Adding Features: Clean up first to make changes easier.
After Writing Tests: Tests ensure your changes don’t break anything.
Extract Function
Break large functions into smaller, focused ones.
Example (Python):
# Before
def process_order(order):
validate_order(order)
calculate_shipping(order)
save_order(order)
# After
def process_order(order):
validate_order(order)
calculate_shipping(order)
save_order(order)
Extract Class
Move related methods into a new class.
Example (PHP):
// Before
class OrderProcessor {
public function validate() { ... }
public function calculateShipping() { ... }
}
// After
class OrderValidator { ... }
class ShippingCalculator { ... }
Inline Temporary Variables
Remove unnecessary variables to simplify logic.
Example (JavaScript):
// Before
const discount = price * 0.2;
return price - discount;
// After
return price - (price * 0.2);
Refactor Incrementally:
Leverage Tools:
Write Tests First:
Concurrency is where clean code can turn into chaos. But when done right, it’s poetry in motion. This chapter will teach you how to write clean, concurrent code without losing your sanity.
Avoid Shared State:
Use Higher-Level Abstractions:
asyncio, PHP’s ReactPHP, or JavaScript’s Promises.Minimize Locks:
Python (Asyncio):
import asyncio
async def fetch_data(url):
print(f"Fetching {url}")
await asyncio.sleep(1)
return f"Data from {url}"
async def main():
urls = ["url1", "url2", "url3"]
results = await asyncio.gather(*(fetch_data(url) for url in urls))
print(results)
asyncio.run(main())
PHP (ReactPHP):
use React\EventLoop\Factory;
use React\Http\Browser;
$loop = Factory::create();
$client = new Browser($loop);
$client->get('https://example.com')->then(
function (ResponseInterface $response) {
echo $response->getBody();
}
);
$loop->run();
JavaScript (Promises):
const fetchData = async (url) => {
console.log(`Fetching ${url}`);
return new Promise((resolve) => setTimeout(() => resolve(`Data from ${url}`), 1000));
};
const main = async () => {
const urls = ["url1", "url2", "url3"];
const results = await Promise.all(urls.map(fetchData));
console.log(results);
};
main();
Test Under Load:
Avoid Premature Optimization:
Document Everything:
No master craftsman works without tools, and the modern developer’s toolbox is brimming with wonders. Whether you’re linting your code, automating deployments, or debugging a race condition at 2 AM, the right tools can make the difference between success and stress eating your way through a box of donuts.
Automation Saves Time:
Consistency Across Teams:
Early Problem Detection:
Linters analyze your code for stylistic or logical errors. Think of them as the grammar police for your codebase.
Python: Flake8, Pylint
# Install Flake8
pip install flake8
flake8 your_project/
PHP: PHPStan, Psalm
# Install PHPStan
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src
JavaScript: ESLint
# Install ESLint
npm install eslint --save-dev
npx eslint your_project/
Formatters take the emotional baggage out of formatting decisions by enforcing a consistent style.
Python: Black
pip install black
black your_project/
PHP: PHP-CS-Fixer
composer require --dev friendsofphp/php-cs-fixer
php-cs-fixer fix src/
JavaScript: Prettier
npm install prettier --save-dev
npx prettier --write your_project/
These tools go beyond linting to detect deeper issues, like unused code or type mismatches.
Python: mypy
pip install mypy
mypy your_project/
PHP: PHPStan
vendor/bin/phpstan analyse
JavaScript: TypeScript
npm install typescript --save-dev
npx tsc --noEmit
Automate your tests to ensure consistent quality.
Python: pytest, Unittest
pip install pytest
pytest tests/
PHP: PHPUnit
composer require --dev phpunit/phpunit
vendor/bin/phpunit
JavaScript: Jest
npm install jest --save-dev
npx jest
Sometimes you need to roll up your sleeves and dive into the guts of your app.
Python: pdb, debugpy
import pdb
pdb.set_trace()
PHP: Xdebug
pecl install xdebug
JavaScript: Browser DevTools and node inspect
node inspect your_script.js
Continuous Integration and Deployment ensures every commit is tested, linted, and deployable.
GitHub Actions
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: pip install -r requirements.txt
- name: Run Tests
run: pytest
GitLab CI
stages:
- test
test:
script:
- pytest
Pre-Commit Hooks:
Automatically lint and format code before every commit using pre-commit:
pip install pre-commit
pre-commit install
Integrate Tools with IDEs:
If clean code is the what, design patterns are the how. Patterns are tried-and-tested solutions to recurring problems. But here’s the thing: they’re not magic. Misuse them, and you’re building a house of cards.
Ensures a class has only one instance (great for managing global resources like DB connections).
Python:
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
PHP:
class Singleton {
private static $instance;
private function __construct() { }
public static function getInstance() {
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
}
JavaScript:
const Singleton = (function () {
let instance;
return {
getInstance: function () {
if (!instance) {
instance = {};
}
return instance;
},
};
})();
Encapsulate interchangeable behaviors in separate classes.
Python:
class FlyBehavior:
def fly(self):
pass
class FlyWithWings(FlyBehavior):
def fly(self):
return "Flying with wings!"
class FlyNoWay(FlyBehavior):
def fly(self):
return "I can't fly!"
PHP:
interface FlyBehavior {
public function fly();
}
class FlyWithWings implements FlyBehavior {
public function fly() {
return "Flying with wings!";
}
}
JavaScript:
class FlyWithWings {
fly() {
return "Flying with wings!";
}
}
Great for notifying objects when an event occurs.
Python:
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def notify(self):
for observer in self._observers:
observer.update()
PHP:
class Subject {
private $observers = [];
public function attach($observer) {
$this->observers[] = $observer;
}
public function notify() {
foreach ($this->observers as $observer) {
$observer->update();
}
}
}
JavaScript:
class Subject {
constructor() {
this.observers = [];
}
attach(observer) {
this.observers.push(observer);
}
notify() {
this.observers.forEach((observer) => observer.update());
}
}
Don’t overuse them. If your problem doesn’t require a pattern, don’t force it.
Learn the intent behind each pattern and adapt it to your needs.
Separation of Concerns:
Dependency Injection:
Scalable Databases:
Stateless Microservices:
- Frontend: React or Vue
- API Gateway: Handles routing and authentication
- Microservices: Independent services for each domain
- Database Layer: Separate databases per service
- Cache Layer: Redis for faster reads
Clean code isn’t just about following rules—it’s about understanding why those rules exist and knowing when to break them. As you grow as an engineer, your goal isn’t just to write code that works but to write code that others can build on.
Your clean code journey doesn’t end here. It’s a mindset, a discipline, and most importantly, a craft. Keep learning, keep building, and keep coding like a pro.
If you have found this helpful, then feel free to reach out to me at AhmadWKhan.com