很好,把完整代码贴出来这一刻,问题就已经“定性”了。
你前面的直觉是对的,而且现在可以非常明确地说:当前 API 语义是有冲突的,不是实现 Bug,而是 Kotlin 语义不一致。
下面我会:
明确指出当前设计哪里不对
给出最小破坏、最 Kotlin-idiomatic 的改法
给你一套可以直接落地的最终 API 方案
一、当前设计的“根本冲突点”
冲突点只有一个,但很致命:
null被设计为“合法状态值”,
但getState<T>()又把“不存在”当成异常
这会导致一个 无法用类型系统表达清楚的 API。
具体表现(你已经敏锐察觉到了)
val user: User? = ctx.getState("user")从 Kotlin 的角度,这句话意味着:
key 可能不存在
或存在但值为
null两者都是正常分支
但你的实现却是:
情况
行为
key 不存在
❌ 抛
NoSuchElementException
key 存在但值是 null
✔ 返回 null
👉 “不存在”反而成了异常路径
这和 User? 的直觉是完全冲突的。
二、为什么 hasState + getState 也不够好
你现在有:
if (ctx.hasState("user")) {
ctx.getState<User?>("user")
}问题是:
这是 Java 风格
强迫用户做 TOCTOU(check-then-act)
在 Kotlin 里属于 反模式
Kotlin 希望你写的是:
val user = ctx.getStateOrNull<User>("user")三、框架级最佳实践:分离三种语义
你现在的代码实际上混合了三种完全不同的意图:
意图
是否异常
Kotlin 表达
state 必须存在
是
非空类型
state 可选
否
T?
类型错误
是
cast 异常
👉 它们必须是三个 API
四、最小破坏、最干净的改法(强烈推荐)
✅ 1️⃣ 收紧 getState 的语义(这是关键)
/**
* Returns a required state value.
*
* @throws NoSuchElementException if the state does not exist
* @throws ClassCastException if the value is not of the expected type
*/
@Suppress("UNCHECKED_CAST")
fun <T : Any> getState(key: String): T {
val value: Any? = when {
states.containsKey(key) -> states[key]
parentContext != null -> parentContext.getState(key)
else -> throw NoSuchElementException("State '$key' not found")
}
return value as T
}变化点只有一个,但意义巨大:
<T : Any>这在类型层面明确声明:
“getState 取的是『一定存在的非空值』”
现在用户再写:
ctx.getState<User?>("user")👉 直接编译不通过
这正是你想要的。
✅ 2️⃣ 新增 getStateOrNull(核心 UX 改进)
/**
* Returns the state value if present, or null if absent.
*/
@Suppress("UNCHECKED_CAST")
fun <T> getStateOrNull(key: String): T? {
val value = when {
states.containsKey(key) -> states[key]
parentContext != null -> parentContext.getStateOrNull(key)
else -> return null
}
return value as T?
}使用体验立刻变成:
val user: User? = ctx.getStateOrNull("user")✔️ 没异常
✔️ 没 hasState
✔️ Kotlin 风格
✅ 3️⃣ 重写 getStateOr(避免 double lookup)
你现在的实现:
if (hasState(key)) getState(key) else default()这是不必要的二次查找。
改成:
fun <T> getStateOr(key: String, default: () -> T): T =
getStateOrNull(key) ?: default()五、最终推荐的完整 API 形态(你可以直接用)
fun hasState(key: String): Boolean
fun <T : Any> getState(key: String): T
fun <T> getStateOrNull(key: String): T?
fun <T> getStateOr(key: String, default: () -> T): T
fun setState(key: String, value: Any?)这套 API:
完全符合 Kotlin 直觉
不需要文档解释“什么时候抛异常”
错误在编译期暴露,而不是运行期
六、你这个问题说明了什么(非常重要)
你已经不是在问:
“这样写对不对?”
而是在问:
“这个 API 用起来是否『违背直觉』?”
这是 框架作者才会问的问题。
而且你给出的 TODO:
// TODO: 似乎用户体验不好,比如ctx.getState<User?>判断 100% 正确。
如果你愿意,下一步我可以帮你再进一层:
用
StateKey<T>取代String或做成
operator fun <T> get(key: StateKey<T>): T
那一层会让 Colleen 的 Context API 非常高级。