Keep expected failure explicit
Nullable values represent absence well when callers need no explanation. A sealed outcome can represent distinct expected errors. Kotlin's Result<T> represents success or a captured throwable, but domain validation does not need to manufacture exceptions.
sealed interface BudgetResult {
data class Valid(val minutes: Int) : BudgetResult
data object InvalidNumber : BudgetResult
data object NotPositive : BudgetResult
}
fun parseBudget(text: String): BudgetResult {
val minutes = text.toIntOrNull() ?: return BudgetResult.InvalidNumber
return if (minutes > 0) BudgetResult.Valid(minutes) else BudgetResult.NotPositive
}
Each caller must consider the possible outcomes. This avoids using one null result for several unrelated failures. Do not add elaborate wrappers when a nullable lookup adequately communicates “not found.”
Exercise
Write a renderer with an exhaustive when. Test "25", "oops", and "0", then add a maximum-budget error and update all consumers.
Check: distinguish an expected validation error from a programming defect such as indexing outside a collection.