首页 > 代码库 > 读书报告之《修改代码的艺术》 (II)续

读书报告之《修改代码的艺术》 (II)续

这里作为(II)的续篇,继续距离 复杂的嵌套if else 的处理。 为了保持篇幅不会太长,所以截断了,以一篇新的文章形式给出。

化简复杂的if else语句,基本的手段

  1. 针对头重脚轻的if else,使用return快速返回,从而减少嵌套层数。
  2. 合并分支。有些分支的执行内容相同,往往意味着可以合并为一个分支
  3. 扁平化。

第二个例子,比较复杂一点,给定一个日期,即年 月 日,让你给出下一天的表述。即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日;如果是某个月的最后一天,那么下一天就是下一个月的第一天 ; 否则只要简单的天数+1就可以了。
  2. 第一个if (day >=28) 作者用意是这样的:如果小于28,不论 是哪个月,都不会是该月最后一天。所以只要天数直接+1就可以了。
  3. 如果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)续