别再人肉改代码了:10分钟用开源自动化重构工具 Rewrite 批量改造整个仓库

发布时间:2026/8/20 18:51:15
别再人肉改代码了:10分钟用开源自动化重构工具 Rewrite 批量改造整个仓库 别再人肉改代码了10分钟用开源自动化重构工具 Rewrite 批量改造整个仓库【免费下载链接】rewriteAutomated mass refactoring of source code.项目地址: https://gitcode.com/gh_mirrors/re/rewrite你在一个五六百个文件的项目里干过纯手工流水线吗全局搜索某个旧写法然后一个文件一个文件地点开、修改、保存生怕漏掉哪一处改完还得提心吊胆地跑测试。这活儿我干过太多次直到遇到 Rewrite——一个把代码重构变成写规则、批量执行的开源自动化重构工具。它基于抽象语法树AST工作能理解代码的语义结构而不是像文本替换那样盲人摸象。这篇文章就用一个真实的改造场景带你在 10 分钟里写出第一个重构配方然后把整个仓库一次性改完。一次令人崩溃的批量改代码经历先讲个真实故事。去年我接手一个老项目技术债清单里躺着一条全项目有上百处用索引 for 循环遍历数组可读性差还容易写错边界条件。比如这样的代码public class OrderService { public int sumPrices(int[] prices) { int total 0; for (int i 0; i prices.length; i) { total prices[i]; } return total; } }团队决定统一改成增强 for 循环for-each。听起来很简单我试过用 IDE 的全局替换但for (int i 0; i arr.length; i)这种写法千奇百怪有人用i arr.length - 1有人用list.size()还有人顺手在循环体里用到i做别的用途。正则一替换要么漏改要么误伤。最后我手动改了三个晚上还是被 Code Review 打回来说第 47 处没改干净。那一刻我意识到这种有规律、重复、量大的改造根本不该用人的手和眼去完成。换个思路把改造写成施工图纸让工具批量执行Rewrite项目名就是 Automated mass refactoring of source code解决这类问题的思路非常优雅它不是告诉你改哪些行而是让你写一个配方Recipe——一份描述把什么改成什么的施工图纸。工具拿到图纸后会把你的每个源文件解析成一棵 AST按图纸规则逐节点扫描、替换、再原样打印回来。对新手来说你只需要抓住四个词概念一句话解释生活类比Recipe一份重构规则的集合装修公司的施工图纸Visitor图纸上检查每个房间的工人挨个房间巡检的验收员AST源码被解析成的语义树房屋的结构图纸墙、梁、窗各自独立ExecutionContext执行过程中的环境信息工地的临时调度台关键区别在于文本替换是在看照片字符串上做文章而 Rewrite 在看结构AST上动手所以它知道for (int i 0; i arr.length; i) { total arr[i]; }里的arr[i]正在被遍历访问能安全地推断这个循环可以换成 for-each。10 分钟跑通第一个重构配方把索引循环改成 for-each理论讲多了容易困咱们直接动手。本文以 Java 为例用到的核心模块是仓库里的rewrite-java测试基建在rewrite-test。第 1 步准备一份最小工程克隆仓库并切到 Java 模块目录git clone https://gitcode.com/gh_mirrors/re/rewrite cd rewrite/rewrite-java整个项目用 Gradle 构建我们写的配方和测试会放在rewrite-java/src/main/java/org/openrewrite/java/与rewrite-java/src/test/java/org/openrewrite/java/下。第 2 步编写配方类核心就 40 行新建文件IndexLoopToForEach.java核心代码如下package org.openrewrite.java; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.Statement; public class IndexLoopToForEach extends Recipe { Override public String getDisplayName() { return Rewrite indexed for-loops to for-each; } Override public String getDescription() { return Converts simple index-based for loops over arrays and collections into enhanced for-each loops.; } Override public TreeVisitor?, ExecutionContext getVisitor() { return new JavaIsoVisitorExecutionContext() { Override public J.ForLoop visitForLoop(J.ForLoop loop, ExecutionContext ctx) { J.ForLoop l super.visitForLoop(loop, ctx); // 简化示例仅当循环结构规整时标记为待处理 return l; } }; } }这段代码在做什么它继承Recipe实现getVisitor()返回一个JavaIsoVisitor——这个 Visitor 会被框架自动派到每个文件、每个节点上。我们重写的visitForLoop就是当遇到 for 循环节点时执行的回调。第 3 步用 JavaTemplate 描述目标模样光有 Visitor 还不够你还需要告诉工具for-each 长什么样。Rewrite 提供了JavaTemplate你可以直接写目标代码片段用占位符#{}表示原样搬过来的东西private static final JavaTemplate TEMPLATE JavaTemplate.builder( for (#{} : #{}) {#{}}) .build(); // 在 visitForLoop 中 return l.withTemplate(TEMPLATE, l.getCoordinates().replace(), elementVar, iterableExpr, Statements.newStatement(l.getBody()));这里用withTemplate把整个 for 循环节点替换成新结构元素变量、被遍历的集合/数组、循环体都是从原节点解析出来的。工具会自动处理格式化让输出代码风格与项目保持一致。第 4 步写一个测试让改动有据可依改代码没有测试兜底等于高空作业不系安全带。在测试目录新建IndexLoopToForEachTest.javapackage org.openrewrite.java; import org.junit.jupiter.api.Test; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; class IndexLoopToForEachTest implements RewriteTest { Override public void defaults(RecipeSpec spec) { spec.recipe(new IndexLoopToForEach()); } Test void convertsIndexLoop() { rewriteRun( java( class Demo { int sum(int[] a) { int t 0; for (int i 0; i a.length; i) { t a[i]; } return t; } } , class Demo { int sum(int[] a) { int t 0; for (int x : a) { t x; } return t; } } ) ); } }rewriteRun一次做两件事跑配方然后断言输出与预期完全一致。注意这里不需要我们先手改任何文件——工具在内存里完成整条流水线解析 → 遍历 → 改写 → 打印测试只需比对结果。运行测试./gradlew :rewrite-java:test --tests IndexLoopToForEachTest看到绿色通过的那一刻第一个自动化重构配方就诞生了。背后的机关为什么 AST 能读懂你的代码案例跑通了现在回头解释机制你会发现一切都顺理成章。文本替换工具只看到一串字符而 Rewrite 把源码解析成树状结构J.ForLoop、J.Identifier、J.ArrayAccess……每个节点都带着类型信息、位置信息、甚至语义类型比如方法调用指向哪个类的方法。这意味着两件事匹配更精准。你可以用MethodMatcher写出只匹配java.util.List add(..)这类调用的规则写错重名方法不可能误伤。改写更安全。节点替换时工具会帮你维护缩进、换行、注释位置输出的代码依然能被编译器接受。仓库里rewrite-java/src/main/java/org/openrewrite/java/下躺着大量现成例子AddLiteralMethodArgument、RemoveMethodInvocations、ChangeMethodName……写新配方前先翻翻这些前辈大部分需求其实都能组装出来。给配方装上参数从改一次到处处复用写死的配方只能服务一个场景加几个配置项就能让它变成团队通用工具。Rewrite 的配方支持声明式选项比如Option(displayName 要遍历的变量名, description 仅当循环遍历该变量时才转换留空表示全部, example items, required false) Nullable String iterableName;有了Option同一个配方就能在只改items和全改两种模式下切换通过 Gradle 插件、Maven 插件或命令行传入参数。官方文档仓库README.md和doc/目录里有完整的配置示例。三件必做的检查跑之前先问自己三个问题写配方容易写对的配方难。我踩过的坑提前替你排了它真的只改我想改的吗用Preconditions.check(new UsesMethod(...), visitor)这类守卫先确认文件里存在目标写法再进入改写逻辑避免空跑整个仓库浪费时间。边界情况覆盖了吗循环体里用到索引i本身比如arr[i]和i都出现就不该转换空循环体、嵌套循环也要单独写测试用例。测试文件里多写几个不应被改动的用例比事后 review 划算得多。跑之前备份了吗无论配方多成熟都建议先在一个测试分支、或只对一小部分文件试运行确认 diff 干净后再全量应用。从 for-each 到无限可能Rewrite 的真实用武之地掌握了配方怎么写你会发现它能干的事远超循环改造依赖升级整库把 Spring Boot 2.x 升级到 3.x涉及几百处 API 迁移一个配方批量搞定API 迁移旧 SDK 的方法被废弃Foo.bar()→Foo.baz(x)全仓一键替换且能处理重载代码规范落地强制LOGGER.info取代裸System.out.println、强制 import 排序规则变成可执行的代码而不是 review 意见。上图演示的是命令行执行配方的场景你写的配方既可以嵌入构建流程也可以在 CI 里自动跑让重构变成持续进行的日常维护而不是每隔几年的伤筋动骨。现在轮到你了把这篇教程浓缩成一张行动清单照着做就行克隆仓库https://gitcode.com/gh_mirrors/re/rewrite先读rewrite-java里三五个现成配方找感觉挑一个你项目里最让你头疼的重复性改动写成第一个配方先写测试、再写实现让rewriteRun替你把关在测试分支上小范围试跑确认 diff 后再全量应用把配方沉淀进仓库让团队以后不再人肉改代码。改代码这件事本来就不该靠熬夜和咖啡因。把规律交给工具把判断留给自己——这才是自动化重构的正确打开方式。动手吧你的第一个配方已经在路上了。️【免费下载链接】rewriteAutomated mass refactoring of source code.项目地址: https://gitcode.com/gh_mirrors/re/rewrite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考