最近阅读项目的历史代码,发现一个简单但是非常普遍的问题,过多的判断逻辑,导致代码非常臃肿,后面维护的人员读起来很吃力。
常见语法
过多嵌套
项目前期开发任务紧,版本迭代快,也不怎么考虑逻辑优化,来一个需求,就加一个 if
,久而久之,就串成了一座金字塔。
if (true) { if (true) { if (true) { if (true) { if (true) { if (true) { if (true) { if (true) { } } } } } } } }
上面的写法应该还挺常见的,我就是在项目中看到下面这个情况,才想写到这篇文章的。
死守一个出口
有时候过于遵循方法的“单个方法出口”,即仅从单个位置返回,反而使代码可读性变差。
if (ture) { result = "x"; } else { if (ture) { result = "y"; } else { result = "z"; } } // this return is single and lonely return result ;
完全按逻辑顺序编码
顺着逻辑顺序来,第一个条件附带一大串操作,第二个条件只有较少操作。
if (true) { doSomethingA(); doSomethingB(); doSomethingC(); doSomethingD(); doSomethingE(); doSomethingF(); doSomethingG(); doSomethingH(); doSomethingI(); doSomethingJ(); doSomethingK(); doSomethingL(); doSomethingM(); doSomethingN(); return true; } doSomethingO(); return false;
优化方法
当出现这些问题,代码已经复杂到接手的人无法理解、难以维护的程度后,只能狠下心重构优化。
那有什么方案可以优雅地优化掉这些多余的 if-else
呢?
尽早 return
在发生多层嵌套导致多次缩进的情况下,很基本的想法就是将条件判断取反,得到一个更清晰的逻辑,特别是在代码逻辑里需要处理多种特殊情况的时候。
从极客时间的专栏课程里学到了这个方法的概念描述:以卫语句取代嵌套的条件表达式 (Replace Nested Conditional with Guard Clauses)。
if (condition) { if (condition1) { /* do somethingA */ } else { return(abc); } /* do somethingB */ } else { return(xyz); }
替换成下面这种,代码逻辑清晰多了。
if (!condition) { return(xyz); } if (!condition1) { return(abc); } /* do somethingA */ /* do somethingB */
这是在处理 if-else
语句时最常用也是最有效的一种方法。
与任何编程实践一样,不应将某个规则视为必须始终遵守的硬性规则。尽早 return
对小功能影响不大,甚至可能增加认知负担,但是,随着函数的大小和复杂性的增加,尽早 return
的逻辑扁平化优势变得越来越重要。
策略模式
有这么一种场景,根据不同参数走不同的逻辑,其实这种场景很常见。最一般的实现:
if (strategy.equals("fast")) { /* 快速执行 */ } else if (strategy.equals("normal")) { /* 正常执行 */ } else if (strategy.equals("smooth")){ /* 平滑执行 */ } else if (strategy.equals("slow")){ /* 慢慢执行 */ }
一般的处理有以下两种:
多态
interface Strategy { void run() throws Exception; } class FastStrategy implements Strategy { @Override void run() throws Exception { /* 快速执行逻辑 */ } } class NormalStrategy implements Strategy { @Override void run() throws Exception { /* 正常执行逻辑 */ } } class SmoothStrategy implements Strategy { @Override void run() throws Exception { /* 平滑执行逻辑 */ } } class SlowStrategy implements Strategy { @Override void run() throws Exception { /* 慢速执行逻辑 */ } }
具体策略对象存放在一个 Map
中,优化后的实现:
Strategy strategy = map.get(param); strategy.run();
上面这种优化方案有一个弊端,为了能够快速拿到对应的策略实现,需要 Map
对象来保存策略;当添加一个新策略的时候,需要重新加载 Map
,否则新策略容易被忽略。
枚举
发现很多同学不知道在枚举中可以定义方法,这里定义一个表示状态的枚举,另外可以实现一个 run
方法。
public enum Status { NEW(0) { @Override void run() { /* do something */ } }, RUNNABLE(1) { @Override void run() { /* do something */ } }; public int statusCode; abstract void run(); Status( int statusCode ) { this.statusCode = statusCode; } }
重新定义策略:
public enum Strategy { FAST { @Override void run() { /* do something */ } }, NORMAL { @Override void run() { /* do something */ } }, SMOOTH { @Override void run() { /* do something */ } }, SLOW { @Override void run() { /* do something */ } }; abstract void run(); }
通过枚举优化之后的代码如下:
Strategy strategy = Strategy.valueOf(param); strategy.run();
JDK 8 学会使用 Optional
如果登录用户为空,执行 action1,否则执行 action2,最常见的实现逻辑:
if (user == null) { //do action 1} else { //do action2 }
使用 Optional 优化之后,让非空校验更加优雅,间接的减少 if 操作。
Optional<User> userOptional = Optional.ofNullable(user); userOptional.map(action1).orElse(action2);
数组小技巧
来自 Google 解释,这是一种编程模式,叫做表驱动法。
本质是从表里查询信息来代替逻辑语句,比如有这么一个场景,通过月份来获取当月的天数。
int getDays(int month){ if (month == 1) return(31); if (month == 2) return(29); if month == 3) return(31); if (month == 4) return(30); if (month == 5) return(31); if (month == 6) return(30); if (month == 7) return(31); if (month == 8) return(31); if (month == 9) return(30); if (month == 10) return(31); if (month == 11) return(30); if (month == 12) return(31); }
优化后代码:
int monthDays[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; int getDays(int month) { return(monthDays[--month]); }
总结
if-else
作为每种编程语言都不可或缺的条件语句,在编程时会大量用到。如果一段代码存在过多的 if-else
嵌套,可读性就会急剧下降,后期维护难度也大大提高。
所以如果写代码时需要使用到 if
,我们可以尝试避免使用 else
。在处理多层嵌套情况下,用其他技术替代 else
关键字,这样代码的可读性能够大大提高。
参考文章 avoid-else-return-early[1].
发表回复