一星期8天

Eight Days a Week - by The Beatles & 小陆

设计和编写可复用的代码

刚开始干这一行的时候,对代码的复用有很高的热情。那时候总是希望自己写出的function、class、模块都是可以复用的,能够优美的解决所有问题。但是往往事于愿违,设计的变更、需求的变更、种种没有预料的情况最终把自己的代码摧毁的面目全非。有时一个简单的function会出现各种不同的版本,SendMessage、SendMessage2、SendMessageEx……在注释中说明其间微妙的区别。复用的计划最终破产。经历打击后,又走向另一个极端:使用copy-paste解决问题。不在乎代码的复用性,放弃优美,走向彻底的实现主义。各种bad smell不断出现在代码中,疯狂的复制粘贴,然后稍加修改。一旦变更,到处修改。面对需求变更、产品升级、杂乱纠缠的代码、过时的文档,真是一种痛苦的经历。

代码复用到底能不能做到呢,怎样才能做到复用呢?

代码复用要求设计具有较高的质量

设计和编写可以复用的代码不是一件容易的事情。简单的说,就是要在需求,概念和最终的实现之间建立一种简单的关系[The Aspects Blog]。一个概念或者需求最好能在实现中有一个直接的体现。比如说,设计一个公司的管理系统,公司里有雇员,那么就设计一个Employee类,表示雇员这个概念。雇员有工资,那么就为这个Employee类加上一个Salery属性,表示雇员的工资。需求和实现之间的关系最理想的情况下应该是1:1,即一个需求对应一个唯一的实现。这样,当需求变化的时候,需要修改的实现也只有一处。这样的情况下,代码的复用性也是最好的,同一个功能只用一份代码来完成。

“需求:实现=1:1”是一个最优状态,达到这个状态首先要对需求有清楚的了解,尽量排除需求的含糊性,这一点是基础。其次在设计上要遵守一些最基本的原则,避免不合理的依赖关系。达到这个1:1很难,一般都会根据具体情况有所权衡。

在设计的不同阶段应该采用不同的抽象层次,不能过早的将问题具体化。具体化的时间过早,往往就意味着一个需求会对应多个实现。一些设计者往往不注重抽象,急于将功能点落实,采用的设计方式经常是这样:需求调查,根据用户的需求提出软件解决方案,根据这个方案进行数据库设计,设计数据库表的字段和关系。数据库设计完成之后,业务逻辑就整理清楚了,再经过简单的程序设计,就开始编码了。编码中很多是对数据库表的直接操作。业务的描述通常是这样:按下某个按钮,打开某个画面,从某个数据表中查询某种数据显示在界面上,按下某个菜单,修改数据表中某个字段……这就是从UI到DB的直接操作,不存在抽象的层次。这样的设计对于需求变化的适应程度是很有限的。

软件的开发和维护过程中经常发生需求变化,需求的变化往往处于不同的抽象层次。比如有一个设备的监控系统,管理着电动机、变压器、压缩机等多种电气设备。用户提出这样一些需求:

1.每次采集到设备的数据后,发现设备的电压大于额定电压50%的时候,自动切断设备的电源。
2.变压器的电流大于额定值的10%的时候,要在界面上显示红色的灯。

这两个需求处于不同的抽象层次,需求1是针对较为抽象的“设备”,而需求2是针对较为具体的“变压器”。这两个不同的抽象层次都应该在设计和编码中有所体现。

如果后来用户变更了需求:“当设备的电压连续一定时间大于额定电压的50%的时候,才将电源切断,否则不切电源,并且在切断电源2秒后,要重新尝试连接电源,发现电压仍然超过,就断开不再连接。”这样的需求变更就应该在“设备”这个抽象层次上进行。如果没有这个抽象层次,这个变更就比较麻烦了。

代码在项目内部,和项目之间的复用

谈到代码复用,最初的想法是设计一个完美的模块,一劳永逸的解决眼前的和长远的所有问题。其实复用也有限制,一个产品或者项目要解决特定的问题,脱离了这个具体环境,很多问题的解决方式是不同的。一般来说,我们的目的应该是写出在项目内可以复用的代码。代码在项目内部可以复用,这应该是设计者追求的目标。一旦达到这个目标,就意味着产品已经有了一个稳定的结构,进入了一个可以高效开发的阶段。复用的代码可以减少bug的发生,减少需求变更的影响。

随着客户业务的不断变化,需求难免要发生变化,这时候,要适时的对设计进行重构,以适应新的需求。原有的代码可以最大限度的复用,很多业务模块不必重新编写,推出新版本的压力就会减少很多。

代码在项目之间的复用有很多形式,可以把某个function复制粘贴到新的项目中,也可以把某些文件引入到新的项目中。这都不是复用的最佳形式。代码在项目间的复用一般以“包”作为最小单位。创建者发布一个包,使用者根据需要决定是否使用。使用的形式可以是静态的编连或者是动态调用。当包的维护者发现了bug,或者添加了新的功能,会在修改后重新发布这个包。使用者根据自己的需要决定是否升级。始终只有一个“包”在进行维护。这才是复用的最佳形式。

“复用别人的还不如我自己写一个呢。”真的吗?

我们经常有这样的体会:为了复用别人的代码,我们需要学习很多东西。查资料,看文档,读例子,甚至要读代码。有时候这个学习的时间甚至会比自己写一个相同功能的代码更长。于是,很多人在工作中不喜欢复用代码,而是倾向于自己写一个。“复用别人的还不如我自己写一个呢。”果真如此吗?

先考虑这个问题:这个“自己写一个”的时间是单纯指编码的时间,还是完整的开发时间。编码只是开发的一个阶段。自己写一个也许不难,也花不了太多时间。但是要测试、修改bug、完善文档……一系列的工作都做下来,这个时间一般都要长于复用代码的时间。

还有一个问题:自己写一个,很难做到好的复用性。下次遇到相似问题,只好再写一个。还不如现在就研究一下别的模块怎么用。

从管理的角度来说,随着项目人员的变化,这些“自己写”的模块将越来越难以维护。一些成熟的模块已经有了完善的文档,比较容易找到相关的资料,学习使用的过程也比较容易交流。开发的时候要使用能买到的最好的工具,也要复用能得到的最合适的代码。

 

posted on 2005-07-21 14:33 小陆 阅读(4220) 评论(15)  编辑 收藏 所属分类: 开发技术

评论

#1楼  2005-07-21 15:10 春鱼      

基于"代码"的"复用"是很低级的复用. 根本没有讨论的价值.   回复  引用  查看    

#2楼 [楼主] 2005-07-21 15:40 小陆      

To:春鱼
我觉得那是项目之间的复用形式,在项目内部的复用是另一回事.
对于很多项目来说,代码在项目内的复用更有讨论价值.   回复  引用  查看    

#3楼  2005-07-21 17:04 春鱼      

你写的有一定的深度, 但我觉得你可能混淆了"项目的迭代"和"复用"以及"组件的复用"和"代码的复制". 还有重构的目的并不是需求的变化, 而是在保持外在逻辑不便的情况下重写代码. 你也是一再强调"设计"的, 但我认为在设计的层面上, 代码的复用没有意义.

一点看法.   回复  引用  查看    

#4楼  2005-07-21 17:35 James      

混淆了概念。
把很多东西搅在一起。   回复  引用  查看    

#5楼  2005-07-21 18:08 idior      

有思考总是好的,支持一下。   回复  引用  查看    

#6楼  2005-07-21 19:12 无名 [未注册用户]

光说这作者混淆了概念,
那希望能说清楚点,那几个高手。。   回复  引用    

#7楼 [楼主] 2005-07-21 21:27 小陆      

对几个概念说一下我自己的理解:
重构:我也觉得重构是在业务逻辑不变的前提下对代码进行优化和重写.但是这个重写的目的应该是在于,适应已有的和预测的需求变化.比如,一开始设计的设备监控只有电动机设备,于是设计了Moto类.以后发生需求的变化,还要控制压缩机.在添加压缩机之前,先对代码进行重构,在业务逻辑不变的情况下重写代码,加入Device类,Moto类是Device的子类,所有的逻辑不变.在程序稳定之后,再派生一个Compressor类,实现新的需求.如果没有需求变化,以后也预料不到,何必重构呢,仅仅因为洁癖吗?
代码复用:项目内代码可以重用我觉得是一个设计成功的标志之一.达到了这一点,项目就进入了一个高效开发的阶段.并且当前版本的代码能够被下一个版本重用,也是一种很理想的情况.我们在代码里建立一个Recordset对象,这是复用,我们扩充Eclipes,添加一个plug in,这个plug in可以直接调用workbench提供的service改变文档的内容,这也应该是一种复用.
代码复用确实是一个十分庞大的主题,包含的东西太多太多,不是一篇短文能够说完的,有些领域也确实超出了我的能力范围.本文主要是我在实践中的一些感受,希望能够带给大家一些新鲜的思考.   回复  引用  查看    

#8楼  2005-07-21 21:49 dotnetfresh      

说点个人观点:代码复用可能是比较"低级"层次的复用,但我感觉目前有多少人,多少项目组能做好这些低级的东西?现在经历的一些项目,感觉copy,paste比比皆是,代码里到处都有"臭味",这些低级的东西不做好,谈何高级?当然,也可能是我所在的团队水平的关系,所以希望各位高手能把所理解的说得稍微详细那么一点点.   回复  引用  查看    

#9楼  2005-07-22 06:34 waxwork3      

我们应该强调软件架构而不应该是什么代码复用。强健的架构才有更长的生命力。   回复  引用  查看    

#10楼  2005-07-22 09:03 simonw      

很多时候软件设计中的种种技术都是一个阶梯式的结构,是一步一步走上来的,而非空中楼阁,我觉的小陆所说的很多的确是项目开发中遇到的很实际的问题。说根本没有讨论的价值,未免有些武断。尤其在方法学上,我认为任何时候都不要把话说的这么绝对。   回复  引用  查看    

#11楼  2005-07-24 10:30 wayfarer      

我同意simonw的意见。在软件开发过程中,很多看似“低级”或者简单的知识,往往被我们忽略,实际上这些基础的知识也很重要。

以代码复用这个知识点而言,也并非“春鱼”所说的,没有讨论的价值。实际上,如果我们使用TDD的方式开发项目时,在测试驱动的过程中,也需要考虑代码复用的问题。当然,这里所谓的复用,可以扩散来思考,也就是从设计的角度,考虑类的划分、包的划分,以达到更大的复用性的目的。

另外,waxwork3所说的“强调软件架构”,固然正确,然而一个好的软件架构,不一定就是直接可以从设计中获得的,同样需要重构、也同样需要考虑模块的划分、也需要考虑需求的变更。关注软件架构,并不等于就一定要和代码级的内容完全脱离。   回复  引用  查看    

#12楼  2005-07-30 12:08 koman [未注册用户]

尤其在方法学上,我认为任何时候都不要把话说的这么绝对。说的很好。
架构这个东西不是地里的大白菜,种下去就有,是一步步提炼出来的。尤其是面向专门业务的架构,需要实际检验的,不是做在那里想出来的。
我们说这是一个良好的设计,那一定是经过实际检验的,所谓强健的架构,至少经过两个版本升级而没有被破坏的,也许这个过程需要两年甚至更长。
设计和编写可复用的代码,并非仅仅是编程,如果在设计中不予以考虑,可以复用的代码能写出来吗?
也许我们看到和听到太多完美的设计,我仔细的分析过,基本上都是我们的开发工具,开发环境的宣传和介绍,基于我们的专业,你会很快赞同甚至附和这些观点。
更多从业人员他们服务于另外一个行业,计算机只是一个工具,在你尚未精通另外一个专业的时候,评价设计是否良好,架构是否强健,需要时间的检验。
不要忽视简单的知识,简单未必能做好。
不要去玩弄概念,最终玩弄的是你自己。   回复  引用    

#13楼  2005-08-31 15:41 vcebook [未注册用户]

to_koman:
说得不错.
有一个螺旋式开发的过程.   回复  引用    

#14楼  2006-06-30 09:18 .Live      

看问题的角度不同,光从“设计”层面来说复用才更没意义。从实践中得到的真理,往往和那些设计理论不尽相同。但是记得哲学里有一条:真理是相对的真理。只要真理在我们实践的环境内是适合的,那么我们就可以在这个范围内讨论和应用它。讨论应该从理解的角度出发,而不是全盘否定,支持楼主的文章和观点。如果在实践发现有什么不符的地方,也会从我的角度有建设性地否定的。
[r]   回复  引用  查看    

#15楼  2006-12-01 08:17 许文      

1.基于代码的重用 --> 2.基于方法的重用 -->3.基于框架的重用。
  回复  引用  查看    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-10-14 22:43 编辑过


相关链接: