if表达式/语句大概是所有开发者日常用到的语法中最多的,但最近在看以前的代码时想到到一个很容易发生但开发的时候经常忽略的问题,本文就记录下这个问题,并在最后提出一些建议。
问题描述
现代绝大部分语言中,if表达式包含三种子句:if、else-if以及else,且每个子句的条件从上倒下顺序求值,直到其中一个条件满足,否则就进入else子句。这大概是所有程序员最熟悉的求值规则。下面就是一个非常正常的程序:
if (mediaInfo.getType() == MP3) {
handleMp3(mediaInfo);
} else if (mediaInfo.getType() == MP4) {
handleMp4(mediaInfo);
} else {
logger.d("not support media type:" + mediaInfo.getType());
}
相信很多人都看到过类似的程序,这个程序虽然可以用switch语句替代,不过这么写也没有太大问题。但是下面这段程序就可能会有问题:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
doWorkS();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
doWorkO();
} else {
doWork();
}
这类代码写Android的朋友应该很熟悉,为了适配手机系统版本,针对特定版本去做特定操作。但是这段代码的问题在于Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
这一行有两种解读意义:第一种是这个子句的执行前提是系统版本大于等于Android 8.0;第二种是这个子句的执行前提是系统版本大于等于Android 8.0且前面条件不满足(也就是系统版本小于Android 12)。如果最初编写的时候是第二种意思,那么会写成:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
doWorkS();
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
doWorkO();
} else {
doWork();
}
但是这样的话,编辑器可能会对Build.VERSION.SDK_INT < Build.VERSION_CODES.S
这个表达式报警告,提示这个表达式是多余的。
最后,我们将问题抽象成下面形式:
if (test_condition_1) {
doWork1();
} else if (test_condition_2) {
doWork2();
} else {
doWorkElse();
}
这类代码执行结果只有一种,但是解读的时候可以得到两种解释,并且是两种有明显逻辑区别的解释,取决于else if
子句的条件是否依赖前面条件的失败。
这种代码解读的二义性问题在代码维护中可能发生难以预料的问题。
解决方案或建议
针对上面问题,对条件语句编写的建议总结如下:
- 在编写
else if
子句的时候就该考虑是不是其他语法更适合,比如switch
表达式或模式匹配; - 一组
if
和else if
子句之间所有的条件相互独立,互不依赖(不依赖判断顺序); - 避免复杂条件;
- 所有的判断条件尽量关注同一知识,比如,判断类型的时候所有条件都在判断类型,而不是其中一个去判断值。