| name | defensive-programming-kotlin |
| description | Tiger Style defensive programming principles for Kotlin. Covers assertions, readability, error handling, and exhaustiveness checks. Use when writing or reviewing Kotlin code, adding assertions, handling errors, or when the user mentions "defensive programming", "tiger style", "assertions", "exhaustive when", "sealed class", or needs a code review.
|
Defensive Programming — Kotlin
Inspired by Tiger Style.
Assertions
Assert preconditions, postconditions, and invariants at every boundary.
Minimum 2 assertions per function.
Standard library assertions
fun processOrder(order: Order) {
require(order.items.isNotEmpty()) { "order must have items" }
require(order.total > 0.0) { "order total must be positive" }
check(state == State.Processed) { "order must be in processed state" }
}
Pair assertions
For every property, assert before writing and again after reading from a boundary.
fun persist(data: ByteArray) {
check(data.isNotEmpty()) { "cannot persist empty data" }
writeToDisk(data)
}
fun load(): ByteArray {
val data = readFromDisk()
check(data.isNotEmpty()) { "corrupt data on disk" }
return data
}
Split compound assertions
requireNotNull(user)
require(user.id > 0)
require(user != null && user.id > 0)
Function boundary assertions
fun calculateTotal(items: List<Item>): BigDecimal {
require(items.isNotEmpty()) { "expected at least one item" }
val total = items.fold(BigDecimal.ZERO) { sum, item ->
require(item.price > BigDecimal.ZERO) { "item price must be positive" }
sum + item.price
}
check(total > BigDecimal.ZERO) { "total must be positive" }
return total
}
Readability
70-line function limit
Every function must fit on one screen. Push control flow up, push computation down.
camelCase (language convention)
Kotlin uses camelCase, not snake_case. Follow the language.
fun parseUserInput(raw: String): Result { ... }
fun parse_user_input(raw: String): Result { ... }
Variable names with units
val timeoutMs = 5000L
val maxRetries = 3
val latencyMsMax = 1000L
val timeout = 5000L
val max = 3
val latencyMax = 1000L
State invariants positively
if (index < length) {
} else {
}
if (index >= length) {
} else {
}
Split compound conditions
if (user.isActive) {
if (user.hasPermission("write")) {
}
}
if (user.isActive && user.hasPermission("write")) {
}
Smallest scope for variables
fun process(order: Order) {
run {
val items = order.fetchItems()
check(items.isNotEmpty())
}
order.submit()
}
Error Handling
All errors must be handled. 92% of catastrophic production failures
come from unhandled non-fatal errors.
Sealed Result types over exceptions
sealed class Result<out T> {
data class Success<T>(val value: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
fun parseJson(raw: String): Result<JsonElement> {
return try {
Result.Success(JsonParser.parseString(raw))
} catch (e: JsonSyntaxException) {
Result.Error(e)
}
}
Crash on programmer errors, handle operational errors
fun readConfig(path: String): Config {
val raw = File(path).readText()
val parsed = Json.decodeFromString<Config>(raw)
check(parsed.host != null) { "config missing host" }
return parsed
}
runCatching with explicit handling
val result = runCatching { riskyOperation() }
result
.onSuccess { value -> println(value) }
.onFailure { error -> logger.error("operation failed", error) }
Exhaustiveness Checks
sealed class + when
sealed class Shape {
data class Circle(val radius: Double) : Shape()
data class Rect(val width: Double, val height: Double) : Shape()
data class Triangle(val base: Double, val height: Double) : Shape()
}
fun area(shape: Shape): Double = when (shape) {
is Shape.Circle -> Math.PI * shape.radius * shape.radius
is Shape.Rect -> shape.width * shape.height
is Shape.Triangle -> shape.base * shape.height / 2.0
}
When with exhaustive boolean chains
fun describe(x: Boolean?, y: Boolean?): String = when {
x == true && y == true -> "both true"
x == true && y != true -> "only x"
x != true && y == true -> "only y"
x != true && y != true -> "neither"
}
From Tiger Style:
"The rules act like the seat-belt in your car: initially they are perhaps a little uncomfortable, but after a while their use becomes second-nature and not using them becomes unimaginable."