connglli / ELEGANT

ELEGANT - a tool to Effectively LocatE fraGmentAtion-iNduced compaTibility issues.
MIT License
3 stars 0 forks source link

weekly report: 2017.12.20-2017.12.26 #12

Closed connglli closed 6 years ago

connglli commented 6 years ago

2017.12.20 ~ 2017.12.26

从这周开始,任务主要是开始寻找更多的测试用例,针对之前 Lili 他们在实验中找到的问题进行分析,从而不断完善目前的版本。这周使用测试用例是 AnkiDroid

编译

首先是对 AnkiDroid 几个有问题(buggy)以及问题被解决后(fixed)提交版本对进行了编译,其中(本周已经做的)主要包括以下 buggy-fixed 对:

API buggy-commit fixed-commit bug fixing manner
onBackPressed d523648 290a5aa workaround-fixing
getActionBar 7d60f35 2897a93 reflection-fixing
setOverScrollMode 7d60f35 2897a93 reflection-fixing
setShowAsAction e2c43d0 791a9ff reflection-fixing
invalidateOptionsMenu 917b9dd 3ffa1a1 polymorphism-fixing

很幸运的是,只是上述几个简单的 api 就已经覆盖了好几种不同的问题解决方式(bug fixing manner),同时暴露了当前版本仍然存在的问题。现在我们对目前收集到的、开发者可能使用的解决方式进行总结,并对目前存在的问题进行阐述。

问题解决方式

为方便描述,我们为每种解决方式都起一个名字,并给出一些简单的代码来描述。

1. direct-checking

这是我们最早想到的一种最简单的解决方式,也就是直接(direct)在 api 调用之前对版本等信息进行检查:

if (android.os.Build.VERSION.SDK_INT > 11) {
  invokeSomeFICApi();
}

2. method-checking

这一种也是我们很容易会想到的,既然每次都是用 1 中提到的最简单的方式,为简化代码,提取为一个公共的方法(method)是一种不错的选择:

if (isCompatible(11)) {
  invokeSomeFICApi();
}

3. static-checking

当跨类的时候,2 中提到的方式也会造成代码冗余,因此,提取代码到一个公共的兼容性检测类中以静态(static)方法的形式提供出来是个容易想到的方式:

if (Compact.isCompatible(11)) {
  invokeSomeFICApi();
}

4. reflection-fixing

直接对版本进行检查(checking)从而得知某些 api 可能在当前版本不合适需要开发者对方法和版本的映射关系有清楚地了解,然而当方法一多,或者开发者不太喜欢去记住哪些方法在哪些版本才出现这一映射关系,直接使用反射(reflection)的方式对开发者认为的可能会出现问题的方法进行检测,是一种简化记忆(但略微牺牲性能)的方式。描述起来可能不是很容易理解,代码更清晰:

// 开发者知道 getActionBar 可能会出现问题,但不知道是哪个版本会出现问题,直接利用运行时环境信息查看是否可用是一种简便(但略微牺牲性能)的方式。
Method getActionBar = Activity.class.getMethod("getActionBar");
if (null != getActionBar) { // 可用!
  ActionBar actionBar = (ActionBar) getActionBar.invoke(this);
  actionBar.setTitle("This is title");
}

5. polymorphism-fixing

某些 API 可能在不同的版本上变现不一致,或者在某个低版本引入但却在某个高版本删掉了,为简化逻辑,利用多态(polymorphism)并假设他们一直是存在的是一种更清晰有效的方式:

// 多态引入
public interface Compact {
    public void invalidateOptionsMenu(Activity activity);
}

public class CompactV3 implements Compact {
    @Override
  public void invalidateOptionsMenu(Activity activity) {
    // 构造一个空方法
  }
}

public class CompactV11 implements Compact {
    @Override
    public void invalidateOptionsMenu(Activity activity) {
        activity.invalidateOptionsMenu();
    }
}

// 使用的时候假设其一直存在
Compact compact;

if (getApiLevel() > 11) {
  compact = new CompactV11();
} else {
  compact = new CompactV3();
}

compact.invalidateOptionsMenu(this);

6. workaround-fixing

最后一种就是某些开发者根据 app 的特定情况使用的 workaround。针对这种,由于其与 app 特定场景以及使用情况有关,我们无法检测更无法预测,因此不作处理,这也是误报存在的地方

当前版本问题阐述

目前为止,我们对这些解决方案总结成了上述 6 种方式,而从我们目前的工作来看,前 3 种以 checking 结尾(考虑到作者的思路就是对版本进行检测,因此以 checking 结尾,而后三种作者的思路聚焦于解决,因此以 fixing 结尾)的方式我们已经考虑到并暂时解决(从我们之前的测试来看,当然仍然需要更多的测试来巩固这一结论)了,而后三种还未解决,其主要原因在于:

  1. 无法定位:对于 reflection-fixing,直接利用 call graph 我们无法定位某个 api 的调用。要想检测到,需要在 call graph 中对 Class.getMethod 这一方法进行查找,并根据具体的方法进行定位。
  2. 无法判断:对于 polymorphism-fixing,这种其实我们已经定位到了(为证实这一点,在代码中引入了一个 call graph 可视化的工具,可以看到 flowdroid 生成 call graph 是可以捕捉到这一点的),因此使我们对其是否已经解决的判断不准确造成的,对这一点,还需要进一步修改。

提前祝许老师和 Lili 新年快乐!🎉🎉🎉

connglli commented 6 years ago

Patterns

There are several patterns developers frequently use to fix these issues. We collected 6 of them and give each of them a name.

1. direct-checking: using if statement directly
if (android.os.Build.VERSION.SDK_INT > 11) {
  invokeSomeFICApi();
}
2. method-checking: using a common method
if (isCompatible(11)) {
  invokeSomeFICApi();
}
3. static-checking: using a static method
if (Compact.isCompatible(11)) {
  invokeSomeFICApi();
}
4. reflection-fixing: using reflection
// Developers are aware of the issues induced by getAction, but don't know which API version.
Method getActionBar = Activity.class.getMethod("getActionBar");
if (null != getActionBar) { // Available
  ActionBar actionBar = (ActionBar) getActionBar.invoke(this);
  actionBar.setTitle("This is title");
}
5. polymorphism-fixing: using polymorphism
// introduce polymorphism
public interface Compact {
    public void invalidateOptionsMenu(Activity activity);
}

public class CompactV3 implements Compact {
    @Override
  public void invalidateOptionsMenu(Activity activity) {
    // leave it empty
  }
}

public class CompactV11 implements Compact {
    @Override
    public void invalidateOptionsMenu(Activity activity) {
        activity.invalidateOptionsMenu();
    }
}

// using a parent
Compact compact;

if (getApiLevel() > 11) {
  compact = new CompactV11();
} else {
  compact = new CompactV3();
}

compact.invalidateOptionsMenu(this);
6. workaround-fixing