Page 153 - 《软件学报》2025年第7期
P. 153
3074 软件学报 2025 年第 36 卷第 7 期
的特性, 但却在配置文件中进行声明. 而 Maven 和 Gradle 在将 mod 打包为可执行 JAR 时, 所有的依赖库都会被涵
盖, 包括实际上并未被使用的 lib, 导致构建产物冗余.
import lib2.A
mod 直接使用传递依赖 lib2
A classA = new A(); 中的类但未在配置文件中声明 构建错误
包含代码
lib2.A
依赖于 依赖于 包含类 public class A {
...
}
模块 mod 依赖库 lib1:v4.0.0 依赖库 lib2
如果升级至
不再 无法访达 lib2 中的类
由于 lib 升级, mod
依赖于
(a) 危害 1 依赖库 lib1:v4.0.1
Class c=Class.forName(“lib2.A”); mod 直接使用传递依赖 lib2 中 运行时错误
的类但未在配置文件中声明
包含代码
lib2.A
依赖于 依赖于 包含类 public class A {
...
}
模块 mod 依赖库 lib1: v4.0.0 依赖库 lib2
如果升级至 由于 lib 升级,
不再 依赖于 mod 无法访达 lib2 中的类
(b) 危害 3 依赖库 lib1: v4.0.1
import lib2.A
mod 直接使用传递依赖 lib2
A class A = new A (); 运行时语义冲突
String lib2Ver= A.getVersion (); 中的类但未在配置文件中声明
包含代码 Lib2.A
public class A{
依赖于 依赖于 包含类 String getVersion (){
return “V1”
}
模块 mod 依赖库 lib1: v4.0.0 依赖库 lib2: v1.0.0 }
如果升级至
Lib2.A
依赖于 包含类 public class A{
String getVersion (){
return “V2”
}
依赖库 lib1: v4.0.1 依赖库 lib2: v2.0.0 }
(c) 危害 4 lib2 从 v1.0.0 升级至 v2.0.0
图 A6 异味 1.4 可能危害与对应触发场景
(3) 下游模块构建时间增加: 这类危害在出现依赖未使用时始终存在. 以图 A7(c) 为例, 模块 mod 并未使用依
赖库 lib 中的特性, 但却在配置文件中进行声明. 下游模块 client 在使用模块 mod 的同时, 也就引入了 mod 的依赖
库 lib 作为传递依赖, 其会在构建时被引入, 需要从中央存储仓库获取对应依赖库, 进而导致构建时间增加, 即增加
下游模块 client 的构建成本.
(4) 下游模块构建产物冗余: 其触发场景为下游模块可执行 JAR 的打包. 以图 A7(d) 为例, 模块 mod 并未使用
依赖库 lib 中的特性, 但却在配置文件中进行声明. 下游模块 client 在使用模块 mod 的同时, 也引入了 mod 的依赖
库 lib 作为传递依赖, 而 Maven 和 Gradle 在将 client 打包为可执行 JAR 时, 所有直接依赖和传递依赖库都会被包
括, 导致下游模块 lib 构建产物冗余.
6) 类别 1.6 依赖范围误用
Maven 与 Gradle 均定义了多种依赖范围, 若未按预期正确使用这些范围, 可能引发多种类型的危害. 我们在
正文表 5 和表 6 中对这些危害进行了梳理. 下文将详细阐述正文中未能详述的各类危害及其触发场景.
(1) 构建错误: 以预期依赖范围为 compile, 实际范围为 test 为例, 在图 A8(a) 中, 模块 mod 在源码中直接引用
lib 中的类 A, 因此其预期依赖范围应该是 compile, 但其实际依赖范围被设置为 test, 代表其只会在测试时出现而
不会在构建时出现, 这就导致 mod 构建时无法找到类 A, 最终构建错误.
(2) 构建时间增加: 以预期范围为 test, 实际范围为 compile 为例, 在图 A8(b) 中, 模块 mod 将直接依赖 lib 的依

