首页 > 代码库 > 读书报告之《修改代码的艺术》 (II)续
读书报告之《修改代码的艺术》 (II)续
这里作为(II)的续篇,继续距离 复杂的嵌套if else 的处理。 为了保持篇幅不会太长,所以截断了,以一篇新的文章形式给出。
化简复杂的if else语句,基本的手段
- 针对头重脚轻的if else,使用return快速返回,从而减少嵌套层数。
- 合并分支。有些分支的执行内容相同,往往意味着可以合并为一个分支
- 扁平化。
第二个例子,比较复杂一点,给定一个日期,即年 月 日,让你给出下一天的表述。即2000-1-1 ==》 2000-1-2, 2000-1-31==》2000-2-1 2000-12-31==》2001-1-1
这里简化了一些,给各位一些看代码的耐心。只给出月 日, 然后二月固定为28天。
int nextdate(int month, int day) { if (day >= 28) // 如果是一个月的最后一天 { if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { if (day >= 31) { // 这个月的最后一天, 需要同时调整 月 日 day = 1; month += 1; if (month > 12) // 一年的最后一天,需要同时调整年 月 日 { month = 1; } } else { day += 1; } } else if (month == 2) { // 非闰年28号是2月的最后一天 day = 1; month += 1; } else if (month == 4 || month == 6 || month == 9 || month == 11) { if (day >= 30) // 是30号 { day = 1; month += 1; } else { day += 1; } } } else // 如果不是一个月的最后一天,则day直接加1 day += 1; return month * 100 + day; // 计算出明天的日期 }
每次看这样的代码,不得不说要减寿半年。所以在正式化简之前,为了不致各位看官的手臂男,我先大概聊一聊这个程序:
- 基本的算法逻辑:计算下一天时,如果是这一年的最后一天,那么就是下一年的1月1日;如果是某个月的最后一天,那么下一天就是下一个月的第一天 ; 否则只要简单的天数+1就可以了。
- 第一个if (day >=28) 作者用意是这样的:如果小于28,不论 是哪个月,都不会是该月最后一天。所以只要天数直接+1就可以了。
- 如果day >=28, 那么对不同的月份,就可能是月末最后一天,也可能不是,具体再用if else处理。
程序的设计聊完了,先做个简单的重构。
- if (day >= 28)这个语句实在是太头重脚轻了,所以使用return快速返回的手法。顺便去掉一层嵌套
int nextdate(int month, int day) { if (day < 28) {// 如果不是一个月的最后一天,则day直接加1 day += 1; return month * 100 + day; // 计算出明天的日期 } if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { if (day >= 31) { // 这个月的最后一天, 需要同时调整 月 日 day = 1; month += 1; if (month > 12) // 一年的最后一天,需要同时调整年 月 日 { month = 1; } } else { day += 1; } } else if (month == 2) { // 非闰年28号是2月的最后一天 day = 1; month += 1; } else if (month == 4 || month == 6 || month == 9 || month == 11) { if (day >= 30) // 是30号 { day = 1; month += 1; } else { day += 1; } } return month * 100 + day; // 计算出明天的日期 }
然后观察剩下的一大串if else语句,有很多重复的代码片段,这个现象说明可能比较适合应用合并分支的手法。
- 重复代码段day =1; month +=1; // 表示如果是月末最后一天时,next date 就是下一个月的1号
- 重复代码段 day += 1; // 表示如果不是月末最后一天时,next date就是 本月天数 + 1
所以我们合并分支条件,将所有判定是月末最后一天的条件合并在一起。整个程序的逻辑就简化为是不是月末最后一天,如果是 do somethin 如果不是 do something。
当然,到这里的时候,已经完全可以重新写一套代码,相对来说速度上比改这个代码肯定还要快一些,现实工作中 这种情形也不少遇到。不过这里为了扣住这个blog主题——“修改代码的艺术”,所以下面还是以代码重构的方式进行
要合并分支,首先需要将这些分支集中到同一层。 千万不要直接上下层合并,只要稍微复杂一些,就连怎么死的都不知道。因此先做一次扁平化操作。具体怎么做,之前已经详细讲述了,这里就直接快进
int nextdate(int year, int month, int day) { if (day < 28) {// 如果不是一个月的最后一天,则day直接加1 day += 1; return month * 100 + day; // 计算出明天的日期 } if ((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) && (day >= 31)) { // 这个月的最后一天, 需要同时调整 月 日 day = 1; month += 1; if (month > 12) // 一年的最后一天,需要同时调整年 月 日 { year += 1; month = 1; } } else if ((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) && !(day >= 31)) { day += 1; } else if (month == 2) { // 非闰年28号是2月的最后一天 day = 1; month += 1; } else if ((month == 4 || month == 6 || month == 9 || month == 11) && (day >= 30)) { // 30号是這些月份的最后一天 day = 1; month += 1; } else if ((month == 4 || month == 6 || month == 9 || month == 11) && !(day >= 30)) { day += 1; } return month * 100 + day; // 计算出明天的日期 }
在合并所有判定是月末最后一天的分支 之前,需要先将这些分支移动到相邻的位置。一般而言,if 。。。 else if 。。。 else 是不可以随便上下移动位置的
if (x < 5) { } else if (x < 10) {}
如果将后面的else if (x<10) 与 if (x<5) 交换位置,一看就知道出问题了。后面的分支已经永远不可能被执行到了。
if (x < 10) { } else if (x < 5) {}
出现这个情况的原因就是 else if (x < 10) ,这既是面子又是个里子。 if x<10面子, else 里子,其实暗含着 x>=5。所以如果写成这样就可以了
if (5 <= x && x < 10) { } else if (x < 5) {}
您开窍了吗? 口才有限,只能点到为止。
移动和合并分支
int nextdate(int month, int day) { if (day < 28) {// 如果不是一个月的最后一天,则day直接加1 day += 1; return month * 100 + day; // 计算出明天的日期 } if ((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) && (day >= 31) || (month == 2) || (month == 4 || month == 6 || month == 9 || month == 11) && (day >= 30)) { // 这个月的最后一天, 需要同时调整 月 日 day = 1; month += 1; if (month > 12) // 一年的最后一天,需要同时调整年 月 日 { month = 1; } } else if (((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) && !(day >= 31)) || ((month == 4 || month == 6 || month == 9 || month == 11) && day < 30)) { day += 1; } return month * 100 + day; // 计算出明天的日期 }
接下来就可以直接应用各种重构手法,将逻辑条件直接提炼为单独的方法,提高代码可读性。类似的技术用到if (month > 12)这个分支,还是直接快进到最终的代码
int nextdate(int month, int day) { if (isLastDayofaYear(day, month)) { // 一年中的最后一天,下一天就是1月1日 day = 1; month = 1; } else if (isLastDayOfaMonth(day, month)) { // 这个月的最后一天,下一天就是下一个月的1号 day = 1; month += 1; } else { // 不是月末最后一天,直接天数+1 day += 1; } return month * 100 + day; // 计算出明天的日期 } private boolean isLastDayOfaMonth( int day, int month) { if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { return day == 31; } else if (month == 4 || month == 6 || month == 9 || month == 11) { return day == 30; } else if (month == 2) { return day == 28; } else { throw new RuntimeException("unknow month"); } } private boolean isLastDayofaYear(int day, int month) { return month==12 && day == 31; }
最后再谈优化,程序到这里,要优化时就容易的多了,直接用个静态map或者干脆数组,可以对isLastDayOfaMonth再次简化以及性能优化。所以不要进行不成熟的优化。代码清晰度上来了,往往会有更好的优化方案。
over
读书报告之《修改代码的艺术》 (II)续
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。