来自 #262 的架构级评审建议。不阻塞合入,仅供参考是否有更好的架构解法。
💡 [建议 · 错误处理] 退出码 3 属硬编码魔法值,缺统一契约 cmd/ban-cli/main.go:37
问题根因:PR 引入「键不存在返回退出码 3」的用法,把动作结果编码进进程退出码。当前仅在 usage 文本和 get 分支里分散散落,命令模式下也用于交互模式(交互模式直接打印提示,不走退出码),两套客户端行为对同一语义的编码并不一致。这是一个「协议语义」被硬编码进 CLI 的实现细节,无集中定义、无文档契约。
为什么低级解法不够:简单加一行注释或 const 常量能缓解,但没有回答:退出码 3 是 CLI 与脚本之间的稳定契约,未来若有更多命令/更多错误类别,是否会出现退出码冲突?分散的 magic number 会让调用方脚本难维护。
架构级方案:定义一组具名的退出码常量(如 exitOK/exitUsage/exitConnErr/exitKeyNotFound),并在文档(usage)与实现间用同一符号引用,避免文本与逻辑不同步。进一步可将「键不存在是非故障」这一语义抽象为客户端 SDK 已暴露的 ErrKeyNotFound,CLI 只在最外层做一次错误→退出码映射,保持单点映射。
代价/收益:代价:需新增一个小的常量/文档约束;收益:退出码从散落的 magic number 变为可维护的稳定契约,脚本依赖不会因未来改动无声破裂。
💡 [建议 · 错误处理] 交互模式下 ctx 超时无法感知重试语义 cmd/ban-cli/main.go:103
问题根因:交互模式新加的 context.WithTimeout(interactiveTimeout=5s) 与 SDK 默认的 RequestTimeout=5s 在同一量级,且 SDK 内部有基于 context 的指数退避重试。当服务端过载返回 overloaded(可重试)时,5s 的 context 截止很可能在退避完成后已耗尽,重试机会被 context 超时静默吞掉——交互式 CLI 拿到的总是超时而非可重试失败的可见诊断。
为什么低级解法不够:单纯把 interactiveTimeout 调大(如 30s)只缓解症状:重试的总预算仍是「context 截止时间」,而重试的次数与退避算法由 SDK 决定(MaxRetries=2、退避 20ms→40ms),两者没有任何联调,单命令最长等待不可预测。
架构级方案:CLI 层不要在 SDK 之上再叠一层自己的超时——SDK 已提供有界重试与 RequestTimeout。让 CLI 的命令调用直接使用 context.Background()(SDK 内部 deadline 兜底)+ SDK 控制的 RequestTimeout,把「超时与重试」的所有权统一交给 SDK 单一来源,CLI 只做展示与退出码映射。
代价/收益:代价:单命令的最长阻塞由 SDK 的 RequestTimeout+MaxRetries 决定,交互模式下用户感知的最坏延迟会变长(默认约 5s+退避);收益:超时/重试语义单一化可预测,CLI 不再存在与 SDK 竞争的独立超时维度,也避免「交互模式表现与命令模式不一致」。
💡 [建议 · 错误处理] 退出码 3 属硬编码魔法值,缺统一契约
cmd/ban-cli/main.go:37问题根因:PR 引入「键不存在返回退出码 3」的用法,把动作结果编码进进程退出码。当前仅在 usage 文本和 get 分支里分散散落,命令模式下也用于交互模式(交互模式直接打印提示,不走退出码),两套客户端行为对同一语义的编码并不一致。这是一个「协议语义」被硬编码进 CLI 的实现细节,无集中定义、无文档契约。
为什么低级解法不够:简单加一行注释或 const 常量能缓解,但没有回答:退出码 3 是 CLI 与脚本之间的稳定契约,未来若有更多命令/更多错误类别,是否会出现退出码冲突?分散的 magic number 会让调用方脚本难维护。
架构级方案:定义一组具名的退出码常量(如 exitOK/exitUsage/exitConnErr/exitKeyNotFound),并在文档(usage)与实现间用同一符号引用,避免文本与逻辑不同步。进一步可将「键不存在是非故障」这一语义抽象为客户端 SDK 已暴露的 ErrKeyNotFound,CLI 只在最外层做一次错误→退出码映射,保持单点映射。
代价/收益:代价:需新增一个小的常量/文档约束;收益:退出码从散落的 magic number 变为可维护的稳定契约,脚本依赖不会因未来改动无声破裂。
💡 [建议 · 错误处理] 交互模式下 ctx 超时无法感知重试语义
cmd/ban-cli/main.go:103问题根因:交互模式新加的
context.WithTimeout(interactiveTimeout=5s)与 SDK 默认的RequestTimeout=5s在同一量级,且 SDK 内部有基于 context 的指数退避重试。当服务端过载返回 overloaded(可重试)时,5s 的 context 截止很可能在退避完成后已耗尽,重试机会被 context 超时静默吞掉——交互式 CLI 拿到的总是超时而非可重试失败的可见诊断。为什么低级解法不够:单纯把
interactiveTimeout调大(如 30s)只缓解症状:重试的总预算仍是「context 截止时间」,而重试的次数与退避算法由 SDK 决定(MaxRetries=2、退避 20ms→40ms),两者没有任何联调,单命令最长等待不可预测。架构级方案:CLI 层不要在 SDK 之上再叠一层自己的超时——SDK 已提供有界重试与 RequestTimeout。让 CLI 的命令调用直接使用
context.Background()(SDK 内部 deadline 兜底)+ SDK 控制的 RequestTimeout,把「超时与重试」的所有权统一交给 SDK 单一来源,CLI 只做展示与退出码映射。代价/收益:代价:单命令的最长阻塞由 SDK 的 RequestTimeout+MaxRetries 决定,交互模式下用户感知的最坏延迟会变长(默认约 5s+退避);收益:超时/重试语义单一化可预测,CLI 不再存在与 SDK 竞争的独立超时维度,也避免「交互模式表现与命令模式不一致」。