利用 Android Annotations 来玩玩契约编程

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/FeeLang/article/details/49000203

关于契约编程

契约编程Contract Programming),我个人理解就好像商业行为一样,买卖双方彼此订立好契约,相互遵守,互利共赢,如果有一方没有遵守契约,那就意味着买卖可能不成立。

这套逻辑同样也适用于开发,提供 API 的一方不但要完成功能,还要尽可能地写清楚使用条件(相当于契约),文档或注释是一种形式,但是我们无法判断使用者到底有没有遵守约定。

举个栗子,定义一个方法 setItemId,参数为 String itemId

void setItemId(final String itemId)

那么,如何保证 Fail-safe 呢?当然最简单的方法就是在方法体内判断一下 itemId 是否为空。

void setItemId(final String itemId) {
    if (TextUtils.isEmpty(itemId)) return;
    ....
}

这样一来,虽然保证了 setItemId 的安全性,但同时也带来了新问题,当参数不合法时,无法及时暴露问题,这样可能会给 Debug 带来一定困难。 fresco 的做法比较直接,如果参数不合法,直接抛异常。

public void addControllerListener(ControllerListener<? super INFO> controllerListener) {
    Preconditions.checkNotNull(controllerListener);
    ...
}

其中 Preconditionsguava 提供的一个类,用于判断变量是否符合前置条件,如果不符合则直接抛异常,及时地暴露问题。这也符合 infer 提倡的编码习惯:

Program safely, annotate nothing!

翻译成土话就是『安全编码,啥也不标』,如果没有任何标记,则意味着参数不能空,如果为空,不好意思,直接抛异常。但是如果可以接受参数为 null 时,怎样通知调用方呢?这时候就要用到 Annotations 了,这个我们下面再讲。

『安全编码,啥也不标』这种习惯比较适用于会写单元测试的开发者,但是像我这种喜欢偷懒,不写单测,还追求极致性能的码农来说,面对 setItemId(final String itemId) 这样的API,很容易变得纠结,因为很想知道它到底资瓷不资瓷空值,如果支持,那我就不需要再判空了。幸好有 Android Annotations, 她让这件事变得比较简单。

Android Annotations

Annotations 可以帮助你写出更有意义的契约,它的表现力要大于注释和文档,而且 Android Studio 可以利用这些 Annotations 帮你检测出潜伏的bug。

国际惯例,使用之前,添加依赖包:

compile 'com.android.support:support-annotations:23.0.1

空值标记

NonNullNullable 是一对,通过源码的 @Target 可以看出,它们可以出现在方法声明、参数声明以及成员变量的声明。

@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD})
public @interface Nullable {
}

下面这段代码,setItemId(null) 肯定会导致 Crash,IDE 也没有提示 bug 风险。

no annotations

作为 setItemId 方法本身,为了保证安全,需要对参数 itemId 做一次判空处理:

public void setItemId(final String itemId) {
    if (!TextUtils.isEmpty(itemId)) {
        Toast.makeText(this, itemId, Toast.LENGTH_SHORT).show();
    }
}

调用方并不知道方法内部做了判空处理,很有可能又做了一次判空操作。明明只需要一次判空就OK了,由于信息不对称,导致调用方和提供方各自进行了一次判空操作,造成性能开销。

但是,如果给参数 final String itemId 加上 NonNull,方法提供方就可以放心大胆地不进行判空操作,因为已经通过 annotation 订立了使用”契约” - 参数不能为空

IED 也提示了 bug 风险。

NonNull

Nullable 也就意味着调用方无需再进行判空操作,道理是一样的。

资源标记

当 API 参数是一个表示资源 ID 的 int 时,可以加上资源标记。

Resources

看一下 StringRes 的定义,支持的类型除了方法、参数、成员变量之外,甚至还支持 局部变量。

@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface StringRes {
}

资源标记除了针对任何资源都是用的 @AnyRes,还包括每种资源所对应的特有的标记。

@AnimatorRes, @AnimRes, @ArrayRes, @AttrRes, @BoolRes, @ColorRes, @DimenRes, @DrawableRes, @FractionRes, @IdRes, @IntegerRes, @InterpolatorRes, @LayoutRes, @MenuRes, @PluralsRes, @RawRes, @StringRes, @StyleableRes, @StyleRes, @XmlRes.

值标记

与资源标记类似,我们还可以使用 @ColorInt 来标记一个表示颜色值 int 变量。甚至还还可以通过 @FloatRang@IntRange 来表示变量的取值范围。

float range

Proguard

Fresco 中使用 @DoNotStrip 来防止混淆,并在 proguard 文件中添加了一条规则:

 # Do not strip any method/class that is annotated with @DoNotStrip
 -keep @com.facebook.common.internal.DoNotStrip class *
 -keepclassmembers class * {
     @com.facebook.common.internal.DoNotStrip *;
 }

现在 Android Annotations 也提供了一个类似的标记 @Keep,与 @DoNotStrip 相比其优势在于可以不用添加 Proguard 规则,Android 的 Gradle 插件自动会帮我们完成这步操作(还在开发中)。

其他

出了以上列举的一些常用的之外,还有很多其他的很有用的 Annotation,例如 @CheckResults@CallSuper 等等,具体可以参考 Android 官方文档 - Improving Code Inspection with Annotations

总结

Android Annotations 绝对是个好东西,不但能够提高代码可读性、而且会大大提高健壮性,希望大家编码时能充分利用起来,也欢迎分享心得。

参考文章


关于我

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页