65.9K
CodeProject 正在变化。 阅读更多。
Home

何时重构是坏主意

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.66/5 (19投票s)

2014 年 5 月 11 日

CPOL

8分钟阅读

viewsIcon

24757

关于进行代码重构时失败的根本原因的讨论

引言

重构代码是投入到质量上的时间,这是一个让开发人员的生活更轻松、工作更有效率的过程。它是开发过程的一部分,用于偿还由于添加功能或修复问题而不可避免地积累的技术债务,我们非常清楚长期忽视重构会导致多么糟糕的后果。现代管理者理解重构的重要性以及它如何影响软件维护和缺陷率的经济成本。

我们都喜欢重构。然而,我相信我并非唯一经历过有时重构会非常糟糕并希望当初能做得不一样的开发者。此外,用于重构的时间现实中是有限且宝贵的;因此,从中获得最大收益至关重要。这就是为什么技术领导者的责任是质疑并积极批评开发团队的重构计划和活动,以确保努力方向正确。回顾我的职业生涯,我现在可以 pinpoint 一些进行代码重构时发生故障的主要根本原因。

1. 缺乏目标与计划

重构可能是一种巨大的时间浪费,仅仅是为了追求某种对我们的应用程序没有影响、意义也很小的美学上的完美。重构不添加或更改任何功能的事实,并不意味着最终结果是无法衡量的。如果我们对想要实现的目标有清晰的认识,重构就是有效的,并且总是会产生非常切实的成果。

我们想通过重构解决什么问题?
有什么切实的优势?
风险是什么?我们如何最小化它们?

这些是确定重构目标的关键问题。
以下是一些目标示例

使代码更健壮,易于更改

代码太不稳定了。每次我们需要进行更改时,都感觉像在触摸扑克牌堆。总会有东西会坏掉,我们修复一个问题,在这个过程中又会产生两个问题,有时与我们修复的问题完全无关。客户不断抱怨那些本来运行正常的东西出现了新的缺陷。这是具有复杂相互依赖关系和高耦合度的应用程序的典型问题。

增加灵活性

很难适应新的需求。新功能总是带来太多挑战,需要仔细规划。客户抱怨漫长而昂贵的估算。这是增长过快或仅根据非常具体的要求量身定制而未顾及大局的应用程序的典型问题。

简化

我们的应用程序不把火箭送往火星,但有时它们的设计就像要做到一样。当然,架构师在他的过度设计中乐在其中,使用了多层抽象来考虑不可能的场景和太多的“万一”。现在是时候回归现实,运用常识了。应用程序的学习曲线太长,简单的维护变得异常复杂和昂贵,因为需要支持所有不必要的层、包装器、接口、深层继承链等等。

清晰度

晦涩的代码可能有很多原因:思维不够清晰,缺乏对他人阅读和理解代码的困难的考虑,或者可能是对工作安全的扭曲认识。无论是什么原因,都太难理解代码中发生的事情了,名称具有误导性或无法传达信息,代码中存在一些奇怪的操作,但没有明显的理由或解释。没有注释、图表或文档可用。没有人能自信地更改代码,应用程序几乎是不可变的。

增加内省能力

跟踪或错误日志要么不可用,要么不够详细和有意义,无法帮助我们弄清楚出了什么问题以及在哪里。当客户报告故障时,调查需要花费大量时间,并且通常需要开发人员直接参与。重现 bug 很难,而且经常需要完全复制客户的环境(数据和软件)在一个可以更容易监控和调试的本地网络中。

减少部署失败

每次部署代码时,通常都会出现一些问题。每次出现的问题都不同。太多的手动部署步骤,太多需要记住的事情,太多的配置要求。如果可能出错的事情太多,那么通常就会出错。这些是我们需要为代码添加约定、默认行为、添加保护类来诊断配置问题等情况。

提高性能

随着时间的推移,应用程序的用户、数据和功能都增加了。虽然代码最初具有可接受的性能,但现在它变得越来越慢,需要采取措施,否则客户就会开始抱怨。这是一个边缘情况(过度的缓慢可以被合理地视为 bug)。在重构之前,我们需要对代码进行性能分析,并确定需要纠正的性能下降的真正因素。在没有任何评估的情况下猜测需要改进什么可能是一个可怕的错误。

一旦目标明确,我们也需要进行计划。根据代码中发现的问题,相应的“药物”可能大不相同。我们需要确定更改的范围,将重构计划分成小阶段,并确保开发团队对要使用的策略达成一致。

2. 没有测试

没有测试,重构可以被描述为将完美工作的生产代码转换为很可能包含多个 bug 且无法按预期工作的代码。根据定义,重构意味着通过非功能性更改进行改进。然而,如果我们添加了缺陷,我们所做的更改实际上是功能性的(即使不是故意的),因为它们通过破坏功能来影响功能。
这就是为什么没有测试的重构不是重构:它只是破坏代码,直到为时已晚才很有可能发现问题。更改越长、越关键,风险就越大。

如果待重构的代码单元测试很少或没有,那么第一步应该是编写它们,至少要保证在开始更改之前有足够的覆盖率。这会减慢我们的速度,因为我们不仅要编写测试,可能还要随着重构的代码一起更改它们。尽管如此,这些努力将通过缩短上线时间、最小化缺陷以及避免因破坏工作代码而产生的坏名声来获得丰厚的回报。

在不幸的情况下,如果待重构的代码工程化程度很差,或者晦涩难懂以至于几乎无法测试,那么一种可行的办法是构建一个高层次的功能测试安全网,以便在更改前后对代码进行比较(有关此主题的更多信息,请参阅参考资料GTAC 2010)。

3. 贪多嚼不烂

为了节省时间,我们可能会诱惑一次性重构大量的代码。这通常会适得其反:在我们重构的同时,可能会出现新的功能请求,而我们不得不在两个地方进行修改。问题会出现,我们将无法长时间发布新代码,最终只能回去维护旧代码。当生产代码正在重构时,它会暴露于新的请求、补丁以及旧的和新的 bug 修复。明智的做法是将暴露和风险限制在小的稳定步骤中,即使总体工作量会更大。

4. 对代码了解不足

一些重构活动不一定需要深入了解代码(例如,为了可测试性而重构),而另一些活动(例如,为了性能而重构)可能需要高度熟悉应用程序的业务逻辑和流程。了解区别很重要,因为缺乏经验可能导致天真的错误,并使项目注定失败。如果重构是在关键组件或复杂业务逻辑的核心中进行的,那么最有经验的开发人员应该承担领导责任,而不是将任务委托给以前在待重构代码上花费时间很少或根本没有时间的实习生或初级开发人员。

5. 忽视业务方面

最后,我们做到了!我们成功地重构了代码,实现了所有目标。好吧,先别急着开香槟,我们还没结束。我们已经看到了重构策略的切实体现在。我们现在需要确保业务部门也看到这些结果。有人为所有这些重构时间付费——下次他们可能不会那么慷慨了。我们不应该去找业务经理或 CEO,吹嘘“技术宅”的成就,比如“松耦合”或“低圈复杂度”。他们很可能听不懂我们在说什么,或者更糟的是,他们会认为我们完全疯了,白白浪费了时间和金钱在一个无用的项目上。相反,我们需要用普遍理解的商业语言与他们交流:金钱。我们可以向他们展示缺陷率如何下降,节省了数百小时的 bug 修复时间。我们应该强调我们能够更快地为我们的程序添加新功能,并按时交付一切。我们需要以业务可理解的方式沟通切实的成果。我们完成了困难且有风险的目标,因为我们喜欢我们所做的事情,并且关心我们工作的质量。让我们确保相关人员知道这一点,下次我们申请额外的时间来重构代码时,我们将拥有更有利的谈判筹码。

参考文献

Wikipedia
软件度量
http://en.wikipedia.org/wiki/Software_metric

Esteban Manchado Velazquez
GTAC 2010:从可测试性故障中吸取的教训
http://www.youtube.com/watch?v=4CFj5thxYQA

Martin Fowler
重构 - 改进现有代码的设计
https://martinfowler.com.cn/books/refactoring.html

© . All rights reserved.