Part 4 · 1 chapters · ~8 min
Idioms
Immutability and copy(), read-only versus mutable collections, scope functions and their overuse, require, check and error, sealed result types versus exceptions, runCatching pitfalls with coroutines (catching CancellationException), avoiding !!, coroutine scope ownership, naming, and ktlint and detekt rules.
5
Domain errors as types
code
sealed interface TransferResult {
data class Ok(val transfer: Transfer) : TransferResult
data class InsufficientFunds(val available: Kobo) : TransferResult
data object LimitExceeded : TransferResult
}
suspend fun createTransfer(cmd: CreateTransfer): TransferResult {
require(cmd.amount.value > 0) { "amount must be positive" } // a bug if violated
val account = accounts.lock(cmd.from)
if (account.available < cmd.amount) return TransferResult.InsufficientFunds(account.available)
return TransferResult.Ok(ledger.post(cmd))
}
// pitfall: runCatching also catches CancellationException, breaking structured concurrency
runCatching { fetch() }.onFailure { if (it is CancellationException) throw it }IDIOMATIC KOTLIN
what reviewers look for
swipe the figure sideways, or tap expand for full screen
1/4
immutable by default
Prefer val and read-only collections; update with copy(). Mutable state should be local or carefully owned.
val and copy()local mutation only