You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中require()使用时机与参数校验后对象状态处理最佳实践

问题解答

一、require()与check()的理解是否正确?

完全正确。

  • require() 用于校验调用方传入的参数是否符合契约,属于对外的参数合法性检查,抛出IllegalArgumentException,明确指向调用方的错误。
  • check() 用于校验方法内部的状态或逻辑是否符合预期,是内部一致性检查,抛出IllegalStateException,指向自身代码的设计或实现故障。

二、能否在循环中使用require()?

语法上可行,但你的代码会引发对象状态不一致的问题:一旦遍历中途遇到非法元素,require()抛出异常时,elementArray已添加部分元素、moduleCount也已累加,这些修改无法回滚,会让对象处于半更新的无效状态。若调用方捕获异常后继续使用该对象,必然触发后续逻辑错误,这是需要避免的。

三、此类场景的最佳实践

针对这种“遍历过程中才能校验参数,且不想重复遍历Sequence”的场景,推荐以下方案:

1. 先构建临时容器完成校验,再原子化更新内部状态

先将输入转换为临时List<Int>(对Sequence来说,这会触发一次终端操作),完成全量校验后,再一次性更新内部状态:

public fun loadElements(e: Iterable<Int>) {
    val tempList = e.toList()
    tempList.forEach { require(it >= 0) }
    
    // 校验通过后,统一修改内部状态
    elementArray.clear()
    elementArray.addAll(tempList)
    moduleCount = tempList.sum()
    
    if (elementArray.size % 2 == 1) {
        elementArray.add(0)
    }
    check(elementArray.size % 2 == 0)
    computeInternalSizes()
}

优点:保证内部状态的原子性,要么全更新要么全不更新;校验逻辑集中,代码清晰。
缺点:对Sequence会触发终端操作,需额外内存存储临时列表,但这是保证状态安全的合理代价——状态一致性的优先级远高于这点性能损耗。

2. 手动实现事务式状态更新(异常时回滚)

如果必须避免提前构建全量临时列表,可以在遍历前备份原始状态,一旦发现异常就回滚:

public fun loadElements(e: Iterable<Int>) {
    // 备份原始状态
    val originalElements = ArrayList(elementArray)
    val originalModuleCount = moduleCount
    
    try {
        elementArray.clear()
        moduleCount = 0
        e.forEach {
            require(it >= 0)
            elementArray.add(it)
            moduleCount += it
        }
        
        if (elementArray.size % 2 == 1) {
            elementArray.add(0)
        }
        check(elementArray.size % 2 == 0)
        computeInternalSizes()
    } catch (ex: IllegalArgumentException) {
        // 回滚到原始状态
        elementArray.clear()
        elementArray.addAll(originalElements)
        moduleCount = originalModuleCount
        throw ex // 重新抛出异常,不隐藏调用方错误
    }
}

优点:无需提前构建全量临时列表,对内存敏感场景更友好。
缺点:需要手动备份和恢复状态,代码复杂度略高;若内部状态较多,备份逻辑会更繁琐。

3. 明确禁止从require()异常中恢复

从设计语义看,require()抛出的IllegalArgumentException属于不可恢复的调用错误,调用方不应捕获并继续使用该对象。你可以在方法文档中明确标注:

若传入包含负整数的Iterable,会抛出IllegalArgumentException,此时对象状态未定义,禁止继续使用。

这种方案依赖调用方遵守契约,适合内部系统或可控调用场景,无需额外回滚逻辑,代码最简洁。

关于是否从require()异常恢复的问题

程序不应该从require()的异常中恢复。require()的语义是“调用方违反契约,当前操作无法继续”,此时对象状态已不可靠,继续使用只会引发更多错误。调用方的正确做法是修复参数后重新调用,而非捕获异常继续执行。

内容的提问来源于stack exchange,提问作者fiatCurrency

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 15:45:32