Kotlin 2.4 中的更为丰富的错误处理:告别无聊的 try/catch,或者说,还没到告别的时候?

观察一门编程语言是如何演进的,总是一件引人入胜的事情------它不仅仅体现在那些重大的新功能上,更在于那些反映其深层理念和优先级变化的细微、渐进式调整。

Kotlin 就是一个让我不断回味、反复研究的例子。这不光是因为我在用它,还因为它总能以出人意料(有时甚至是令人惊喜)的方式不断发展。

Kotlin 2.4 引入了一个大家期待已久的功能:丰富错误(Rich errors) 。这是一种用联合类型来表达多个可能返回类型的方式。你可以把它想象成 User | Error,而不是把所有东西都包装进 Result<T> 或者直接抛出异常。

为什么经典的错误处理不够用

Kotlin 提供了多种处理失败的方式:

  • try/catch 异常处理------代码嘈杂,只能在运行时捕获,而且经常被滥用。
  • sealed class Result<T> ------结构更清晰,但需要很多样板代码和封装。
  • 来自 Arrow 库的 Either<L, R> ------虽然好用,但需要引入第三方库。

所有这些方法要么隐藏了失败的可能性 ,要么在为预期失败建模时增加了不必要的摩擦。

丰富错误(Rich Errors)的出现改变了游戏规则,它让错误在类型层面变得明确和强制。

丰富错误是如何运作的

假设你有一个从后端加载用户资料的函数。有了"丰富错误"功能后,你的函数会变得更"诚实":

kotlin 复制代码
fun loadUserProfile(id: String): User | NetworkError | NotFound

这声明了:我将返回一个 User,或者一个 NetworkError,或者一个 NotFound 的情况。没有包裹。没有猜测。

这是你如何使用它的:

kotlin 复制代码
val result = loadUserProfile("12345")

when (result) {
    is User -> showProfile(result)
    is NetworkError -> retryLater()
    is NotFound -> show404()
}

编译器会强制你覆盖每一种可能的情况。你再也不会忘记处理 404 错误了。

带有丰富错误的认证流程

让我们以一个常见的移动场景为例:登录

旧方式(使用结果包裹)

kotlin 复制代码
sealed class LoginResult {
    data class Success(val token: String): LoginResult()
    object InvalidCredentials: LoginResult()
    object NetworkFailure: LoginResult()
}

fun login(email: String, password: String): LoginResult

然后你手动解包结果,并且可能会忘记处理某个分支。

丰富错误方式:

kotlin 复制代码
fun login(email: String, password: String): AuthToken | InvalidCredentials | NetworkFailure

现在你的代码是:

kotlin 复制代码
when (val result = login("email", "password")) {
    is AuthToken -> navigateToHome(result)
    is InvalidCredentials -> showError("Please try again")
    is NetworkFailure -> showError("Please check your connection")
}

清晰、简洁,并且在编译时强制执行。没有魔法封装,没有 getOrNull() 的取巧,也没有隐藏的异常。

测试丰富错误

现在测试也更简单了。因为错误只是返回类型:

kotlin 复制代码
val result = login("wrong email", "wrong password")  
assertTrue(result is InvalidCredentials)

无需 assertThrows。无需异常模拟。只有纯函数和可预测的结果。

底层:联合类型(Union)

这个功能是由 Kotlin 2.4 中的联合类型(|)驱动的:

kotlin 复制代码
fun something(): A | B | C

在内部,编译器将其解糖(desugars)为类似于密封类联合的东西------但为你提供了轻量、富有表现力的语法。 无需手动声明密封类。一切都是内联的、符合人体工程学的、可组合的。

如何尝试

通过添加以下内容,在 Kotlin 2.4 中启用联合类型:

scss 复制代码
languageSettings {
    enableLanguageFeature("UnionTypes")
}

然后开始编写富有表现力、类型安全、零意外的代码。

注意事项和局限

  • 目前在 Kotlin 2.4 中是实验性功能(需要选择启用)。
  • 工具(如 IDE 支持、反射)正在追赶中。
  • 仅当错误是预期情况而非真正的"异常"时使用(磁盘损坏?仍然使用 throw)。

何时使用丰富错误

  • 当你:

    • 在为业务逻辑建模时(验证、404、API 失败)。
    • 想要类型安全的替代方案来替代抛出异常。
    • 在许多分支中需要提前返回。
  • 避免在:

    • 你处理不可恢复的系统错误时(内存不足、磁盘损坏)。
    • 你想在顶层捕获所有内容时。

结论

丰富错误不仅仅是一项功能------它是一种思维模式的转变。 我们正在从"隐藏错误直到它们爆炸"转变为"将失败声明为一等公民的返回结果"。对于像我这样长期以来一直希望有一种更具表现力、更轻量的方式在 Kotlin 中为失败建模------而无需将所有内容都包装在 Result<T> 中或构建冗长的密封层次结构------这感觉就像一股清新的空气,不是吗?

相关推荐
康小汪6 小时前
AI 写 Vue 总写成同一个味道:给它装 Skills,装的不是知识是边界
前端·vue.js·人工智能·ai编程·最佳实践
智能运维指南7 小时前
研制过程“两张皮“怎么破:研制一体化支撑平台的定位、能力边界与落地前提
java·前端·人工智能·嘉为蓝鲸
白山编程大哥9 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
Flynt10 小时前
我把热榜那篇"禁用 axios"逐条实测了一遍:结论大体对,坑在它没讲的地方
前端·javascript·axios
Csvn10 小时前
bundle优化
前端
北冥有鱼11 小时前
发版后动态路由点击报 `Failed to fetch dynamically imported module`」问题上的完整排查与修复过程
前端·vue.js
静默回滚11 小时前
视频压到 25 MB 以内,码率要算、音轨要扣、余量要留
前端
用户372037492601811 小时前
把 10MB 中文语义模型塞进浏览器:STSB 做到教师模型的 93%,但线上 top1 只有 42%
前端
光锥智能11 小时前
海康机器人举行2026移动机器人新品发布会:箱式存拣升级,「明智」系列发布
java·前端·机器人
计算机魔术师11 小时前
Anthropic 上市文件最吓人的一幕,不是亏损 807 亿
前端