Page 151 - 《软件学报》2025年第7期
P. 151
3072 软件学报 2025 年第 36 卷第 7 期
版本会被遮蔽, 但 mod 在源码中试图调用仅存在于 1.0.0 版本中的方法 methodV1, 这导致模块构建错误.
(a) 危害 1 import lib.Dup
包含 Dup classDup=new Dup (); 构建错误
classDup.methodLib2 ();
lib.Dup
依赖于 包含类 public class Dup{
void methodLib1 ();
}
依赖库 lib1
依赖于
模块 mod lib.Dup
包含类 public class Dup{ 被忽略!
void methodLib2 ();
}
依赖库 lib2
import lib.RunDup
(b) 危害 3
RunDup runner=new RunDup ();
包含 runner.runMtdLib ();
运行时错误
lib.Dup
依赖于 包含类 public class Dup{
void methodLib1 ();
}
依赖库 lib1 被忽略!
依赖于
模块 mod lib.Dup
包含类 public class Dup{
void methodLib2 ();
}
包含类
依赖库 lib2 lib.RunDup
import lib.Dup;
public class RunDup{
public void runMtdLib (){
Dup classDup= new Dup ();
classDup.methodLib2 ();
}
}
(c) 危害 4 import lib.Dup
包含 Dup classDup= new Dup (); 运行时语义冲突
System.out.println (classDup.methodDup ());
lib.Dup
依赖于 包含类 public class Dup{
String methodDup (){
return “Lib1”
模块 mod 依赖于 依赖库 lib1 }
包含类 }
lib.Dup
public class Dup{
依赖库 lib2
String methodDup (){
return “Lib2”
}
}
图 A4 异味 1.2 可能危害与对应触发场景
(2) 运行时错误: 其触发场景为模块运行时调用了仅存在于被遮蔽的库中的特性. 以图 A5(b) 为例, 模块 mod
直接依赖于 lib1:2.0.0 和 lib2, lib2 又依赖于 lib1:1.0.0, 并且在 runMtdLib 中调用了仅存在于 lib1:1.0.0 中的方法
methodV1. 当 mod 试图调用 runMtdLib 时, 由于 lib1:1.0.0 被遮蔽, 最终出现运行时错误.
(3) 运行时语义冲突: 其触发场景为模块运行时调用了被遮蔽库中签名相同但实现不同的方法. 以图 A5(c) 为
例, 模块 mod 的依赖树中同时存在 lib1 的 1.0.0 和 2.0.0 版本, 两个版本的 lib1 中都包含签名相同的 methodDup 方
法, 但其具体实现不同. 当 mod 调用 methodDup 时就会出现语义冲突, 预期调用的是 2.0.0 版本中的 methodDup,
应返回“V2”, 而实际调用的是 1.0.0 版本中的方法, 返回的是“V1”.
4) 类别 1.4 未声明依赖
未声明依赖可能引发一系列严重的后果, 包括但不限于模块构建错误、运行时错误以及运行时语义冲突. 以
下将详细阐述这些危害的具体触发场景.
(1) 构建错误: 其触发场景为模块源码中使用但未声明的依赖库未在依赖树中出现. 以图 A6(a) 为例, 模块
mod 中引用依赖库 lib2 中的类 A, 但并未在配置文件中声明 lib2, 而是通过依赖库 lib1:4.0.0 引入 lib2 作为传递依
赖. 然而 lib1 升级到 4.0.1 后不再依赖于 lib2, mod 的依赖树中不再存在 lib2, 无法找到对应类, 导致构建错误.
(2) 运行时错误: 其触发场景为模块运行时使用但未声明的依赖库未在依赖树中出现. 以图 A6(b) 为例, mod
利用反射特性引用依赖库 lib2 中的类 A, 但并未在配置文件中声明. 和上例类似, 在 mod 依赖树中不再存在

