重构改善既有代码的设计-- 重构原则

来源:互联网 发布:理正岩土局部搜索算法 编辑:程序博客网 时间:2024/04/29 15:48


重构定义:对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

为何重构:重构改进软件设计、重构使软件更容易理解、重构帮助找bug、重构提高编程速度

何时重构:三次原则(三次再做类似的事)、添加功能时重构、修补错误时重构、复审代码时重构

是什么让程序如此难以修改?四个原因:

1、难以阅读的程序,难以修改;

2、逻辑重复的程序,难以修改;

3、添加新行为时需要修改已有代码的程序,难以修改;

4、带复杂条件逻辑的程序,难以修改

我们希望程序:容易阅读;所有逻辑都只在唯一地点制定;新的改动不会危及现有行为;尽可能简单表达条件逻辑。

修改接口:让旧街口调用新接口。当你要修改某个函数名称时,请留下就函数,让它调用新函数。千万不要复制函数实现,那会让你陷入重复代码的泥沼中难以自拔。你应该使用Java提供的deprecation(不建议使用)设置,将旧接口标记为deprecated。这么一来你的调用者就会注意到它了。

何时不该重构:重写(而非重构)的一个清楚讯号就是:现有代码根本不能正常运作。你可能只是试点做点测试,然后就发现代码中满是错误,根本无法稳定运作。记住,重构之前,代码必须起码能够在大部分情况下正常运作。

重构和设计:预先设计-写代码-重构。使用CRC卡或类似的东西来检验各种不同想法,然后才得到第一个可被接受的解决方案,然后才能开始编码,然后才能重构。

附录一:劳而无获— Ron Jeffries

Chrysler Comprehensive Compensation(克莱斯勒综合薪资系统)的支付过程太慢了。虽然我们的开发还没结束,这个问题却已经开始困扰我们,因为它已经拖累了测试速度。
  Kent Beck、Martin Fowler和我决定解决这个问题。等待大伙儿会合的时间里,凭着我对这个系统的全盘了解,我开始推测:到底是什么让系统变慢了?我想到数种可能,然后和伙伴们谈了几种可能的修改方案。最后,关于「如何让这个系统运行更快」,我们提出了一些真正的好点子。
  然后,我们拿Kent的量测工具度量了系统性能。我一开始所想的可能性竟然全都不是问题肇因。我们发现:系统把一半时间用来创建「日期」实体(instance)。更有趣的是,所有这些实体都有相同的值。
  于是我们观察日期的创建逻辑,发现有机会将它优化。日期原本是由字符串转换而生,即使无外部输入也是如此。之所以使用字符串转换方式,完全是为了方便键盘输入。好,也许我们可以将它优化。
  于是我们观察日期怎样被这个程序运用。我们发现,很多日期对象都被用来产生「日期区间」实体(instance)。「日期区间」是个对象,由一个起始日期和一个结束日期组成。仔细追踪下去,我们发现绝大多数日期区间是空的!
  处理日期区间时我们遵循这样一个规则:如果结束日期在起始日期之前,这个日期区间就该是空的。这是一条很好的规则,完全符合这个class的需要。采用此一规则后不久,我们意识到,创建一个「起始日期在结束日期之后」的日期区间,仍然不算是清晰的代码,于是我们把这个行为提炼到一个factory method(译注:一个著名的设计模式,见《Design Patterns》),由它专门创建「空的日期区间」。
  我们做了上述修改,使代码更加清晰,却意外得到了一个惊喜。我们创建一个固定不变的「空日期区间」对象,并让上述调整后的factory method每次都返回该对象,而不再每次都创建新对象。这一修改把系统速度提升了几乎一倍,足以让测试速度达到可接受程度。这只花了我们大约五分钟。
  我和团队成员(Kent和Martin谢绝参加)认真推测过:我们了若指掌的这个程序中可能有什么错误?我们甚至凭空做了些改进设计,却没有先对系统的真实情况进行量测。
  我们完全错了。除了一场很有趣的交谈,我们什么好事都没做。
  教训:哪怕你完全了解系统,也请实际量测它的性能,不要臆测。臆测会让你学到一些东西,但十有八九你是错的

附录二:优化一个薪资系统— Rich Garzaniti

  将Chrysler Comprehensive Compensation(克莱斯勒综合薪资系统)交给GemStone公司之前,我们用了相当长的时间开发它。开发过程中我们无可避免地发现程序不够快,于是找了Jim Haungs — GemSmith中的一位好手 — 请他帮我们优化这个系统。
  Jim先用一点时间让他的团队了解系统运作方式,然后以GemStone的ProfMonitor特性编写出一个性能量测工具,将它插入我们的功能测试中。这个工具可以显示系统产生的对象数量,以及这些对象的诞生点。
  令我们吃惊的是:创建量最大的对象竟是字符串。其中最大的工作量则是反复产生12,000-bytes的字符串。这很特别,因为这字符串实在太大了,连GemStone惯用的垃圾回收设施都无法处理它。由于它是如此巨大,每当被创建出来,GemStone都会将它分页(paging)至磁盘上。也就是说字符串的创建竟然用上了I/O子系统(译注:分页机制会动用I/O),而每次输出记录时都要产生这样的字符串三次﹗
  我们的第一个解决办法是把一个12,000-bytes字符串缓存(cached)起来,这可解决一大半问题。后来我们又加以修改,将它直接写入一个file stream,从而避免产生字符串。
  解决了「巨大字符串」问题后,Jim的量测工具又发现了一些类似问题,只不过字符串稍微小一些:800-bytes、500-bytes……等等,我们也都对它们改用file stream,于是问题都解决了。
  使用这些技术,我们稳步提高了系统性能。开发过程中原本似乎需要1,000小时以上才能完成的薪资计算,实际运作时只花40小时。一个月后我们把时间缩短到18小时。正式投入运转时只花12小时。经过一年的运行和改善后,全部计算只需9小时。
  我们的最大改进就是:将程序放在多处理器(multi-processor)计算器上,以多线程(multiple threads)方式运行。最初这个系统并非按照多线程思维来设计,但由于代码有良好分解(well factored),所以我们只花三天时间就让它得以同时运行多个线程了。现在,薪资的计算只需2小时。
  在Jim提供工具使我们得以在实际操作中量度系统性能之前,我们也猜测过问题所在。但如果只靠猜测,我们需要很长的时间才能试出真正的解法。真实的量测指出了一个完全不同的方向,并大大加快了我们的进度。


0 0
原创粉丝点击