何时(如果有的话)废弃生产代码并重新开始?

时间:2008-09-27 23:29:45

标签: refactoring

我被要求进行代码审查并报告在我们的新产品中添加新功能的可行性,这是我迄今尚未亲自处理过的新产品。我知道很容易挑选别人的代码,但我会说它的形状很糟糕(尽量尽可能客观)。我的代码审核中的一些亮点:

  • 滥用线程: QueueUserWorkItem并且一般使用很多,而线程池代理具有无法提供信息的名称,例如{{ 1}}和PoolStart。线程之间也缺乏适当的同步,特别是在UI线程以外的线程上访问UI对象。

  • 魔术数字和魔术字符串:代码中定义了一些PoolStart2Const,但很多代码都依赖于文字值

  • 全局变量:许多变量被声明为全局变量,可能会也可能不会被初始化,具体取决于所遵循的代码路径以及发生的顺序。当代码出现时,这会非常混乱也在线程之间跳来跳去。

  • 编译器警告:主解决方案文件包含500多个警告,并且我不知道总数。我从Visual Studio收到警告,它无法再显示警告。

  • 半完成课程:代码已经处理并添加到这里和那里,我认为这导致人们忘记了之前所做的事情,所以看似有一些半完成的课程和空的存根。

  • 未在此处发明:该产品复制了其他产品使用的公共库中已存在的功能,例如数据访问助手,错误记录助手和用户界面助手。

  • 关注点分离:当我们读到有关典型的“用户界面 - >业务层 - >数据访问层”3层架构时,我认为有人正在颠倒这本书。在此代码库中,UI层直接访问数据库,因为业务层部分实现但由于未充分充实而被忽略,并且数据访问层控制UI层。大多数低级数据库和网络方法都在对主窗体的全局引用上运行,并直接显示,隐藏和修改窗体。在实际使用相当薄的业务层 的情况下,它也倾向于直接控制UI。大多数此低级代码还使用Enum在发生异常时显示错误消息,并且大多数吞下原始异常。这当然使得在尝试重构之前开始编写单元测试以验证程序的功能会有点复杂。

我只是在这里略微表面,但我的问题很简单:花时间重构现有的代码库,一次关注一个问题,或者你会考虑重写整个事情是否更有意义?从头开始?

编辑:为了澄清一点,我们确实有项目的原始要求,这就是为什么重新开始可能是一个选项。另一种表达我的问题的方法是:代码是否能够达到维持成本的成本大于倾销和重新开始的成本?

29 个答案:

答案 0 :(得分:34)

没有任何违法行为,从头开始重写代码库的决定是新手软件开发人员常犯的严重管理错误。

要警惕许多缺点。

  • 重写阻止新功能被冷开发数月/年。很少,如果有任何公司能够承受这么长时间的停滞不前。
  • 大多数开发时间表都难以确定。这次重写也不例外。现在,通过延迟开发来扩大前一点。
  • 将重新介绍通过痛苦经历在现有代码库中修复的错误。 Joel Spolsky在this article中有更多的例子。
  • 成为Second-system effect的受害者的危险 - 总而言之,“那些在尝试做他们上次没做的所有事情之前只设计过一次的人”,加载项目在制作第一版时,他们推迟了所有的东西,即使它们中的大多数也应该在第二版中被推迟。''
  • 一旦这个昂贵,繁重的重写完成,下一个继承新代码库的团队可能会使用相同的借口进行另一次重写。程序员讨厌学习别人的代码。没有人写出完美的代码,因为完美是如此主观。找到任何真实世界的应用程序,我可以给你一个诅咒的起诉书和从头开始重写的理由。

无论你是否最终从头开始重写,现在开始重构阶段是一个很好的方式,让我们真正坐下来理解问题,这样如果真正需要重写将更顺利,并提供现有的代码库诚实地看看是否需要重写。

答案 1 :(得分:32)

要实际废弃并重新开始?

如果当前代码没有按照您的意愿执行操作,则更改成本过高。

我相信现在有人会将Joel关于Netscape的文章与他们的代码联系起来以及它是如何可怕和一个巨大的错误。我不想详细讨论它,但是如果你链接那篇文章,在你这样做之前,请考虑一下:IE引擎,允许MS快速发布IE 4,5,5.5和6的引擎继承,完全摧毁Netscape的IE引擎......这是新的。 Trident在放弃IE 3引擎后是一个新引擎,因为它没有为他们未来的开发工作提供合适的基础。 MS做了Joel说你绝对不能做的事情,并且它是因为 MS这样做他们有一个允许他们完全消除Netscape的浏览器。所以请...在你联系Joel并说“哦,你永远不应该这样做,这是一个可怕的想法”之前,只是默想一下这个想法。

答案 2 :(得分:13)

我发现有用的经验法则是,如果给定代码库,如果我必须重写超过25%的代码以使其工作或根据新的要求修改它,你也可以从头开始重写。

原因是你到目前为止只能修补一段代码;超过一定程度,它会更快完成。

有一个潜在的假设,即您有一个机制(例如彻底的单元和/或系统测试),它会告诉您重写的版本是否与原始函数在功能上等效(在需要的地方)。

答案 3 :(得分:13)

  

如果需要更多时间来阅读和理解代码(如果可能的话)   而不是重写整个应用程序,我说废弃它并重新开始。

要非常小心:

  1. 你确定你不仅仅是懒惰而且不打扰阅读代码
  2. 与其他人制作的垃圾相比,你是否对自己所写的优秀代码感到傲慢。
  3. 记住经过测试的代码比想象中尚未编写的代码更值得信赖
  4. 用我们主持的主人和霸主的话来说,Joel - things you should never do
    放弃工作代码并不总是错的 - 但你必须确定原因。

答案 4 :(得分:9)

我看到一个应用程序在引入生产后的两年内重新构建,其他应用程序用不同的技术重写(一个是C ++ - 现在是Java)。在我看来,这两项努力都没有成功。

我更喜欢采用更加进化的方法来处理糟糕的软件。如果您可以“组件化”您的旧应用程序,以便您可以引入新的要求并与旧代码接口,那么您可以放松自己进入新环境,而无需“出售”零价值(从商业角度)投资重写。

建议的方法 - 为您希望与之接口的功能编写单元测试1)确保代码按预期运行; 2)为您可能希望在旧基础上进行的任何重构提供安全网。 / p>

糟糕的代码是常态。我认为IT因为支持重写/重新架构等而从业务中得到了不好的说法。他们付钱并“信任”我们(作为一个行业)提供可靠,可扩展的代码。遗憾的是,业务压力经常导致快捷方式导致代码无法维护。有时它是糟糕的程序员......有时是糟糕的情况。

要回答你的改述问题......代码维护成本可能超过重写成本......答案显然是肯定的。但是,我没有在你的例子中看到任何东西,这让我相信这是你的情况。我认为这些问题可以通过测试和重构来解决。

答案 5 :(得分:8)

就业务价值而言,我认为由于代码的内部状态,可以为重写提供真实案例极为罕见。如果该产品面向客户并且目前正在生效并带来资金(即不是封存或未发布的产品),那么请考虑:

  • 您已经有客户使用它。他们熟悉它,并且可能围绕它建立了一些自己的资产。 (与之相关的其他系统;基于它的产品;他们必须改变的流程;他们可能需要重新培训的员工)。所有这些都花费了客户的钱。
  • 重新编写可能长期成本低于进行困难的更改和修复。但你无法量化,除非你的应用程序不比Hello World复杂。重写意味着重新测试和重新部署,可能是您客户的升级途径。
  • 谁说重写会更好?你能诚实地说你的公司现在正在编写闪亮的代码吗?将原始代码转换为意大利面的做法是否得到纠正? (即使主要罪魁祸首是单一开发人员,他的同事和管理层在哪里,通过评审,测试等确保质量?)

就技术原因而言,如果原版有一些技术依赖已经成为问题,我建议可能是重大改写的时候了。例如现在没有支持的第三方依赖等等。

总的来说,我认为最明智的举措是逐个重构(非常小的部分,如果真的 那么糟糕),并逐步改进内部架构,而不是在一个大的下降。

答案 6 :(得分:7)

我认为沙子中的线条是基本维护时间为25% - 比应有的时间长50%。有一段时间,维护遗留代码变得过于昂贵。许多因素有助于最终决定。时间和成本是我认为最重要的因素。

答案 7 :(得分:7)

这个问题的两个思路:你有原始要求吗?您是否确信原始要求是准确的?测试计划或单元测试怎么样?如果你有这些东西,可能会更容易。

戴上我的客户帽,系统是否正常工作还是不稳定?如果你有一些不稳定的东西你就会有改变的论据;否则你最好一点一点地重构它。

答案 8 :(得分:6)

如果有干净的界面并且您可以清晰地描绘模块边界,那么可能值得逐个模块或逐层重构它,以便您可以将现有客户迁移到更清洁的更多稳定的代码库,随着时间的推移,在你重构每个模块后,你将重写所有内容。

但是,基于代码审查,听起来不会有任何干净的界限。

答案 9 :(得分:5)

我想知道那些投票支持报废和重新开始的人是否曾经成功地重构过一个大项目,或者至少看到一个他们认为可以使用重构的大项目状况不佳?

如果有的话,我在相反的方面犯错:我看过4个大项目,这是一个混乱,我提倡重构而不是重写。在一对夫妇中,只剩下一行原始代码,并且主要界面在很大程度上发生了变化,但这个过程从未涉及整个项目无法像原来那样运行,超过一周。 (并且行李箱顶部从未损坏过。)

也许存在一个如此严重破坏的项目,试图重构它将注定要失败,或者我重构的先前项目之一可能会通过“干净的重写”得到更好的服务,但是我我不知道我怎么认识它。

答案 10 :(得分:4)

我同意马丁的观点。你真的需要权衡从头开始编写应用程序所涉及的工作量与应用程序的当前状态以及有多少人使用它,他们喜欢它等等。通常我们可能想要从头开始,但是成本远大于收益。我总是遇到一些丑陋的代码,但我很快意识到这些“丑陋”区域中的一些确实是错误修复并使程序正常工作。

答案 11 :(得分:4)

我会尝试考虑系统的体系结构,看看是否可以废弃和重写特定明确定义的组件,而无需从头开始。

通常会发生的事情是,您可以这样做(然后将其出售给客户/管理层),或者您发现代码是如此糟糕和纠结的混乱,您更加确信您需要重写并有更有说服力的论据(包括:“如果我们正确设计它,我们永远不需要废弃整个事情并进行第三次重写。)

缓慢的维护最终会导致架构漂移,这会使重写更加昂贵。

答案 12 :(得分:4)

尽早并经常废弃旧代码。如有疑问,请将其丢弃。困难的部分是让非技术人员相信维护成本。

只要得出的价值似乎大于运营和维护的成本,软件仍然有正值。围绕重写这个问题:“我们会从重写中获得更多价值吗?”或者“我们从重写中得到多少价值?”你会节省多少人工小时?

请记住,重写投资只有一次。重写投资的回报永远持续下去。的永远

将价值问题集中在具体问题上。你在上面列出了一堆。坚持下去。

  • “我们会通过降低成本来获得更多价值 丢弃我们不使用的垃圾 但还是要趟过?“

  • “我们会从丢弃那些不可靠且破裂的垃圾中获得更多价值吗?”

  • “如果我们理解它,我们会获得更多价值 - 不是通过记录,而是替换我们作为团队建立的东西吗?”

你做作业吗?你将不得不面对以下的表演停止者。 这些将来自您的执行食物链中的某个人,他们会做出如下回应:

  • “它坏了吗?”当你说“它不是坠毁”时,“他们会说”它没有破坏 - 不要修复它。“

  • “您已完成代码分析,您了解它,不再需要修复它。”

你对他们的答案是什么?

这只是第一道障碍。这是最糟糕的情况。这并不总是会发生,但确实会以惊人的频率发生。

执行食物链中的某个人会有这样的想法:

  • “重写不会产生足够的价值。不要简单地重写,而是让它扩展。”理由是,通过创建足够的值,用户更有可能购买重写。

扩大范围 - 人为地 - 增加价值的项目通常是注定的。

相反,你可以做最小的重写来代替这个糟糕的东西。然后扩展以满足实际需求并增加价值。

答案 13 :(得分:3)

如果您完全了解您的应用程序是如何工作的(并且完全是指它,而不仅仅是对它应该如何工作的一般概念),您只能给予重写肯定的肯定,并且您或多或少知道如何使它更好。任何其他情况,它是在黑暗中拍摄,这取决于太多的东西。如果可能的话,也许逐步重构会更安全。

答案 14 :(得分:2)

如果可能的话,当我需要重构基线时,我通常更愿意在一段时间内重写代码的较小部分。通常存在许多较小的问题,例如幻数,差评等,这些问题往往会使代码看起来比实际情况更糟糕。因此,除非基线非常糟糕,否则请保留代码,并在维护代码的同时进行改进。

如果重构需要大量工作,我建议制定一个小的重新设计计划/待办事项列表,为您提供按顺序处理的事项列表,以便您可以将基线带到更好的状态。从头开始始终是一个冒险的举措,并且您无法保证在完成后代码会更好。使用这种技术,您将始终拥有一个随着时间的推移而改进的工作系统。

答案 15 :(得分:2)

尽早。每当你预感到你的代码慢慢变成一个丑陋的野兽,它很可能消耗你的灵魂并让你头痛,你知道问题在于代码的底层结构(所以任何修复都是黑客攻击,例如,引入一个全局变量),然后是时候重新开始了。

出于某些原因,人们不喜欢丢掉宝贵的代码,但如果你觉得自己重新开始做得更好,那么你可能是对的。相信你的直觉,并记住这不是浪费时间,它教你一种不接近问题的方法。您可以(应该)始终使用版本控制系统,这样您的宝宝就不会真正迷失。

答案 16 :(得分:2)

具有过高的圈复杂度的代码(如在大量模块中超过100)是一个很好的线索。此外,它有多少错误/ KLOC?这些错误有多重要?在修复错误时引入错误的频率。如果您的答案很多(我现在无法记住规范),那么保证重写。

答案 17 :(得分:1)

我自己没有使用指标的经验,但是 文章 "Software Maintainability Metrics Models in Practice"讨论 或多或少同样问这里他们做过的两个案例研究。 它从以下编辑注释开始:

  

过去,当一个维护者   收到新代码维护,   经验法则是“如果你必须   改变超过40%的人   别的代码,你扔掉它   重新开始。“可维护性指数   这里提到的[MI]给出了更多   可量化的方法来确定何时   “把它扔出去重新开始。”这个   工作由美国航空公司赞助   部队信息战中心和   美国能源部[DOE],   美国能源部合同号爱达荷州外地办事处   DE-AC07-94ID13223。)

答案 18 :(得分:1)

我认为规则是......

  • 第一个版本总是丢掉

所以,如果你学到了你的课程,或者他/她的课程,那么你可以继续写下来,因为你更了解你的问题领域。

并非没有可以/应该保留的部件。经过测试的代码是最有价值的代码,因此如果它没有任何除样式之外的任何实际方法的缺陷,没有理由将其所有抛出。

答案 19 :(得分:1)

什么时候(如果有的话)废弃生产代码并重新开始?

从来没有必要这样做,但逻辑会指示(对我来说,无论如何)一旦你通过拐点,你在现有代码库中花费更多时间重新修复和修复错误,而不是添加新功能,它是是时候把旧东西丢弃了,重新开始。

答案 20 :(得分:0)

我会考虑应用程序是否按预期执行操作,是否需要进行修改,并确信应用程序已在所有将要使用的场景中经过全面测试。< / p>

如果应用程序不需要更改,请不要投入时间。但是,如果它不能按您的需要运行,您需要控制投入的时间和时间进行更正,将其废弃并重新写入您的团队可以支持的标准。没有什么比你必须支持/破译但仍然必须忍受的可怕代码更糟糕的了。请记住,墨菲定律说,当你必须让事情发挥作用时,它将会在晚上10点发生,而且永远不会有效。

答案 21 :(得分:0)

生产代码总是有一些价值。我真正全力以赴并重新开始的唯一情况是,如果我们确定知识产权受到不可挽回的污染。例如,如果某人从前一个雇主那里带来了大量代码,或者大部分代码都是从GPLd代码库中删除的。

答案 22 :(得分:0)

我认为这里有很多问题很大程度上取决于你所处的位置。

从客户角度看,该软件运作良好吗? (如果是,则要非常小心改变)。我认为除非你在系统正常工作时扩展功能集,否则没什么意义。您是否计划扩展该软件的功能和客户群?如果是这样,那么你有更多理由改变。

尽管任何事情只是试图理解其他人的代码,即使编写得很好也很困难,但写得不好时我会想象几乎不可能。你所描述的听起来像是很难扩展的东西。

答案 23 :(得分:0)

是的,完全可以发生。我已经看到通过这样做可以节省资金。

这不是一项技术决定,而是一项商业决策。代码重写是长期收益,而“如果它不是完全破坏......”是一个短期收益。如果您是第一年专注于将产品推向市场的创业公司,那么答案通常只是与它一起生活。如果你在一家成熟的公司,或者当前系统的错误造成了更多的工作量,那么公司的资金就会增加......那么他们可能会为此付出代价。

尽可能向GM提出问题,尽可能使用美元价值。 “我不喜欢处理它”没有任何意义。 “这需要两倍的时间来完成所有事情,直到修复”意味着很多。

答案 24 :(得分:0)

每次看到有关重构的讨论时,我都会发布这本书。每个人都应该阅读Michael Feathers的“有效使用遗产代码”。我发现它是一本优秀的书 - 如果不出意外,它是一本有趣的阅读和激励。

答案 25 :(得分:0)

我从来没有完全抛出代码。即使从foxpro系统转到c#系统。

如果旧系统有效,那么为什么要扔掉它?

我遇到了一些非常糟糕的系统。在不需要的地方使用线程。可怕的继承和滥用界面。

最好了解旧代码正在做什么以及为什么要这样做。然后改变它以免混淆。

当然,如果旧代码不起作用。我的意思是甚至无法编译。那么你可能有理由重新开始。但实际发生的频率是多少?

答案 26 :(得分:0)

当代码到达不再可维护或可扩展的点时。充满了短期的hacky修复。它有很多耦合。它有很长的(100 +行)方法。它在UI中具有数据库访问权限。它会产生大量随机,无法调试的错误。

底线:维护时比重写更贵(即花费更长时间)。

答案 27 :(得分:0)

如果需要更多时间来阅读和理解代码(如果可能的话),那么就要重写整个应用程序,我会说废弃并重新开始。

答案 28 :(得分:0)

我曾经相信只是从头开始重写,但这是错误的。

http://www.joelonsoftware.com/articles/fog0000000069.html

改变主意。

我建议的是找出一种正确重构代码的方法。保留所有现有功能并随时测试。我们都看到了可怕的代码库,但随着时间的推移保持应用程序的知识非常重要。