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:
-
They should be small.
-
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:
-
Feature Envy: The
Orderclass is starting to calculate discounts. Does anOrderknow about global pricing policies, or should that be a separate “Policy” or “Calculator”? -
Output Arguments: The
handlefunction still feels like a “God Function” that orchestrates too much. -
Hidden Abstractions: The
0.9and100.0are “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
processlike 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) withprocess, andtotalWithDiscountwith a specificcalculateDiscountedTotal. -
Kotlin Idioms: Using
sumOfreplaces the manualforloop, 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:
-
The Single Responsibility Principle (SRP):
An
Orderclass 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 fundamentalOrderdata class to change it. -
The Problem of Divergent Change:
If the
Orderclass 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
Orderclass grows. -
-
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 ofif/elsestatements. By moving it to aProcessoror aDiscountCalculator, 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?