把断言(Assert)用的淋漓精致,提高代码的健壮性

4,393 阅读4分钟

目录

  • 一、什么是断言,什么情况下应该使用androidAssert?

  • 二、在release版本中移除断言代码,只在debug中保留

  • 三、集成AndroidAssert库

  • 总结

一、什么是断言,什么情况下应该使用androidAssert?

通常断言(assert)是在单元测试时,用来校验函数返回的结果。在自动化测试用来校验程序运行结果。

但是我们接下来要讨论的并不是单元测试中使用断言,而是在项目业务代码中使用断言

我们一起来看几个,大家非常熟悉的例子。这些情况下使用断言会让代码更加优雅,更加健壮。

例子1,writeFile

/**
 * 我们希望只在子线程中调用writeFile(),主线程中调用可能会导致ui卡顿或者anr。
 *
 * 如果在主线程调用writeFile(),我们打印日志警告开发者
 */
public void writeFile() {
    //主线程,打印日志警告开发者
    if(!ThreadTypeUtil.isSubThread()){
        Log.e(TG, "you should call writeFile at sub thread");
    }
    // write file ...
}

当出现有开发人员,尝试在主线程中调用writeFile()方法时,我们通过 日志警告 ,开发者。但是,日志提示很弱,往往会被开发者忽略掉。

于是我们改良了下代码:

public void writeFile() {
    //debug版本,主线程中调用 writeFile ,直接抛出异常中断程序运行
    if(!ThreadTypeUtil.isSubThread()){
        if(BuildConfig.DEBUG) {
            throw new RuntimeException("you should call writeFile at sub thread");
        }
    }
    // write file ...
}

当程序员企图在主线程中调用writeFile(),在debug模式下,我们直接抛出异常,让程序崩溃。以中断他的开发。强制他优化代码。

我们引入断言库,继续改造代码,让代码更简洁漂亮:

/**
 * 在debug模式,下将直接抛出异常 AssertionFailedError。让开发者
 * 在release模式,不会抛出异常,会正常执行writeFile()函数。
 */
public void writeFile() {
    AndroidAssert.assertSubThread();
    // write file ...
    Log.i(TG, "writeFile...");
}

AndroidAssert.assertSubThread()断言为子线程的意思是,断定当前线程一定是子线程,如果不是,那么抛出异常 AssertionFailedError。

AndroidAssert是一个开源库,使用前需要引入这个开源库,后面会讲。

继续优化

只有在debug版本,AndroidAssert 类,才有用;

在release版本的apk上,能否把 AndroidAssert 相关调用的代码删除?

我们先挖个坑,把例子讲完,再将release删除代码的方法。大家先忍一忍。

例子2,updateUI

/**
 * 在release版本,如果在子线程中调用updateUI,我们直接return,不做ui更新操作。
 * 但是在debug版本,如果在子线程中调用updateUI,直接出异常,让开发者发现异常调用并解决。
 */
public void updateUI() {
    boolean isMainThread = ThreadTypeUtil.isMainThread();
    if (!isMainThread) {
        AndroidAssert.fail("updateUI must be called at main thread");
        return;
    }
    //update ui ....
    Log.i(TG, "updateUI...");
}

例子3,startMainActivity

/**
 * 断言context为非空,如果为null,debug模式下抛出异常 AssertionFailedError
 */
public void startMainActivity(Context context) {
    AndroidAssert.assertNotNull("context must not null", context);
    if (context == null) {
        return;
    }
    //startMainActivity...
    Log.i(TG, "startMainActivity...");
}

二、我们继续改良,例子1,在release版本中移除断言代码,只在debug中保留

只有在debug版本,AndroidAssert 类,才有用;

在release版本的apk上,能否把 AndroidAssert 相关调用的代码删除?

或者说打包的时候,把 AndroidAssert 相关的调用的代码 和 AndroidAssert类的代码 全部删除,再打包。

于是我想到了proguard。

在proguard中添加如下配置即可:

# -dontoptimize ## 注意注意注意,proguard中配置dontoptimize;将会导致proguard不做代码优化,不会删除AndroidAssert类
-assumenosideeffects class com.it.uncle.lib.util.AndroidAssert{
    public *;
}

注意,注意,千万注意:不能开-dontoptimize,开了assumenosideeffects将失效

对用法有疑惑的可以,看下这篇blog:blog.csdn.net/jiese1990/a…

以及官方wiki:www.guardsquare.com/en/products…

校验assumenosideeffects是否生效

  1. 反编译debug和release包对比。

比如,我们demo里的TestActivity

public class TestActivity extends MainActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        AndroidAssert.assertNotNull(getIntent());
    }
}

我们分别反编译debug和release包,找到TestActivity类的代码对比:

  1. 配置成功后,打包在mapping中搜索:com.it.uncle.lib.util.AndroidAssert

proguard未配置 -assumenosideeffects 的mapping.txt文件

proguard配置了 -assumenosideeffects 的mapping.txt文件:

三、集成AndroidAssert库

  • gradle

    implementation 'com.ituncle:android-assert:0.0.2'
    
  • 初始化sdk,尽早调用,建议在Application#onCreate的时候调用。

//初始化----断言失败时,是否抛出异常
AndroidAssert.enableThrowError(BuildConfig.DEBUG);//我们设置为debug模式下,断言失败才抛出异常
  • 添加proguard,在开启混淆的版本中,移除AndroidAssert的代码

    # -dontoptimize ## 注意注意注意,proguard中配置dontoptimize;将会导致proguard不做代码优化,不会删除AndroidAssert类
    -assumenosideeffects class com.it.uncle.lib.util.AndroidAssert{
        public *;
    }
    



总结

android-assert是一个非常简单轻量的android断言库。类似于junit的Assert类。

android-assert不是用来写测试用例的,可以直接在项目代码中使用他。

在debug模式下,断言失败将会抛出断言异常 AssertionFailedError,在release模式下,将不会抛出异常。

更多关于android-assert,看github文档:https://github.com/AITUncle/AndroidAssert