- 即时通讯
- 后端
【免费下载链接】WuKongIM
More than just IM 不只是即时通讯(IM)
本文基于仓库中的实施计划文档 docs/superpowers/plans/2026-07-05-remove-legacy-code.md,并结合当前仓库源码逐一核验落地结果。它完整还原了 WuKongIM 将旧版 v1 runtime 代码(
internal/legacy、pkg/legacy、test/legacy)从仓库中安全移除的整套方法论:从测试依赖替换、文档边界更新,到目录删除、引用清扫与多级验证。读者读完可以掌握"在大型 Go 单体仓库中安全退役历史代码树"的完整实操流程:如何界定删除范围、如何先解除测试对旧包的依赖、如何用 import-boundary 测试与文档规则守住"不得重建 legacy"的长期不变量,以及如何用分层go test验证矩阵和分批提交策略控制风险。
WuKongIM 是一个"不止于即时通讯"的开源项目,其代码库在演进中经历了 v1 → v2 的运行时重构。当被提升(promoted)的 v2 代码已经稳定落在规范路径上之后,残留的旧 v1 代码树就成为历史包袱:它们不再被生产代码引用,却仍然被部分测试、文档和目录结构约定所纠缠。本文要解决的核心问题就是:如何干净、彻底、可回滚地移除这些 legacy 代码树,同时保证对外契约(HTTP/API 兼容、Prometheus 指标名)不受影响。
一、任务目标与架构前提
实施计划的核心目标(Goal)非常明确:
移除旧的 v1 runtime 代码树,因为被提升的 v2 代码已经运行在规范路径(canonical paths)上。
所谓"规范路径",是本次清理行动的架构锚点(Architecture):
cmd/wukongim—— 服务入口internal—— 内部产品内核(app / access / usecase / runtime / infra)pkg/channel、pkg/cluster、pkg/controller、pkg/transport—— 被提升的公共能力包
执行顺序被严格划分为三个阶段:
- 先移除活跃测试中对 legacy 包的非生产引用;
- 然后更新边界文档(FLOW 文档)与守护测试(import-boundary 测试);
- 最后删除
internal/legacy、pkg/legacy、test/legacy三个目录树。
技术栈与约束工具为:Go、go test、仓库 FLOW 文档、AGENTS.md、包级 import-boundary 测试。这本质上是一套"代码迁移 + 契约固化"的组合拳:先用测试把依赖摘干净,再让文档和守卫测试变成永久的"禁止回归"机制,最后才动刀删除。
落地核验:入口已切换到规范路径
计划中的关键证据之一是cmd/wukongim/main.go导入的是internal/app而非internal/legacy。核验当前仓库 cmd/wukongim/main.go 第 12 行,确实为:
"github.com/WuKongIM/WuKongIM/internal/app"这证明生产入口早已完成 v2 化,legacy 树对生产代码而言已经"无依无靠",只差物理删除。
二、清理前置证据:当前状态审计
在动手之前,计划先做了严格的现状审计(Current Evidence),确保"删得掉、删得对":
1. 聚焦 preflight 测试已通过(计划原文给出的两条命令):
GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test ./cmd/wukongim -run 'TestImportBoundaryUsesCanonicalInternalApp|TestDependencyBoundaryUsesCanonical|TestDependencyBoundaryDoesNotReachLegacyBenchInternal' -count=1GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test ./scripts -run TestPromotedProductionCodeDoesNotImportLegacyPackages -count=1GOWORK=off用于禁用 Go workspace 聚合,保证测试针对仓库自身模块;GOCACHE指向独立缓存目录,避免共享缓存干扰;-run用正则只跑与 import-boundary 相关的聚焦用例。
2. 非 legacy 目录下的生产代码不再引用旧包:
- 除
internal/legacy、pkg/legacy、test/legacy之外的非测试生产文件,均不导入github.com/WuKongIM/WuKongIM/internal/legacy或github.com/WuKongIM/WuKongIM/pkg/legacy。
3. 剩余的硬引用集中在测试与文档,是本次清理的全部作战目标:
| 残留引用位置 | 引用内容 | 清理方式 |
|---|---|---|
cmd/wkdb/main_test.go | 仅为了HashSlotForKey导入pkg/legacy/cluster | 替换为pkg/hashslot |
pkg/slot/proxy系列测试 | 把pkg/legacy/cluster、pkg/legacy/transport当作测试 harness | 替换为本地确定性测试集群 |
scripts/package_promotion_docs_test.go | 仍断言 legacy 目录和 slot-proxy fallback 文档存在 | 改为断言"stale 目录必须不存在" |
AGENTS.md、internal/FLOW.md、pkg/slot/FLOW.md、pkg/cluster/FLOW.md、pkg/client/FLOW.md、docs/development/PROJECT_KNOWLEDGE.md | 仍描述 legacy 保留策略 | 改为"已删除 / 规范路径"措辞 |
说明:计划文档撰写于 2026-07-05,其中提到的
cmd/wkdb、internal/FLOW.md等路径在当前仓库中已经历后续演进(如cmd/wkdb目录已不存在)。这属于正常迭代,不影响本文所述清理方法论本身的成立;所有不变量(legacy 不得重建、hash-slot 统一由pkg/hashslot提供、slot-proxy RPC 统一走pkg/cluster.Node.RegisterRPC)均已由当前代码固化。
三、范围界定:删什么、不删什么
任何大删除最怕误伤,计划用一份明确的 Scope 划出红线:
删除(Delete):
internal/legacy/pkg/legacy/test/legacy/
本轮不删除、不重命名(Do not delete or rename in this pass):
- 由被提升代码实现的 legacy-compatible 公共 HTTP/API 契约——对外协议是兼容面,与旧 Go 源码树无关;
docs/superpowers/reports/下的历史报告——它们是有价值的演进记录,只更新"活跃文档",不触碰历史档案;- Prometheus 指标名(如
wukongim_channelv2_*)——这是外部可观测性契约,删除指标名会破坏监控面板和告警,不属于旧 Go 源码树的范畴; - 未跟踪的用户草稿
docs/superpowers/plans/2026-07-05-legacy-app-slot-store-adapter.md——不在本次任务范围内。
这个边界原则可以提炼成一句话:"删除的是旧的实现代码树,保留的是对外可观测、可兼容的契约面。"
四、Task 1:摘除最简单的 legacy 测试导入(wkdb hash-slot helper)
Step 1:替换导入
cmd/wkdb/main_test.go中唯一用到pkg/legacy/cluster的地方是 hash-slot 辅助函数。替换方式:
// 替换前 "github.com/WuKongIM/WuKongIM/pkg/legacy/cluster" // 替换后 "github.com/WuKongIM/WuKongIM/pkg/hashslot"函数调用同样替换:
// 替换前 cluster.HashSlotForKey("u1", 16) // 替换后 hashslot.HashSlotForKey("u1", 16)这里的关键洞察是:测试只是"借用"了 legacy 包里的一个纯函数,而不是依赖其运行时。HashSlotForKey的本质就是crc32.ChecksumIEEE([]byte(key)) % hashSlotCount,在 pkg/hashslot/hashslottable.go 中可以找到这个 canonical 实现:
func HashSlotForKey(key string, hashSlotCount uint16) uint16 { if hashSlotCount == 0 { return 0 } return uint16(crc32.ChecksumIEEE([]byte(key)) % uint32(hashSlotCount)) }把这类纯函数从 legacy 包"平迁"到pkg/hashslot,是典型的"先摘依赖、后删目录"手法:被迁移的旧代码树往往藏着少量被测试依赖的纯工具函数,先把它们提升到规范包,测试就能与旧树解耦。
Step 2:验证
GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test ./cmd/wkdb -count=1期望结果:PASS。
Step 3:保留字符串级 import-boundary 守卫
计划特别强调:不要因为 legacy 包已不存在,就顺手删掉边界测试里的 legacy 路径字符串。这些字符串守卫(如TestPromotedProductionCodeDoesNotImportLegacyPackages)的价值恰恰在于:即使包被删除了,它们仍然在防回归——任何试图重新引入pkg/legacy/*导入的代码都会立刻被测试拦截。
核验当前仓库 cmd/wukongim/main_test.go,可以清楚看到这类守卫仍然活跃:测试以github.com/WuKongIM/WuKongIM/internal/legacy/、github.com/WuKongIM/WuKongIM/pkg/legacy/cluster、pkg/legacy/controller、pkg/legacy/channel、pkg/legacy/transport等前缀为禁用集,一旦依赖闭包中出现就t.Fatalf。也就是说:目录可以被删除,但"禁止引用 legacy"的哨兵永远不会删除。
补充原则:只有"断言 legacy 目录必须存在"的测试才需要更新;只含 legacy 路径字符串的守卫测试一律保留。
五、Task 2:从 pkg/slot/proxy 测试中移除 legacy harness
这是整个计划中最有技术含量的一步。pkg/slot/proxy(Slot 元数据代理)的单元测试此前把pkg/legacy/cluster和pkg/legacy/transport当作**测试夹具(harness)**使用,而不是被测对象本身。
涉及文件清单
pkg/slot/proxy/testutil_test.gopkg/slot/proxy/integration_test.gopkg/slot/proxy/authoritative_rpc_test.gopkg/slot/proxy/channel_migration_rpc_test.gopkg/slot/proxy/plugin_binding_rpc_test.gopkg/slot/proxy/channel_runtime_meta_page_integration_test.gopkg/slot/proxy/channel_page_integration_test.gopkg/slot/proxy/user_page_integration_test.gopkg/slot/proxy/rpc_hashslot_fallback_test.gopkg/slot/proxy/cmd_conversation_state_rpc_test.go
注:这些是计划撰写时点的文件清单;当前仓库中
pkg/slot/proxy仍保留authoritative_rpc_test.go、testutil_test.go、rpc_hashslot_fallback_test.go、plugin_binding_rpc_test.go、channel_migration_rpc_test.go等文件(部分集成测试文件在后续演进中发生了合并/重命名),核心替换逻辑保持一致。
Step 1:引入确定性的本地测试集群
将基于raftcluster.Cluster的测试 harness 替换为一个本地测试类型,它实现Cluster接口,并额外实现hashslot_compat.go中定义的可选 proposer 接口。
计划为这个本地集群规定了完整的行为契约:
| 方法 | 要求行为 |
|---|---|
SlotIDs() | 返回配置的物理 Slot 列表 |
SlotForKey(key) | 通过HashSlotForKey(key)和本地hashSlotToSlot映射表定位物理 Slot |
HashSlotForKey(key) | 使用pkg/hashslot.HashSlotForKey(key, hashSlotCount) |
HashSlotsOf(slotID) | 返回分配给该物理 Slot 的逻辑 hash slot |
HashSlotTableVersion() | 返回非零的测试修订号 |
LeaderOf(slotID) | 返回配置的 leader,否则返回ErrNoLeader |
IsLocal(nodeID) | 与本地节点 ID 比较 |
PeersForSlot(slotID) | 返回配置的 peer 集合 |
RPCService(ctx, nodeID, slotID, serviceID, payload) | 分发到目标测试节点注册的StoreRPC handler |
ProposeWithHashSlot(ctx, slotID, hashSlot, cmd) | 把 Slot FSM 命令应用到该 hash slot 的权威测试节点 DB |
ProposeLocalWithHashSlot(ctx, slotID, hashSlot, cmd) | 仅当本地节点是配置的 leader 时应用,否则返回ErrNotLeader |
ProposeWithHashSlotResult(ctx, slotID, hashSlot, cmd) | 在需要命令输出的测试中返回 Slot FSM 的 apply 结果 |
计划特意强调这个设计意图:这是pkg/slot/proxy的专用 harness,而不是被提升的pkg/cluster.Node的 harness。原因很实在——pkg/cluster.Node已经有自己的 slot-proxy 端口测试,而pkg/slot/proxy的测试需要直接访问它播种并断言的metadb.DB分片。测试夹具的抽象层级必须匹配被测单元的边界,而不是照搬生产集群的复杂度。
核验当前仓库 pkg/slot/proxy/hashslot_compat.go,可以看到这套接口契约的真实落地形态:
type hashSlotKeyer interface { HashSlotForKey(string) uint16 } type hashSlotProposer interface { ProposeWithHashSlot(context.Context, multiraft.SlotID, uint16, []byte) error } type hashSlotResultProposer interface { ProposeWithHashSlotResult(context.Context, multiraft.SlotID, uint16, []byte) ([]byte, error) } type localHashSlotProposer interface { ProposeLocalWithHashSlot(context.Context, multiraft.SlotID, uint16, []byte) error }而 pkg/slot/proxy/testutil_test.go 当前的导入列表只包含pkg/hashslot、pkg/raftlog、pkg/slot/fsm、pkg/slot/multiraft等规范包,已经没有任何pkg/legacy痕迹——这正是 Task 2 执行完毕的直接证据。
Step 2:替换两节点测试 helper
计划要求尽量保留既有 helper 名称,仅替换内部实现:
startTwoNodeShardedStores(t):创建两个测试节点、两个 DB、物理 Slot1与2,leader 分别为1 -> node1、2 -> node2;startTwoNodeHashSlotStores(t, hashSlotCount):创建同样的两节点,但带一张确定性的逻辑 hash-slot 表(按 slot 均分);testStoreNode.cluster字段改为新的本地集群类型(或 helper 可接受的接口),不再使用*raftcluster.Cluster;newLegacyTestStore更名为newProxyTestStore,通过Store.RegisterRPCHandlers把 RPC handler 注册进本地测试集群的 handler map。
名称从newLegacyTestStore到newProxyTestStore的变更虽小,却是一个重要的信号:测试工具的名称不该携带被删除代码的标记。
Step 3:替换 legacy 错误断言
proxy 测试中针对raftcluster的错误哨兵需要整体迁移到pkg/slot/proxy自身的错误定义:
// 替换前 raftcluster.ErrNoLeader raftcluster.ErrNotLeader raftcluster.ErrSlotNotFound // 替换后 ErrNoLeader ErrNotLeader ErrSlotNotFound推荐使用errors.Is(err, ErrNoLeader)风格的断言,以获得最大的兼容性。核验当前仓库 pkg/slot/proxy/errors.go,可以看到这三个哨兵已经以routeError类型落地,并且自带别名兼容机制:
var ( ErrNoLeader = &routeError{ message: "slot/proxy: no leader", aliases: []string{ "cluster: no slot leader", "cluster/routing: no slot leader", }, } ErrNotLeader = &routeError{ message: "slot/proxy: not leader", aliases: []string{ "cluster: not leader", "cluster/propose: not leader", }, } ErrSlotNotFound = &routeError{ message: "slot/proxy: slot not found", aliases: []string{ "cluster: slot not found", }, } )routeError.Is(target)会同时匹配自身 message 与历史别名,配合routeErrorMatches/containsRouteAlias做子串级兜底。这个设计保证了旧集群返回的错误文本仍能被新哨兵识别——错误契约在迁移过程中不断裂,这比简单替换哨兵变量名更深一层。
Step 4:替换 hash-slot helper
// 替换前 raftcluster.HashSlotForKey(uid, count) // 替换后 hashslot.HashSlotForKey(uid, count)Step 5:清除 legacy 导入
用 ripgrep 做包级扫描,期望零匹配:
rg -n 'github\.com/WuKongIM/WuKongIM/pkg/legacy|raftcluster|legacytransport' pkg/slot/proxy计划允许一种例外:注释中解释被删除历史时可以保留文字,但更推荐零匹配——历史解释交给docs/superpowers/reports/这类档案,活跃代码里不留旧词。
Step 6:验证
GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test ./pkg/slot/proxy -count=1期望:PASS。如果被修改的文件带//go:build integration构建标签,则在单元包变绿后追加聚焦集成验证:
GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test -tags=integration ./pkg/slot/proxy -count=1期望:PASS(仅当确有 integration 标签文件被改动时才需要执行)。
六、Task 3:更新边界测试与文档
删除代码树之前,必须先让"文档和守卫测试"与新世界保持一致,否则删除后文档会指向不存在的路径,守卫测试也会因断言目录存在而失败。
Step 1:更新 package promotion 文档测试
scripts/package_promotion_docs_test.go需要三处调整:
- 从"必须存在的目录"清单中移除
pkg/legacy/channel、pkg/legacy/cluster、pkg/legacy/controller、pkg/legacy/transport; - 把
internal/legacy、pkg/legacy、test/legacy加入"必须不存在的 stale 目录"清单; - 把
TestSlotProxyLegacyFallbackIsDocumented替换为 promoted-only 检查:验证pkg/slot/FLOW.md和docs/development/PROJECT_KNOWLEDGE.md提到pkg/cluster.Node.RegisterRPC是 slot-proxy RPC 的唯一注册路径。
核验当前仓库 scripts/package_promotion_docs_test.go 第 77-85 行,可以看到这条守卫已经反转落地:
for _, staleDir := range []string{ "internal/legacy", "pkg/legacy", "test/legacy", } { if _, err := os.Stat(filepath.Join(root, staleDir)); err == nil { t.Fatalf("stale legacy directory %q should not exist", staleDir) } }同文件第 176-179 行还固化了 Go 导入禁用前缀:
forbiddenPrefixes := []string{ "github.com/WuKongIM/WuKongIM/internal/legacy", "github.com/WuKongIM/WuKongIM/pkg/legacy", }并配套TestPromotedProductionCodeShouldNotImportLegacyPackages(扫描cmd/wukongim、internal、pkg、test/e2e下所有非测试.go文件),扫描时对internal/legacy、pkg/legacy目录直接SkipDir——既不误伤(目录已删)也不放过(前缀仍在黑名单)。
Step 2:更新 AGENTS.md 分层规则
从目录树中移除以下条目:
internal/legacy/pkg/legacy/cluster/pkg/legacy/channel/pkg/legacy/controller/pkg/legacy/transport/test/legacy/e2e/
删除旧层规则:
`internal/legacy/*` 是旧 v1 server runtime 保留区;新增或迁移能力不要依赖 legacy。替换为简洁的新规则:
旧 v1 runtime 已删除;新增代码不得重新引入 `internal/legacy` 或 `pkg/legacy/*`。核验当前仓库 AGENTS.md 第 78 行,这条规则以英文固化在代码质量约束中:
- New code MUST NOT recreate `internal/legacy` or `pkg/legacy/*`.AGENTS.md是仓库给 Agent/开发者的"宪法",把删除决定写进层规则,等于把一次性清理升级为长期架构约定。
Step 3:更新 FLOW 文档
四个 FLOW 文档各有精确的措辞替换:
| 文档 | 修改要点 |
|---|---|
internal/FLOW.md | 删除"旧 v1 runtime 保留在internal/legacy"的陈述;legacy-compatible措辞仅在描述公共 HTTP 兼容性时保留 |
pkg/slot/FLOW.md | 删除internal/legacy/app/slot_proxy_rpc.gofallback 语句;只保留被提升的pkg/cluster.Node.RegisterRPC桥接作为唯一注册路径 |
pkg/cluster/FLOW.md | 将"帮助 Slot 元数据代理调用方迁移出pkg/legacy/cluster的过渡桥"改为"被pkg/slot/proxy使用的 canonical 兼容端口" |
pkg/client/FLOW.md | 从 helper 使用说明中移除test/legacy/e2e引用 |
核验当前仓库 pkg/slot/FLOW.md 第 21-23 行的"Boundaries"段落,措辞已经完全切换为规范路径:
- Slot proxy handlers register through the promoted `pkg/cluster.Node.RegisterRPC` bridge; they do not construct a second cluster transport or routing table.而 pkg/cluster/node_slot_proxy_port.go 第 174 行正是该桥接的实现点:
n.RegisterRPC(serviceID, slotProxyRPCHandlerFunc(handler))配合 pkg/slot/proxy/promoted_cluster.go 中的promotedRPCRegistrar接口与 pkg/slot/proxy/rpc_registration.go 的Store.RegisterRPCHandlers,形成了完整的"Store 注册 → Node 端口桥接"闭环。文档不再描述历史,只描述当前唯一路径。
Step 4:更新项目知识库
docs/development/PROJECT_KNOWLEDGE.md需要记录清理完成的事实:
- "former v1 server runtime lives under
internal/legacy" → "former v1 server runtime has been removed"; - "old implementations live under
pkg/legacy/*" → "oldpkg/legacy/*implementations have been removed"; - 移除对
internal/legacy/app/slot_proxy_rpc.go的引用; - 保留
wukongim_channelv2_*等 Prometheus 原始指标名作为外部兼容别名的说明。
Step 5:验证文档测试
GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test ./scripts -count=1期望:PASS。
七、Task 4:物理删除 legacy 代码树
当测试与文档都"无 legacy 依赖"之后,才轮到最终的物理删除:
Step 1:确认受跟踪的删除范围
git ls-files internal/legacy pkg/legacy test/legacy | wc -l计划记录的执行前证据为933 个受跟踪文件——这是一个规模不小的代码树,印证了删除必须分步进行、而不是一把梭。
Step 2:删除受跟踪文件
git rm -r -- internal/legacy pkg/legacy test/legacy期望结果:Git 为旧 runtime 树暂存删除操作。git rm -r --的--防止路径被误解析为选项,这是对含特殊字符路径的稳妥写法。
Step 3:检查删除树下的未跟踪残留
git status --short internal/legacy pkg/legacy test/legacy如果删除树内部残留未跟踪的.DS_Store、.log、.test或临时文件,作为删除清理的一部分一并处理;绝不越界删除这三棵树之外的无关未跟踪文件。这个"只清理范围内残留"的约束与 Scope 红线一脉相承。
核验当前仓库:
internal/legacy、pkg/legacy、test/legacy三个目录均已不存在,git工作区中已无对应路径,Task 4 已完整落地。
八、Task 5:Stale 引用清扫
删除之后,要用三道扫描确保"尸骨无存":
Step 1:严格 Go 导入扫描
rg -n 'github\.com/WuKongIM/WuKongIM/(internal/legacy|pkg/legacy)' --glob '*.go'期望:零匹配。
Step 2:严格活跃文档路径扫描
rg -n '\binternal/legacy\b|\bpkg/legacy\b|\btest/legacy\b|legacy_e2e' AGENTS.md internal pkg cmd scripts docs/development docs/wiki docker test期望:
- 对已删除的源码/测试目录零引用;
legacy-compatible措辞在描述公共 API 兼容性时可以保留;wukongim_channelv2_*指标名可以保留(不属于本次删除范围)。
核验当前仓库,AGENTS.md 中唯一的legacy目录引用正是禁止性规则("MUST NOT recreate"),而docs/development/PROJECT_KNOWLEDGE.md中保留的legacy字样均属于消息格式兼容、历史指标别名等语义化用途,而非目录路径引用——符合计划的预期语义。
Step 3:目录缺席检查
test ! -e internal/legacy && test ! -e pkg/legacy && test ! -e test/legacy期望:退出码 0。test ! -e是 POSIX 标准的存在性否定检查,三个目录都不存在才算通过。
九、Task 6:验证矩阵
计划为这次清理设计了一套由窄到宽的验证矩阵,先跑窄检查、后跑全量:
GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test ./cmd/wukongim ./cmd/wkdb ./cmd/wkbench ./scripts -count=1GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test ./pkg/slot/proxy ./pkg/cluster ./pkg/db/message ./pkg/db/transfer ./pkg/slot/fsm -count=1GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test ./internal/... -count=1然后跑全量单元套件:
GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test ./... -count=1期望:全部 PASS。
默认不跑全量集成测试,只有当被改动的 integration 标签 proxy 测试确实需要证明时,才追加聚焦集成:
GOWORK=off GOCACHE=/private/tmp/wukongim-go-build-cache go test -tags=integration ./pkg/slot/proxy -count=1这个矩阵体现了三个原则:先窄后宽(改动包先行)、默认不重(集成测试按需触发)、分层兜底(窄层失败先定位,不盲目动全量)。
十、Task 7:提交策略——三次提交保持可回滚
为了评审和回滚干净,计划把整个改动拆成三个语义独立的提交:
| 提交 | 消息 | 内容 |
|---|---|---|
| 1 | test: remove legacy package dependencies from active tests | cmd/wkdb/main_test.go、pkg/slot/proxy/*_test.go |
| 2 | docs: mark legacy runtime removed | AGENTS.md、FLOW 文档、docs/development/PROJECT_KNOWLEDGE.md、scripts/package_promotion_docs_test.go |
| 3 | chore: delete legacy runtime trees | internal/legacy、pkg/legacy、test/legacy |
每次提交前执行:
git diff --check期望:无空白错误。这个三段式提交让每次回滚都"颗粒度合理":测试依赖解除、文档契约更新、物理删除各自独立成 commit,评审者可以逐层确认,回滚也可以只回滚删除而保留契约。
十一、风险与控制
计划最后给出了四组风险及其控制手段,这些是大型仓库清理中反复踩坑的经验沉淀:
测试变得"太假"的风险:
pkg/slot/proxy的本地 harness 替代raftcluster后,覆盖率可能意外失真。控制手段:保留所有既有行为断言,并用pkg/cluster现有的node_slot_proxy_port测试与默认集群测试作为Node的真实覆盖来源——本地 harness 只负责 proxy 单元边界,真实集群行为由 pkg/cluster 的正式测试兜底。核验当前仓库确实存在 pkg/cluster/node_slot_proxy_port_test.go 与slot_proxy_port_external_test.go。误删历史的风险:文档删得太激进会抹掉有用演进记录。控制手段:只更新活跃文档,
docs/superpowers/reports/历史报告一律不动。过度删除
legacy一词的风险:把legacy这个词从全仓库抹掉是错的——公共 API 兼容性措辞可以保留,要删除的只是已删除实现路径的引用。这个边界在 Task 5 的扫描规则中体现得最清楚。全量测试偶发 flake 的风险:
go test ./...可能暴露无关的偶发失败。控制手段:先聚焦重跑失败的包,再决定是否动代码——不要因为无关 flake 去"顺手修复"而扩大变更面。
十二、落地全景:从计划到当前仓库的闭环
回看整个计划,它展示了一条清晰的"代码退役流水线":
审计残留引用 → 摘除测试依赖(纯函数提升+本地harness) → 更新文档与守卫测试 → 物理删除 → stale扫描 → 分级验证 → 分批提交而当前仓库的源码状态,为每一步都提供了可验证的落地证据:
- 入口:cmd/wukongim/main.go 只导入
internal/app; - hash-slot 唯一来源:pkg/hashslot/hashslottable.go 提供
HashSlotForKey; - proxy 本地错误契约:pkg/slot/proxy/errors.go 定义
ErrNoLeader/ErrNotLeader/ErrSlotNotFound并兼容旧错误文本; - proxy 可选接口:pkg/slot/proxy/hashslot_compat.go 定义 hash-slot keyer 与三类 proposer;
- RPC 注册唯一路径:pkg/cluster/node_slot_proxy_port.go 的
n.RegisterRPC(...),并由 pkg/slot/FLOW.md 固化描述; - 禁止回归守卫:AGENTS.md 的 "MUST NOT recreate" 规则、scripts/package_promotion_docs_test.go 的 stale 目录断言、cmd/wukongim/main_test.go 的 legacy 前缀黑名单;
- 目录物理状态:
internal/legacy、pkg/legacy、test/legacy全部不存在。
这套方法论的价值超越 WuKongIM 本身:任何经历大版本重构的 Go 项目,都可以复用"先摘测试依赖、再改契约文档、最后删目录、并以守卫测试固化不变量"的顺序,在不破坏对外契约的前提下完成历史代码树的干净退役。对于想深入了解的读者,原始实施计划全文位于 docs/superpowers/plans/2026-07-05-remove-legacy-code.md,配套的长期项目知识可查阅 docs/development/PROJECT_KNOWLEDGE.md。
- 即时通讯
- 后端
【免费下载链接】WuKongIM
More than just IM 不只是即时通讯(IM)
相关推荐
WuKongIM ChannelV2 Worker 迁移到 workqueue 批处理有界池:从计划到源码级落地
WuKongIM ChannelV2 Worker 迁移到 workqueue 批处理有界池:从计划到源码级落地 导读 :本文围绕 WuKongIM 仓库中的一
即时通讯后端Skill_Seekers 技术债清理指南:任意限制移除与死代码实施计划全解析
Skill_Seekers 技术债清理指南:任意限制移除与死代码实施计划全解析 导读 :本文基于 Skill_Seekers 仓库中的 ARBITRARY_LI
人工智能AI 应用AI 技能MCP 服务RAG数据工程网页爬虫Webiny Webhook 投递保留期配置(deliveryRetentionDays)落地指南:从实施计划到源码验证
Webiny Webhook 投递保留期配置(deliveryRetentionDays)落地指南:从实施计划到源码验证 本文围绕 Webiny 开源仓库中的一
CMS后端前端云原生AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考