kotlinbeginner

Preconditions — require, check, and error

Validate inputs and state with Kotlin preconditions: require, check, error, and custom assertions.

kotlin
class BankAccount private constructor(
    val accountNumber: String,
    val owner: String,
    private var _balance: Double
) {
    val balance: Double get() = _balance

    companion object {
        fun create(accountNumber: String, owner: String, initialBalance: Double = 0.0): BankAccount {
            // require — validates function arguments (throws IllegalArgumentException)
            require(accountNumber.isNotBlank()) { "Account number cannot be blank" }
            require(accountNumber.length == 10) { "Account number must be 10 chars, got ${accountNumber.length}" }
            require(owner.isNotBlank()) { "Owner name required" }
            require(initialBalance >= 0) { "Initial balance cannot be negative: $initialBalance" }

            return BankAccount(accountNumber, owner, initialBalance)
        }
    }

    fun deposit(amount: Double) {
        require(amount > 0) { "Deposit amount must be positive: $amount" }
        _balance += amount
    }

    fun withdraw(amount: Double) {
        require(amount > 0) { "Withdrawal amount must be positive: $amount" }
        // check — validates object state (throws IllegalStateException)
        check(_balance >= amount) { "Insufficient funds: balance=$_balance, requested=$amount" }
        _balance -= amount
    }

    fun transfer(to: BankAccount, amount: Double) {
        require(amount > 0) { "Transfer amount must be positive" }
        check(this !== to) { "Cannot transfer to same account" }
        check(_balance >= amount) { "Insufficient funds for transfer" }
        _balance -= amount
        to._balance += amount
    }

    override fun toString() = "$owner ($accountNumber): \$${"%,.2f".format(_balance)}"
}

// Custom assertion helpers
fun requireEmail(email: String): String {
    require(email.contains("@")) { "Invalid email format: $email" }
    require(email.length <= 255) { "Email too long: ${email.length} chars" }
    return email
}

fun <T> requireNotEmpty(collection: Collection<T>, name: String = "collection"): Collection<T> {
    require(collection.isNotEmpty()) { "$name must not be empty" }
    return collection
}

// checkNotNull — runtime null check (throws IllegalStateException)
fun processConfig() {
    val host: String? = System.getenv("DB_HOST")
    // val dbHost = checkNotNull(host) { "DB_HOST environment variable must be set" }
    // use dbHost...
}

// error() — always throws IllegalStateException
fun statusMessage(code: Int): String = when (code) {
    200 -> "OK"
    404 -> "Not Found"
    500 -> "Server Error"
    else -> error("Unknown status code: $code")
}

fun main() {
    // Normal flow
    val account = BankAccount.create("1234567890", "Alice", 1000.0)
    println(account)

    account.deposit(500.0)
    println("After deposit: $account")

    account.withdraw(200.0)
    println("After withdraw: $account")

    // Validation failures
    println("\n--- Validation failures ---")

    listOf(
        { BankAccount.create("", "Test") },
        { BankAccount.create("123", "Test") },
        { BankAccount.create("1234567890", "Test", -100.0) },
        { account.withdraw(99999.0) },
        { statusMessage(999) }
    ).forEach { action ->
        try {
            action()
        } catch (e: IllegalArgumentException) {
            println("Argument error: ${e.message}")
        } catch (e: IllegalStateException) {
            println("State error: ${e.message}")
        }
    }

    // requireNotNull for cascading
    val name: String? = "Alice"
    val validName = requireNotNull(name) { "Name must not be null" }
    println("\nValid name: $validName (length: ${validName.length})")
}

Use Cases

  • Input validation at function boundaries
  • State machine invariant checks
  • Defensive programming for public APIs

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.