前言
前段时间,我发现业务代码中有许多疑似多余的空(null)检查,为什么说是疑似呢?
师傅曾说过:
当你发现一个问题的时候,先别太早下结论,首先确定它是不是个问题,再确定问题的本质,最后由此想出解决方法。
程序员本质上也就是干这两件事,发现问题和解决问题。
故全文围绕这两个问题展开:
- 多余的空检查是什么,为什么?
- 怎么样避免多余或遗漏的空检查?
AOSP和JDK的代码实践可算是千锤百炼,通过分析和总结归纳,我比较认可的以下方式:
- 在私有或包内访问域的方法参数,使用基于Android Studio和@NonNull/@Nullable注解的Android静态检查方法,表明方法是否处理null参数,提升合作效率和代码语义。
- 在公有的方法参数(如SDK的API方法),使用正常的空检查,能处理的情况提前返回,不能处理的情况直接抛出IllegalArgumentException,由调用者负责处理。
最后,我们再简单谈谈其他空检查的注解和Android Studio的静态检查工具的其他用法。
问题三连:多余空检查是什么,为什么,怎么样?
代码源于现实生活,让我们举个栗子。
多余的空检查就像坐地铁过安检,收益是需要权衡的。
多次安检固然比较安全,但是也会带来效率上的开销。
一次检查基本上可以拦截大部分问题,后续检查的收益就比较低了。
每一句代码都是需要深思熟虑后才去写的。早期写代码大家可能就是报着“多做无害”的心态,反正不会崩溃。这样容易写出很多冗余代码,最终导致代码腐烂。
多余空检查的开销我没有仔细研究过,应该就是一两个计算机指令,最终换算成CPU时钟周期的计算,在当前CPU性能普遍比较高的今天,也不在话下,只能说,这么做很不极客Geek。
如何避免多余的空检查?
言归正传,如何避免多余的空检查,首先需要知道需要检查的内容,基本上就是外部输入参数或者外部函数返回值,而相对地,不太需要检查的是内部的参数和函数返回值。
- 外部入参和返回值处理:
外部入参和返回值都属于外部输入,跨越信任边界,可视为来源不可信,必须校验其合法性。在入参明显与函数职责不合法,可直接返回IllegalArgumentException,认为这是程序员的逻辑错误,比如对拷贝数组的函数传入null,null参数不在拷贝数组的函数处理范围内,传入null是要找茬? - 内部的参数和函数返回值处理:
我们不能明确或要求每个函数都处理非空的入参,或者返回值为非空,那么就需要使用额外的方式,约定函数的信息,在Java中,一种比注释更好的方式是注解。
下面主要介绍,基于@NonNull/@Nullable注解以及配合Android Studio的静态检查工具,形成了程序的“约定”。
主要思路
- 使用android.annotation或android.support.annotation包下面的@NonNull和@Nullable,对方法或参数进行注解。
- 使用Android Studio的inspection静态检查工具,在编码期进行静态检查,及时发现不合理的冗余检查或遗漏检查。
优缺点
-
优点:
- 提前发现问题:在编码期就能及时发现不合理的冗余检查或遗漏检查,成本较低。
- 开销小:@NonNull和@Nullable为源码注解,在编译期就会被编译期无视,不增加运行时开销。
-
缺点:
- 约束力较差:仅仅以静态扫描的告警形式表现,而不是编译时error,约束力较弱。
- 依赖Android Studio:依赖于体量较大的IDE,且需要团队成员均使用才能发挥较好的效果。如果能有独立的命令行lint工具,就最好不过。
配置方法
导入对应的annotation包: 在APP侧,需要增加对Android support包的依赖;在framework开发中,可以直接使用android.annotation包。
在Analyze -> Inspection Code,配置inspection静态扫描工具对应的注解类为android.annotation或android.support.annotation包的注解,如下图所示:
- 在编码中,通过选择自定义的规则,扫描文件或者文件夹就可以得到扫描报告,并进行快速修改,如下图所示:
扩展杂谈
从上文Android Studio的配置可以看出两点:
- 其实有其他null检查的注解方式,如Jetbrain提供的注解和Java提供的注解等可以选择;
- inspection静态扫描不仅支持null扫描,还有其他非常丰富的规则集可以运用到日常的代码开发中。
这里先做简单地介绍,后面有机会统一学习和实践后再做详细探讨。
- Android Studio由Jetbrain公司开发,被Google收购,提供了org.jetbrain.annotation注解,搭配Analyze -> infery nullibility功能进行使用,但需要导入org.jetbrain的jar包。
- java提供了javax.validation.annotation,但不在Android支持的API范围内(Android对Java的包是有选择的引入,特别是javax扩展包,这里不做详解),和JSR305库提供注解,同样需要额外导入jar包。
- inspection静态扫描工具非常好用,规则配置非常灵活,且可以导入导出配置文件,适合团队使用,后面会分享具体的配置细节。
个人实践总结和心得
- 工具只是辅助工具,不要过度依赖工具,最后还是得根据实际情况,由开发者进行判断。毕竟工具只是为了解决人类在某些方面脑力有限而做出来的工具,像调用栈和参数这种东西,人脑也记不了几层。
- 越早发现问题成本越低。比起在调试、测试、Beta、上线商用时被发现问题,编码期发现和解决问题的成本简直不要太低,差了好几个数量级呀。所以越早发现问题成本是最低的,防患于未然,做扁鹊大哥吧。
- 敬佩Google以开发者为核心的生态布局。在Android开发中,Google的套件,如Android Studio的强大功能(跟它的卡和吃内存是有关系的),framework开发用到的Gerrit(代码检视平台)和repo(多代码仓操作脚本)等等,实在是让人觉得伟大。