Repository navigation
Conversation
Contributor
Author
|
commit 有点多,review 合并的时候可以squash下 |
失败改成一套封闭的码表与几个明确的边界,不再靠裸 Error 和就地拼文案。 - 码表:eleckoi/<域>/<原因>,29 个领域码 + 兜底 eleckoi/internal;详情参数限定 8 个(field/issues/path/operation/guard/what/expected/actual)。 - 边界:① 产生地 → ② 宿主出口 → ③ IPC(失败以对象过桥)→ ④ 插件运行时 → ⑤ preload 解包 → ⑥ 渲染层唯一文案入口。形状判定,不依赖 instanceof。 - 呈现:行内 FailureAlert 与应用级 AppToast → AppErrorDialog 两条通道;严重度 默认由码决定,调用点只能升级到应用级,没有降级参数。 - 闸:check:error-contract 的四条绝对规则(码不许丢、契约失败必须带 issues、 空 catch 必须命名、不许裸抛);docs/error-contract.md 与客户端码表由生成器产出。 - 协作:docs/error-handling.md 写清加码与接边界的步骤;ADR 0040 记 15 条决策。 含 Windows CI 基线的修复(上游 eleckoi#16),否则 Windows 上八处既有失败挡住本改动。 373 个文件,+10215/−1862。开发期的逐票历史留在 fork 的 archive/eh13-dev-history 分支。
KuramiWan
force-pushed
the
feat/error-handling-v0213
branch
from
October 23, 2026 09:19
3ba241f to
acecf81
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
这套设计回答一个问题:一个失败从产生到用户看见,要过六条边界(领域 → Remote 出口 → 子进程 / 运行时插件 → IPC → 渲染层),怎么让它不丢语义,同时让三个读者各读到该读的东西——用户读一句话,开发者读一行日志,程序读一个码。
今天的问题正好相反:失败在第二条边界上就只剩一句话。用户看到开发者写的中文句,程序拿不到码、只能比字符串,日志里没有能回去查的东西。所以这套设计做四件事,外加一个执行者。
一、用户可见文案只有一个来源,而且不在抛出点。
抛出点保留原中文句,但它是开发者诊断——进日志,可以带 id 和路径。用户读的那句话由渲染层按码给:
好处有三个:同一个失败在聊天窗口和设置面板不会出现两种说法;改文案只改一处;「用户读到开发者诊断」这种事故在结构上不会再发生。这是对仓库「文案就地写中文」惯例的明确例外。
二、码表是内部枚举,不是对外契约。
29 条码 + 一个兜底码,形态
eleckoi/<域>/<原因>:加码自由,改名要一次全局替换;不承诺第三方或 Android 按码实现——实测生产代码里按eleckoi/*分支的地方是 0 处,没有消费者就不该有承诺。兜底码的规则是不猜:认不出的失败折成eleckoi/internal,把原码记进details.what。猜成某个域码,等于给调用方一个假的可判别失败。三、严重度是场景的属性,不是失败的属性。
同一个
io/failed,在读一张角色卡时是功能内失败,在启动读配置时是整个应用的失败。所以通道默认由码给(5 条应用级码:plugin/load-failed、db/schema-mismatch、storage/corrupt、sender/untrusted、lifecycle/disposed),调用点只能上调——解析函数的参数里根本没有下调方向,「只能上调」是结构保证。两条通道:inline就地失败面 + 重试;app对话框 / 窗口级提示。四、日志一行读完,不用回查码表。
一行是「动作键 + 码 + 开发者诊断 + 参数化详情 + 时间」:
动作键是有语义的操作名,不是随机 id。启动写一行、正常退出写一行——有 start 没 end,就是崩溃或强杀。日志自己写不了就降级 stdout + 一条 stderr,不让日志成为应用起不来的原因。
两条硬约束撑住上面这些:失败按形状识别,不用
instanceof(dsh-product-data把契约包内联了,跨 bundle 副本原型不同,instanceof必假,码与详情会整条丢掉);过边界不重新包信封,客户端拆 Remote 结果时直接throw result.error,保留官方失败实例。明确不做:冻结公开契约、按失败原因决定恢复策略、给副窗口补 toast、引入 lint / 格式化链、全面 Result 化、运行时错误服务。闸是这套设计的执行者。
check:error-contract四条规则:禁止丢码、契约违例必须带issues、空 catch 必须具名(failureAlreadyPublished/rejectionIsAwaitedElsewhere/bestEffort)、不许裸抛。四条都是绝对值,不建豁免账本;只看代码位置,作者脚本示例里出现的throw new Error不会被误判。这份 diff 里是什么。 373 个文件:55 个是新地基(契约、折叠、闸、失败面组件、日志);180 个是把裸抛换成带码抛出——同一句式替换,其中 127 个文件只做这一件事;77 个是接线(preload、IPC、窗口失败面、日志挂点),用户可见行为的变化在最后这一桶;43 个是既有测试的断言更新;18 个是闸 / 生成物 / 文档 / 配置。其中 8 个文件不是本线的改动,是并入的上游 PR #16(Windows CI 基线修复):0.2.13 在
windows-latest上本来就红 7 条,不并它,本 PR 的 Windows CI 看不出错误处理改动的真实结果。想读的话按这个顺序:packages/shared/src/contracts/errors/→ 任选一条边界(ipc.ts+desktopIpcHandler.ts)→apps/web/src/utils/failureText.js。提交只有一条。 开发期按 15 张票推进,留下 240 个提交(含 85 个工作票的合并气泡)。它们对审阅没有帮助,合进 main 还会把历史弄乱,所以分支压缩成一个提交。逐票历史留在 fork 的
archive/eh13-dev-history,想知道某处为什么这么改可以去那里查。合并方式不限:分支只剩一条提交,merge / squash / rebase 在 main 上都只多一个节点。Evidence
闸:四条规则从基线收到绝对值。 同一脚本分别跑基线
a5bb881(696 个源文件)与 tipab4e437(725 个):Windows 真机(并入 #16 后,
C:\Users\15985\Desktop\code\ElecKoi-eh13,Node 24.20.0 / pnpm 10.32.1):unit10 文件 / 84 用例 ·ui174 / 1256 ·host63 / 497 ·tooling9 / 43 ·integration关键 2 文件 27 用例——共 258 文件 / 1907 用例,0 失败;闸测试在真实 CRLF 检出下 8/8。GUI 组(desktop/browser与应用窗口)在这台机器上起不来:Electron 44 启动即 APPCRASH0x80000003(最小窗口脚本、交互式计划任务、--no-sandbox都一样),与本次改动无关,故未纳入。Windows CI(
windows-latest,本 PR 的 run39931028086):pnpm test326 文件 / 2260 用例全过;pnpm build(含四条契约规则与全部生成物校验)、Electron SQLite ABI、DSH 握手三步也全过,整条 19m30s(fork 上同一 commit 另跑过一遍,17m36s,也是绿的)。这一轮 CI 抓出并修掉 3 个只在 Windows 出现的问题:闸测试的 CRLF + shebang、契约文档生成器读details.ts的正则、码表生成器把D:\裸路径当 ESM 导入。用户看到的是按码的句子,不是开发者诊断。
error?.message当文案;主窗口写着页面加载失败:{error?.message}resolveFailure(...).text。renderer-failure-text-wiring.test.jsx喂DesktopError(lookupNotFound, '开发者诊断:找不到 x', { what: '角色卡' }),断言界面显示找不到角色卡。且不含「开发者诊断」;裸Error仍用它的 message应用级码真的走应用级通道。
channel只被当成 CSS class,storageCorrupt在设置面板里就地显示成开发者诊断failure-alert-escalation.test.jsx:storageCorrupt升级到窗口级对话框,lookup/not-found仍就地,无订阅者时退回就地兜底IPC 不再跨进程抛异常。
ipcMain.handle的 handler 抛错,跨进程只剩messageipc-failure.test.ts断言码与详情原样过结构化克隆全量门禁(
bash eh13-final-gates.sh,在并入 #16 之前的集成分支ab4e437上跑;其后的 17 个文件(#16 的 8 个 + 四处收口)由上面的 Windows 真机与本 PR 的 CI 覆盖)两个环境红:
android-apk-resources(缺 Pillow,本机python3没有 pip)、updater-network(Windows autoUpdater 用例,Linux 平台不匹配——补上 X server 后仍红在同一断言)。Merge Danger
Door: one-way(对约定)/ two-way(对代码)
这是这条设计第一次对上游表态。 合并即把「抛码 + 渲染层出文案」定成基线:规则 4 现在默认判失败,新写的裸抛当场红。代码可以整体 revert,但 revert 会连带撤掉闸、生成的契约文档与 ADR 0040,回退成本高于一般重构。并进来的 #16 是上游已开的那条 Windows CI 修复:本 PR 需要它,Windows CI 才能反映错误处理改动的真实结果;#16 合入后这些提交自动变成 no-op。
Blast Radius: repo-wide
error.message或instanceof的外部代码要改读.code(仓库内已扫干净)。isDSHRemoteError标记:拿到 IPC 失败对象的 DSH 代码会把它当 RemoteError,这是有意为之;代价与退路写在packages/shared/src/contracts/errors/ipc.ts文件头。docs/error-contract.md、插件 API 参考。<p role="alert">直显(关键窗口先接,组件与接线已就绪)。请求维护者
这套写法只有在后续改动里被使用才算成立。合入后请按错误处理协作声明写新的失败,要点六条:
DesktopError/productFailure;message写开发者诊断(日志用),不是给用户看的句子;RemoteResult走unwrapOrFail或throw result.error,不写new Error(result.error.message);resolveFailure(...).text;宿主、领域、运行时插件不拼用户句子;failureAlreadyPublished/rejectionIsAwaitedElsewhere/bestEffort)或写非空处理体;codes.ts→details.ts→ 文案表 → 重生成 → 测试的顺序加;生成物不手改;pnpm check:error-contract:四条规则都是绝对值,不建豁免账本。哪条做不到或有异议,请在合并前说——合入后这几条由闸执行,不再是建议。