Joshua Kerievsky 在 Yahoo! 上的 Refactoring 小组里面发表了下面的帖子,引发了激烈讨论:
最近几年,我听到有一些人说只有在开发用户故事的时候才能重构。我从来都不赞同这个说法,因为我觉得有的情况只需要偿还技术债。最近几天,我和同事们重构了一下我们电子教学系统的代码。这并不是由用户故事驱动的。只是我们欠下了足够多的技术债,再也无法忍受,而现在是还债的好时候。系统中原来有一个邪恶的单例类,是系统代码中的核心角色。我们现在正在清除这个类,这样可以改善更多的代码设计。这工作感觉真棒。它让我们处在了一个有利的位置,将来开发用户故事就更容易了。 与此同时,我们继续每周交付一次——少量的 bug 修复和大量的重构。一如既往地,自动化的“微测试”和故事测试给我们大量的自信和勇气。无论如何,我觉得有必要分享这个经历,或许能引起一些有趣的讨论。
Dale Emery 试图弄清楚 Josh 的问题场景:
Dale:我怀疑业界的普遍意见是不提倡技术人员介入业务决策。在决定偿还债务之前,决策者对业务和技术风险都需要有深刻的理解。如果缺乏足够的理解,决策者的决定就是不可靠的。你的例子很特殊,其中这样的风险很少。 Josh:是的,我们的例子的确特殊。但是,我认为通常情况下,让业务人员和技术人员在一起紧密工作是非常好的事情。这样,大家都能共同做出关于技术债的决定。当然,我们也不希望在大规模的项目里面,开发人员不跟其他人商量,就自行决定花上几个礼拜的时间来重构。
Dale:我认为(如果我假设错了,欢迎指正),你的客户拥有很强的技术背景,而且,至少绝大部分技术人员深刻理解了你们的业务。更进一步讲,你们自己就是你们的客户。当你们决定偿还技术债务的时候,你们非常清楚这样做给业务带来的影响,而且很可能恰恰是(因为)你对业务影响有足够的了解。
Josh:是的,之所以决定去重构清除技术债务,是因为
- 时机—— 我们刚刚给最大的客户完成了一个非常重要的发布,该短暂地走下“特性列车”了。
- 未来—— 更多的特性在等着我们,已有的开发经验告诉我们现在的技术债会拖慢前进的步伐。
- 通用语言—— 我们在代码中有一个很美妙的音乐隐喻…但是,一些旧隐喻(书籍)的残余部分还分散在各处。
弄明白了 Josh 的问题场景(以及整个主题的其他内容),Adam Sokra 提出:
你们的迭代时间长度不固定,从事增量式开发,而且尽可能频繁地发布。有时,你们有一组用户故事等待开发,并会设法尽快交付给客户。其他时候,你们就只用努力改善现有的设计。你们既是客户,也是开发人员。 这听起来就像是我曾经遇到过的每一个愉快的开源项目,与 Scrum 或者 XP 项目没多大关系。我不认为你们做错了什么,但是对那些试图弄明白敏捷环境下如何重构的人们来说,我不能肯定这有多大帮助。
不懂技术的业务人员有需求,技术团队也有需求,而我们就得在这二者之间博弈,这也是 Scrum 和 XP 的核心概念之。我们希望确保技术问题被熟练地解决,我们也希望业务人员可以最大程度地把握开发什么与何时开发的问题。
在你所描述的环境里面并不存在这样的对立关系。你们可以自由决定添加哪些特性,什么时候添加,什么时候暂停交付、转而关注于纯技术问题。
因此 Adam 提出 Josh 的环境并不适合于大多数的项目,最重要的差别在于:在 Josh 的项目里面,技术 / 非技术人员之间在沟通、理解和优先级等方面不存在矛盾。
Ron Jeffries 认为项目中该进行的重构的多少是由业务决定的。毕竟,重构是一种投资,无法在短期内看到价值。他也反对在“不重构”和“停下来重构”之间做二元选择:
有一个假设需要指出,这就是:有些时候,最好是停下或者减缓“前进”的步伐进行清理。 对于很多人而言,这样的事情总是顺理成章:代码太糟糕了,只能停下或者延缓特性开发的进度,对代码进行清理。
对我可不是这样。这些说法缺乏连贯性。在清理代码的时候,我们为之付出的努力只有在将来才能受益。有些可能明天就产生回报,有些可能几个星期、几个月也没有回报。从来就没有立即带来好处的情况。
所有延缓特性开发进度的重构工作都是对未来的投资。需要弄清楚的是:是否值得付出这样的投资,怎样的投资是确实值得付出的,以及何时做出这样的投资是值得的。
Ron 继续就“何时进行重构才是值得付出的投资”这一问题提出了自己的看法:
何时重构更合适,为什么?未来有如此多的可能性,随着时间流逝,特性的业务价值会不断增长。下面描述了两种可能的方法: 1、不做重构。特性的业务价值增长得越来越慢,直至不再增加,甚至可能因为引入的错误超过了增加特性带来的好处而导致下滑。
2、停止开发特性,进行重构。短时间内,特性的价值不会增长。过了这段时间,其价值又开始重新增长。我们认为优化代码之后,特性价值的增长速度会快于以往,但还需要一些时间,才能赶上不做重构的情形下特性的价值增长状况。这之后,我们认为,我们又将跑在前面。
对比这两种方式,我们可以得出什么结论?首先,只有重构结束之后的某个时间,重构给特性增加的价值才能浮现出来。其次,为了弄清楚重构(增加的特性价值)在什么时候能超过(不做重构时的特性价值),我们需要一些数字:重构会花多长时间,它会如何影响开发速度?而且,多长时间之后代码又会再次变糟,又是什么导致这些数字回落?
停下来、重构、稍微落后于特性、专注于少量特性、不赶进度而是优化代码,如此循环往复,却从来不受益;这样的状况完全有可能。我们希望这不可能…然而,这只在人们都很高效、技艺纯熟的情况下才可能…这是我前面提到的要点之一:对于专家,你的建议是合适的。
但是,这两种结果只是说明“停下来重构”的方法可能是自讨苦吃。是否有其他更好的方法?答案是肯定的。
让我们假设存在“重构加速变量(Refactoring Accelerator,RA)”,在上面第一种“不重构”的情况下,RA 是 0.0。在第二种情况下,我们把它设成 1.0,这是完全相反的另一个极端。如果 RA 值在两者之间,会发生什么?
首先,如果特征价值是 RA 的函数会怎么样?在 0 到 1 的某个范围(0<x<1),如果 RA 小于这个范围的某个值(x),特征价值的增长率始终是下降的。我们重构不够,代码质量不断恶化,我们失去的越来越多。而对于某个值 x,如果 RA=x,特征的增长就是一个稳定值。我们的开发不会加快,但也不会减慢,保持了之前已有的速度。
现在,如果我们设定 RA>x,又会发生什么?我们开发是会更快,还是速度减慢?我们知道在 RA=1.0 的时候,开发速度确实是减慢的,减到了 0(但是之后我们可以加速)。
答案取决于开发速度随代码洁净度变化的曲线。我们知道代码越洁净,带来的开发速度也就越高。而且,最开始的干净代码会带来超高比例的良性效应,而追求代码尽善尽美到细枝末节,这样引发的良性效应则不会很多。
当 RA 稍微大于 x 的时候,可以得到更快的特性开发进度:我坚信这个是可能的,而且也不存在确切的不可能证据。如果确实如此,这种策略交付的价值会显著大于“停下来重构”的策略。
因此,如果这是对的——我也非常确信它的正确性,那么对于已经精通重构的团队来说,“停下来重构”策略从来就不是最佳策略。
Ron 这里提出的要点是“停下来重构”从来不是擅长重构的团队应该选择的最佳策略。
这篇报道比较长,却只是那篇非常有趣的讨论的节选。在讨论之初,Josh 介绍的并不是一个新问题。实际上,笔者在两年前就写过相应的社评“重构是必要的浪费”,而且InfoQ 上面也经常会出现关于重构的讨论。整个社区在这个问题上依旧没有达成共识。
查看英文原文: Stop and Refactor?
评论