Page 225 - 《软件学报》2021年第8期
P. 225
黄子杰 等:检测 JavaScript 类的内聚耦合 Code Smell 2507
[7]
实现参考了 Fowler 的影音店案例 [9,10] 和 JS 类检测的文献 .
1. public class Rental {
2. private Movie movie; 1. function Rental(data){
3. private int daysRented; 2. this._data=data;
4. public Rental(Movie movie,int daysRented){ 3. }
5. this.movie=movie; 4. Rental.prototype.getMovie=function(⋅){
6. this.daysRented=daysRented; 5. return this._data_.movie;
7. } 6. }
8. public int getDaysRented(⋅){return daysRented;} 7. Rental.prototype.getDaysRented=function(⋅){
9. public Movie getMovie(⋅){return movie;} 8. return this._data_.daysRented;
10. } 9. }
(a) 一个 Java 类 (b) ES2015 标准发布前的一种 JS 类设计模式
1. class Rental{
2. constructor(data){this._data=data;}
3. get daysRented(⋅) {
4. return this._data_daysRented;
5. }
6. get movie(⋅){
7. return this._data.movie;
8. }
9. }
(c) 一个利用糖衣语法实现的 JS 类
Fig.1 Demonstration code showing Java and JS class implementations
图 1 Java 和 JS 类的实现代码示例
JS 类是基于 JS 原型继承(prototype-based inheritance)构建的一种经典模型 [11] ,它的实现可分为两类 [12] .
(1) 在早于 ES2015 的语言标准中,类是基于函数的设计模式,实现方式不唯一,图 1(b)是一种常见的实现.
(2) 如图 1(c),ES2015 及更新的标准用糖衣语法(syntactic sugar)简化了类的实现方式.
检测 JS 类首先需要定位构造器(constructor,例如图 1 中的灰色高亮部分)和成员属性(例如_data),再遍历函
数的原型链(例如图 1(b)的 Rental.prototype)搜索其成员函数和子类.构造器是类的显著特征,可用于创建对象.
定位构造器,可通过查找类的实现函数,或查找构造器的调用语句(例如 new 语句).目前,已有效果较好的、基于
[7]
抽象语法树遍历的开源 JS 类检测工具 JSDeodorant [13] 、JSClassFinder .
本文需要检测的 JS 类信息为类的成员(即函数和属性)、类的函数及其签名、类引用的外部数据提供者
(foreign data provider,简称 FDP)和类引用的内部成员.某类的“外部数据”指的是软件系统中非本类的类成员.分
析引用关系,可通过遍历代码中的访问操作(access)实现.访问操作是指对对象的引用、对其成员属性的引用
(reference)或对其成员函数的调用(call).
检测 JS 类及相关信息的挑战包括:
(1) 检测难以适用于所有的类实现方式.实现类的设计模式,有至少 5 种较为常用的、适用于不同版本语
言标准的方式 [7,13] ,给软件组件的判定和标准化带来困难.
(2) 检测类与类间的关系困难.受 JS 的语言特性限制,类成员属性的类型在静态分析过程中是未知的.类
型信息的缺失,导致分析引用关系的困难.这种缺失无法使用动态分析完全弥补,因其依赖爬虫,不适
用于运行在服务端的 JS 程序.
1.2 结构分析
结构分析是最常见的 Code Smell 检测方法,通常包括 2 个阶段,即信息收集和检测判定:在信息收集阶段,
分析工具通过遍历抽象语法树获取检测对象的结构信息;在检测判定阶段,需要根据度量的定义将收集得到的
信息计算为度量值,再依据规则判定是否存在 Code Smell.规则根据 Code Smell 的定义设计,它通常由一系列度