Page 192 - 《软件学报》2026年第5期
P. 192
潘伟丰 等: 基于动态分析和引力公式的关键类识别 2071
END IF
END FOR
我们以图 5(a) 中的 Java 代码片段为例, 展示从执行轨迹获取方法之间调用关系的过程. 假设该代码片段对应
的 GUI 界面仅包含一个“搜索书本”按钮, 且测试用例仅涉及“点击搜索书本按钮”的操作. 为了模拟用户在 GUI 界
面上的操作, 我们通过 main() 方法 (该方法未在代码片段中定义) 调用 Bookstore 类的 searchBook() 方法来替代
GUI 操作, 以获取对应的执行轨迹. 通过对调用过程的监控, 可以获得图 5(b) 所示的执行轨迹, 并通过算法 1 获得
了方法之间的调用关系 (如图 5(c) 所示). 具体的分析过程如下: 首先, 将执行记录按照 traceID 分组, traceID 相同
的归为一组, 表示它们属于同一个 trace, 并按照 eoi 值对属于同一个 trace 的记录进行升序排列. 以图 5(b)
为例, 由于图 5(b) 是一次方法调用 (main() 调用 searchBook()) 得到的执行轨迹, 故它们具有相同的 traceID
(6675601285158273025), 属于同一个 trace. 经过升序排列后, 各记录的位置如①–⑧所示. 其次, 我们将该排序后的
trace (记录集) 输入到算法 1 中, 由算法 1 根据各记录的 ess 值自动确定方法之间的调用关系. 根据算法 1 可知, 1) 若
当前记录的 ess 值大于前一条记录的 ess 值, 这意味着前一条记录对应的方法调用了当前记录的方法. 例如, 在图 5(b)
中, 记录⑥的 ess 值 (ess=2) 大于其前一条记录⑤的 ess 值 (ess=1), 这表示记录⑤的方法 searchBook() 调用了记录
⑥的方法 getBook(Boolean). 因此, 可以得到图 5(c) 中的调用关系①. 2) 若当前记录的 ess 值小于或等于前一条记
录的 ess 值, 这说明前一条记录所代表的方法已经执行完毕. 此时, 算法 1 将执行一系列的出栈操作, 直到栈顶记
录的 ess 值小于当前记录的 ess 值, 这表示栈顶记录中的方法调用了当前记录中的方法. 例如, 在图 5(b) 中, 记录⑦
的 ess 值 (ess=2) 等于其前一条记录⑥的 ess 值, 这表示记录⑥对应的方法 getBook(Boolean) 已执行完毕, 此时我
们需要回溯到调用 getBook(Boolean) 的 searchBook() 方法处, 并得到 searchBook() 和 getOffers() 之间的调用关系
(图 5(c) 中的调用关系②). 同理, 我们可以得到示例代码中的其他调用关系 (如图 5(c)).
public class BookStore { $1;1731815295558386900;public org.example.Catalog.<init>();<no-session-
private final Catalog catalog/* GVR */ = new Catalog()/* INS */; id>;6675601285158273025;1731815295558183600;1731815295558203300;DESKTOP-0JCC7QM;2;2 ③
private final CRM crm/* GVR */ = new CRM(catalog)/* INS */; $1;1731815295562346300;public org.example.CRM.<init>(org.example.Catalog);<no-session-
public void searchBook() { id>;6675601285158273025;1731815295562335100;1731815295562342600;DESKTOP-0JCC7QM;3;2 ④
catalog/* ACC*/.getBook(false);/* MEC */ $1;1731815295562375900;public org.example.BookStore.<init>();<no-session-
crm/* ACC */.getOffers();/* MEC */ id>;6675601285158273025;1731815295554350300;1731815295562373300;DESKTOP-0JCC7QM;1;1 ②
} $1;1731815295563238100;public void org.example.Catalog.getBook(boolean);<no-session-
} id>;6675601285158273025;1731815295563217900;1731815295563233800;DESKTOP-0JCC7QM;5;2 ⑥
$1;1731815295563287100;public void org.example.Catalog.getBook(boolean);<no-session-
public interface CatalogInterface { id>;6675601285158273025;1731815295563281300;1731815295563284900;DESKTOP-0JCC7QM;7;3 ⑧
void getBook(final boolean complexQuery); $1;1731815295563295200;public void org.example.CRM.getOffers();<no-session-
} id>;6675601285158273025;1731815295563270800;1731815295563293600;DESKTOP-0JCC7QM;6;2 ⑦
$1;1731815295563302000;public void org.example.BookStore.searchBook();<no-session- ⑤
public class Catalog implements CatalogInterface /* IMP */{ id>;6675601285158273025;1731815295562400500;1731815295563300400;DESKTOP-0JCC7QM;4;1
public void getBook(final boolean complexQuery) { $1;1731815295563311700;public static void org.example.Main.main(java.lang.String[]);<no-session- ①
// nothing to do here id>;6675601285158273025;1731815295550527700;1731815295563310200;DESKTOP-0JCC7QM;0;0
}
} (b) 获得的trace
public class CRM {
private final Catalog catalog;/* GVR */ public static void org.example.Main.main(java.lang.String[])###public org.example.BookStore.<init>()
public CRM(Catalog catalog/* PAR */) { public org.example.BookStore.<init>()###public org.example.Catalog.<init>()
this.catalog/* ACC */ = catalog; public org.example.BookStore.<init>()###public org.example.CRM.<init>(org.example.Catalog)
} public static void org.example.Main.main(java.lang.String[])###public void
private void getOffers() { org.example.BookStore.searchBook()
public void org.example.BookStore.searchBook()###public void org.example.Catalog.getBook(boolean)①
catalog/* ACC */.getBook(false);/* MEC */
} public void org.example.BookStore.searchBook()###public void org.example.CRM.getOffers() ②
} public void org.example.CRM.getOffers()###public void org.example.Catalog.getBook(boolean)
(a) 示例代码 (c) 获得的方法调用关系
图 5 方法调用关系提取示例
在获得了方法之间的调用关系后, 我们可以从方法的完整签名中获得方法所在的类, 进而将方法之间的调用
关系映射为方法所在类之间的调用关系. 例如, 在图 5(c) 中, ②所标识的调用关系 (searchBook() 和 getOffers() 之
间的调用关系) 可以映射为类 BookStore 和 CRM 之间的关系. 最终, 我们可以获得软件执行过程中涉及的类的集
合, 以及类之间的调用关系.
④ 对类的集合进行扩充
除了类之外, 软件中的接口 (或纯抽象类) 和枚举也发挥着非常重要的作用. 然而, 接口和枚举通常不能直接

