mcpField
E
error-handling

error-handling

github.com/epicenterhq开发4.8k82 次安装3 周前更新
代码仓库

关于 error-handling

面向 Epicenter 项目的编码技能,指导开发者使用 wellcrafted 处理异常与 `Result` 值,明确错误契约、保留类型化失败,并处理所有可能的错误分支。

主要功能

  1. 01选择抛出异常或 `Result` 契约
  2. 02包装同步和异步操作
  3. 03映射已知失败并重抛未知异常
  4. 04处理所有可能的 `Err` 分支
  5. 05明确异步结果的所有权

适用场景

  1. 01将抛出异常的 API 转换为返回 `Result`
  2. 02将未知异常保留为程序错误
  3. 03检查 `Result` 的消费路径

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/epicenterhq/epicenter --skill error-handling

安装后在 Claude Code 中输入/error-handling确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
http-boundaries.md
toast-on-error.md
wrapping-boundaries.md

详细介绍

错误处理

本技能负责定义抛出异常与 Result 值之间的边界,以及正确消费 Result。可与 define-errors(用于变体设计)、logging(用于诊断)、query-layer(用于 RPC 呈现)和 hono(用于响应 API)配合使用。

信息来源

所有关于 Wellcrafted 行为的说明,都应以官方 wellcrafted-dev/wellcrafted 源码和测试为依据。维护本指南时,请确认 Epicenter 安装的版本与所查阅的源码版本一致。如果不一致,请报告版本差异;依赖项更新由本技能之外的流程负责。其他技能、示例、生成的文档和 DeepWiki 只能作为线索,不能作为权威依据。

仅在需要时阅读对应的参考资料:

先选择契约

所需契约模式
调用方接收 Result<T, E>使用 trySync 或 tryAsync 转换会抛出异常的操作
失败时有有效的回退值从 catch 返回 Ok(fallback)
失败必须作为数据传播从 catch 返回类型化的 defineErrors 工厂结果
只有已知的外部失败应转换为 Err映射已知异常,并重新抛出未知异常
外层 API 基于异常保留其抛出异常的契约
无论成功、失败、取消还是提前返回,都必须执行清理使用 finally

同步操作使用 trySync,返回 Promise 的操作使用 tryAsync。

const { data: response, error } = await tryAsync({
	try: () => fetch(url),
	catch: (cause) => RequestError.TransportFailed({ cause }),
});
if (error !== null) return Err(error);
return Ok(response);

defineErrors 工厂已会返回 Err(...)。将原始 cause 传给工厂,并由工厂通过 extractErrorMessage 组合错误消息。不要在 catch 边界使用原始的 Err(cause):抛出的值可能是 null 或 undefined,而未类型化的原因会丢失领域层面的失败信息。

跨越明确的抛出边界

只要调用方仍可恢复、报告、重试或传播失败,就将 Result 保留为数据。仅在外层 API 已将抛出异常作为失败通道时才使用 unwrap:

import { unwrap } from 'wellcrafted/result';

const document = unwrap(await openDocument(id));

unwrap 会返回 Ok.data,并抛出 Err.error。抛出异常是该边界的契约,并不表示失败出乎意料。如果此函数需要恢复、补充上下文或执行清理,请改用 error !== null 进行判断。

处理所有可能的 Err 分支

  • 如果一个值可能是 Result<T, E>,就检查其错误分支,或有意将其向外传递。
  • 解构后,error 是原始的 E。应返回 Err(error),而不是 error。
  • 如果保留了整个 Result,就原样返回:if (result.error !== null) return result。
  • error !== null 是可靠的判别方式。绝不要构造 Err(null) 或 Err(undefined)。
  • 如果 catch 分支总是返回 Ok<T>,且推断类型会收窄为 Ok<T>,则只解构数据字段是正确的。
  • 如果成功数据无关紧要,则只解构错误字段是正确的。原则是处理所有可能出现的 Err,而不是解构未使用的字段。

优先使用立即执行的守卫,让成功路径保持线性。

拥有 Promise

tryAsync 会返回一个 Promise。请明确指定其归属方:

  • 当此函数检查 Result 时,使用 await。
  • 当调用方拥有 Promise<Result<...>> 时,使用 return tryAsync(...)。
  • 不要直接使用 void tryAsync(...):普通失败会以 Err 的形式正常完成,因此 Promise 拒绝处理器无法捕获它们。尽力而为的操作仍需要由异步归属方负责:等待 Result,并明确记录或忽略其错误分支。
  • 在 UI 的即发即弃代码中,丢弃以 Result 正常完成的 Promise 之前,先附加呈现逻辑:void save().then((result) => toastOnError(result, 'Save failed'))。如果 Promise 可能拒绝,请先转换或捕获该拒绝。

当原生 Try-Catch 能表达约定时,请保留它

以下情况适合使用传统的 try-catch:

  • finally 代码块负责清理;
  • 生成器必须 yield 一个失败,而不是返回 Result;
  • 框架边界会将异常直接转换为所需的响应格式;
  • 代码捕获一种已知异常,并重新抛出其他所有异常;
  • 外围公共 API 有意抛出异常。

不要将未知的编程错误转换为通用的领域失败。将所有抛出都映射为 Err,可能会用具有误导性的可重试错误掩盖 Bug。

最终检查

  1. 函数抛出异常还是返回 Result,约定是否明确。
  2. 包装器是否涵盖一种连贯的失败含义。
  3. 捕获的值是否转换为类型化错误、有意采用的回退方案,或选择性地重新抛出。
  4. 每个可能出现的 Err 分支是否都由明确命名的尽力而为归属方处理、转发、记录、呈现或明确忽略。
  5. 未知 Bug 是否仍会到达适当的崩溃边界或框架错误边界。

评论

支持 Markdown