关于重构
重构是一种改善已有代码和设计的有效手段,Martin Fowler的著作Refactoring:Improving the Design of Existing Code
一书里提出了若干种重构的模式,深刻地影响了众多的开发人员。但如果认为重构只能做到小范围的代码优化,或者设计优化,并视之为无法影响更高层面工作的雕虫小技,那就大错特错了。之后 Joshua Kerievsky 的著作 Refactoring to Patterns
则创造性地把重构和模式联系在一起,让人们认识到重构的巨大威力。重构从来不是程序员躲在象牙塔孤芳自赏的技术,也可以对系统的设计开发发挥巨大的作用。
如果说Martin的Refactoring
只是深刻地影响了普通开发人员的程序设计和代码编写,Joshua的Refactoring to Patterns
则
切切实实地给架构设计人员或者tech
lead的工作指出了革命性的变化。那么,对于项目的功能开发,重构又意味着什么呢?对于项目经理来讲,应用重构的技术和思想,对整个项目的功能开发是否
能带来特别的好处?下面将通过一个例子给大家展示在开发新功能时,对开发的每一步都保持重构的思想,将整体功能的开发分解成若干步骤的“重构”,从而非常
简易清晰地完成功能开发。
新功能的提出
某系统中存在 ReferenceFile
类,作为用户向系统上传的文件的抽象,是系统其他地方会使用到的参考文档。在最初的需求中,参考文档与用户上传的文件一一对应,并且用户能指定某些系统特
定的属性,比如文档类别、文档管理部门等。于是在最初的设计中,该类的属性包含两部分:一部分是前面提到的系统属性;另一部分是描述文件信息的属性,比如
文件名、文件存储路径等。请注意,这里我将“参考文档”与“上传文件”两个概念区分出来,是为了便于下文解释。总的来说,在这个阶段,“参考文档”就是系
统对“上传文件”的抽象。
接到这个需求之后,我们使用TDD,很快就驱动设计出该类的业务方法;再使用Acceptance
TDD,又对该类的功能的进行了全面的覆盖;最后使用hibernate的O/R
Mapping,按照属性和表字段一对一的关系,把该类和数据库的表关联起来。完成UI方面的设计,并把前后台整合在一起。系统上线试运行后用户认为这块
很好地契合了需求。
但是,需求总是不断在变的。上线过程中,用户的上级部门提出参考文档应该可以对应到多个上传文件,系统其他地方使用时把其下所有上传的文件作为一个“参考
文档”整体来对待。也就是说,对 ReferenceFile
类而言,其中的系统属性仍然是保持一份,但是上传文件的属性则变成多份。概括下来,客户提出的新需求如下:
1. 参考文档可以管理多个上传文件
2. 用户创建或者修改参考文档时,可以同时上传多个文件,并能对已上传的文件进行删除修改
3. 系统在其他地方仍然是针对参考文档来参考引用用户上传的文件
4. 参考文档的预览和展示需要调整成支持多个上传文件
实现过程
该系统是标准的j2ee web 分层系统,包括web UI、controller、service、domain
model、dao这几层。本文的重点是如何应用重构开发功能,本文将着重关注于domain层的改动,会包括domain model
API的改动,以及domain model 持久化机制的改动。其他层次,比如controller、service等,因为主要是作为domain
model的消费者,主要是使用domain model 的public API,故放在一起作为整体来对待,下文将统一称为client
代码。至于最外层的 web UI层,则因为主要是根据系统功能提供交互上的操作和内容展现,而且大部分情况下也会有专门UI设计开发,本文就不涉及了。
另外,系统还包括大量不同层次的测试代码,比如unit test、functional test、integration
test和regression test等等。从另外一个角度,测试代码又可以分成2部分:text fixture和test case。test
fixture主要是负责测试数据的准备,test case才是测试用例的实现代码。前面提到的测试,除了unit test之外都主要是基于
web UI 模拟用户使用系统功能,test case 主要是针对 web UI 来写,故对于这部分的测试而言,domain model
的修改主要会影响到测试数据的准备。而对于 unit test,又可以根据SUT的不同,分为几个部分:针对model的unit
test、针对client(包括controller和service)的unit test。其中,针对model的unit
test也只是model API的消费者,也可以视为domain model的client。针对controller和service的unit
test,理论上也只针对于SUT的API,对model的API依赖也只是在test
fixture那块。所以,根据我们的分析,我们知道测试代码可以简化成两部分,一部分是与controller/service类似的domain
model的client,另一部分是使用domain model生成一组aggregation的test fixture。
综上所述,我们把整个功能实现过程中涉及的工作主要归类为:domain model API的改动、domain
model持久化机制的修改、domain model client的修改,以及test
fixture的修改。现在对于需要做什么事情,就变得清晰了。我们接下来对前面三项工作来分析。
面临的现状
仔细分析我们面临的情况:
1. 文件的相关信息在原始的 ReferenceFile 类里面是作为一对一的属性组存在
2. ReferenceFile 类使用 Hibernate 进行属性字段一对一的持久化
3. ReferenceFile 类以及原功能有 unit test、dao test,以及functional test 覆盖
此时的 ReferenceFile 类是这样的:
<!-- {cps..16}-->public
class
ReferenceFile {
private
String category;
private
String fileName;
//
相应的 getter/setter,以及业务方法
}
ReferenceFile 类的hibernate映射文件是这样的:
<!-- {cps..17}-->
<
class
name
="ReferenceFile"
table
="referenceFiles"
>
<
id
/>
<
property
name
="category"
/>
<
property
name
="fileName"
/>
//
</
class
>
回头看看在这次功能调整中,我们需要做哪几项任务?其中会涉及哪些方面?
-
domain model 的修改
-
domain model 持久化机制的修改
-
domain model 增加一对多的关系
抽取新类
domain model 的修改
很明显,随着需求的变化,作为一组时时刻刻同时出现而且内聚性非常强的属性,原来记录文件相关信息的属性组,比如文件名、上传路径以及类型等等,以及操作
这些属性的方法需要抽取到一个单独的类里面。Martin Fowler 在Refactoring:Improving the Design of
Existing Code里面写到“...consider where it can be split...A good sign is
that a subste of the data and a subset of the methods seem to go
together.”因此,我们决定把这些属性组和方法抽取到一个新类。新的类的职责变成维护上传文件的相关信息,而 ReferenceFile
则化身为一组上传文件的集合,不用操心文件的存储和具体细节,更利于系统其他地方进行引用。
那我们该如何进行演化呢?这里我们可以使用 Martin Fowler在Refactoring书中的“Extract
Class”技巧。请大家自行参阅,就不具体讲了。经过这一步,我们现在可以得到这样一个结构:ReferenceFile has an
Attachment。 这两个类的代码大概如下:
<!-- {cps..18}-->public
class
ReferenceFile {
private
String category;
private
Attachment attachment;
//
相应的 getter/setter,以及业务方法
}
public
class
Attachnment {
private
String fileName;
//
相应的 getter/setter,以及业务方法
}
domain model 持久化机制的修改
接下来,我们需要修改 ReferenceFile
的持久化机制。在原始的设计里面,ReferenceFile类的属性一一对应到数据库表中的字段。现在属性被分到了两个对象里面,为了
Hibernate依旧能把这些属性都持久化到一张数据库表里面,我们使用了 Hibernate 提供的
component配置。下面是改动后的配置:
<!-- {cps..19}-->
<
class
name
="ReferenceFile"
table
="referenceFiles"
>
<
id
/>
<
property
name
="category"
/>
<
component
class
="Attachment"
>
<
property
name
="fileName"
/>
//
</
component
>
</
class
>
运行测试,OK,所有的测试都pass了。至此,我们抽取新类的步骤就完成了。接下来,我们需要完成“一对多”的演化。
公开新类
domain model 的修改
在这里面,我们需要将 ReferenceFile 类里面的 Attachment 类公布出来,直接在client
code里面使用这个类。这样,原本属于 Attachment 类的方法就能彻底地从 ReferenceFile
类里面移走,ReferenceFile类只留下必要的业务方法和 Attachment 对象的getter/setter。Martin
Fowler在Refactoring:Improving the Design of Existing Code里提到“move
methods”,我们采用这种技巧,很容易地把原来与Attachment类相关的业务方法都移到Attachment类里
面,ReferenceFile类里面只保留对attachment属性的getter/setter方法。公布Attachment对象之后的结构:
<!-- {cps..20}-->public
class
ReferenceFile {
private
String category;
private
Attachment attachment;
//
相应的 getter/setter,以及业务方法
}
public
class
Attachnment {
private
Long id;
private
String fileName;
//
相应的 getter/setter,以及业务方法
}
domain model 持久化机制的修改
这里,我们就考虑把Attachment单独持久化到自己的数据库表里面了。原来的component就变成了现在一对一关联。改动后的配置如下:
<!-- {cps..21}-->
<
class
name
="ReferenceFile"
table
="referenceFiles"
>
<
id
/>
<
property
name
="category"
/>
<
one-to-one
name
="attachment"
class
="Attachment"
/>
//
</
class
>
<
class
name
="Attachment"
table
="attachments"
>
<
id
/>
<
property
name
="fileName"
/>
</
class
>
实现类之间的一对多联系
domain model 的修改
到这里,读者就能发现这是一种Kent Beck曾经总结过的“First One, Then Many”情况。关于“First One,Then
Many”,Kent
Beck曾写了一篇文章介绍如何可靠地拥抱变化,原文链接如下http://www.threeriversinstitute.org
/FirstOneThenMany.html。在那篇文章中,Kent的问题是面对未来可能的需求变化,如何使用 Succession
的方式帮助系统架构平滑演化。下面是 Kent 的观点:
Applied to software design, succession refers to creating a design
in stages. The first stage is not where you expect to end up, but it
provides value. You have to pay the price to progress from stage to
stage, but if jumping to the final stage is prohibitively expensive in
time or money, or if you don't know enough to design the "final" stage,
or if (as postulated above) the customers don't know enough to specify
the system that would use the final stage, then succession provides a
disciplined, reasonably efficient way forward.
那么,在本文的功能开发之中,我们是如何做到的?
- 增加字段attachments,以及getter/setter
- 修改原来单个Attachment的getter/setter,改成从attachments里面得到首元素或者往里面添加新元素,如getAttachments().get(0)
- 运行测试,确保所有测试都通过
- inline
ReferenceFile类里面的对单个Attachment的getter/setter方法。这里要注意test
fixture里面对domain
model的aggregation的创建,而且因为涉及对List的操作,所以可能需要修改原来的测试代码和test fixture
- 运行测试,确保所有测试都通过
到这里,“一对多”的工作完成之后,ReferenceFile 和 Attachment 类就变成了下文的样子:
<!-- {cps..22}-->public
class
ReferenceFile {
private
String category;
private
List<Attachment> attachments;
//
相应的 getter/setter,以及业务方法
}
public
class
Attachnment {
private
Long id;
private
String fileName;
//
相应的 getter/setter,以及业务方法
}
domain model 持久化机制的修改
为了能实现一对多的实体关系,我们需要引入新的表作为“多”方,并保持“一”方的主键。使用Hibernate提供的one-to-many很容易做到这点,接下来是简单的配置文件:
<!-- {cps..23}-->
<
class
name
="ReferenceFile"
table
="referenceFiles"
>
<
property
name
="category"
/>
<
set
name
="attachments"
cascade
="all"
>
<
key
column
="id"
/>
<
one-to-many
class
="Attachment"
/>
</
set
>
//
</
class
>
<
class
name
="Attachment"
table
="attachments"
>
<
property
name
="fileName"
/>
</
class
>
结论
至此,我们就完成了新功能的开发,可以看出整个过程的思路非常明显,而且因为主要是沿着重构的思想一路下来,思路非常清晰。另外,因为重构已经有成熟的IDE支持,我们可以利用到IDE的很多便利,这从另一方面也给我们带来了非常的效率。
从整个过程来看,重构的一些方法和思想,不仅可以让我们对遗留代码进行优化,使之能有利于新功能的开发(比如本文中的抽取新类和公开新类,都是为了下文的
“由一到多”的功能开发),而且可以让我们在开发功能的时候能从一个更高的角度来分解功能的开发工作,从而把原本复杂无序的过程简化抽象成一段明确的重构
链。那么,重构是否就是开发人员开发软件的领域专属语言呢(refactoring as DSLs to developers'
development)?敬请期待本博关于这点的其他博文。
分享到:
相关推荐
《Refactoring思想和实践》这本书深入探讨了这一主题,为开发者提供了宝贵的指导。 重构的核心在于对现有代码结构进行有目的、有系统的改造,以消除设计上的不良模式和冗余,提高代码的内聚性和耦合度。这一过程...
**《Refactoring to Patterns》**一书由Joshua Kerievsky编写,于2004年8月5日由Addison Wesley出版社出版。该书深入探讨了如何将设计模式与重构技术相结合,以实现更高效、更安全的设计变更。作者通过实际项目中的...
《Refactoring To Patterns》不仅是一本介绍重构技巧的书籍,更是一本关于如何将设计模式与重构相结合,以提高软件质量和可维护性的宝贵指南。通过学习书中的各种重构方法和设计模式,开发者能够更好地理解和解决...
本书不仅展示了一种应用模式和重构的创新方法,而且有助于读者结合实战深入理解重构和模式。书中讲述了27种重构方式。本书适于面向对象软件开发人员阅读,也可作为高校计算机专业、软件工程专业师生的参考读物。
1. Refactoring; 2. Design Patterns; 3. Refactoring to Patterns; All chm format books with high quality!
**重构**是一种软件工程实践,指的是在不改变外部行为的前提下对现有代码进行改进的过程。重构可以提高代码的质量、可读性和可维护性,并减少未来的维护成本。 **设计模式**是针对特定问题的通用解决方案。它描述了...
.. 现在,在众所期盼之中,Joshua Kerievsky的《重构与模式》第一次深入和全面地论述了设计模式的运用与演进式的重构过程,并揭示了两者之间至关重要的联系,又一次革命性地改变了我们的设计方式。 本书阐述了...
通过学习和实践《重构至设计模式》中的知识点,开发者可以更好地理解和应用设计模式,从而编写出更加优雅和高效的代码。以上只是书中所涵盖的一部分知识点,更多深入的内容,如装饰器模式、观察者模式等,都有详细的...
重構技術的書,雖然是04年的書,值得大家一看
总而言之,《Refactoring to Patterns》是一本实践性很强的书籍,它通过丰富的案例和实际代码示例,展示了软件设计师如何利用重构来改进设计,以及如何将设计模式应用于软件开发过程中以解决具体问题。书中包含的...
《重构与模式(Refactoring To Patterns)》这本书深入探讨了软件开发中的一个重要主题:如何将现有的代码结构逐步改进,使其更符合设计模式,从而提升软件的可读性、可维护性和扩展性。这本书是面向已经有一定编程...
This book is about the marriage of refactoring—the process of improving the design of existing code—with patterns, the classic solutions to recurring design problems. Refactoring to Patterns ...
在阅读《Addison Wesley Refactoring To Patterns.chm》这个电子版文件时,读者可以期待获得以下方面的知识: 1. **重构的基本原则**:了解何时应该进行重构,以及如何避免在重构过程中引入新的错误。 2. **重构的...
In this first book to provide a hands-on approach to refactoring in C# and ASP.NET, you’ll discover to apply refactoring techniques to manage and modify your code. Plus, you’ll learn how to build a...
一共12个包,全下载解压 重构与模式(Refactoring to patterns).part08.rar
- **逐步改进**:书中提倡采用小步骤的方式进行重构,逐步优化代码结构,避免一次性做出重大改动带来的风险。 - **持续重构**:强调重构应成为日常开发工作的一部分,通过持续的改进来维护代码的质量。 通过...
.. 现在,在众所期盼之中,Joshua Kerievsky的《重构与模式》第一次深入和全面地论述了设计模式的运用与演进式的重构过程,并揭示了两者之间至关重要的联系,又一次革命性地改变了我们的设计方式。 本书阐述了...
好书推荐之: 重构与模式.Refactoring.to.Patters.part1 好书推荐之: 重构与模式.Refactoring.to.Patters.part1 120M,所有分两部分传了