Page 148 - 《软件学报》2025年第7期
P. 148
孙伟杰 等: Java 依赖异味的实证研究与统一检测技术 3069
现形式, 因为它们有着不同的版本调停机制. 在出现版本冲突时, Maven 会选择依赖树中离模块 (也就是树根) 最
近的依赖库; 而 Gradle 会选择较高版本的依赖库. 以#SCG-79 [91] 为例, 模块的依赖树中同时存在 google-auth-library-
credentials:0.4.0 和 google-auth-library-credentials:0.7.0. 此时, Maven 选择版本 0.4.0, 而 Gradle 选择版本 0.7.0. 由于
模块中实际使用的是 0.7.0 版本, 引用了 0.4.0 版本中不存在的类 ServiceAccountSigner, 因此依赖管理工具若为
Maven 则会出现 ClassNotFoundException, 若为 Gradle 则不会.
4) 类别 1.4 未声明依赖: 模块直接使用了未在配置文件中声明的依赖库 (6/47). 模块源代码中使用某依赖库中
[5]
的类, 但此依赖库并未在项目的配置文件中得到明确声明. 在图 A1(d) 对应的#EL-1849 中, 模块使用了依赖库
asm 但未在自身的配置文件中声明, 但直接依赖 moxy 引入了 asm 作为其传递依赖, 从而让模块能够正常访问
asm 中的类以进行构建和运行. 最终开发者将 asm 声明为直接依赖, 解决了依赖库未声明的情况.
这类异味在 Maven 和 Gradle 中皆存在, 产生原因在于未声明的依赖库同时作为传递依赖存在, 这使得在模块
构建或者运行时并不会直接报告缺少声明的依赖库. 这种情况下, 开发者会误以为依赖库已经被正确地声明了, 因
为模块能够正常工作.
5) 类别 1.5 未使用依赖: 模块在配置文件中声明的依赖库实际上并未被使用 (6/47). 模块源代码中并未使用某
依赖库中的任何类, 但此依赖库却在配置文件中声明. 在图 A1(e) 对应的#HFJS-581 [50] 中, 项目在构建文件中声明
了依赖库 javax-samples, 但实际上并未引用其中类. 最终开发者在配置文件中移除了依赖库 javax-samples.
这类异味在 Maven 和 Gradle 中皆存在, 产生原因主要是 Maven 和 Gradle 只会在无法找到对应依赖库时报
错, 而不会在出现冗余依赖库时报错. 这意味着当模块中存在未被使用的依赖库时, 构建工具并不会发出警告或报
错信息. 因此, 开发者很容易忽视这些未使用的依赖库, 错误地认为它们是模块所需的一部分.
A1.2 项目粒度
1) 类别 2.1 构建工具配置缺失: 项目中缺少对其所使用构建工具的配置 (2/47). 项目中缺少所使用的构建工具
的版本等关键信息的配置文件. 在图 A2(a) 对应的#PK-441 [92] 中, 项目中缺少了对 Maven 必要的文件如 maven-
wrapper.properties, 导致无法准确确定使用的 Maven 版本. 最终这种不一致性导致了开发环境与持续集成 (CI) 中
Maven 版本的不匹配, 进而导致项目输出的不一致.
开发过程中使用
maven-wrapper.properties Maven
Maven v3.8.6 maven-wrapper.properties
项目 pr 运行时使用 Maven v3.9.0 缺失
(a) 类型 2.1
调用封装器脚本 调用封装器 JAR
gradle-wrapper.jar
项目 pr Gradle 封装器脚本 gradlew gradle-wrapper.jar 缺失
(b) 类型 2.2
调用封装器脚本以 调用封装器 JAR gradle-wrapper.jar
启动 Gradlev5.6.4 gradle-wrapper.jar 异常
项目 pr Gradle 封装器脚本 gradlew 冲突
(c) 类型 2.3 v5.6.4 版本的预期校验和: 3dc39a 文件的实际校验和: ad63ba
包含 依赖于
模块间依赖库未统一管理
项目 pr 模块 mod1 依赖库 lib: v1.0.0
包含
依赖于 未集中管理
(d) 类型 2.4 模块 mod2 依赖库 lib: v1.0.0
包含 依赖于
模块间依赖库版本冲突
项目 pr 模块 mod1 依赖库 lib: v1.0.0
包含
依赖于 冲突
(e) 类型 2.5 模块 mod2 依赖库 lib: v2.0.0
图 A2 项目粒度依赖异味特征实例.

