在软件开发过程中,随着时间的推移,如果没有遵循架构设计的纪律,应用程序或代码库可能会通过累积的方式增长,最终形成一个难以维护的“泥球”。缺乏架构的设计使得在这个基础上进行构建变得困难。开发者可能会开始害怕代码,因为一个区域的代码更改可能会在应用程序的不相关部分引发错误。由于源代码是开发者工作环境的重要组成部分,这种大泥球会导致开发者不满,甚至导致高开发者流失率。
就像“十二步计划”一样,第一步是认识到有一个问题。如果是一个在大泥球中工作的开发者,可能已经感觉到有些不好的事情正在发生。有趣的是,项目之外的开发者更有可能提出警告,因为内部人员对代码库的熟悉可能会掩盖问题的真正规模。
如果没有独立的、值得信赖的开发者来审视代码,那么接下来最好的做法是对代码库进行某种形式的代码分析。在Visual Studio 2013 Professional中,可以通过选择菜单:Analyze -> Calculate Code Metrics for Solution来完成。也可以使用这些代码度量的输出来获得支付账单的人的支持,以帮助解决问题。通过将这些输出导入Excel,可以得到一些非常有用的图表来显示问题所在:
这张图显示了左侧的“可维护性”和底部的类耦合,代码库大小由气泡的大小表示。从这个图中,可以看到,无论项目大小如何,可维护性都在60-100之间。这看起来相当不错,但“可维护性”是一个相当粗糙的度量。
这张图显示了圈复杂度,它显示了代码中可能的独立路径数量。从这个案例中,可以看到,测试这个代码库并验证每条路径将是一个巨大的问题,而且随着代码库大小的增加,这个问题会显著恶化。
当大多数人遇到大泥球时,他们的第一反应是尖叫着逃跑。这可能是离开工作或试图进行内部转移,尽可能远离。坏消息是,有这么多的大泥球存在,当移动时,很可能会遇到一个新的大泥球。
另一种选择是从内部改进代码。要做到这一点,应该从分类开始:决定什么不能被拯救,决定什么不需要被拯救,决定什么需要被拯救并且可以被拯救。
什么不需要被拯救?如果一个特定项目的变更率非常低,那么无论代码本身有多糟糕,提高代码质量的投资回报率都会很低。这是因为糟糕的代码最显著的成本是它增加了变更成本。
什么不能被拯救?如果代码库的大小如此之大,其中的问题如此之多,那么就会有一个交叉点,重写项目的成本超过了修复它。如果这是情况,那么就需要做出决定,创建一个新项目来替代这个现有项目的功能性,一旦完成,就可以退役这个现有的无法拯救的项目。
以下适用于剩余的部分——可以被拯救的。
如果要重构一个已经部署的代码库,那么需要一个重要的测试框架来确保为了改进代码所做的更改不会在编译的应用程序中产生新的问题。
最初,很可能没有现有的单元测试,而且代码也不太可能接受引入单元测试。(单元测试和测试驱动开发的一个明显好处是它鼓励良好的代码实践。不幸的是,反过来也是如此——糟糕的代码实践使得单元测试更加困难,如果不是完全不可能的话。)
一种可能性是自动化“整个系统”的测试。这可以通过使用现有的前端测试自动化工具(例如“Project White”)来完成,并通过使用虚拟机设置并行测试环境,以便在每一步都可以评估变更的影响。
从资助软件改进的人那里获得支持只是需要做的沟通的一部分。技术团队也需要有人向他们介绍计划(可能还有问题的声明),特别是为了他们不感觉到对他们自己的工作有任何隐含的批评。
“这将花费太长时间/买不起/发布的发布时间表将会滑落”——肯定会遇到这种反应。这是人类偏见的一个症状,它倾向于立即获得更大的长期回报。
首先,构建代码库中主要组件的黑盒概述。它们的目的是什么,它们需要什么输入,它们产生什么输出。绘制出它们之间的关系,给一个当前系统中纠缠程度的感觉。
接下来,决定希望拥有的首选架构或模型“如果有时间的话”。这将指示对于软件执行的业务流程来说,最佳适应的软件架构是什么。
当然,将需要改变的确切细节将高度依赖于自己的代码,但以下通常是一个很好的开始。
一些代码补救技术的例子:
// 通过功能区域将大类分割成小类。可以通过开始使用
partial classes
来做到这一点,这样就可以在IDE中移动代码块而不会破坏构建。一旦对此感到满意,将每个部分类蜂巢到它自己的独立类中,可以作为单元进行测试。
// 解耦使用接口 - 当类作为参数传递给其他类的方法是构造函数时,引入一个由类实现的接口,然后传递那个而不是具体类。再次,这有助于单元测试,但也使得多个人更容易在同一个代码库上工作,依靠这些接口。
// 让编译器承担更多责任 - 通过用枚举类型替换任何隐式数组索引,用它们的泛型等价物替换任何未类型化的列表或数组等。
// 引入诊断 - 跟踪语句和性能计数器,让可以调查编译的应用程序正在做什么以及它做得有多好。
// 定义自己的应用程序的自定义异常,并用它包裹任何框架异常,以添加业务影响信息到异常中,如果一个被引发。
// (例如,而不是“文件未找到”有一个业务异常,像“无法加载今天的汇率 - 使用之前的日期汇率(文件未找到)”。
// 添加XML注释标签到代码中。这些可以构建到开发人员文档中(使用像Sandcastle这样的工具),或者它们也可以显示在IDE中,作为开发人员引用这些组件时的评论。
// 识别任何跨切面的关注点(如日志记录)并引入一个标准模式和实践库(如PRISM)来代替自己的自定义代码。同样,如果有一个DIY数据库层,那么建议转移到像NHibernate或Entity Framework这样的东西。
还有很多其他的代码补救技术 - 在分享的精神下,如果有任何,请在评论部分留下它们,将在这里包含它们。