代码清理

用于清偿技术债务的 9 项策略

David Laribee

2009 年 12 月期的《MSDN 杂志》中,我对如何确定并构建用于处理技术债务的方案提出了建议。总之,我认为必须确定将来可能给您带来麻烦的债务。为很少使用的基本代码部分应用卓越技术,将不会帮助您提高工作效率。

此外,我希望您了解从管理层获得许可和支持对清偿债务的重要性,并希望您拥有一些基本工具以为清偿债务初步构建绝对可靠方案。

现在我们来介绍一下可能有助于您清偿高利率技术债务的策略。对于处理技术债务,有许多行之有效的策略。有关征服难度代码的模式、工具和技术的完整目录,本文不予介绍。我将提供一些更适用的技巧,它们是我多年来积累的技巧中的一部分。

学习、学习、再学习

如果您遇到问题,但不确定如何解决,则可能需要学习新知识和技能以帮助您理清代码。常言道,学习是基础。

学习可以分多种形式。可能需要顾问或课堂培训形式的外界帮助。也可能通过阅读书籍进行学习。

尝试使您的团队都加入学习。您可以在团队中开办一个书友会。或者借助说明性演示文稿形式达到课程或会议的效果。

Coding Dojo 技术需要整个团队协作和实际操作。基本的 Coding Dojo 涉及编程并将编程看作一个团队来处理。我是从剧院最高楼座观看到的旋转对获得的灵感。在此方法中,团队的两个成员合作进行编程任务,在某个成员离开而其他团队成员进入 dojo 时使用“标记”隔开。

当您按照自己的进度学得非常好或希望开展书友会时,我可以为您推荐几本好书,这些书籍都涉及到如何提高旧代码的可维护性。

Michael Feathers 撰写的《Working Effectively with Legacy Code》(Prentice Hall 2004) 恰如其名,其中介绍了一种基于模式、用于分解旧代码的方法。他在书中指出旧代码即未经测试的代码。此代码很难更改,并且您无法确保所做更改不会导致回归缺陷。此书介绍了许多重要策略,用于减少代码中的耦合并使代码更具可测性。

Kyle Baley 和 Donald Belcham 出版了最新合著书籍:《Brownfield Application Development in .NET》(Manning Publications,2010)。他们采用系统方法改进所谓的褐地(与新开发或初始开发相对)基本代码。本书的优点之一,即推荐的方法可以广泛应用,并且其代码示例根据 Microsoft .NET Framework 设计,读者很可能从中受益。我也非常喜欢他们采用团队实践方法的方式。也就是说,当您更改通用基本代码时,从实现某些基础内容(例如,持续集成和版本控制)中获得的成功非常有利用价值。

交际

您必须处理某些杂乱代码,而这些代码很有可能是当前您的团队中的某个成员所编写。当推论当前状态的代码时,很有必要考虑到这一点。人们往往不愿承认自己的错误,这会导致改进速度缓慢。

可通过借鉴过去所犯错误的一些实例尝试避免发生这种情况。保持敬业精神、避免个人攻击并鼓励原始代码的作者对如何改进该代码提出建议。

再次强调,您很可能是杂乱代码的开发人员之一。请跟我读:“我不是代码。我每天都在学习并专心寻找可以进步的更好方法。我不允许同事的批评和我的自负阻碍团队的进步。”

事实上,克服这些问题需要时间。我发现推论和谈论进步的最好方法是关注现在和不久的将来,而不是过去。此代码可能是什么呢?您希望它的发展趋势如何?

事实证明,在工作中进行一些交际并考虑对他人进行情感投资很难进步。

引入模型

某些代码很复杂,以至于很难理解它的真正意图。也许所有类都位于单个命名空间中。也许基本代码位于复杂的依赖关系网中,从而利用堆栈而大大延长短期记忆位置能力。

诸如以上症状经常在体系结构级和设计级(而不是实现级)表示债务诊断。对我而言,这是最严重的债务,通常会导致最大的成本更改。

Brian Foote 和 Joseph Yoder 调用的体系结构的模型不可辨别(称为“大泥球”),其中所有内容互相依存 (laputan.org/mud):

“大泥球是一个偶然的甚至随意的结构化系统。其组织(如果可以这样命名)更多由私利而不是由设计驱使。但是,它却一直受到欢迎程,这并不能表明通常不需要体系结构。”

我敢肯定,当今正在生产的大部分软件应用程序都是大泥球。它们的价值毋庸置疑。世界上存在数十亿行可怕的代码,使人们赚了很多钱。顺理成章地,这些大泥球正在帮助企业所有者和股东实现胜利梦想,过上奢侈的生活。

问题是大泥球应用程序的更改成本越来越高。当企业环境仍在不断变化时,软件变得“力不从心”。解决此问题的典型策略是使用核弹级软件:大规模重写。大规模重写存在许多风险,所以通常最好尝试改进负责系统的设计。

通常需要向您的系统中引入一个模型,才可以开始采用某些较低级别的技术。典型示例是分层式体系结构。通常这意味着 UI 与服务交互,服务与某种模型交互,以此类推,模型与持久性层交互。

将您的代码分为几层可能并不现实。首先将代码组织到使用体系结构的层命名的命名空间中。

现在,需要执行的任务是:强制执行以下规则:高级层(用户界面层)可能仅依赖下一更高级别的层(服务层)。强制执行此规则的简单方法是将您的层移动到 Visual Studio 的单独项目中。如果违反该规则,该解决方案不会进行编译。

通过应用此规则,可以降低耦合。该模型不再与您的应用程序的视图耦合。通过引入模型,可以提高聚合。层内部的类的作用都相同,为最终用户显示数据或封装业务行为。

在层之间引入外观并使较高级别的层(例如,UI)依赖较低级别层(而不是层内部的精细类)提供的外观。您可在适当的时候逐步将此技术应用到以上过程。

将模型强加于整个大泥球很有用,现在您可以开始确定更具有针对性的清偿技术债务的机会。也就是说,假设您在 CompanyX.ProductY.Model 中处理大量工作时,可能会使用静态分析工具深入了解以发现最具耦合性或最复杂的类。

通过测试进行近距离空中支援

不更改系统行为的更改过程称为重构。提供了专用于面向对象 (refactoring.com) 和关系数据库 (agiledata.org/essays/databaseRefactoringCatalog.html) 代码的完整重构模式语言:提取方法、拆分表格等等。事实是,如果没有完全理解基本代码,则很难应用这些既精细又安全的方法。

因此,如何启动对旧项目的更改呢?首先要注意,如果可以选择的话,对您做的更改进行测试始终比较安全。在更改代码时可能引入错误。但是,如果在更改代码之前对代码进行测试,更可能查出错误。

霰弹式修改实践,即在不能确保要进行的更改不会带来危害性缺陷的情况下直接插入代码,不是强制进行更改的唯一方式。

更改代码之前,请先确定系统中是否存在可编写测试的可靠界面。这些测试具有黑盒多样性。也就是说,一边提供系统输入,一边检查输出。进行更改时,不断运行测试以验证更改尚未破坏现有行为。

处理紧密耦合的系统部分时,应用此策略可能颇具难度。测试成本可能远远超出清偿债务带来的收益。此稳定的成本收益分析贯穿更改基本代码的整个过程,有时,直接重写应用程序或大部分应用程序基本代码更具经济效益。

测量可观察的效果

根据您要改进的代码区域构建度量标准。为了叙述方便,假设您正在尝试更好地组织应用程序的核心业务逻辑。存在许多获取此命名空间的类型成员的途径:交换语句、嵌套 if 语句等。度量(例如,圈复杂度)可大概说明所做改进是否简化了代码。

您可以使用 Ndepend 代码分析工具获取对基本代码的特定部分的精确特定度量 (ndepend.com)。NDepend 为 .NET 程序集中的命名空间、类型和成员提供了功能强大的代码查询语言 (CQL)。

请参见图 1 中的 CQL 语句。请注意,我正在检查特定命名空间内的度量,如耦合度和复杂性(只是 NDepend 提供的众多度量中的一小部分)。这表示我已引入了一个模型,因此我可重点研究代码的可定义区域。如果我成功引入正面更改,则随着时间的推移,应该显示度量(如耦合和复杂性)逐渐减小。

图 1 NDepend CQL

-- Efferent coupling outside a namespace
SELECT TYPES 
WHERE TypeCe > 0 
      AND (FullNameLike "MyCompany.MyProduct.Web")

-- Afferent coupling inside a namespace
SELECT TYPES 
WHERE TypeCa > 0 
      AND (FullNameLike "MyCompany.MyProduct.Web") 

-- Top 20 most complicated methods
SELECT TOP 20 METHODS 
WHERE CyclomaticComplexity > 4 
      AND FullNameLike "MyCompany.MyProduct.Web"

此策略还有很好的副作用,即清偿债务后度量可帮助您坚持改进并维护规则。在向已改进区域重新引入新债务时,它们还将提供早期警告系统。

专用改进流

您没有生活在真空中。在改进工作期间,很可能会要求您继续提供新功能并对现有功能进行修改。提供新功能的压力会使您感觉在被枪指着工作。但进行维护是无法改变的事实,您应该接受而不是设法忽略。

解决此问题的一种方法是保证企业批准提供资源(单个成员、两个成员或整个团队),在提供新功能的同时提高收益。

这可能是一个高效策略,但最好是整个团队(修改基本代码的所有开发人员和测试人员)都参与到改进工作中来。经常尝试使成员成对倒班。在改进流中工作时间最长的开发人员交班后,而另一开发人员负责向接班的一对概述发生的事情。

通过传播更加贴近集体所有权的知识降低了风险并改进了设计。有时,您将找到直接依赖于尝试提供某些新功能的方式的改进机会。当您开始改进新功能或修改后的功能时,最好查看列表以确定该团队是否尚未识别与您正要执行的改进工作相交的改进区域。

改进机会每时每刻都在发生着,经常被动态识别,进行简单的重构,使团队成员下次遇到该代码时进行区分,即可实现改进。

当您在提供新功能的同时改进现有基本代码时可持续进行成本收益分析。如果改进工作成本过高,则将其重新添加到您的列表并在改进规划中对其进行讨论。

迭代、迭代、再迭代

您已经偿清了一些债务。现在应该返回第一个步骤并针对下一个需要解决的项目进行确定、排列优先级并达成共识,难道不是吗?

是的,但仅仅毫无意义地研究您的列表还不够。您必须确保不会产生比您要清偿的债务更多的债务。您还应定期将您所学到的知识应用到未来的新开发和改进之类的工作中。

改进基本代码的机会定期更改。这些机会不断出现,其重要性也时起时落。高利率债务的动态特性的原因随版本不同而有所不同。

对我来说非常有效的是每周都安排一次与开发人员的短时间会议,在该会议中审查新债务项并排列积压的现有债务项的优先级。这将使您已构建的共识有效并使列表更新。同样,我将首先偿清很可能降低您的当前版本或项目的性能的债务。

会议一开始,首先审查新项目。安排检验人讲述案例,并允许其他人对其进行投票:这是否对积压的项目有益?完成新项目后,审查旧项目。是否存在不再适用的工作?完成这项工作是否会立即产生价值,也就是说,是否会清除日常障碍?最后,排列其他机会的优先级,即重新排列您的列表。列表顶端的项应该是要进行的下一个改进工作。

坚持改进

当您和您的团队痛快地支付高利率技术债务时,您可能也会提供新软件。了解了扎实的编程技术并向您的代码中引入新模式后,请进一步应用此知识。可能会将附加工作堆积在现有的技术债务上,因此,将不可避免地产生延迟。

为业务相关人员设置新工作的期望值非常重要。获得较高质量的代码比获得匆忙完成的粗糙代码所需的时间更长。这使我又想起了我在 2009 年 12 月发表的一篇文章中重新引入的系统思考概念。对于我来说,这是一种文化观念。也就是说,组织可以按照惯例将眼光放长远或继续“先消费,后付款”的思想模式 — 哦,这是技术债务的一块多么富饶的滋生地呀。切勿忘记中心问题,如何首先实现我们的最终目的?

在您学习如何改进基本代码时,您很有可能会开发一些可应用到新代码的团队标准。我建议您使用类似 wiki 的工具捕获这些标准并开展小型的非正式学习会话,在其中您可以与您的团队分享您的成果。您还将开发用于处理类似改进项的技术。如果您注意到您已执行了相同操作以更正有缺陷的设计,或对实施进行了三次或四次清理,请将其编入您的团队的培训内容中。也就是说,将这些内容写在常见位置,简单地说,将这一位置告知所有人。

共同工作

技术债务是人为问题。缺乏知识或具有不切实际的期望的人员创建了杂乱代码且目前正在对产生的后果进行处理。这些人员将作为一个小组来解决这个问题。

提出类似的建议的确不错,但是令我不解的是,像您这样对自己的职业充满热情的软件专业人员竟然不被完全认可。

一场成功的转变要求对涉及的所有人(即整个团队)的价值系统进行根本性更改。最终必须偿清质量经济,但是近期您必须具有执行这一步骤的信念。您必须深思熟虑才能改变这种文化,这可能确实是一项艰巨的任务。我可以为您提供的最有用的建议是:不要单独行动。整个团队共同努力并确保结果与每个人都息息相关。

设置类似“我们希望覆盖率达到 90%”或“我们希望始终进行测试驱动开发 (TDD)”的目标相对来说没有意义。解决当前和不久的将来妨碍您前进的问题区域。这可能意味着可能会也可能不会引入 TDD 和实时覆盖率报告。它可能是更简单的内容,例如确保您的团队了解面向对象的分析和设计的基础知识。

开始进行区分

我希望我已为您提供了解决债务问题的一些工具和技术,或者至少为您提供了您已确定的某些隐式建议和体验,意识到解决技术债务问题无疑是一个产品与产品的问题很重要。例如,在您的工作环境中,可能开发方和业务方互不信任,而您还必须在备受质疑的压力下讲解您的案例。

没有现成的步骤可以指导您如何减少债务,但是对于时间和地点,目前就是开始进行区分的最好时期。通往卓越技术的历程一开始可能很慢也很曲折。总之,只要不懈努力、不断学习并抱着积极的态度应对,便可度过这个难关,将债务残缺的代码恢复为黑色。我鼓励您坚持执行这一计划。这不仅会增加您的客户的价值,还将极大地扩充软件专家的工具箱。

Dave Laribee 是 VersionOne Inc 的产品开发团队教练。他经常在地方和国家级开发活动中发表演讲,曾获 2007 和 2008 年 Microsoft Architecture MVP 殊荣。他在 CodeBetter 博客网(网址为 thebeelog.com)发表博客文章。