写点什么

代码手术刀 - 自定义你的代码重构工具

  • 2024-02-06
    北京
  • 本文字数:3671 字

    阅读完需:约 12 分钟

代码手术刀-自定义你的代码重构工具

​前言笔者近日在做代码仓库的存量代码缩减工作,首先考虑的是基于静态扫描的缩减,尝试使用了很多工具来对代码进行优化,例如 PMD、IDEA 自带的 inspect 功能、findBugs 等。但是无一例外,要么过于“保守”,只给出扫描结果,但是无法实现一键优化,要么直接就是有 bug(这里特指 IDEA2023.1.5 专业版-inspect 功能扫描 problems 清单里的 unused declaration)。对于懒人而言,挨个手动点击几百次按钮和坐牢无异,遂自己写了一个工具对大部分已明确的优化点进行一键修改(具体是使用 lombok 的 @Data 注解替换显式的 getter/setter 以及 toString 方法)。


本文内容主要分为三个部分,第一部分详细讲述工具实现的思路,第二部分会对用到的开源工具 javaParser 进行简要的介绍,第三部分提供了工具细致的使用说明。


实现思路在翻阅历史代码时,发现不少工程仓库里很多类依然是用的 IDE 生成的 getter/setter,如果使用 Lombok 的 @Data 注解替换,可以带来几个优点。


•显而易见的是,能够使代码变得更加整洁,减少代码量,并且减少今后新增字段时带来的重复劳动。


•可读性得到了提高,在其他同事参与开发时无需检查 getter/setter 里是否做了逻辑。


•避免遗漏,减少犯错的风险,之前因为其他同事的接口数据漏写 get 方法,徒增了不少的沟通成本。


回过头来看,如果我们要写一个工具,对整个代码工程所有类进行全量扫描,并且使用 lombok 来替换其中的“没有特殊逻辑”的 getter 和 setter,需要哪些步骤。


1.扫描整个工程代码,可以是多模块的工程。


2.读取其中的“.java”文件。


3.过滤其中不需要的类,例如 interface,没有 field 的类(大概率是作为 service 出现),注解的声明等等。


4.删除 getter/setter 方法,这里需要判断在 get 和 set 方法里是否有特殊逻辑。


5.给类打上 @Data 注解,并且把 lombok 包引入进来。


6.把修改后的内容写入 java 文件。


下面对每个步骤的实现进行说明。


工程扫描工程扫描比较简单,给一个工程路径,然后递归调用,过滤出所有的.java 文件即可。


private static List<File> scanJavaFiles(File file) {    List<File> result = Lists.newArrayList();    if (file.isDirectory()) {        File[] files = file.listFiles();        if (files == null) {            return result;        }        for (File f : files) {            result.addAll(scanJavaFiles(f));        }    }    if (file.getName().endsWith(".java")) {        result.add(file);    }    return result;}
复制代码


使用注解替换 getter/setter 在拿到所有文件的列表之后,需要对其进行处理。


1.过滤掉无字段的类。


2.过滤掉已经使用 lombok 注解的类。


3.判断是否有显式 getter/setter(这里需要注意,boolean 类型的字段需要特殊处理)


4.判断 getter/setter 是否为简单的返回和赋值操作。


5.删除 getter/setter。


6.添加 @Data 注解。


7.添加 lombok 包的引入。

​​

这里使用 github 上开源的工具 javaParser 来对类进行解析、代码提取、删除以及内容新增,javaParser 会在下一章节进行介绍。


这里简单粗暴了一些,直接使用 equals 判断方法体,其实 javaParser 提供了更完善的 api 来分析语义。


​​

更新 java 文件

在完成对代码的清理之后,需要将内容更新到 java 文件,CompilationUnit 重写了 toString 方法,可以支持直接将代码转换成字符串的形式。



JavaParser 介绍

JavaParser 是什么?

JavaParser 是一个开源的 Java 源代码分析工具,它提供了一系列简单的 API 来解析、修改和生成 Java 代码。



编辑

举个例子,我们可以使用 javaparser 轻松的实现下面几个操作:

1.分析代码中的类、方法、字段等元素,提取类的继承关系、方法的参数和返回类型等。

2.更改源码,例如重命名方法、修改方法体、添加或删除代码行等。

3.可以使用它来生成代码片段,例如创建新的类、方法或字段,或者生成代码文档。

在上一章节里就用到了数据提取,源码替换功能。这里附上 JavaParser 的相关链接:

官网:JavaParser - Home github:https://github.com/javaparser/javaparser wiki:https://github.com/javaparser/javaparser.wiki.git javadoc:javaparser-core 3.25.8 javadoc (com.github.javaparser)

核心组件

JavaParser 的主要构成由以下几个组件组成:

1.Lexer(词法分析器):词法分析器的作用是读取源代码文本,并将其分解成一系列的词法单元(tokens),如关键字、标识符、字面量、运算符等。这是解析过程的第一步。



通常不需要咱们显式调用,JavaParser 将具体的细节实现隐藏在内部,调用方只需要使用开放 api 即可完成源码到 AST 的转换。具体可以翻阅com.github.javaparser.GeneratedJavaParser

2. Parser(语法解析器):语法分析器接收词法分析器生成的 tokens,并根据 Java 语言的语法规则将它们组合成各种语法结构,如表达式、语句、类定义等。这个过程构建出一个抽象语法树(AST)。

com.github.javaparser.JavaParser这是最常用的类,用于触发解析过程并生成 AST,在上一章节中,使用 StaticJavaParser 将源文件解析成 CompilationUnit,在 parse 方法的内部使用了 JavaParser 完成这一解析过程。

3. AST(抽象语法树):AST 是 JavaParser 的核心数据结构,它以层次化的方式表示了源代码的结构。AST 由一系列的节点组成,每个节点表示源代码中的一个元素,如类、方法、字段、表达式等。每个节点都包含有关该元素的信息,例如名称、类型、修饰符等。

AST 是后续操作(如遍历、分析、修改)的基础,也是使用方操作最频繁的类。上一章节使用的com.github.javaparser.ast.CompilationUnit是一个非常重要的类,它代表了 Java 源代码文件的根节点,是这个结构的抽象表示,包含整个文件的结构,例如:

• 包声明(Package Declaration)

•导入声明(Imports)

•类型声明(Type Declarations),这可能是类、接口、枚举或注解

•注释(Comments)

•任何顶级的注解

通过操作 CompilationUnit 提供的公有方法,可以访问和修改文件中的元素。包括:

•获取和设置包声明

•获取和添加导入声明

•获取和添加类型声明

•获取和添加注释

•使用访问者模式来遍历 AST 中的节点



4. Visitors(访问器):顾名思义,这是一个采用访问者模式设计的组件,可以用于遍历和操作 AST 。开发者可以编写自定义的 Visitors,通过遍历 AST 来访问特定类型的节点,执行代码分析、重构、生成等任务。

com.github.javaparser.ast.visitor.GenericVisitorcom.github.javaparser.ast.visitor.VoidVisitor这两个访问器提供了默认实现,如果需要自定义访问器,可以继承它们来实现自己的业务逻辑。

5. Printer(打印器):这个也很好理解,Printer 用于将 AST 转换回 Java 源代码的字符串表示形式。它可以将修改后的 AST 打印回原始源代码文件,或将 AST 打印为格式化的代码字符串。在上一章节的最后提到的 CompilationUnit 重写的 toString 方法,实际上就是使用了 Printer 来完成 AST 到源码字符串的转换。

上面的一些组件是 javaParser 中比较核心且常用的部分,当然 javaParser 还提供了一些便捷的工具类以及用法,这些内容笔者也没有接触过,有需要的读者可以自行翻阅文档。

工具使用方式

第一章提到的 jar 包和源码均已上传私服,可以直接通过 maven 插件的方式运行。

添加依赖

        <dependency>            <groupId>com.jd.omni.opdd</groupId>            <artifactId>lombok-replace</artifactId>            <version>0.0.1-SNAPSHOT</version>        </dependency>

复制代码


添加 maven 插件

            <plugin>                <groupId>org.codehaus.mojo</groupId>                <artifactId>exec-maven-plugin</artifactId>                <version>3.0.0</version>                <executions>                    <execution>                        <goals>                            <goal>java</goal>                        </goals>                    </execution>                </executions>                <configuration>                    <mainClass>com.jd.omni.opdd.tools.lombok.LombokConverter</mainClass>                    <arguments>                        <argument>../../pop-jingme-customs</argument>                    </arguments>                </configuration>            </plugin>

复制代码

插件中的 argument 节点需要替换成工程的路径,可以是绝对路径也可以是相对路径

执行插件

执行 mvn exec:java


​可以在控制台看到:


注意事项

使用工具处理完成后,一定要 build 的一下检查是否有编译错误,虽然在删除操作时做了较为严苛的校验,但是有些特殊的变量名可能没有考虑到,这部分问题是可以通过编译检查出来的。

另外,对于没有引用 lombok 的模块,需要手动添加依赖。

写在最后

代码重构应该像手术刀一样,快、准、狠,正所谓君子生非异也,善假于物也。本文主要起一个抛砖引玉的作用,重点在于 JavaParser 的介绍,笔者写的这个小工具非常简单,之前也写过 B-PaaS 一键生成 matrix.json,一键根据错误码定义生成 i18n 文件,大都不难。


作者:京东零售全渠道生态 谭磊

来源:京东零售技术 转载请注明来源


发布于: 刚刚阅读数: 4
用户头像

还未添加个人签名 2024-01-12 加入

京东零售那些事,有品、有调又有料的研发资讯,带你深入了解程序猿的生活和工作。

评论

发布
暂无评论
代码手术刀-自定义你的代码重构工具_Java_京东零售技术_InfoQ写作社区