Keep things simple
The Power of Simplicity
Measuring complexity
LoC ("lines of code") are not the number of lines in your Kotlin module, or the number of lines in a file — even though some automatic software tool might try to tell you so.
When counting lines of code in the scope of measuring how complex a certain function, apply these principles:
Complexity is measured in lines of code that require mental processing.
Count a line if it adds cognitive load (something a developer has to think about when reading the code), for example:
- Actual executable statements
- Logic that influences flow (e.g., conditions, loops, function calls)
Ignore these when counting complexity LOC:
- Empty lines: Used for readability, not logic.
- Comments: don’t add execution complexity.
- Closing brackets when they don’t add complexity
- Opening Braces if used for Structure: If a function or class declaration has { on a new line, it’s just formatting.
- Import Statements: They are necessary but don’t require processing once written by a developer reading code.
- Package Declarations: Present in every file — don’t contribute to logic.
- Annotations: Metadata for compiler/frameworks, but don’t affect code logic and thus complexity directly.
- Logging Statements (
log.debug(...)): Logging is important but doesn’t contribute to program logic. - Redundant statements (which should be removed at the next possibility either way), e.g.:
- Explicit return Statements in Single-Expression Functions: If a function can be expressed as a single expression, the explicit return is redundant (and thus does not directly impact code complexity).
For example:
// 13 physical lines
package my.project
import java.util.*
val x = y.f()
// Check if x abc
if (x.abc) {
return false
}
// continue if otherwise
doThing(x)
// meaningful LOC: 3
val x = y.f()
if (x.abc) return false
doThing(x)
Examples of common pitfalls
Keep it simple and cognitive load minimal
// Simple ✅
fun isAdult(age: Int) = age >= 18
// Complex ❌
class AgeValidator {
companion object {
private const val ADULT_AGE = 18
fun validateAdultAge(ageToValidate: Int): Boolean {
return performAgeCheck(ageToValidate)
}
private fun performAgeCheck(age: Int) = age >= ADULT_AGE
}
}
Over-engineering (most common pitfall possibly?)
// Simple ✅
fun getActiveUsers() = users.filter { it.isActive }
// Over-engineered ❌
class UserFilterStrategy {
interface FilterCriteria {
fun matches(user: User): Boolean
}
class ActiveUserFilter : FilterCriteria {
override fun matches(user: User) = user.isActive
}
fun filterUsers(users: List<User>, criteria: FilterCriteria) =
users.filter { criteria.matches(it) }
}
Premature optimization (that's usually not actually faster)
// Simple ✅
val userNames = users.map { it.name }
// Premature optimization ❌
val userNames = ArrayList<String>(users.size).apply {
users.forEach { user ->
add(user.name)
}
}
