Skip to Content
Course content

59: Static Analysis with Detekt

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was leading a review for a critical payment module. The code worked perfectly—the tests passed, and the logic was sound. But the PR thread turned into a battlefield. We spent nearly two hours arguing over "nitpicks": one developer hated the nested if statements, another complained that a function was 80 lines long, and I was fighting for consistent naming conventions. It was a waste of everyone's mental energy. We weren't debating architecture; we were debating aesthetics and maintainability, which is exactly the kind of friction that kills a team's velocity.

That's why I started insisting on static analysis. Specifically, for Kotlin, that means Detekt. Unlike the compiler, which tells you if your code can run, Detekt tells you if your code should be written that way. It's essentially an automated code reviewer that doesn't get tired and doesn't have an ego. It scans your source code without executing it, looking for "code smells"—patterns that aren't necessarily bugs but are likely to lead to bugs or make the code a nightmare to maintain six months from now.

Catching the Complexity Creep

One of the most valuable things Detekt does is track "Cyclomatic Complexity." I've seen developers write functions that look like a recursive labyrinth of nested loops and conditionals. While the JVM can handle it, a human brain cannot. Detekt flags these as ComplexMethod or ComplexCondition.

// This might pass the compiler, but Detekt will scream at you
fun processOrder(order: Order) {
    if (order.isValid) {
        if (order.items.isNotEmpty()) {
            for (item in order.items) {
                if (item.isAvailable) {
                    if (item.discount > 0) {
                        // We are four levels deep here. 
                        // Detekt flags this as too complex.
                        applyDiscount(item)
                    }
                }
            }
        }
    }
}

When Detekt flags something like this, it's a signal to stop and refactor. Instead of arguing in a PR, the developer sees the warning in their IDE and realizes they should probably extract those nested checks into smaller, named functions. It moves the conversation from "I don't like this" to "The project standards say this is too complex."

Tuning the detekt.yml Configuration

Now, here is a word of caution: out-of-the-box settings are often too strict or occasionally too lenient for a specific project. If you just apply the defaults, you'll likely find yourself fighting the tool. That's where the detekt.yml file comes in. This is your project's "style law book."

I always recommend spending an hour with your team to calibrate this file. Maybe your team is fine with functions being 30 lines long instead of 20. Or maybe you want to disable MagicNumber warnings for specific mathematical constants. You can toggle rules on and off, or change the thresholds. For example, if you find the LongParameterList rule is too aggressive, you can bump the limit from 6 to 8 parameters. If you don't customize this file, your team will eventually start ignoring the warnings entirely—and once a developer starts ignoring the analyzer, the tool becomes useless.

Plugging into the Build Pipeline

Running Detekt manually is fine, but it's not how you actually ensure quality. You want it tied to your Gradle build. By adding the Detekt plugin to your build.gradle.kts, you can make the build fail if the "debt" exceeds a certain threshold. I personally like to set a buildUponDefaultConfig = true flag so I only have to specify the rules I want to override.

The real magic happens in the CI/CD pipeline. When a developer pushes code, the CI runs ./gradlew detekt. If the code is too messy, the build fails. This forces the cleanup to happen before the code even reaches a human reviewer. By the time I open a PR now, I know the formatting is correct and the complexity is under control, so we can spend our time talking about the actual business logic instead of where the curly braces go.




📋 Practical Task

Refactoring the Legacy UserProfileManager

You have been handed a legacy class called UserProfileManager that is riddled with code smells. Your goal is to clean it up based on typical Detekt rules (Complexity, Long Methods, and Magic Numbers).

The Code:

class UserProfileManager {
    fun updateProfile(user: User, name: String, email: String, age: Int, address: String, phone: String, city: String, zip: String) {
        if (user != null) {
            if (name.isNotEmpty()) {
                if (email.contains("@")) {
                    if (age > 18) {
                        // Update logic here
                        println("Updating user to $name")
                    } else {
                        throw Exception("Too young")
                    }
                }
            }
        }
    }

    fun calculateLoyaltyPoints(years: Int, spends: Double) {
        // Magic numbers alert!
        if (years > 5 && spends > 1000.0) {
            println("Gold Status: " + (spends * 0.15))
        } else if (years > 2 && spends > 500.0) {
            println("Silver Status: " + (spends * 0.05))
        }
    }
}

Your Task:

  1. Reduce Parameter List: The updateProfile function has too many arguments. Refactor the user details into a data class (e.g., ProfileDetails).
  2. Flatten the Nesting: Use guard clauses (early returns) in updateProfile to remove the deep if nesting.
  3. Eliminate Magic Numbers: In calculateLoyaltyPoints, replace the hardcoded values (5, 1000.0, 0.15, etc.) with named constants (const val) that describe their purpose.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.