欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《重构,改善代码的既有设计》02-重构的原则

程序员文章站 2024-03-21 19:11:40
...

02重构的原则

2.1何为重构

重构的定义

使用一系列的手法,对软件内部结构的一种调整,目的是:
	在不改变软件可观察行为的前提下,
	提高可理解性,
	减低修改成本(时间成本、人力成本、质量成本);

重构的关键

运用大量微小且保持软件行为的步骤,一步步达成大规模的修改;
重构过程中,每一步改动后软件保持可用状态;

重构和优化的对比

***相同点***
	都需要修改代码;
	都不会改变程序的整体功能;
***不同点***
	重构的目的
		重构是为了让软件更容易理解、更容易修改;
		可能使得程序运行更快,也可能使程序运行更慢;
	优化的目的
		只关心让程序运行更快;
		最终的代码可能更难以理解和维护;

2.2两顶帽子

两顶帽子 from Kent.Beck

使用重构技术开发软件时,我把自己的行为分配给2种截然不同的行为:
	添加新功能
	重构

添加新功能

	添加新功能时,我不应该修改既有代码,只管添加新功能;
	通过添加测试并让测试运行正常,我可以衡量自己的工作进度;

重构

	重构时,我就不能添加功能,只管调整代码的结构;
	此时我不应该添加任何测试;
	除非发现之前有遗漏的功能;
	只在绝对必要时修改测试(处理接口变化);

2.3为何重构

重构改进软件的设计

	程序的内部设计或者架构会逐渐腐败变质;
	人们只为短期目的修改代码时,经常没有完全理解架构,于是代码逐渐失去了自己的结构;
	代码结构的流失有累积效应,越难理解,越难保护,腐败的越快;
	经常重构,有助于代码维持应有的形态;
	
	改进设计的一个重要方向就是,
		消除重复代码;
		**确定所有的事物和行为在代码里只表述一次------这是优秀设计的根本**

重构使软件更容易理解;

	对于代码,除了计算机之外,还有其他读者;
	后续的读者,要读懂或修改代码,对比计算机,这些读者才是更重要的;
	很多时候,这位后续的读者,便是我们自己;
	
	重构,帮助我们更清晰的表达,程序逻辑;

重构帮助找到bug

	对于代码的理解,可以帮助我们找到bug;
	而对代码进行重构,
		就可以深入理解代码的所作所为,
		并立即把新的理解反应在代码中;
		搞清楚代码结构的同时,可以验证自己的一些假设;
		于是,bug无处可藏;
		
	“**我不是一个特别好的程序员,我只是一个有着特别好的习惯的还不错的程序员**”---from Kent.Beck

重构提高编程速度

	重构可以提高质量
		改善设计、提升可读性、减少bug;
	在短期内可能花费一些时间在重构上,
		随着时间的推移和功能的负责,重构可以大大的提高开发速度;
	
	**两种团队**
		需要添加新功能时,内部质量良好的软件,可以很容易的找到在哪儿修改、如何修改;
		良好的模块划分,使我只需要理解代码库的一小部分,就可以做出修改;
		代码清晰,引入bug的概率就会变小;
		即时引入了bug,调试定位也会容易的多;
		理想情况下,质量高的代码,会逐渐演化成一个平台
			在其上,可以很容易的构造与其领域相关的新功能;
		这种现象称之为“**设计耐久性假说**”,
			通过投入精力概设内部设计,增加软件的耐久性,从而可以更长时间的保持开发的快速;

2.4何时重构

三次法则

第一次做某件事,只管去做;
第二次做类似的事,会产生反感,但是无论如何,还是可以去做;
第三次,再做类似的事,你就应该重构;
即为:
	**事不过三,三则重构**;

重构的6种方式

方式1,预备性重构,让添加新功能,更容易;

重构的最佳时机:
	就在添加新功能前;
对代码结构做一些调整,添加新功能就会很容易;
如果不做重构,
	就需要复制结构类似代码,违背只表述一次逻辑;
	将来需要修改,就需要修改多处;

方式2,帮助理解的重构,是代码更易懂;

修改一两个变量名,让他们更清晰的表达意图;
将一个常函数,拆分成几个小函数,结构明了;

“**初步的重构,就像扫去窗上的尘埃,使我们得以看到窗外的风景**”---from Ralph Johnson

重构引领我们获得更高层次的理解;
	缺少了这些细微的理解,就无法看到隐藏在混乱背后的危机;

方式3,捡垃圾式重构

确认代码中的垃圾:
	函数迂回复杂、代码重复;

如果垃圾很容易重构,那就重构他;

如果时间和精力不允许,那就把它记下来,完成当下的任务后再去重构他;

**让营地比你来的时候更整洁**;

重构的妙处:
	每个小步骤,都不会破坏代码;
	即便一块垃圾在好几个月之后才终于处理干净,
	即便每次清理垃圾并不完整;
	代码也不会被破坏;	

方式4,有计划的重构和见机行事的重构

肮脏的代码需要重构;
	但漂亮的代码,也需要很多重构;

“**每次要修改时,首先令修改很容易(有时候,会很难),然后再进行这次容易的修改**”---from Kent Beck

有计划的重构
	如果他对过去忽视了重构,那么需要花一些时间来优化代码库;
		对于很多公司来说,这是一种常态;
	在重构上花一个星期的时间,会在未来几个月发挥价值;
见机行事的重构
	大部分的重构,应该是不起眼的,见机行事的行为;
	开发过程中遇到问题,重构使得工作变得简单或者代码清晰;
		
将重构与添加新功能在版本控制中,分开添加;
	可以各自独立的审阅和批准这些提交;
	这个不是毋庸置疑的原则,当你感觉有价值时,就这样做;

方式5,长期重构;

大型重构需要花费较长时间;
	比如替换一个正在使用的库;
	比如把代码抽取出来给另外模块使用;
	比如处理一大堆的混乱关系;
编码让一个团队专门做重构
	有效策略:
		让整个团队达成共识;
		未来的一段时间,逐步解决问题;
	策略优点:
		重构不会破坏代码;
		每次小改动,整个系统正常工作;
		
替换一个正在使用的库
	先引入一层新的抽象;
	使其兼容新旧两个库的接口;
	一旦调用方已经完全改为使用这层抽象;
	替换下边的库将会容易的多;
	**Branch By Abstraction**

方式6,代码审核时重构

Dont know why

如何对经理说

坚持做正确的事;

何时不应该重构

如果看见一块凌乱的代码,
	如果不需要修改它,那么久不需要重构;
	只有当需要理解其工作原理时,对其重构才有价值;

如果重写比重构还容易,那就别重构了;

2.5重构的挑战

挑战1-延缓新功能开发

重构的唯一目的
	就是让我们开发的更快;
	用更少的工作量,创造更大的价值;
特殊情况
	一个大规模的重构很有必要进行;
	马上要添加的功能非常小;
	先把功能添加上,再完成这次大规模的重构;

如果一个问题我已经见过;
	我更倾向于重构它;
有时一块丑陋的代码,我得先看见它几次;
	我才有劲头去重构它;
如果一块代码,我很少触碰或者它不经常带来麻烦,
	我就不去重构它;
如果我还没想清楚如何优化代码,
	我倾向于延迟重构;
	或者先做些实验,测试下能否有所改进;

挑战2-代码所有权

重构的影响范围
	重构会影响模块内部;
	重构会影响模块与系统其它部分的关系;
		修改函数名;
		函数的调用,可能由另一支团队的代码;
		函数的调用,可能由客户的代码调用;
代码所有权的边界,会妨碍重构;
	函数改名,需要保持原来的函数声明;
	使其把调用传递给新的函数;
	这会让接口变得复杂,
	这就是为了避免破坏使用者的系统不得不付出的代价;
	我们可以把旧的接口标记为“不推荐使用”
		让旧的接口逐步退休;
		有些时候,旧的接口必须一直保留下去;

挑战3-主干-分支开发方式

常见的主干-分支开发方式
	在分支上开发完整的功能;
	直到功能可以发布到生产环境,才把代码合并会主干;
	优点:
		能够保持主干不受尚未完成的代码影响;
		能够保留清晰的功能添加版本记录;
		在某个功能出问题时,可以很容易的撤销修改;
	缺点:
		在分支上开发的越久,合并会主干的难度越大;
		合并难度,随着时间,指数上升;
解决方法
	持续集成(Continuous Integration);
	每个团队开发成员,每天至少向主干集成一次;
	避免了任何分支彼此差异太大,从而大大降低了合并的难度;

挑战4-测试

重构的重要特征
	不会改变程序的可观察行为;
	我们总是会犯错误,每个重构步骤都是微小的改变;
	造成bug时,我们只需要检查最后一步的小修改;
	如果找不到原因,我们可以回滚到版本控制中的最后一个可用版本;
快速发现错误
	要做到这点,我们要有一整套的完备测试套件;
	且,执行速度要快,否则没人愿意频繁的运行测试;
	绝大多数情况下,
		先要重构,先要有可以自测试的代码;
自测试代码
	自测试的代码,要求很高;
	团队在此投入时间和经理,绝对是物超所值的;
	自测试代码,使得重构称为可能,而且添加新功能更加安全;
	自测试代码,回答了“重构风险太大,可能引入bug”的问题;
		如果没有自测试代码,这种担忧是完全合理的;
自动化重构
	C语言上没什么实现;

挑战5-遗留代码

重构可以让我们很好的理解遗留系统;
“没有测试就加上测试”
	很难完整执行;
	没有简单的解决办法;

挑战6-数据库重构

解决数据库重构经常出问题的方法:
	借助数据迁移脚本;
	将数据库结构的修改和代码结合;
	使得大规模的、涉及数据库的修改可以比较容易的开展;
数据库重构的关键
	小步修改;
	每次修改完整;
	分布到多次生产发布来完成;
	即便某次修改造成了问题,比较容易回滚;

2.6重构、架构、YAGNI

重构与架构

重构极大的改变了人们考虑软件架构的方式;
早起架构思维:
	在任何人写代码之前,必须先要完成软件的设计和结构;
	但是,只有真正使用了软件,看到软件对工作的影响,人们才会明白自己到底需要什么;
	
重构技术,使得即便运行了很多年的程序,也可以大规模的修改其架构;

重构对架构的最大影响:
	通过重构,
	能够得到一个设计良好的代码库;
	使其能够 优雅 应对不断变化的需求;

YAGNI

You Aren't Going to Nead It,你不会需要它;
个人理解:
	代码功能,不超前、不滞后、重构到最需要的状态;

2.7重构与软件开发过程

重构是否有效

与团队采用的其他软件开发实践紧密相关;

重构的基石

重构的第一块基石
	自测试代码;
重构的第二块基石
	每个团队的成员都要掌握重构技能;
	能够在需要时展开重构,而不会影响他人的工作;
	这就是我鼓励CI(持续集成)的原因

协同效应

自测试代码;
持续集成;
重构;
YAGNI;
重构是YAGNI的基础;
YAGNI使得重构易于开展;

2.8重构与性能

编写快速软件的秘密

除了对性能有严苛要求的实时系统;
先写出可调优的代码;
然后调优以求获得足够的速度;

三种编写快速软件的方法

方法1、最严苛的时间预算法

每个组件分配一定的时间和空间,严格遵循;

方法2、持续关注法;

程序开发,要求任何程序员,在任何时间、做任何事,都要设法保持系统高效能;
通常没有效果;

**经验教训**
		哪怕你完全理解系统;
		也请实际度量它的性能;
		臆测会让你学到一些东西,但是臆测有时候不一定是对的;
	
**关于性能**
		如果你对大多数程序进行分析,就会发现,
		大半时间都耗费在一小半的代码上;
		如果一视同仁的优化所有代码,
		九成的优化是白费劲的;
		因为被优化的代码很少被执行;

方法3、性能提升法;

	利用关于性能的”九成“的统计数据,优化性能;
	编写代码时,只注重构造良好的程序;
	不对性能投以特别的关注;
	直至性能调优阶段,再遵循特定的流程来调优程序性能;
	
	利用一个度量工具监控程序的运行;
	分析出程序中哪些地方大量的消耗时间和空间;
	
	**重构可以帮助优化性能;**
		构造良好的代码,让我们有比较充裕的时间进行先调整;
			能够更快的添加新功能,有更多的时间用于性能调优;
		构造良好的代码,在进行性能分析时有较细的粒度;
			度量工具可以把我们带入范围较小的代码段;
			从而性能调优也比较简单;

2.9重构的起源

重构,没有真正的起源;

2.10自动化重构

开发工具的支持;
Java	
相关标签: 重构