5.9 Cut-And-Paste Programming(剪贴编程)
|
反模式名称:Cut-and-Paste Programming 别名:Clipboard Coding(剪贴板编码)、Software Cloning(软件克隆)、Software Propagation(软件繁殖) 最常见规模:应用层 重构方案名称:Black Box Reuse(黑盒复用) 重构方案类型:软件 根源:懒惰 不平衡的力量:资源管理、技术转移管理 轶事证据:“嘿,我以为你已经修复了那个错误,为什么它又这样做?”“伙计,你工作得真快。3周内完成40万行代码是了不起的进展!” |
“不要管质量,看看它的宽度。”
——Vince Powell和Harry Driver(英国喜剧作家)
5.9.1 背景
Cut-And-Paste Programming是软件复用中非常常见但却是退化的形式,会造成维护的恶梦。它来自于更容易修改已有软件而不是从头编写的观念。通常这是对的,代表了良好的软件开发本能。但是,也很容易过度使用这一方法。
5.9.2 一般形式
通过在软件项目的多个地方发现相似的代码段可以鉴别该反模式。通常,项目中会有许多程序员正在通过跟随更有经验的开发人员的示例来学习如何开发软件。然而,他们学习的方法是修改那些已经证实可以在相似情况下工作的代码,潜在地定制它来支持新数据类型或稍微改变了的行为。这会造成重复的代码,可能会具有一些短期的积极效果,例如提高用于对工作成绩进行度量的代码行数量值。而且,由于开发人员对在他的应用中使用的代码具有完全的控制,可以很容易扩展这些代码,迅速实现用来满足新需求的短期修改。
5.9.3 症状和后果
● 虽然做了很多局部修正,同样的软件错误仍反复出现于软件的多个地方。
● 代码行数量增加了,但整体生产率并没有提高。
● 代码审阅和评审不必要地扩展。
● 难以定位和修正特定错误的所有实例。
● 代码被认为是不需解释的。
● 复用代码时只做了最少的工作。
● 该反模式导致过多的软件维护成本。
● 软件缺陷在整个系统中被复制。
● 可复用的软件资产没有被转换成易于复用和文档化的形式。
● 开发人员为错误建立多个独特的修正,无法从这些变化形式建立标准的修正方法。
● Cut-and-Paste Programming形式的复用让开发的代码行数量虚假地增加,但是不能像其他形式的复用一样降低维护成本。
5.9.4 典型原因
● 建立可复用的代码要付出大量的努力,而开发机构强调短期盈利而不是长期投资。
● 没有和代码一起保留软件模块的上下文环境或示意图。
● 开发机构不提倡或不奖励可复用构件,而且对开发速度的要求掩盖了所有其他的评估因素。
● 开发人员缺乏抽象的能力,往往伴随着对继承、合成和其他开发策略的贫乏理解。
● 开发机构强调代码必须完全匹配新任务才能进行复用。为了满足被认为是(事实上也许并不是)独特问题集的需要,复制代码来解决假想的不足。
● 可复用构件在建立之后没有被充分地文档化,或者开发人员并不容易获得它。
● 在开发环境中有“不是在这里发明的”综合症的作用。
● 开发团队缺乏远见或前向思维。
● 在开发人员不熟悉新技术或新工具时,容易发生Cut-and-Paste反模式。由于他们不熟悉,所以会拿过一个可以工作的例子,修改或调整它来满足特定需要。
5.9.5 已知例外
在只有尽快放出代码这惟一的目标时,Cut-and-Paste Programming反模式是可以接受的。不过,其代价就是维护成本的增加。
5.9.6 重构方案
在白盒复用是系统扩展的支配形式的开发环境中,常常会发生软件克隆。在白盒复用中,开发人员主要通过继承来扩展系统。毫无疑问,继承是面向对象开发的重要部分,但是它在大型系统中存在一些缺点。首先,给一个对象建立子类进行扩展要求对该对象的实现方式,例如被继承基类指定的限制和使用模式,具有一定的了解。大部分面向对象语言都只有很少的限制,派生类中可以实现各类扩展,导致对子类的非最优使用。此外,一般而言,只在应用编译时(对编译语言而言)才有白盒复用的可能,因为所有子类都必须在生成应用之前完全定义好。
另一方面,黑盒复用具有不同的优点和限制,常常是中型和大型系统中进行对象扩展时更好的选择。在黑盒复用中,对象被通过它指定的接口按照原样使用,客户不能改变对象接口的实现方式。黑盒复用的关键益处在于,通过类似接口定义语言的工具的支持,对象的实现可以独立于它的接口。这可以让开发人员在运行时把接口映射到特定的实现来利用晚期绑定。可以使用静态对象接口来编写客户端,但是随着时间的流逝,可以从支持相同对象接口的更先进的服务中受益。当然,缺点就是支持的服务被限制在那些支持相同接口的服务中。与白盒复用中的接口变化或实现变化相似,对接口的改变通常必须在编译时进行。
白盒复用和黑盒复用的区别反映了面向对象编程(OOP)和面向构件编程(COP)之间的差异。白盒的子类化是面向对象编程的传统特点,而从接口到实现的动态晚期绑定则是面向构件编程的标志。
重新设计软件的结构来减少或消除克隆,要求修改代码来强调对重复软件部分的黑盒复用。在整个生命期中大量使用Cut-and-Paste Programming的软件项目中,恢复你的投资的最有效办法是重构代码集形成强调对功能的黑盒复用的可复用库或构件。如果把这项工作作为一个单独的项目进行,重构过程通常会相当困难、长期而且成本较高。它要求一个强有力的系统架构师来监督和执行这个过程,并协调对软件模块不同扩展版本的优点和限制的讨论。
有效重构以消除多个版本的工作包括3个阶段:代码挖掘、重构和配置管理。代码挖掘是系统性的确定同一段软件的多个版本。重构过程包括开发该代码段的标准版本并把它重新插入到代码集中。配置管理是制定一组策略来帮助预防将来再次出现Cut-and-Paste Programming。大多数时候,除了教育工作,它还需要监督和检测策略(代码评审、审阅和验证)。在所有3个阶段中,管理层的接受对于保证获得资金和支持都是很重要的。
5.9.7 示例
我们怀疑有一段代码被多个开发机构反复克隆,而且很可能现在还在被克隆。这段代码在数十个开发机构中被看到了几百次。它是一个用来实现链表类的代码文件。它没有使用模板或宏,而是用一个头文件来定义链表中存储的数据结构,因此每个链表被定制成只能操作指定的数据结构。不过,代码的原作者(有传闻说他最初是LISP程序员)在链表代码中引入了一个瑕疵:删除一个条目时链表无法释放它占用的内存,而只是重新编排指针。有些时候,这段代码被修改了来修正这个缺陷;但更经常的是这个缺陷仍然存在。显然代码集是一样的:变量名、指令甚至是格式在每个例子中都是完全一样的,连文件都通常被命名为<prefix>link.c,其中的前缀是一两个字母,隐含地指出该链表管理的数据结构。
5.9.8 相关解决方案
Spaghetti Code常包含Cut-and-Paste Programming反模式的多个实例。由于Spaghetti Code的结构并不利于构件复用,很多时候,Cut-and-Paste Programming是复用已有代码段的惟一可用方式。这当然会导致不必要的代码膨胀和维护恶梦。但是经验表明,与使用Cut-and-Paste Programming的实例相比,没有Cut-and-Paste Programming的Spaghetti Code通常是更糟的一团乱麻。
通过实现软件复用过程或组织方式,可以在新的开发中把克隆最小化[Jacobson 1997]。在大型软件开发中,一定程度的克隆是不可避免的。但是,在发生克隆的时候,必须有正规化的过程来把这些克隆体合并到共同的基线[Kane 1997]。
小型反模式:Mushroom Management(蘑菇管理)
Mushroom Management也被称为Pseudo−Analysis(伪分析)和Blind Development(盲目开发),常常可以被说明为:“让你的开发人员呆在黑暗中,用肥料喂养他们。”一位有经验的系统架构师最近说道:“永远不要让软件开发人员和最终用户交谈。”而且,没有最终用户的参与,“风险就是你最后会构建错误的系统。”
反模式问题
在某些架构和管理圈子中,有明确的政策要把系统开发人员和最终用户隔离开。需求是通过一些中间人传递的二手信息,这些中间人包括架构师、项目经理或需求分析师。Mushroom Management认为最终用户和软件计划在项目启动时都已经充分理解了需求,而且需求被设想成稳定不变的。
在Mushroom Management中有几个错误的假设:
q 现实中,需求频繁发生变化,会占用大约30%的开发成本。在Mushroom Management 项目中,直到项目交付时都没有发现这些变化。用户的接受度总是一个重大的风险, 而在Mushroom Management中它成为了至关重要的。
q 最终用户很少能理解需求文档的含义,而在他们体验用户界面原型时更容易把需求的 含义可视化。原型可以让最终用户通过与原型特性的比较来明确他们的真实需要。
q 在开发人员没有理解产品的整体需求时,他们不太可能了解需要的构件交互和必要的 接口。因此,会做出不良的设计决策,往往导致只有不牢固接口,不能满足功能需求 的烟囱构件。
Mushroom Management通过建立一个不确定性环境来影响开发人员。文档化的需求常常不够详细,而且没有有效的途径来获得澄清。为了完成工作,开发人员必须做出假设,从而导致伪分析,也就是在没有最终用户参与的情况下进行面向对象分析。某些Mushroom Management项目完全消除了分析,直接从高层次需求进入到设计和编码。
重构方案
风险驱动的开发是根据原型和用户反馈进行的螺旋式开发过程。风险驱动的开发是迭代的增量式开发过程(参阅第7章Analysis Paralysis反模式)的特殊形式。这时,每个增量就是一次外部迭代。也就是说,每个项目增量都包括对用户界面功能的扩展。增量包括用户界面试验,包括实际的体验。试验可以评估每个扩展的接受性和可用性,其结果会在下一次迭代的选择中影响项目的方向。由于项目经常性地评估用户接受性,并使用它作为输入来影响软件的开发,从而可以把用户拒绝接受的风险最小化。
风险驱动的开发最适合于用户界面密集,只需要相对简单的基础结构支持的应用。以本地文件作为存储基础的个人计算机应用程序是风险驱动的开发的最佳对象。
变化
在开发团队中包含一位领域专家是在项目决策中包含领域输入的非常有效的方法。无论什么时候出现领域相关的问题,团队成员都有专家在场提供帮助。不过,这种方法的主要风险是该领域专家只代表了来自该领域群体的一个观点。





