• 全部
  • 问答

重构的目的,以及重构时的“测试先行”——to:Schlemiel(维特根斯坦的扇子)

BirdGu 2004-01-13 03:04:05
你在给“iamwls(-----魔高一尺,道高一丈-----):”的回贴里的两个问题值得讨论:
1. 重构的目标是否一定是一个模式。我认为不是。不错,模式常常会指明重构的方向,在某些情况下模式也确实是重构的目标,但这却不是必然。重构的目的是改进程序的质量、驱除程序中的smell。具体说就是要高内聚、低耦合,并提高程序的可读性。Fowler在他书中举出的smell of code都是对高内聚、低耦合这一原则的违反,或是降低程序的可读行,而重构的方法都是在内聚、耦合、可读性这几方面做文章的。
模式是实践中形成的解决常见问题的通常解法。但不是唯一解法,也不是任何情况下的最优解。因此软件中也不是要处处用模式,更何况某些情况下Pattern会成为AntiPattern。模式也就更不会总是成为重构的目标了。
而且某些重构的粒度是非常小的,是不可能以某个模式为目标的,比如extractMethod,moveMethod等。

2. 如果要对一个没有任何单元测试代码的软件进行重构,此时能不能做到“测试先行”,如何做到“测试先行”。我觉得在一定条件下还是可以的。因为每一个重构的步骤是很小的。所以是可以针对这一小步骤写单元测试代码的。然后随着重构范围的扩大,在逐渐扩大测试覆盖的范围。因此也不能就说是Mission Impossible。不过前提是能把要重构的部分与软件其它部分隔离开来。如果软件设计的实在太差,代码件的依赖关系是在太复杂,这时到真有可能成为Mission Impossible。如果一定要对这样的软件进行重构,而非重写,我也不知道该怎么做最好。希望能听到高见。
...全文
125 点赞 收藏 62
写回复
62 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
qingshan 2004-01-18
关注
回复
scalene 2004-01-16
BTW, http://expert.csdn.net/Expert/topic/2608/2608186.xml?temp=.8524591是我提出的关于OO以外编程技术Refactory的讨论,不过一直没有人参与,希望博士和O6z两位老哥帮忙给看一下.
回复
scalene 2004-01-16
ozzzzzz(希望敏捷):
嘿嘿,好像有点说远了。因为我们讨论的是如果必须重构时,策略应该是怎样的。不过还是按照你的思路讨论一下。
其实某些时候说一段代码是否“垃圾”,也是一个比较模糊的用词。我觉得,某些遗留代码,经历了不同的发展阶段,加入了不同风格的设计和代码实现,产生结构的僵化和难于理解,就应该算是垃圾代码。你说的“需要不断的修正结构”是对的,是XP所鼓励的工作方式,也应该是“Refactory”这个词提出的本意。为什么Refactoring这本书里介绍的实际上都是一些比较low level的代码改进方式呢?实际上应该是基于这样渐进工作的前提的,所以不应当出现特别严重的设计问题。从这个意义上讲,其实我们所讨论的内容并不是真正意义上的refactory。
至于你所说的“选择设计一个适于添加这些代码的结构”,我想是有前提的。一是没有太严重的Dead Line限制,一是对现有代码的充分理解。如果这两点能够满足的话当然很好,不过我更想知道你对不符合这两点要求时的考虑方式。谢谢。
回复
jeffyan77 2004-01-15
elements: 元素

elementary: 基础的,元素性的

elementary particles:基本粒子
回复
ozzzzzz 2004-01-15
scalene(南瓜汤)
我觉得这不是一个高深的问题,而是我们经常需要面对的普通的问题。就是因为它普通,所以就会有多种的积极和消极的解决方法。而我们自己也往往使用过多种方法解决类似的问题,这个时候问题才变得好像高深起来。权衡总是一种困难的。
问题总是不断积累的,最后到了不能在承受的时候就会爆发。所以根本的解决办法还是在最早解决那些看来不是很大的问题,而如果是一个产品则更需要不断的修正结构。我是一个没有运气的人,害怕发生什么最后往往就会发生,所以我学会把问题解决在最初。
当你的代码真的是经过了市场的考验和测试的代码的时候,你面对的往往不是那些垃圾的代码,只是结构出现问题的代码。而在这些代码的基础上添加新功能,而往往是一种挑战。所以delphi的编译器那么久才升级一次。而当你要添加的代码在现有结构上是不可能的时候,我还是选择设计一个适于添加这些代码的结构,然后把原来的代码也如同这些新代码那样逐步的添加进来。也就是要作一些重构代码片断,以利于把他们添加到一个新框架的工作。
回复
scalene 2004-01-15
ozzzzzz(希望敏捷) :
呵呵,这好像是一个比较高深的问题。
垃圾代码的产生,未必是因为开始没有设计一个好的结构。而也有可能是由于需求的不断变化,而市场的压力导致代码不能及时被重构产生的。XP似乎很重视开发环境是否宽松,原因因该在这里。
重新设计一个结构,某些时候可能意味着:如果原先的代码是经过测试和市场考验的,那么你将面临着抛弃这些可能是很大部分的工作而另起炉灶的危险。况且某些时候你不得不面对这样的困境:版本要一个一个的出,功能要不断地加,而代码质量也到了非重写不可的地步了。
重新设计一个结构是否就一定更好?你比我的经验更丰富,可能做这样的事更有信心一些。某些时候我是不敢。如果不能按时交活,可能是对整个项目组的生存的威胁。所以基于已有代码,新的测试工作不至于太大;结构调整优先,让我可以比较从容地加入新的功能。等到项目到了时间比较宽裕的时候,在把剩余的工作补齐。
对于你说的大段代码,一些挠头的程序,在实际操作中,是尽量避免先对它们的修改。程序中的Bad Smell会很多,接口性的调整之所以简单,就是因为很有可能不需要去读具体的代码,而只要简单Re-engineer出大致的UML就可以进行了。
这样的问题,等到你调整结构遇到不能不改时,可能会代价更小:因为你很可能会更清楚的理解,到底是怎样的问题导致这样的怪兽程序的产生。
也就是我对于垃圾程序的忍耐度比你高一点,也是对于重新设计没有信心的体现。
回复
ozzzzzz 2004-01-15
scalene(南瓜汤)
其实可能现在我已经不相信重构是个银钳子了,所以我越来越依赖从开始就设计一个好的结构,当然这个结构是通过重构而产生的,只是它不是通过对于一堆垃圾代码产生的。
所以我对于你说的情况,就是采取只去寻找那些确实需要的代码,然后把他们重构,以便于在上面添加新功能,或者把他们添加到新代码中。
重构书上说三个时候你应该重构,为添加新功能作准备,消除味道,读不懂代码。我现在添加一条,为把一段代码添加到别的代码中对于这段代码重构。

而对于读不懂的代码是不是要先理解在重构的问题其实很好解释。我曾经看到过(我想多数人都看到过)上千行的方法,这个时候你怎么办?我在看到过一个从文件中读取设置的方法,这个方法有多长我也忘记了,反正非常长。我只能一段代码一段代码的划分开来,看看到底有多少个段,一段我就换为一个小方法。最后才看明白它的到底那个XML文档是什么意思。
回复
scalene 2004-01-15
BirdGu(鲲鹏):
呵呵,应该不象你想象那么多。因为我从来也没说这种调整是一步到位的,而应该是渐进的。也就是,在开始的调整结构阶段,是局部调整-结构调整-局部调整-结构调整-...的过程。当结构调整到比较清晰的时候,至少是把我们最关心的,需要进行功能扩展或修改的模块结构调整到比较清晰,和其他模块依赖关系很清楚的时候,就可以进入下一个阶段了。
回复
BirdGu 2004-01-15
我说的“读懂代码”是指至少把要重构的部分和可能会影响到的部分读懂。不过如果要象scalene(南瓜汤)说的做结构上的调整,恐怕要读懂的代码部分不会少。
回复
scalene 2004-01-15
ozzzzzz(希望敏捷) :
我所说的只是一个大体的思路,你的做法“选择先去作一个细小粒度的重构”我并不拒绝,如你所说,这样放在手边的小粒度重构可以帮助我们熟悉代码,找到一些分析系统的切入点。要是能够通过熟悉代码找到简单解决问题的方式当然好(不过要是那样只能说明原来的开发人员太弱智,这么简单的办法都想不到),不过如果不能呢?一般情况都是这样。通过看一些代码,了解原有结构复杂的症结所在,或部分症结所在。真正开始解决问题,还是要从调整结构开始。BirdGu(鲲鹏) 所说“读懂代码”并不准确,可以象ozzzzzz所说的,先试图修改一个局部,有了一定心得就可以开工了。“退回起从新开始是你永远存在的选择”当然没错,我想说的只是选择一条简单的途径,可以一定程度上减少你的重复工作。
回复
ozzzzzz 2004-01-15
scalene(南瓜汤)
这样的问题我没有遇到过,嘿嘿微软应该遇到过,记得word一个版本和前某个版本不兼容。
在这个时候我会选择先去作一个细小粒度的重构。这些小粒度的重构,只能解决一些小问题,对于全局的问题不会有什么作用。但是不要忘记这些小的问题可以帮助我们熟悉系统的结构,理解其内部的逻辑,也许通过这些东西你就可以找到新的解决方案而不用继续重构下去了。如果不能也不会有什么大问题,这些小的修改往往可以在无测试的情况下使用,而且还有很多工具支持,所以代价不会很大。而一个好的SCM工具会让你发现没有什么东西是无法挽回的,退回起从新开始是你永远存在的选择。
其实我在任何情况下都会先选择细粒度的重构,反正工具会替我作。
回复
BirdGu 2004-01-15
对于不熟悉的代码,首先要做的恐怕不是重构,而是读懂代码吧?
回复
scalene 2004-01-15
ozzzzzz(希望敏捷) :
从需求出发/重新设计优先,这两点我都同意,上面的贴子也有提到。
不过有时候对于不熟悉的代码,实际需求又不允许重做(不知你有没有碰到,一个文档格式需要维护,但是文档结构事实上存在于代码中的东东),如果不先做重构,可能连工作量的大小都估计不清楚。
所以先做结构性调整,才能知道那些是可以重写的,那些是暂时不能动的。
而一上来就low level的refactory,很容易因为一些遗留下来的依赖性问题,造成无法修复的错误。
回复
ozzzzzz 2004-01-15
scalene(南瓜汤)
其实你的问题在我看来不是单纯的重构可以解决的,面对这样复杂的局面需要多种手段结合。记得一年多以前,我给小西还是石头说过这个问题,就是如何在一个胡乱的又没有测试配合的且无文档的环境下开始重构。
这个时候不是先开始重构的问题,而是先确定是不是值得重构的问题。而作出这些判断的基础在于首先了解需求,了解你到底要解决什么样的业务问题。我的经验是项目往往不是那么相同,很多功能已经不需要了或者有了更好的解决方案。太看重代码级的复用,而要把多于的结构都删除,只保持现在还需要的那些功能的主要代码。这个时候这些代码可能不是不能运行的,没有关系。你需要的是保持代码量不要太多,这样为这些代码所要准备的测试就会不那么多,重构也不会那么困难。简单说就是先确定需求,然后分析到底什么变化了,什么没有变化。保留那些没有变化的并且没有更好解决方法的部分。
而如果你还不能够对于原理的整体设计和模块结构有太多的了解,需要通过重构来理解,这个时候我往往干脆再设计一个新的。
回复
scalene 2004-01-15
和重构到模式相比,refactoring to framework的确更准确一些,因为它体现了需求和领域经验的含义在里面。收到。
回复
iamwls 2004-01-15
ding
回复
scalene 2004-01-15
ozzzzzz(希望敏捷):
我想我明白你的意思。
不过我想我们的讨论是有前提的:一个结构混乱,功能复杂的系统。我的经验是,对于这样的系统,如果一上来就按照比较低层次的smell去refactory,往往会造成很大的反复,或者根本不可能进行下去。因为这种代码的表现往往是模块间依赖关系的混乱。如果这个问题不先解决,重构的效率往往会很低。而相比较之下,类接口,模块接口的调整,一些代码的移动,在这种情况下,往往是相对代价较小的。
所以我会先对一些public方法过多,或模块public类过多,或耦合性问题的Smell优先解决。我觉得,在这样的情况下,是适当的。
回复
Chuanyan 2004-01-15
TO scalene(南瓜汤)
对于重构和测试,我在此贴中已经第一个表示对BirdGu (鲲鹏)的认同了.然后在讲到测试环境的时候,我才说一说我接触过的环境.可能你对我的整体思路有片面理解:)
回复
ozzzzzz 2004-01-15
终于忙完了。
我先来谈谈大粒度重构的目标问题。我觉得重构到模式这个说法不好。而应该说refactoring to framework,当然这就有了与之相对的framework from refactoring,还会有refactoring on framework。我的观点是这也是三顶帽子,你一次也只能带一顶帽子。最开始是从重构中得到一个框架;然后是在框架中添加功能,随之就在框架的基础上重构。这样的过程会持续一定的时间,随后可能会发生一种无法在现有框架下继续功能的状况。这个时候你是不是该设立一个明确的目标呢?如果我们只是考虑结构,就可以不确定这样一个目标。而当我们需要考虑效能的时候,问题就出现了。这个时候是我们先设立一个高效的框架,还是先构建一个可运行的程序,然后对其进行优化?生存还是毁灭,这是一个问题。
我想不同的人会有不同的选择,他们的理由都很充分,而且我认为他们几乎都是正确的。关键是要针对自己的情况进行选择,如果你认为你有把握全局的能力,面向对象是你的思维模式,这个时候你就可以设定一个明确的目标。如果你觉得把握不了,权衡一下,设立一个局部的目标。如果你根本就找不到头绪,干脆就没有目标,只针对味道好了。这三种选择未必是说设立一个明确的目标就比设立一个局部目标水平高,比一点一点消除味道水平更高。这只能说你设计框架的能力是高到低的,而重构的水平往往是低到高的。对于是否是味道,没有一个普遍的标准,重构而得到的结构也未必就是类似的。关键是找到一个可以帮助我们作出类似困难选择的标准,让我们针对自己的情况在不同的场景先更容易的作出权衡。

关于测试的问题,我想BirdGu (鲲鹏)提到的使用MockObject和本地专用的测试数据库。比如我建议Schlemiel(维特根斯坦的扇子)让你的所有的组员都在本地统一安装一个HSQL,这样的测试成本是不高的,而且会减少很多的集成测试的麻烦。对于你的情况这是非常核算的做法,因为我认为你的习惯是不会去依赖PL/SQL的,更不会去搞存储过程这些东西。
回复
scalene 2004-01-14
swinging(山不在高) :
不使用ORACLE可能不好吧,因为本身是有针对性的
-----------------------------------------------
因为我的前提是Unit Test,对于你的数据逻辑层对象当然Oracle是必须的,但是业务逻辑层对象并不关心这些。
对于组合的情况,我建议放在集成测试中,而且也不是所有逻辑都需要测。
当然这只是建议,如果使用Oracle本身就很简单,那也没必要剥离出不同的测试数据库环境了。
回复
加载更多回复
相关推荐
发帖
研发管理
创建于2007-08-27

1211

社区成员

软件工程/管理 管理版
申请成为版主
帖子事件
创建了帖子
2004-01-13 03:04
社区公告
暂无公告