Uncle Bob’s Clean Code – Chapter 7: Error Handling.

Uncle Bob’s main thesis here is that error handling is important, but if it obscures the logic, it’s wrong. In the old days of C or early Java, we used return codes (like -1 or NULL) which forced the caller to handle the error immediately, cluttering the “happy path” of the code.

Here are the core principles from this chapter:


1. Use Exceptions Rather Than Return Codes

When you use return codes, the caller must immediately check for them. This leads to deeply nested if statements. By throwing an exception instead, the logic of what the code does is separated from how it handles failure.

The “Dirty” way (Return Codes):

val record = repository.fetch(id)
if (record != null) {
    val status = controller.process(record)
    if (status == SUCCESS) {
        logger.log("Done")
    } else {
        logger.log("Error in processing")
    }
} else {
    logger.log("Record not found")
}

The “Clean” way (Exceptions):

try {
    val record = repository.fetch(id)
    controller.process(record)
    logger.log("Done")
} catch (e: ProcessingException) {
    logger.log(e.message)
}

2. Write Your Try-Catch-Finally Statement First

Uncle Bob suggests that try blocks are like transactions. When you write code that could fail, you should start by defining the scope of that failure. This helps you maintain the state of your program regardless of what happens in the try.

3. Provide Context with Exceptions

Don’t just throw a generic Exception. Create informative error messages and mention the operation that failed and the type of failure. In Kotlin/Java, this means creating Custom Exception Classes that represent your business domain (e.g., DeviceShutDownException instead of RuntimeException).

4. Don’t Return Null (and Don’t Pass Null)

This is Uncle Bob’s most famous “crusade” in this chapter.

  • Returning Null: Forces the caller to add a null check. If they forget, the application crashes.

  • Passing Null: Passing null into methods is even worse. It creates messy defensive code at the start of every function.

The “Clean” Alternative: The Special Case Pattern Instead of returning null, return an empty list or a “Mock/Null” object that represents nothingness but still behaves like the expected object.


Kotlin Corner: The “Null” Paradox

Kotlin actually solves many of Chapter 7’s complaints with Null Safety (?). However, Uncle Bob would still argue that using ?.let { ... } everywhere is just a modern version of the “if != null” clutter. He would still prefer you use the Special Case Pattern or Exceptions to keep the logic flow linear.

Interactive Challenge:

Imagine you have a User class and a UserDirectory. If a user isn’t found, the “Dirty” way is to return null.

How would you implement a “GuestUser” (Special Case Pattern) so that the calling code doesn’t need an if or a ? to show the user’s name?

(Try to write a small snippet in Kotlin, and I’ll show you how it cleans up the calling code!)

The Solution: Special Case Pattern

Instead of returning null, we return an object that represents a “Guest.” This object implements the same interface as a real User, so the rest of the app doesn’t have to care who is logged in.

interface User {
    val name: String
    fun canAccessPremium(): Boolean
}

class RegisteredUser(override val name: String) : User {
    override fun canAccessPremium() = true
}

// THE SPECIAL CASE: This object "stands in" for null
class GuestUser : User {
    override val name: String = "Guest"
    override fun canAccessPremium() = false
}

class UserService(private val directory: UserDirectory) {
    
    fun getActiveUser(email: String): User {
        return try {
            directory.findUserByEmail(email) // Returns a RegisteredUser
        } catch (e: UserNotFoundException) {
            // Chapter 7: Catch the error and return the Special Case
            GuestUser()
        }
    }
}

// THE CALLER (Clean & Linear)
fun welcome(email: String) {
    val user = userService.getActiveUser(email)
    
    // No "if (user != null)" needed!
    // No "user?.name" needed!
    println("Welcome, ${user.name}") 
}

Why this is “Clean”:

  • The Happy Path: Look at the welcome function. It’s one straight line of logic. It doesn’t have to worry about errors or nulls.

  • Encapsulation: The logic for what a “Guest” can do is hidden inside the GuestUser class, not scattered across if statements in your UI.

One final note on Chapter 7: “Don’t Pass Null”

Uncle Bob says: “Returning null is bad, but passing null into methods is worse.” If you find yourself writing if (user == null) return, it’s usually because someone violated the rule of not passing null in the first place.

Loading more content...