Learning Notes: Clean Code

Posted on Jun 29, 2025

“Reading a clean code should make you smile the way a well-crafted music box or well-designed car would.”

Most engineers in the early days of their careers have possibly read or heard about the book: Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin [1]. As one of those aforementioned engineers; I recently read the book, and am sharing my notes to have references for myself in the future.

I’ll iterate over takeaways and add my thoughts where relevant.

Introduction

  1. Manager expectations: “If I don’t do what my manager says, I’ll be fired” → Probably not. Managers defend schedules; it’s your job to defend code with equal passion. Think doctor-patient: patient says “doctor, hurry - don’t wash your hands, that takes too much time.” Not gonna happen.

Especially inexperienced people might struggle saying no to managers. This analogy perfectly summarizes the dynamic.

  1. Clean code experience: Reading clean code should make you smile like a well-crafted music box or well-designed car.

This pretty much summarizes what clean code does. While you’re reading the code, it should provide what you’d expect to see.

Naming

  1. Naming conventions: Classes/objects should have noun/noun phrase names. Avoid verbs.
    • Good: Customer, WikiPage, Account, AddressParser
    • Bad: Verify
  2. It should not be ambigious, it should perfectly describe what it does, and there should be nothing hidden.
  3. Choose appropriate abstraction level names
  4. Long names for long scopes
  5. Name side effects: create_or_return_oos() vs get_oos()
    # ❌ bad: function name does not describe what it actually does
    def get_oos(id: str) -> OOS:
        oos = db.get_oos(id)
        if not oos:
            return OOS()
        return oos
    

Functions

  1. Functions should do one thing well, and only that one thing.
# ❌ bad: does multiple things
def process_order(order):
    validate(order)
    charge_payment(order)  # side effect
    send_receipt(order)    # side effect
    return True

# ✅ good: single responsibility
def complete_order(order):
    if validate(order):
        charge_payment(order)
        send_receipt(order)
  1. Stepdown rule: One level of abstraction per function

  2. Use Polymorphism over conditionals. Combine switch with Abstract Factory [2].

    # ❌ bad
    if animal.type == "DOG":
        animal.bark()
    elif animal.type == "CAT":
        animal.meow()
    
    # ✅ good
    class Animal:
        def speak(self): pass
    
    class Dog(Animal):
        def speak(self): print("Woof!")
    
    class Cat(Animal):
        def speak(self): print("Meow!")
    
    animal.speak()
    
  3. Fewer functions are better

  4. Avoid flag arguments - use separate functions instead:

    # ❌ bad
    def create_user(name, is_admin): ...
    
    # ✅ good
    def create_admin(name): ...
    def create_customer(name): ...
    
  5. Function naming: Use verb-names (verify(user), calculate_total())

  6. No side effects: Function should only do what it says (overlapping with the Naming chapter)

    # ❌ bad: modifies external state
    def check_password(input_password):
        if input_password == stored_password:
            login_attempts = 0  # side effect!
            return True
        return False
    
    # ✅ good: pure function 
    def verify_password(input_password, correct_password):
        return input_password == correct_password
    
  7. Functions should either do something or answer something, not both.

Comments

  1. “Don’t comment bad code — rewrite it.” - Brian W. Kernighan and P.J. Plaugher

    # ❌ bad: comment explaining confusing code
    # check if user is active and over 18
    if u['a'] and u['age'] > 18: ...
    
    # ✅ good: self-explanatory
    if user.is_active and user.age > 18: ...
    
  2. Comments are failures - they compensate for failure to express ourselves in code

    • Don’t use comments when you can use a function/variable
    • Never comment out code - others won’t delete it
    • Use abstract classes over concrete ones to hide unnecessary details
  3. Law of Demeter (minimize object navigation):

    # ❌ bad: violates Demeter
    report.get_user().get_address().format()
    
    # ✅ good: direct access
    report.format_user_address()
    

Error handling

  1. Use exceptions over return codes:

    # ❌ bad
    if save_file() == ERROR_CODE: ...
    
    # ✅ good
    try:
        save_file()
    except IOError as e:
        handle_error(e)
    
  2. Don’t return null - throw error or return special case object:

    # ❌ bad
    def find_user(user_id):
        return users.get(user_id)
    
    # ✅ good
    def find_user(user_id):
        return users.get(user_id, GuestUser())
    
  3. Don’t pass null - use assertions

Boundaries

  1. Good software accommodates change without heavy rework
  2. Use adapters for third-party services:
    # ✅ good: adapter pattern
    class PaymentAdapter:
        def process_payment(self, amount):
            PayPalSDK.charge(amount)
    

Unit tests

Note: Nowadays, this part is especially useful, as we are accelerating towards AI agents and we need strong verification mechanisms to keep the AI on leash. Tests help you guide AI towards your customized “correct"s.

  1. Three laws of TDD:

    1. Don’t write production code until you’ve written a failing unit test
    2. Don’t write more test than needed to fail (compilation failure counts)
    3. Don’t write more production code than needed to pass the current failing test
  2. Testing philosophies:

    • One assert per test
    • Single concept per test
  3. F.I.R.S.T principles:

    • Fast: Milliseconds execution
    • Independent: No test dependencies
    • Repeatable: Same results everywhere
    • Self-Validating: Automatic pass/fail
    • Timely: Write tests first

Classes

  1. Use encapsulation (private variables/utilities)

  2. Classes should be small (measure by responsibilities)

  3. Single Responsibility Principle:

    • A class should have only one reason to change
    • Organize like toolbox drawers vs junk drawers
    # ❌ bad: mixed concerns
    class User:
        def save(self): ...
        def log_activity(self): ...
    
    # ✅ good: separated concerns
    class UserSaver:
        def save(self, user): ...
    
    class ActivityLogger:
        def log(self, event): ...
    
  4. Cohesion:

    • Few instance variables
    • High cohesion when all methods use all variables
    # ✅ good: methods use all variables
    class Rectangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
    
        def area(self):
            return self.width * self.height
    
        def perimeter(self):
            return 2 * (self.width + self.height)
    

Systems

  1. Dependency Injection: Apply Inversion of Control (IoC) to dependencies

    # ❌ bad: internal dependency creation
    class ReportService:
        def __init__(self):
            self.db = Database()
    
    # ✅ good: passed dependency
    class ReportService:
        def __init__(self, database):
            self.db = database
    
  2. Objects shouldn’t instantiate dependencies - delegate to authoritative mechanism

  3. Use the simplest solution that works

This is indeed really important, because sometimes a person has all the context regarding a specific task. However, the next person might not have enough context, and in case the first person leaves, the whole thing suddenly becomes a burden, and bottleneck.

Emergence

  1. Kent Beck’s Simple Design Rules:
    1. Passes all tests
    2. No duplication
    3. Expresses programmer intent
    4. Minimal classes/methods

Concurrency

  1. “Objects are processing abstractions. Threads are schedule abstractions.” - James O. Coplien
  2. Concurrency issues appear under stress
  3. Decoupling strategy: separates “what” from “when”
  4. Execution models:
    • Producer-Consumer: Queue-based resource management
    • Readers-Writers: Throughput vs. stale information tradeoffs
    • Dining Philosophers: Resource contention problem (threads = philosophers)
  5. Avoid race conditions: Don’t use multiple methods on shared objects
    # ❌ bad: unsafe shared access
    counter = 0
    def increment():
        global counter
        counter += 1  # race condition!
    
    # ✅ good: thread-safe with lock
    from threading import Lock
    lock = Lock()
    counter = 0
    def safe_increment():
        global counter
        with lock:  # exclusive access
            counter += 1
    

Smells and Heuristics

Comments

  1. Inappropriate info: Comments should be technical notes only
  2. Obsolete comments: Update or delete immediately
  3. Redundant comments: Remove unnecessary explanations
  4. Poorly written: Fix or remove unclear comments
  5. Commented-out code: Delete fearlessly (VCS remembers)

Environment

  1. Multi-step builds: Building should be single trivial operation
  2. Multi-step tests: Run all unit tests with one command

Functions

  1. Too many arguments: Ideal = zero, >3 requires justification
  2. Output arguments: Change state of owning object instead
  3. Flag arguments: Indicate multi-purpose functions - eliminate
  4. Dead functions: Delete unused code

Summary

  1. Boundary conditions: Test all edge cases explicitly

  2. DRY principle: Duplication = missed abstraction opportunity

  3. Vertical separation: Declare variables near usage site

  4. Principle of Least Surprise: Code where readers expect it

  5. Explicit dependencies: No hidden assumptions between modules

  6. Encapsulate conditionals:

    # ❌ bad
    if user.is_authenticated and user.has_premium:
    
    # ✅ good
    if user_has_premium_access(user):
    
  7. Avoid negative conditionals:

    # ❌ bad
    if not user.is_not_verified: ...
    
    # ✅ good
    if user.is_verified: ...
    
  8. Function abstraction levels: Descend only one level

  9. High-level configuration: Keep config data at top

  10. Law of Demeter (void transitive navigation): Do not use chain executions such as .get_this().get_that()

References

[1] Martin, R. C. (2008). Clean Code : a handbook of agile software craftsmanship. http://ci.nii.ac.jp/ncid/BA87924669

[2] Refactoring Guru. (n.d.). Abstract Factory. Refactoring.guru. https://refactoring.guru/design-patterns/abstract-factory