Kotlin中require与check函数的区别与应用
1. Preconditions.kt中的require与check函数解析在Kotlin标准库的Preconditions.kt文件中require和check是两个常用的参数校验函数。它们看起来功能相似但设计意图和使用场景有本质区别。作为Kotlin开发者理解这两个函数的差异对编写健壮代码至关重要。require主要用于验证函数参数的有效性当条件不满足时抛出IllegalArgumentException。这是防御性编程的重要手段可以避免无效参数进入函数逻辑。而check则用于验证对象状态当条件不满足时抛出IllegalStateException通常用于确保对象在特定操作前处于合法状态。2. require函数深度剖析2.1 基本用法与实现原理require函数的签名如下public inline fun require(value: Boolean, lazyMessage: () - Any): Unit { if (!value) { val message lazyMessage() throw IllegalArgumentException(message.toString()) } }典型使用场景是验证函数参数fun calculateArea(width: Double, height: Double): Double { require(width 0) { 宽度必须大于0 } require(height 0) { 高度必须大于0 } return width * height }关键细节require使用内联函数lazy message的设计避免了不必要的字符串拼接开销。只有当条件不满足时才会执行消息构造逻辑。2.2 最佳实践与性能考量消息构造优化对于复杂错误消息应该使用lambda表达式延迟构造// 不推荐 - 无论是否抛出异常都会构造字符串 require(condition, 复杂消息${expensiveOperation()}) // 推荐 - 仅在抛出异常时构造消息 require(condition) { 复杂消息${expensiveOperation()} }多条件校验顺序应该按照参数校验成本从低到高排序fun process(data: ListData?, threshold: Int) { require(threshold 0) { 阈值不能为负 } // 简单检查 require(data ! null) { 数据不能为空 } // 中等复杂度 require(data.all { it.isValid() }) { 包含无效数据 } // 高成本检查 }与null检查的配合在Kotlin中通常使用非空类型声明但需要时可以与require结合fun save(user: User?) { require(user ! null) { 用户不能为空 } // 后续user自动智能转换为非空类型 }3. check函数详解3.1 设计目的与典型场景check函数的签名与require几乎相同但抛出的是IllegalStateExceptionpublic inline fun check(value: Boolean, lazyMessage: () - Any): Unit { if (!value) { val message lazyMessage() throw IllegalStateException(message.toString()) } }它主要用于验证对象状态class Connection { private var isOpen false fun send(data: ByteArray) { check(isOpen) { 连接未建立 } // 发送逻辑 } fun open() { isOpen true } }3.2 状态验证模式前置条件验证在关键操作前验证状态fun execute() { check(!isCancelled) { 任务已取消 } // 执行逻辑 }不变式维护确保对象始终满足某些条件class BankAccount { private var balance: Double 0.0 fun withdraw(amount: Double) { check(amount 0) { 取款金额必须为正数 } check(amount balance) { 余额不足 } balance - amount check(balance 0) { 余额不能为负 } // 不变式检查 } }线程安全验证在并发环境中验证状态Volatile private var initialized false fun init() { synchronized(this) { check(!initialized) { 重复初始化 } // 初始化逻辑 initialized true } }4. require与check的对比分析4.1 异常类型差异函数抛出异常类型语义含义requireIllegalArgumentException参数不符合要求checkIllegalStateException对象状态不合法4.2 使用场景对照require适用场景验证函数参数的有效范围确保输入数据满足前置条件公共API的入口参数校验check适用场景验证对象内部状态一致性确保操作执行时的环境条件维护类的不变式(invariant)4.3 性能影响对比两者在性能开销上几乎相同都是内联函数。但误用可能导致额外开销// 反模式 - 在循环内部进行冗余检查 fun processItems(items: ListItem) { items.forEach { item - require(item.isValid()) // 每次循环都检查 // 处理逻辑 } } // 优化方案 - 提前批量检查 fun processItems(items: ListItem) { require(items.all { it.isValid() }) items.forEach { item - // 处理逻辑 } }5. 高级应用与常见问题5.1 自定义扩展函数可以基于require/check创建领域特定的验证函数// 邮箱格式验证扩展 fun requireEmail(email: String) require( email.matches(Regex(^\\S\\S\\.\\S$)) ) { 无效邮箱格式: $email } // 状态机验证扩展 fun T T.checkState(predicate: T.() - Boolean, message: () - String) { check(predicate(), message) }5.2 与Kotlin契约的结合Kotlin 1.3引入的契约系统可以与这些函数配合ExperimentalContracts fun validate(value: Int) { contract { returns() implies (value 0) } require(value 0) } // 编译器能推断出后续value 0为true fun test(value: Int) { validate(value) println(value 1) // 安全操作 }5.3 常见陷阱与解决方案过度使用问题// 不必要检查 - Kotlin类型系统已经保证非空 fun length(s: String) require(s ! null).let { s.length } // 正确方式 - 直接使用 fun length(s: String) s.length错误消息质量问题// 不良实践 - 无助于调试 check(state, { 状态错误 }) // 良好实践 - 包含诊断信息 check(state) { 状态错误当前值$currentValue期望$expectedValue }测试策略 应该为require/check条件编写专门的测试用例Test fun should throw when negative width() { assertThrowsIllegalArgumentException { calculateArea(-1.0, 10.0) } } Test fun should throw when connection not open() { val conn Connection() assertThrowsIllegalStateException { conn.send(byteArrayOf()) } }6. 工程实践建议代码审查要点检查所有公共API是否对关键参数使用require验证状态变更方法是否包含必要的check确保错误消息包含足够调试信息静态分析配置 配置Detekt等静态分析工具检查style: MandatoryBracesIfStatements: active: true PreferCheckOverRequire: active: true allowedPattern: .*Spec$性能敏感场景处理 对于性能关键路径可以使用特殊构建变体inline fun T T.requireFast( condition: T.() - Boolean, lazyMessage: T.() - Any ) { if (BuildConfig.DEBUG !condition()) { throw IllegalArgumentException(lazyMessage().toString()) } }与日志系统的集成 可以扩展函数记录验证失败inline fun T T.checkWithLog( condition: T.() - Boolean, lazyMessage: T.() - Any ) { if (!condition()) { val msg lazyMessage() logger.error(State check failed: $msg) throw IllegalStateException(msg.toString()) } }在大型Kotlin项目中合理使用require和check可以显著提高代码健壮性。根据我的经验建议在代码规范中明确规定所有公共方法的参数必须使用require验证所有状态变更操作必须使用check保持一致性错误消息必须包含可操作的调试信息这种实践可以将许多运行时错误提前到开发阶段暴露大幅降低生产环境的事故率。