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

今天继续从本书的第二章学习,

昨天我们已经总结了下面三个内容

1. 降低修改的风险

2. 需要修改大量相同的代码

3. 时间紧迫,必须修改

今天继续第四点

4. 修改时应当测试哪些方法

作者提出了影响结构图的概念。说穿了,就是CallRelation和ReferenceRelation,就是查看某个方法(变量)被哪些方法引用,以及自身又引用了哪些方法,依次类推。这个复杂的关系网实际就是一颗风险评估树(图)。通过这棵树,我们可以知道某个修改会影响到哪些节点。这项参数,既是风险的直接量化指标,同时又是验证修改的测试指标。这是很朴素的思想,不管有意无意,你肯定已经在这么做了,不要告诉我你的boss从来没问过你这样的问题:“这个修改有风险吗?”。

5. 多处修改是否将所有相关类解依赖

实际还是上面的问题,上面的风险评估树(图),不同的节点,地位是不一样的。有些节点就显得特别关键,其中,作者归纳了一种叫汇点的概念。汇点就是所有的修改的影响都能从这个点感知,所以只要针对这个点测试就足够了,没必要满大街的测试了。打个比方就是医学上所谓的全息部位,人的耳朵就是人身上的一个全息点,身体的其他部位有任何毛病,都能在耳朵上看出来。

所以测试时,只要在汇点这个地方解开依赖就可以了。

6. 修改巨型方法

作者举例了两种常见的巨型方法样式

  • ?  列表型。一般是长长的switchcase或者很多的处理步骤。
  • ?  锯齿型。一般是复杂的ifelse多重嵌套。这种代码很难提炼方法,需要小心。

对列表型的巨型方法,其逻辑总体还是清晰的,可以通过将每一个列表项提炼出单独的方法,从而将巨型方法瘦身。

对锯齿型的巨型方法,其逻辑一般来说就有点反人类了。原书中没给出什么有意义的内容,这里就以自己的切身体会举几个具体的例子。

在正式举例之前,先引用一些大牛的作品, 以免显得井底之蛙。《重构》一书中的关于简化条件表达式的一章,绝对不容错过。然后《effective java》一书中也有关于if else的简化。然后是这篇博文if/else的使用心得。这些都是非常基础的内容,看似直白,其实却是代数中的交换律、结合律、分配律,是大智若愚,是各种技巧的基础。

化简复杂的if else语句,基本的手段就两个:

  1. 针对头重脚轻的if else,使用return快速返回,从而减少嵌套层数。
  2. 扁平化。基本原理如下
  if (expr1) {
		     if (expr2) {
		    	 // do something 1
		     } else {
		    	 // do something 2
		     }
		} else {
		    if  (expr3) {
		    	// do something 3
		    } else {
		    	// do something 4
		    }
		}

必然等价于

  if (expr1 && expr2) {
			// do something 1
		} else if (expr1 && !expr2) {
			// do something 2
		} else if (expr3) {
			// do something 3
		} else {
			// do something 4
		}

好了,唠嗑就到这。下面第一个例子,这是一个按分数计算成绩等级的方法,也就是分为A,B, C, D, E五个等级。好有熟悉感啊

   /**
	 * 按成绩排等级:90分以上A, 80-90 B, 70-80 C, 60-70 D, 60以下 E
	 * @param score
	 * @return string of degree, such as A, B, C, D, E
	 */
	String degree(int score) {
		if (score < 80) {
			if (score < 70) {
				if (score < 60) {
					return "E";
				} else {
					return "D";
				}
			} else {
				return "C";
			}
		} else {
			if (score < 90) {
				return "B";
			} else {
				return "A";
			}
		}
	}

这个例子是从网上随便搜的一个例子,在改动之前不得不吐槽一下(好像每次拿到别人写得代码时总是不自觉的会这样) : 这么简单的程序也能写得如此复杂,实在不知道是该夸你还是贬你?

采用扁平化策略,先化简if (score < 80)这个分支。逻辑条件很容易出错,这里建议亦步亦趋的做。先按照扁平化的公式,做一个形式上的变形。

	String degree(int score) {
		if (score < 80 && score < 70) {
			if (score < 70) {
				if (score < 60) {
					return "E";
				} else {
					return "D";
				}
			} else {
				return "C";
			}

		} else if (score < 80 && !(score < 70)){
			if (score < 70) {
				if (score < 60) {
					return "E";
				} else {
					return "D";
				}
			} else {
				return "C";
			}

		} else {

这里我不急着化简if (score < 80 && score < 70)中的逻辑条件,也不急着处理if (score < 80 && score < 70)分支中的内容。 同样,我也不急着处理新增的else
if (score < 80 && !(score < 70))分支,而且这个分支的内容我也只是复制原始分支中的内容。记住,这样做是绝对等价的,因而到目前为止的改动,虽然代码变化很大,但实际上是相当安全的。

接下来,化简if (score < 80 && score < 70)  ==> if (score < 70), 同时化简这个分支中的执行代码,实际只要去掉永远不会被执行的语句就可以了

if (score < 80&& score < 70) { ==> if(score< 70)

if (score < 70) {

if (score < 60) {

return"E";

}else{

return"D";

}

}else{

              return"C";

}

......

对 else if (score < 80 && !(score < 70)这个分支也做同样的简化,于是代码就变成这个样子

	String degree(int score) {
		if (score < 70) {
			if (score < 60) {
				return "E";
			} else {
				return "D";
			}

		} else if (score < 80 && score >= 70){
			return "C";

		} else {
			if (score < 90) {
				return "B";
			} else {
				return "A";
			}
		}
	}

继续扁平化,最终就变成

	String degree(int score) {
		if (score < 60) {
			return "E";

		} else if (score < 70 && score >= 60) {
			return "D";

		} else if (score < 80 && score >=70){
			return "C";

		} else if (score < 90){
			return "B";

		} else {
			return "A";

		}
	}

最后,else if (score < 70 && score >= 60) 这个条件也是有化简的余地 .因为只有在if (score < 60)不成立时,才会判断这条语句,这就意味着score >=60必然成立,所以可以简化为else if(score < 70)


	String degree(int score) {
		if (score < 60) {
			return "E";

		} else if (score < 70) {
			return "D";

		} else if (score < 80){
			return "C";

		} else if (score < 90){
			return "B";

		} else {
			return "A";

		}
	}

以上是最终代码,与开始的代码比较,一切的言语和解释都是多余的


最后说一下,这个例子的原始代码 作者愿意是为了性能优化,因为一般情况下,成绩分布总是80分一下占大部分,所以这样写成这样,减少条件判断的次数。简单说来就是借用一下霍夫曼编码的思想。这里我只能呵呵了, 这里直接摘录《C++编程规范》一书中的一节内容:

不要进行不成熟的优化。 优化的第一原则:不要优化。 第二原则(仅适用于专家,不是砖家哦):还是不要优化。再三测试,而后优化。

为了这点性能,有必要把程序写成这样吗?难道就没有更好的方法,显然不是了

时间: 2024-10-27 06:29:04

读书报告之《修改代码的艺术》 (II)的相关文章

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

这里作为(II)的第二个续篇,继续复杂的嵌套if else 的处理. 为了保持篇幅不会太长,以一篇新的文章形式给出. 化简复杂的if else语句,基本的手段 针对头重脚轻的if else,使用return快速返回,从而减少嵌套层数. 合并分支.有些分支的执行内容相同,往往意味着可以合并为一个分支 扁平化. 这里给出最后一个举例,也是从网上随便搜索摘录的 原始代码 List<TWorkFlowwork> wfwList=errorProcessingService.findWorkFlowwo

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

<修改代码的艺术>,英文名<Working Effectively with Legacy Code>,中文翻译的文笔上绝对谈不上"艺术"二字,愧对艺术二字(当然译者不是这个意思).书中第三部分不论是例子还是解说都有点混乱,远不如<重构--改善既有代码设计>一书.此书精华在于第一.二部分. 如何学习这本书,作为一个最底层的码农,作为长期在别人代码上修修补补的苦逼二手货开发人员,我只能给的建议就是:你可以将它看做是如何做定制功能的指导书--从某种意义上

修改代码的艺术笔记

使用单元测试使修改代码变得简单. 在编程的时候考虑测试:使用类的方法来代替方法,这样可以通过在测试中编写继承类,改变相应方法的行为,达到避免执行某些函数的目的,更好的解依赖. 使用包含预处理的头文件来制造接缝.#ifdef TESTING...  endif 通过修改链接时的包含路径,另外的写专门用于测试的类.(最佳,清晰而且便于维护测试代码) 如果是函数调用的内部函数是多态的,通过基类的对象传参,通过测试对象控制内部函数的行为,而不要封装new来的对象在函数内部. 不用static和私有函数,

修改代码的艺术读后感

这本书提到了一个我曾近不知道的概念:遗留代码.所谓遗留代码,指的是随着时间流逝,之前的代码纵使再完美无缺,也不可避免的产生腐化,失去原有的便利而显得腐朽发臭或者说没有编写测试的代码,或者说是遗留代码有许多预防措施,但是它的产生不可避免.如何解决它是整本书的核心. 书的第一部分,介绍了代码的修改机理,包括感知.分离和接缝和工具的使用,同时简要介绍了遗留代码以及测试的重要性.可以说,扭转遗留代码离不开测试. 遗留代码修改算法包括以下几步: (1) 确定改动点:(前提:理解代码) (2) 找出测试点:

读书报告之《改动代码的艺术》 (I)

<改动代码的艺术>,英文名<Working Effectively with Legacy Code>,中文翻译的文笔上绝对谈不上"艺术"二字.愧对艺术二字(当然译者不是这个意思).书中第三部分不论是样例还是讲解都有点混乱,远不如<重构--改善既有代码设计>一书. 此书精华在于第一.二部分. 怎样学习这本书,作为一个最底层的码农,作为长期在别人代码上修修补补的苦逼二手货开发者,我仅仅能给的建议就是:你能够将它看做是怎样做定制功能的指导书--从某种意义

《编写可读代码的艺术》读书笔记

表面内容 1.代码的写法应当是别人理解他所需的时间最小化.一条注释可以让你更快理解代码.尽管减少代码行数是一个好目标,但是八里街代码所需的时间最小化是一个更好的目标. 2.选择专业的词,比如函数名使用getxxx(),这个get没有表达出很多信息,是从缓存中得到?从数据库中得到?或者从网络得到?如果是网络,可以用更专业的fetchxxx()或者downloadxxx() 3.tmp,retval这样泛泛的名字,可以根据情况命名,比如tmpFile,让人知道变量是一个临时的文件.(tmp这个名字只

《编写可读代码的艺术》——简单总结

上个月好像冥冥中自有安排一样,我在图书馆看到这本 <编写可读代码的艺术> ( The Art of Readable Code) 期间因为工作的原因,停停看看,这几天终于看完了,可以大概总结如下: 1. 把信息装进名字里,给变量起个好名字 2. 审美,把代码分成段落,对齐 3. 应当取个好名字,而不是用注释去粉饰它 4. 用注释记录你的思想,比如当时为什么要这样写,记录开发过程中有哪些思考 5. 将自己代码中的不足和瑕疵记录下来,方便今后别人的维护,不要顾忌别人的看法! 6. 注释应该言简意赅

编写可读代码的艺术笔记

编写可读代码的艺术 表面层次上的改进 命名.注释以及审美--可以用于代码库每一行的小提示. 简化循环和逻辑 在程序中定义循环.逻辑和变量,从而使得代码更容易理解. 重新组织你的代码 在更高层次上组织大的代码块以及在功能层次上解决问题的方法. 精选话题 把"易于理解"的思想应用于测试以及大数据结构代码的例子. 第1章:代码应当易于理解 1.代码应当易于理解. 2.代码的写法应当使别人理解它所需的时间最小化. 第一部分:表面层次的改进 第2章:把信息装到名字里 1.使用专业的单词. 2.避

编写可读性代码的艺术

在做IT的公司里,尤其是软件开发部门,一般不会要求工程师衣着正式.在我工作过的一些环境相对宽松的公司里,很多程序员的衣着连得体都算不上(搞笑的T恤.短裤.拖鞋或者干脆不穿鞋).我想,我本人也在这个行列里面.虽然我现在改行做软件开发方面的咨询工作,但还是改不了这副德性.衣着体面的其中一个积极方面是它体现了对周围人的尊重,以及对所从事工作的尊重.比如,那些研究市场的人要表现出对客户的尊重.而大多数程序员基本上每天主要的工作就是和其他程序员打交道.那么这说明程序员之间就不用互相尊重吗?而且也不用尊重自