diff --git a/docs/release-notes/v3.0.0.md b/docs/release-notes/v3.0.0.md deleted file mode 100644 index d8af38f7..00000000 --- a/docs/release-notes/v3.0.0.md +++ /dev/null @@ -1,99 +0,0 @@ -# 停更三年后,我用两个 AI Agent 把 AspectCore 推到了 v3.0 - -AspectCore 是我 2017 年开始做的一个 .NET AOP 框架,挂在 NCC(.NET Core Community)下面,NuGet 累计下载量过百万。2023 年 v2.4.0 之后我没怎么维护了,issue 积了 19 个,最老的挂了五年。其实也不是不想维护,就是工作忙起来之后,一个人的开源项目很容易就放下了,放下越久越不想捡。 - -今年我把它捡了回来,但做法变了。我拉了两个 AI coding agent 进团队,一个写代码,一个审代码,我自己只管定方向。十天后发了 v3.0.0-rc.1。听起来像噱头,但从我的经验看,这次确实跑通了。下面把过程记下来,也包括中间踩的坑和还没解决的问题。 - ---- - -## 为什么现在重新做 - -两件事赶一块了。 - -一是 .NET 在快速淘汰运行时代码生成。NativeAOT 到 .NET 10 已经是 LTS,`System.Reflection.Emit` 在 AOT 下直接报错,而 AspectCore 整个拦截管道都建在 Emit 上。不动就等着被淘汰。Castle DynamicProxy 到今天也没解决同样的问题,这对 AspectCore 来说是个窗口,但窗口不会一直开着。 - -二是我有个私心:想看看 AI Agent 能不能真的交付一个完整版本,不是 demo 级别,是能发版的那种。之前试过让 Agent 写点零碎的功能,但从设计到实现到测试到发版的完整迭代,我也没试过。 - ---- - -## 先把 CI 搞起来 - -在让 Agent 碰代码之前,我先花了几天搞了个看起来很无聊的事:搭 CI。原来的 CI 是 AppVeyor 上一个跑不动的老 pipeline,我迁到 GitHub Actions,然后一层层加门禁:单测覆盖率 95%、E2E 覆盖率 80%、Lint、CodeQL、NativeAOT 发布验证。测试从几百个推到两千八百多个。 - -为什么非要这么做?原因很简单:Agent 的代码质量不靠我盯,靠 CI 卡。评审 Agent 的规则很死板,但死板正是我要的——CI 不绿就不合并,覆盖率差 0.13% 也打回。我们有个 PR 因为 E2E 覆盖率差 0.13%(79.87% < 80%)被打回,Agent 老老实实补了两个测试,推到 80.32% 才过。没有这层卡点,这 0.13% 的缺口就会被忽略,积少成多就是技术债。 - -同时我写了 AGENTS.md,把项目结构、构建命令、Git 规范、模块边界写清楚,相当于给 Agent 写一份新人入职手册。没有这份上下文,Agent 会猜错目录、用错命令、往错误的分支提交。花半天写清楚,后面每个任务少走一圈弯路,我觉得很值。 - ---- - -## 日常怎么跑 - -我定方向。比如"默认引擎保持 DynamicProxy 不变",这个决定基于社区迁移成本,Agent 算不出来。"TFM 最低 net6.0""先做 benchmark 再做迁移指南",这些也是我拍的。涉及用户迁移成本和社区心理的判断,目前还得人来。 - -实现 Agent 负责所有工程执行,方案它写,代码它写,测试它跑,CI 挂了它修,PR 全是它的。评审 Agent 对每个 PR 做 code review,检查 CI、审逻辑、打回说理由、通过了合并,它有独立的 GitHub 账号操作,我不在中间经手。另外还有个内部 agent 专门在设计阶段做结构化对抗,专找兼容性和平台限制的问题。 - -这套流程不是一开始就顺的。前几天 Agent 经常猜错模块边界,或者写出能跑但不符合项目约定的代码,被评审 agent 打回好几次才慢慢对齐。AGENTS.md 也是在这个过程中不断补的,不是一次写完的。 - ---- - -## 设计方案被打回四轮 - -NativeAOT 是整个 v3 最难的部分,设计方案写了四版才通过。 - -前两轮是低级错误:文档和口头结论不一致、DIM 在 netstandard2.0 下编译不过。按说实现 agent 应该在方案里就注意到这些,但它没有,倒是评审 agent 抓到了。这也说明设计阶段的对抗评审不是多余的,很多问题到实现阶段才发现的话,修的成本会高得多。 - -第三轮比较有意思。方案里 IAsyncEnumerable 的代码模板在 `try/catch` 块内放了 `yield return`,这违反 C# 语言规则 CS1626,生成出来的代码压根编译不了。这种问题不跑编译器你想不到,但评审 agent 做了语言规则层面的形式化检查,指了出来。第四轮才通过。 - -没有这几轮打回,后面实现阶段肯定要返工。Code review 顶多发现命名、边界条件这种局部问题,设计评审抓的是接口兼容性、平台限制、语言规则,后者修起来成本是前者的十倍。从我的经验看,设计评审比 code review 更该让 Agent 做对抗。 - ---- - -## 最终做了什么 - -十天,从 v2.4.0 到 v3.0.0-rc.1,ROADMAP 短中期全部清完。 - -NativeAOT 支持是主线。Source Generator 引擎在编译时为每个被拦截方法生成强类型调度委托,运行时直接调用,在已验证的 16 个场景中避免了 Emit 与 Expression.Compile;但部分动态代码路径(如 ProxyTypeCompiler、ILEmitVisitor、MethodReflector 及泛型回退的 MakeGenericMethod)仍会在 NativeAOT 发布时触发分析警告。这 16 个场景在原生二进制里验证通过,冷启动 18ms。Castle DynamicProxy 不支持,Metalama 是闭源的编译时方案但不是代理模式,开源的运行时+编译时双引擎 AOP 框架里,这应该是第一个。当然这个判断基于我目前的了解,如果有遗漏欢迎指出。 - -性能这边有个剧情反转。一开始 Source Generator 引擎比 DynamicProxy 慢 38%,inline activation 每次分配 object[] 和各种 Tuple,把编译时委托的优势全吃了。三轮优化后翻过来了:SG 比 DP 快 49%,内存分配少 69%。核心手段是条件编译,net8.0+ 用 ObjectPool 和 FrozenDictionary,net6.0 走 fallback,不破坏兼容。 - -| 场景 | v2.x (DP) | v3.0 (SG) | -|------|:---:|:---:| -| 同步方法拦截 | 220 ns / 360 B | 112 ns / 112 B | -| ValueTask | 1,975 ns / 929 B | 1,514 ns / 232 B | -| NativeAOT 冷启动 | 不可用 | 18 ms | - -还做了几件事:C# 9-13 语言特性全覆盖(record、init-only、主构造函数、partial properties、ref return、async enumerable),Castle 迁移工具(双向适配器、功能对比、竞品 benchmark、迁移指南),19 个历史 issue 清理到剩 1 个。 - -有些事情还没做完。NativeAOT 目前只覆盖了 Source Generator 路径,DynamicProxy 路径在 AOT 下还是不可用;benchmark 只跑了常见场景,极端边界条件的性能数据还没系统收集;Castle 迁移工具的自动化程度还不够,很多配置需要手动改。这些留到后续版本。 - ---- - -## 跑个数 - -| | | -|---|---| -| 合并 PR | 50+ | -| 测试用例 | 2800+ | -| Benchmark 点 | 74 | -| NativeAOT 验证 | 16/16 | -| Issue 处理 | 18/19 | -| CI checks | 8 项全绿 | -| 时间 | 10 天 | - ---- - -## 一些观察 - -门禁是 Agent 协作的最低条件。没有覆盖率卡点和 CI 强制检查的项目,不适合交给 Agent 提 PR。Agent 不会自己判断"这个改动会不会搞坏别的模块",但 CI 会。 - -AGENTS.md 不是文档债务,是生产力投资。花半天把项目结构和规范写清楚,换来的是 Agent 后面每个任务少走一圈弯路。 - -当然这套方式也不是没有局限。涉及架构方向、用户迁移成本、社区接受度的判断,Agent 能给选项和 tradeoff 分析,但拍板还得是人。另外 Agent 对项目历史的理解完全依赖你提供的上下文,它不会"知道"三年前为什么做某个设计决定,除非你写进 AGENTS.md 或者在任务里说明。 - ---- - -三年前我以为这个项目到头了。现在它跑在 NativeAOT 上,冷启动 18 毫秒,同步拦截 112 纳秒。但还有不少坑要填,NativeAOT 的 DynamicProxy 路径、更全面的 benchmark、Castle 迁移的自动化,这些都是后面要做的事。 - -下次你看到一个停更的开源项目——试试拉两个 Agent。至少对我来说,这次改变了我对"一个人能维护多少项目"的预期。 - -https://github.com/dotnetcore/AspectCore-Framework