专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
程序员小灰  ·  清华大学《DeepSeek学习手册》(全5册) ·  昨天  
程序员小灰  ·  3个令人惊艳的DeepSeek项目,诞生了! ·  13 小时前  
OSC开源社区  ·  宇树王兴兴早年创业分享引围观 ·  2 天前  
程序猿  ·  “未来 3 年内,Python 在 AI ... ·  3 天前  
51好读  ›  专栏  ›  SegmentFault思否

重构 - 条件逻辑判断

SegmentFault思否  · 公众号  · 程序员  · 2020-02-23 10:00

正文

本文转载于 SegmentFault 社区

作者:勤劳的双手



No.1

案例分析



如何去除 If,else,switch 条件判断?

对于具有一定复杂逻辑的代码实现,避免不了出现 if,else,switch 等逻辑判断。当逻辑分支越来越多的时候,大大地加大了阅读的难度。这种情况,我们该如何处理呢?


No.2

switch 与 if else 谁快



对同一个变量的不同值作条件判断时,可以用 switch 语句与 if 语句,哪个语句执行效率更高呢,答案是 switch 语句,尤其是判断的分支越多越明显。 (具体测试的代码,小伙伴可以试一下)
public static void main(String[] args) {
testIF("12");
testSwitch("12");
}
public static void testIF(String arg) {
long t1 = System.nanoTime();
if ("1".equals(arg)) {
System.out.println(arg);
} else if ("2".equals(arg)) {
System.out.println(arg);
} else if ("3".equals(arg)) {
System.out.println(arg);
} else if ("4".equals(arg)) {
System.out.println(arg);
} else if ("5".equals(arg)) {
System.out.println(arg);
} else if ("6".equals(arg)) {
System.out.println(arg);
} else if ("7".equals(arg)) {
System.out.println(arg);
} else if ("8".equals(arg)) {
System.out.println(arg);
} else if ("9".equals(arg)) {
System.out.println(arg);
} else if ("10".equals(arg)) {
System.out.println(arg);
} else if ("11".equals(arg)) {
System.out.println(arg);
} else if ("12".equals(arg)) {
System.out.println(arg);
} else if ("13".equals(arg)) {
System.out.println(arg);
} else if ("14".equals(arg)) {
System.out.println(arg);
} else {
System.out.println(arg);
}
long t2 = System.nanoTime();
System.out.println("test if : " + (t2 - t1));
}
public static void testSwitch(String arg) {
long t1 = System.nanoTime();
switch (arg) {
case "1":
System.out.println(arg);
break;
case "2":
System.out.println(arg);
break;
case "3":
System.out.println(arg);
break;
case "4":
System.out.println(arg);
break;
case "5":
System.out.println(arg);
break;
case "6":
System.out.println(arg);
break;
case "7":
System.out.println(arg);
break;
case "8":
System.out.println(arg);
break;
case "9":
System.out.println(arg);
break;
case "10":
System.out.println(arg);
break;
case "11":
System.out.println(arg);
break;
case "12":
System.out.println(arg);
break;
case "13":
System.out.println(arg);
break;
case "14":
System.out.println(arg);
break;
default:
System.out.println(arg);
break;
}
long t2 = System.nanoTime();
System.out.println("test switch: " + (t2 - t1));
}
最终现实结果:
12
test if : 482713
12
test switch: 24870

No.3

逻辑分支



逻辑分支多为什么看起来费劲呢?
复杂!复杂!代码圈复杂度高!
什么是代码圈复杂度?


圈复杂度

概念: 用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。

计算公式:

· 计算公式为: V(G)=e-n+2 。其中,e 表示控制流图中边的数量,n 表示控制流图中节点的数量。其实,圈复杂度的计算还有更直观的方法,因为圈复杂度所反映的是“判定条件”的数量,所以圈复杂度实际上就是等于判定节点的数量再加上 1,也即控制流图的区域数,对应的计算公式为: V(G)=区域数= 判定节点数 +1

· 对于多分支的 CASE 结构或 IF-ELSE 结构,统计判定节点的个数时需要特别注意一点,要求必须统计全部实际的判定节点数,也即每个 ELSEIF 语句,以及每个 CASE 语句,都应该算为一个判定节点。判定节点在模块的控制流图中很容易被识别出来,所以,针对程序的控制流图计算圈复杂度 V(G) 时,最好还是采用第一个公式,也即 V(G)=e-n+2 ;而针对模块的控制流图时,可以直接统计判定节点数,这样更为简单。


No.4

如何重构这样的代码呢?



1) NULL Object空对象模式


描述:当你在处理可能会出现 null 的对象时,可能要产生相对乏味的代码来做相应的处理,使用空对象模式可以接受 null,并返回相应的信息。

代码示例:
interface ILog {
void log();
}
class FileLog implements ILog {
public void log() {
}
}
class ConsoleLog implements ILog {
public void log() {
}
}

class NullObjectLog implements ILog {
public void log() {
}
}
public class LogFactory {
static ILog Create(String str) {
ILog log = new NullObjectLog();
if ("file".equals(str))
log = new FileLog();
if ("console".equals(str))
log = new ConsoleLog();
return log;
}
}

2) 表驱动法(Table-Driven Approach)


描述:表驱动法是一种设计模式,可用来代替复杂的 if、else 逻辑判断。

观察下面的一维数组的形式可以发现,定义了 2 个变量。
例如:int a[12],a[x]=y;

相当于函数 y=f(x)
(在此例子中,x 和 y 为均为 int 类型) ,于是就变成了我们平时熟悉的普通的 c 函数。而函数一般通过数学表达式和逻辑判断的形式得出结果,而这样的结果一般的来说有数学规律,例如像 n! 就很适合于使用函数实现。

而表驱动法的函数关系是人为定义的,如果采用函数,一般会出现很多的 if、else 判断。所以表驱动法适合于去实现
“人造逻辑” 的函数。



例子 1: 假设你要编写一个计算医疗保险费用的程序,其中保险费用是随着性别、年龄、婚姻状况 和是否吸烟而变化的。 (这是代码大全书上的例子,原版是 Pasca 版本,方便阅阅读改成 Java 办) 。这时候,第一反应可能就是一大堆的 if-else 语句,如下:
enum SexStatus {
Female, Male
}
enum MaritalStatus {
Single, Married
}
enum SmokingStatus {
NonSmoking, Smoking
}
public double ComputeInsuranceCharge(SexStatus sexStatus, MaritalStatus maritalStatus, SmokingStatus smokingStatus, int age) {
double rate = 1;
if (sexStatus.equals(SexStatus.Female)) {
if (maritalStatus.equals(MaritalStatus.Single)) {
if (smokingStatus.equals(SmokingStatus.NonSmoking)) {
if (age < 18) {
rate = 40.00;
} else if (age == 18) {
rate = 42.50;
} else if (age == 19) {
rate = 45.00;
}
...
else if (age > 65) {
rate = 150.00;
}
} else if (smokingStatus == SmokingStatus.Smoking) {
if (age < 18) {
rate = 44.00;
} else if (age == 18) {
rate = 47.00;
} else if (age == 19) {
rate = 50.00;
}
...
else if (age > 65) {
rate = 200.00;
}
}
} else if (maritalStatus == MaritalStatus.Married) {
//......
}
}
return rate;
}
但是仔细看一下代码,其实保险费率和性别、婚姻、是否抽烟、年龄这个几个因素有一定的关系,尤其年龄的变化区间是相当大,按照上述的写法,可想而知,代码的复杂会达到什么样子的程度。

这时候,肯定有人会想不需要对每个年龄进行判断,而且将保险费用放入年龄数组中,这样将极大地改进上述的代码。不过,如果把保险费用放入所有影响因素的数组而不仅仅是年龄数组的话,将会使程序更简单,类似于可以设计一个
费率表格 ,来降低代码的复杂度呢。

· 定义好费率表格之后, 你就需要确定如何把数据放进去。你可以用从文件中读入费率表格数据。

· 当你建立好数据之后, 便做好了计算保险费用的一切工作。现在就可以用下面这个简单的语句来代替前面那个复杂的逻辑结构了。
Table rateTable = HashBasedTable.create();
enum RateFactor {
MALE_SINGLE_NONSMOKING,
MALE_SINGLE_SMOKING,
MALE_MARRIED_NONSMOKING,
MALE_MARRIED_SMOKING,
FEMALE_SINGLE_NONSMOKING,
FEMALE_SINGLE_SMOKING,
FEMALE_MARRIED_NONSMOKING,
FEMALE_MARRIED_SMOKING,
}
public double ComputeInsuranceCharge(RateFactor rateFactor, int age) {
int ageFactor;






请到「今天看啥」查看全文