Uncle Bob’s Clean Code – Meaningful Names (Chapter 2) and Functions (Chapter 3)

1. Meaningful Names:

Uncle Bob argues that a name should tell you why it exists, what it does, and how it is used.

The Bad (Disinformation/Mental Mapping):

var d: Int = 0 // elapsed time in days
val theList = mutableListOf<IntArray>()

The Clean (Kotlin):

val elapsedTimeInDays: Int
val flaggedCells = mutableListOf<Cell>()

2. Functions (Chapter 3)

The two main rules:

  1. They should be small.

  2. They should do one thing.

He also introduces the Step-down Rule: Your code should read like a top-down narrative. Each function should be followed by those at the next level of abstraction.

Interactive Exercise: The “One Thing” Challenge

Here is a Kotlin method that violates several principles from Chapters 2 and 3. It calculates a total, updates a database, and prints a report.

The “Dirty” Code:

// Chapter 3 Violations: Multiple responsibilities, hidden side effects
fun handle(o: Order) {
    var t = 0.0
    for (i in o.items) {
        t += i.price * i.qty
    }
    
    if (t > 100.0) {
        t *= 0.9 // Hardcoded magic numbers (Ch 17: G2)
    }
    
    // Side effect: Why is a "handle" function touching the DB and Console?
    database.saveTotal(o.id, t)
    println("Order ${o.id} processed for: $t")
}

How would you refactor this?

If we were to apply the Single Responsibility Principle and Small Functions, how would you break this down?

Try to write in Kotlin/Java how you would split this into smaller, well-named pieces. we’ll then see how Uncle Bob would likely structure it to follow the Step-down Rule.

Wrong solution: 

data class Item(val name: String, val price: Double, val qty: Int)
data class Order(val id: String, val items: List<Item>) {
        val total: Double
            get() {
                var sum = 0.0
                for (i in items) {
                    sum += i.price * i.qty
                }
                return sum
            }

    fun totalWithDiscount(): Double {
        var discount = total
        if (discount > 100.0) {
            discount *= 0.9
        }
        return discount
    }
}
data class Database(val name: String) {
    fun saveTotal(orderId: String, total: Double) {
    // Save to DB
    }
}
fun handleOrder(order: Order, database: Database) {
   val amount = order.totalWithDiscount()
    // Side effect: Why is a "handle" function touching the DB and Console?
    database.saveTotal(order.id, amount)
    println("Order ${order.id} processed for: $amount")
}

Right Solution:

We’ve successfully moved the logic into the domain object (Order), which makes the code much more “Tell, Don’t Ask.” However If we put on our “Uncle Bob” glasses and look at Chapters 3 (Functions) and 17 (Smells and Heuristics), there are still a few refinements we can make to reach “Clean Code” perfection.

Remaining “Smells” to Address:

  1. Feature Envy: The Order class is starting to calculate discounts. Does an Order know about global pricing policies, or should that be a separate “Policy” or “Calculator”?

  2. Output Arguments: The handle function still feels like a “God Function” that orchestrates too much.

  3. Hidden Abstractions: The 0.9 and 100.0 are “Magic Numbers.”

The “Uncle Bob” Refactor (Kotlin Style)

Here is how we might polish this to follow the Step-Down Rule and Command Query Separation (CQS):

data class Item(val price: Double, val qty: Int)
data class Order(val id: String, val items: List<Item>)

class OrderProcessor(private val database: Database) {

    // Chapter 3: The function is small and explains the "To" narrative.
    fun process(order: Order) {
        val finalAmount = calculateDiscountedTotal(order)
        saveResults(order.id, finalAmount)
        reportResults(order.id, finalAmount)
    }

    private fun calculateDiscountedTotal(order: Order): Double {
        val rawTotal = order.items.sumOf { it.price * it.qty }
        return applyStandardDiscount(rawTotal)
    }

    private fun applyStandardDiscount(amount: Double): Double {
        val DISCOUNT_THRESHOLD = 100.0
        val DISCOUNT_RATE = 0.9
        
        return if (amount > DISCOUNT_THRESHOLD) amount * DISCOUNT_RATE else amount
    }

    private fun saveResults(orderId: String, amount: Double) {
        database.saveTotal(orderId, amount)
    }

    private fun reportResults(orderId: String, amount: Double) {
        println("Order $orderId processed for: $amount")
    }
}

Why this is “Cleaner”:

  • The Step-Down Rule: You can read process like a table of contents. If you care about how it calculates, you look at the next function. If you don’t, you stop reading.

  • Small Functions: Every function is roughly 1–4 lines long.

  • Descriptive Names: We replaced handle (vague) with process, and totalWithDiscount with a specific calculateDiscountedTotal.

  • Kotlin Idioms: Using sumOf replaces the manual for loop, reducing the surface area for “Off-by-one” errors (Chapter 17: G1).

Why the calculate discount is not the order class took care of?

That is a classic “Clean Code” debate! we’ve hit on a very important distinction between Domain Objects and Business Policies.

In Chapter 10 (Classes) and Chapter 6 (Objects and Data Structures), Uncle Bob discusses the difference between a Data Structure and an Object.

The Argument for keeping it in Order

If we put calculateDiscount in the Order class, we are following Information Expert (a GRASP principle). The Order has the data, so it should have the behavior. This is the “Object-Oriented” way.

The “Clean Code” Argument for moving it out

Uncle Bob often advocates for keeping Data Structures (like Order and Item) simple and “dumb” if they are just transferring data, while moving Business Rules (like Discounts) into dedicated classes. Here is why:

  1. The Single Responsibility Principle (SRP):

    An Order class should represent the state of a purchase (items, id, date). A Discount Policy is a business rule that changes frequently. If the marketing department changes the discount from 10% to 15%, we shouldn’t have to “break open” our fundamental Order data class to change it.

  2. The Problem of Divergent Change:

    If the Order class contains:

    • Logic for calculating totals

    • Logic for applying seasonal discounts

    • Logic for XML/JSON serialization

    • Logic for validation

    Then the class has too many reasons to change. Every time the business changes a rule, the Order class grows.

  3. Context Dependency:

    What if we have different discounts for different types of customers (e.g., VIP vs. Guest)? If that logic is inside Order, the class becomes a mess of if/else statements. By moving it to a Processor or a DiscountCalculator, we can swap policies easily.


How to decide? (The “Hygiene” Test)

Logic Type Where it should go Why?
Intrinsic Logic Order (e.g., sumOf items) It is a fundamental property of the data.
Extrinsic Policy DiscountService or Processor It’s a rule imposed by the business/marketing.

Refined Kotlin Example: The Middle Ground

A “Clean” way to handle this while keeping the syntax nice is to use a Policy Pattern or just a clean separation in the service:

// The Data Structure (Chapter 6: Simple and transparent)
data class Order(val id: String, val items: List<Item>) {
    val rawTotal: Double get() = items.sumOf { it.price * it.qty }
}

// The Policy (Chapter 10: Encapsulating the "Reason to Change")
class DiscountPolicy {
    fun apply(amount: Double): Double {
        return if (amount > 100.0) amount * 0.9 else amount
    }
}

// The Coordinator (Chapter 3: Small, narratively driven functions)
class OrderProcessor(
    private val database: Database,
    private val discountPolicy: DiscountPolicy
) {
    fun process(order: Order) {
        val total = discountPolicy.apply(order.rawTotal)
        database.saveTotal(order.id, total)
    }
}

By doing this, if the discount logic becomes complex (e.g., “Buy one get one free” or “Holiday sales”), we only change the DiscountPolicy class. The Order remains a stable, clean data structure.

Does that distinction between “What the data is” vs. “What the business rules are” make sense in your current project? Or do you prefer the “Rich Domain Model” where the object handles everything?

 

Loading more content...