在使用新的系统概念或新技术的地方,人们必须建立一个可以丢弃的系统,因为即使是最好的计划也不是无所不知的,以至于第一次就做好了。因此打算扔掉一个;无论如何,你会的。
——弗雷德·布鲁克斯,神话般的人月[强调我的]
建一个扔掉。他们就是这么告诉我的。然后他们告诉我,我们现在都很敏捷,所以我们应该无情地重构。是什么赋予了?
重构我的出路总是更好吗?如果没有,任何人都可以提出一个经验法则来帮助我决定何时坚持下去,何时放弃并重新开始?
如果你在做测试驱动的开发,你几乎可以通过重构来摆脱任何麻烦。我已经毫不费力地更改了主要的设计决策,并挽救了十年前的代码库。
唯一的例外是当您发现您的架构从头到尾完全错误时。例如,如果您使用线程编写应用程序,但您发现需要一堆异步状态机。到那时,继续扔掉初稿。
对于小型系统来说,丢弃是可以的,但如果系统的规模很大,你根本没有资源这样做。
但是,您可以创建一个仅实现实际项目非常重要的功能的小型试点项目。经过一些试验和错误、学习和丢弃东西,你最终会获得一个坚实的核心,并对实际项目有更好的理解。然后,通过添加所需的所有功能,让项目的规模扩大。但是一旦你到了那里,你就不能扔掉核心。只有重构。
如果你足够无情,重构的最终结果将与你从头开始重建时得到的结果非常接近,但在此过程中你不会被非工作系统卡住。
The Mythical Man Month 的中心点之一是软件开发的难点在于弄清楚该说什么,而不是如何说。
我最近对此的解释是,您从初稿中获得的最大价值是您收集并以测试形式保存的需求。如果您注意不要测试实际上不是系统要求的东西,您可以重构自己的方式来摆脱任何混乱。
只要您不将自己编码到一个必须开始丢弃测试的陷阱中,您就可以在不丢失大量实际工作的情况下丢弃尽可能多的代码。
我在这里的一般建议是将现有系统从其糟糕的设计重构为具有更好设计的系统。这可以维护系统并允许它随时部署。如果您从头开始,可能需要一段时间才能部署,或者永远不会。
如果您只是在谈论在没有现有系统的情况下编写一些全新的代码,那么编写一点代码通常是个好主意,但是您想要,然后将其丢弃,因为它从未部署并重新开始(使用 TDD)。
有一点是重构是浪费时间的。你只需要从头开始。如果你保持你的设计相当灵活,并且你意识到你还不知道一切,你就不必扔掉任何东西。当然,一个类可能会变得多余,但你不会丢弃整个系统。
有一个灵活的设计是能够正确重构的必要条件。没有设计或死板的设计意味着你最终会丢掉一些东西——要么是因为你无法重构,要么是因为不断的重构会降低代码库的可维护性。很少有人能做到一丝不苟和纪律严明,能够完成一长串小规模的重构以保持完整性。除非你有一支全明星队,否则这种退化会发生!
TL;DR:你可以通过重构来摆脱大多数麻烦。但是,有时您将无法重构过去的某些设计元素。当这种情况发生时,是时候重新开始了——尽管希望您可以重新使用您现有的一些组件。
不同的情况需要不同的方法。就我个人而言,我更喜欢尽可能地重构为更好的设计。与重写相比,重构导致的错误更少。
但是,即使您打算扔掉一个,编写一堆验收测试以确保您的第二个版本在正确的轨道上仍然是一个好主意。然后,您可以逐步迁移到下一个版本,同时确保您的功能不会从用户的角度发生变化。听起来有点像重构,我猜只是有点草率。
在谈论敏捷时,你可以同时做这两件事,但一般来说,你会做尖峰(原型)只是为了尝试特定的问题,了解它们并能够做出更好的估计。当你真正编写应用程序时,当你做一个简单的峰值和重构时扔掉。
亲切的问候
当我尝试解决新问题或新功能时,我会制作原型。之后,我将根据我所学的进行重建。实际上,这听起来很像重构……什么?也许是同一件事?嗯……
我认为扔掉一个有时是最好的方法,但它可能会伤害。我发现效果很好的一件事是扔掉一个,但要选择好你的技术。
例如,我用 Ruby on Rails 编写了一个大型代码库,在过去的 2-3 年里,RoR 有了很大的进步。我还在架构方面做出了一些需要修复的决定。所以,我要扔掉一个,然后从头开始构建一个新的。但是,我仍然可以使用 70-80% 左右的旧代码,因为我仍在使用 Ruby 和 Rails 编写代码。
对此有所帮助的主要因素是 Rails 迫使您编写结构良好的代码,并将业务逻辑和表示层分离。我第一次没有把它完美,但由于一切都很好地分离和干燥,将代码移植到 Rails v2.1,重新架构问题区域,重新编写一些“问题”功能一直是相当无痛的体验。
因此,通过从一开始就选择一项伟大的技术,我已经能够扔掉一个,但仍然可以带走 70-80% 仍然有效的旧东西。
在《神话人物月》的一篇文章中,布鲁克斯警告说,他发现如果你确实打算扔掉 1 个,那么你最终会扔掉 2 个!
我个人在现实生活中看到了这种情况;我们将项目的第 1 版分配给一个平庸的程序员作为快速丢弃,因为“我们计划稍后丢弃它——无论如何我们都会丢弃它。” 我们最终不得不为第 2 版重写它,但那个也被扔掉了。我从未见过第 3 版——公司倒闭了。
我认为当 Brooks 说“打算扔掉一个,无论如何你都会扔掉”时,它更像是“待发现的 bug 数量为 'n+1'”。也就是说,这是对墨菲定律的严肃陈述,而不是实用建议。从中吸取的教训是原型是有价值的,好的写作就是重写,不要害怕放弃一些不起作用的东西。
然而,它必须归结为一个判断电话,因为正如 Joel Spolsky 在几篇文章中所谈到的那样,丢弃并重新开始的选择很诱人,因为代码编写比阅读更容易,编写比维护更有趣,所以你的自然倾向将永远是重新开始,即使这并不是最好的事情。
我认为您的版本控制系统在这里发挥了重要作用。如果你运行一个易于分支的分布式版本控制系统(现在是 git、mercurial),那么你将能够更轻松地进行原型设计和重构,同时仍然拥有有效的工作副本。其他任何事情都需要更多的纪律。
作为该组织的开发经理,我“不允许”编写生产代码。
我(ab)使用该规则来删除解决一个或其他问题的快速、肮脏的概念验证代码,然后我将其签入源代码控制并指向一个“适当的”开发人员并说“这就是它的方式做完了,现在好好做。”
这与我们在这里“扔掉”的距离一样接近,而且我最多可能要花几个小时才能凑齐。花时间在错误处理、边界检查和所有其他可以编写好代码的东西上,对于这类工作来说是浪费时间,但这意味着那些因编写生产代码而获得报酬的人可以花他们的时间。编写生产代码的时间,并且在代码审查时间方面不要有“它只是一个原型”之类的借口。
建造一个扔掉的东西经常被用作没有正确完成工作的借口。这意味着您实际上并没有在此过程中遇到足够多的问题来学习足够的知识来充分利用任何人的时间。而做的恰到好处,却又把它扔掉,更是浪费。
正如一些人之前所说,任何软件中最重要的功能是它可以发布。考虑到这一点,我会在任何一天构建“一个让人们为我付费的产品”,而我在重构方面的无情就是只允许足够的重构来获得一个可以工作且可以合理维护的产品。
很容易在任何支持分支概念的配置管理系统上构建一个丢弃。如果您要对现有系统进行彻底的设计更改,该系统是该领域的并且是您的薪水来源;你该死的更好的分支;原型; 如果它不起作用,就把它扔掉。
重构大型遗留摇钱树系统通常会导致简单的老式黑客攻击。重构听起来比我猜的黑客要好得多。