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  之间的关系. 最终, 我们可以获得软件执行过程中涉及的类的集
                 合, 以及类之间的调用关系.
                    ④ 对类的集合进行扩充
                    除了类之外, 软件中的接口         (或纯抽象类) 和枚举也发挥着非常重要的作用. 然而, 接口和枚举通常不能直接
   187   188   189   190   191   192   193   194   195   196   197