Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
59: Static Analysis with Detekt
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:
- Reduce Parameter List: The
updateProfilefunction has too many arguments. Refactor the user details into a data class (e.g.,ProfileDetails). - Flatten the Nesting: Use guard clauses (early returns) in
updateProfileto remove the deepifnesting. - Eliminate Magic Numbers: In
calculateLoyaltyPoints, replace the hardcoded values (5, 1000.0, 0.15, etc.) with named constants (const val) that describe their purpose.
There are no comments for now.