Page 152 - 《软件学报》2025年第7期
P. 152
孙伟杰 等: Java 依赖异味的实证研究与统一检测技术 3073
lib2 后, 由于 mod 并未在源码中而是利用反射特性动态引入类 A, 所以 mod 不会出现构建错误, 但在运行时则会
因为无法找到类 A 而出现运行时错误.
import lib1.Dup
(a) 危害 1
包含 Dup classDup= new Dup ();
classDup.methodV1 ();
lib1.Dup 构建错误
依赖于 包含类 public class Dup{
void methodV2 ();
}
依赖库 lib1: v2.0.0
依赖于
模块 mod lib1.Dup
依赖于 包含类 public class Dup{
void method V1 ();
}
依赖库 lib2 依赖库 lib1: v1.0.0
(b) 危害 3 import lib1.RunDup
RunDup runner=new RunDup ();
包含 runner. runMtdLib ();
运行时错误
lib1.Dup
依赖于 包含类 public class Dup{
void methodV2 ();
}
依赖库 lib1: v2.0.0
依赖于
模块 mod lib1.Dup
依赖于 包含类 public class Dup{
void methodV1 ();
}
依赖库 lib2 依赖库 lib1: v1.0.0
lib2.RunDup
包含类
import lib1.Dup;
public class RunDup{
public void runMtdLib (){
Dup classDup=new Dup ();
classDup.methodV1 ();
}
}
(c) 危害 4 import lib2.RunDup
RunDup runner=new RunDup ();
System.out.println (runner.runDupMtd ());
包含
lib1.Dup
public class Dup{ 运行时语义冲突
依赖于 包含类 String methodDup (){
return “V2”
}
依赖库 lib1:v2.0.0 }
依赖于
模块 mod lib1.Dup
依赖于 包含类 public class Dup{
String methodDup (){
return “V1”
依赖库 lib2 依赖库 lib1: v1.0.0 }
}
包含类
lib2.RunDup
import lib1.Dup;
public class RunDup{
public String runDupMtd (){
Dup classDup= new Dup ();
return classDup.methodDup ()
}
}
图 A5 异味 1.3 可能危害与对应触发场景
(3) 运行时语义冲突: 其触发场景为模块直接使用但未声明的依赖库版本升级后出现签名相同但实现不同的
情况. 以图 A6(c) 为例, 模块 mod 中直接使用 lib2:1.0.0 中的方法 getVersion, 但并未在配置文件中声明 lib2, 而是
通过依赖库 lib1:4.0.0 引入 lib2:1.0.0 作为传递依赖. 然而 lib1 升级到 4.0.1 时同时升级 lib2 版本, 引入传递依赖
lib2:2.0.0, 其中方法 getVersion 的签名并未改变, 但其具体实现不同. 当 mod 调用 version 时就会出现语义冲突, 预
期调用的是 1.0.0 版本中的 getVersion, 应返回“V1”, 而实际调用的是 2.0.0 版本中的方法, 返回的是“V2”.
5) 类别 1.5 未使用依赖
未使用依赖不会如前述异味一般造成构建错误等破坏性的危害, 但会造成构建时间增加、构建产物冗余等危
害. 以下将详细阐述这些危害的具体触发场景.
(1) 构建时间增加: 这类危害在出现依赖未使用时始终存在. 以图 A7(a) 为例, 模块 mod 并未使用依赖库 lib 中
的特性, 但却在配置文件中进行声明, 导致在构建时该依赖会被引入, 需要从中央存储仓库获取对应依赖库, 进而
导致构建时间增加, 即增加构建成本.
(2) 构建产物冗余: 其触发场景为模块可执行 JAR 的打包. 以图 A7(b) 为例, 模块 mod 并未使用依赖库 lib 中

