53

我被要求进行代码审查并报告在我们的一个新产品中添加新功能的可行性,这是我迄今为止没有亲自参与过的产品。我知道挑剔别人的代码很容易,但我会说它的状态很糟糕(同时试图尽可能客观)。我的代码审查中的一些亮点:

  • 线程的滥用: QueueUserWorkItem线程通常被大量使用,线程池委托具有无意义的名称,例如PoolStartPoolStart2。线程之间也缺乏适当的同步,特别是在 UI 线程以外的线程上访问 UI 对象。

  • 魔术数字和魔术字符串:一些Const' 和Enum' 是在代码中定义的,但大部分代码都依赖于文字值。

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

  • 编译器警告:主要解决方案文件包含 500 多个警告,我不知道总数。我收到了来自 Visual Studio 的警告,它无法再显示任何警告。

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

  • Not Invented Here:该产品复制了其他产品使用的通用库中已经存在的功能,例如数据访问帮助程序、错误记录帮助程序和用户界面帮助程序。

  • 关注点分离:我认为有人在阅读典型的“UI -> 业务层 -> 数据访问层” 3 层架构时将这本书颠倒了。在这个代码库中,UI 层直接访问数据库,因为业务层部分实现但由于没有充分充实而大部分被忽略,而数据访问层控制着 UI 层。大多数低级数据库和网络方法都在对主窗体的全局引用上进行操作,并直接显示、隐藏和修改窗体。在实际使用比较薄的业务层地方,它也倾向于直接控制 UI。大多数低级代码也使用MessageBox.Show在发生异常时显示错误消息,并且大多数会吞下原始异常。这当然使得在尝试重构之前开始编写单元测试以验证程序的功能变得更加复杂。

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

编辑:澄清一下,我们确实有项目的原始要求,这就是为什么重新开始可能是一种选择。我的问题的另一种表达方式是:代码是否可以达到维护成本大于丢弃和重新开始的成本的地步?

4

29 回答 29

36

无意冒犯,从头开始重写代码库的决定是新手软件开发人员常犯的严重管理错误。

有许多缺点需要警惕。

  • 重写会阻止新功能在几个月/几年内被冷开发。很少有公司能够承受这么长时间的停滞不前。
  • 大多数开发计划都很难确定。这次改写也不例外。现在,通过延迟开发来扩大前一点。
  • 通过痛苦的经验在现有代码库中修复的错误将被重新引入。Joel Spolsky 在本文中有更多示例。
  • 成为第二系统效应牺牲品的危险——总而言之,“以前只设计过一次东西的人会尝试做所有他们“上次没能做”的事情,把所有的项目都装满他们在制作第一版时推迟的事情,即使他们中的大多数也应该在第二版中推迟。''
  • 一旦完成了这种昂贵、繁重的重写,下一个继承新代码库的团队可能会使用同样的借口进行另一次重写。程序员讨厌学习别人的代码。没有人写出完美的代码,因为完美是如此主观。找我任何现实世界的应用程序,我可以给你一个该死的控告和理由,让你从头开始重写。

无论您最终是否从头开始重写,现在开始重构阶段都是真正坐下来理解问题的好方法,以便在真正需要时重写会更加顺利,以及让现有代码库看起来更诚实真正看看是否需要重写。

于 2008-09-28T03:14:19.967 回答
32

真正报废并重新开始?

当当前代码没有按照您的意愿执行时,并且更改成本高昂。

我相信现在有人会链接 Joel 关于 Netscape 丢弃他们的代码以及它是多么可怕和一个巨大的错误的文章。我不想详细谈论它,但如果你链接那篇文章,在你这样做之前,请考虑一下:IE 引擎,允许 MS 快速发布 IE 4、5、5.5 和 6 的引擎继任者,彻底摧毁 Netscape 的 IE 引擎……它是新的。Trident 是他们抛弃 IE 3 引擎后的一个新引擎,因为它没有为他们未来的开发工作提供合适的基础。MS 做了乔尔说你绝不能做的事,这是因为MS 这样做是为了让他们拥有一个浏览器,可以让他们完全超越 Netscape。所以请……在你联系乔尔并说“哦,你永远不应该这样做,这是一个糟糕的主意”之前,先思考一下这个想法。

于 2008-09-27T23:39:12.197 回答
13

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

原因是到目前为止您只能修补一段代码;超过某个点,它会更快地完成。

有一个基本假设是您有一种机制(例如彻底的单元和/或系统测试),它将告诉您您的重写版本在功能上是否与原始版本相同(在需要的地方)。

于 2008-09-27T23:43:15.790 回答
13

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

对此要非常小心:

  1. 你确定你不只是懒惰而且懒得看代码吗
  2. 与其他人产生的垃圾相比,您是否对自己将编写的出色代码感到自大。
  3. 请记住,经过测试的工作代码比想象中的尚未编写的代码更有价值

用我们尊敬的主人和霸主Joel 的话来说——你不应该做的事情,
放弃工作代码并不总是错误的——但你必须确定原因。

于 2008-09-28T00:29:01.703 回答
9

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

对于糟糕的软件,我更喜欢一种更进化的方法。如果您可以“组件化”您的旧应用程序,以便您可以引入您的新需求并与旧代码交互,那么您可以轻松进入新环境,而无需“出售”零价值(从商业角度)投资重写。

建议的方法 - 为您希望与之交互的功能编写单元测试 1) 确保代码的行为符合您的预期,以及 2) 为您可能希望在旧基础上进行的任何重构提供安全网。

糟糕的代码是常态。我认为 IT 因支持重写/重新架构/等而受到商业界的诟病。他们付钱并“信任”我们(作为一个行业)来交付可靠、可扩展的代码。可悲的是,业务压力经常导致使代码无法维护的捷径。有时是糟糕的程序员……有时是糟糕的情况。

回答你改写的问题......代码维护成本是否会超过重写成本......答案显然是肯定的。但是,我在您的示例中看不到任何内容,这使我相信这是您的情况。我认为这些问题可以通过测试和重构来解决。

于 2008-09-28T01:50:05.347 回答
8

就商业价值而言,我认为仅由于代码的内部状态而可以进行重写的真实案例极为罕见。如果产品是面向客户的,并且当前是活的并且带来了资金(即不是封存或未发布的产品),那么请考虑:

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

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

不过,总的来说,我认为最明智的做法是逐个重构(如果真的那么糟糕,那就是非常小的部分),并逐步改进内部架构,而不是一次大跌。

于 2008-10-13T21:44:39.223 回答
7

关于这一点的两个想法:你有最初的要求吗?您有信心原始要求是准确的吗?测试计划或单元测试呢?如果你有这些东西,它可能会更容易。

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

于 2008-09-27T23:37:19.143 回答
7

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

于 2008-09-27T23:40:31.383 回答
6

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

但是,根据 codereview,听起来似乎没有任何清晰的界限。

于 2008-09-28T00:29:37.240 回答
5

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

如果有的话,我犯了相反的错误:我见过 4 个大项目是一团糟,我主张重构而不是重写。在一对夫妇中,几乎没有留下一行原始代码,并且主要接口发生了重大变化,但该过程从未导致整个项目在超过一周的时间内无法像最初那样正常运行。(并且树干顶部从未损坏)。

也许存在一个严重损坏的项目,以至于尝试重构它注定要失败,或者我重构的以前的项目之一可能会更好地通过“干净的重写”来服务,但我不确定我会知道如何识别它。

于 2008-09-30T22:09:33.697 回答
4

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

于 2008-09-27T23:42:24.593 回答
4

我会尝试考虑系统的架构,看看是否有可能废弃和重写特定定义良好的组件,而无需从头开始一切。

通常会发生的情况是,您可以这样做(然后将其出售给客户/管理层),或者您发现代码是如此可怕和纠结,以至于您更加确信您需要重写和有更令人信服的论据(包括:“如果我们设计得当,我们永远不需要废弃整个东西并进行第三次重写)。

缓慢的维护最终会导致架构漂移,这将使以后的重写成本更高。

于 2008-09-28T00:25:24.473 回答
4

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

只要派生的价值似乎大于运营和维护成本,软件仍然会产生积极的价值。围绕重写的问题是:“我们会从重写中获得更多价值吗?” 或者“我们将从重写中获得多少价值?” 您将节省多少工时维护?

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

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

  • “我们是否会通过丢弃我们不使用但仍必须涉水而过的垃圾来降低成本,从而获得更多价值?”

  • “我们会从丢弃不可靠和损坏的垃圾中获得更多价值吗?”

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

你做作业。您将不得不面对以下表演障碍。这些将起源于您的执行食物链中的某个地方,来自以下人员的回应:

  • “坏了吗?” 当你说“它没有崩溃”时,他们会说“它没有坏——不要修复它。”

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

你对他们的回答是什么?

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

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

  • “重写并不能创造足够的价值。与其简单地重写,不如让我们扩展它。” 理由是通过创造足够的价值,用户更有可能购买重写。

一个范围扩大——人为地——以增加价值的项目通常注定要失败。

相反,做最小的重写来替换该死的东西。然后扩展以满足实际需求并增加价值。

于 2008-09-28T00:33:37.293 回答
3

如果你完全知道你的应用程序是如何工作的(我的意思是完全我的意思,而不仅仅是对它应该如何工作有一个大致的了解)并且你或多或少确切地知道如何制作它,你只能对重写给出明确的肯定更好的。任何其他情况,它都是在黑暗中拍摄,这取决于太多的事情。如果可能的话,也许逐步重构会更安全。

于 2008-09-27T23:44:44.603 回答
2

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

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

于 2008-09-28T08:57:52.293 回答
2

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

如果重构需要大量工作,我建议您列出一个小的重新设计计划/待办事项列表,其中列出了要处理的事项,以便您可以将基线提升到更好的状态。从头开始总是一个冒险的举动,并且不能保证完成后代码会更好。使用这种技术,您将始终拥有一个随着时间的推移而改进的工作系统。

于 2008-09-28T01:25:24.377 回答
2

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

于 2008-09-28T01:28:00.237 回答
1

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

从来不需要这样做,但是逻辑会决定(无论如何对我来说)一旦你通过了一个拐点,你在现有代码库中花费更多的时间返工和修复错误而不是添加新功能,就该扔垃圾了旧的东西,重新开始。

于 2008-10-13T21:53:51.420 回答
1

我认为规则是...

  • 第一个版本总是被抛弃

所以,如果你学到了你的课,或者他/她的课,那么你现在可以继续写它,因为你更好地理解了你的问题领域。

并不是说没有可以/应该保留的部分。经过测试的代码是最有价值的代码,所以如果它在样式之外没有任何实际的缺陷,那么没有理由把它全部扔掉。

于 2008-09-28T02:55:33.193 回答
1

我自己没有任何使用指标的经验,但文章 “实践中的软件可维护性指标模型”讨论了这里提出的两个案例研究中或多或少相同的问题。它从以下编者注开始:

过去,当维护人员收到要维护的新代码时,经验法则是“如果您必须更改他人代码的 40% 以上,则将其丢弃并重新开始。” 此处提到的可维护性指数 [MI] 提供了一种更可量化的方法来确定何时“扔掉并重新开始”。这项工作由美国空军信息战中心和美国能源部 [DOE]、爱达荷州外地办事处赞助,DOE 合同号 DE-AC07-94ID13223。)

于 2008-09-28T02:06:00.643 回答
0

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

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

于 2008-10-03T19:02:55.870 回答
0

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

于 2008-09-28T00:12:41.977 回答
0

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

于 2008-09-28T02:21:29.857 回答
0

我从来没有完全抛弃过代码。即使从 foxpro 系统转到 ac# 系统。

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

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

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

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

于 2008-09-28T00:43:44.977 回答
0

是的,这完全有可能发生。我已经看到这样做可以省钱。

这不是技术决策,而是商业决策。代码重写是长期收益,而“如果它没有完全崩溃……”是短期收益。如果您是第一年专注于将产品推出市场的初创公司,那么答案通常就是忍受它。如果您在一家成熟的公司,或者当前系统的错误导致更多的工作量,因此需要更多的公司资金......那么他们可能会这样做。

尽可能向您的 GM 提出问题,尽可能使用美元价值。“我不喜欢处理它”没有任何意义。“在解决这个问题之前,做所有事情都需要两倍的时间”意味着很多。

于 2008-09-28T01:15:49.110 回答
0

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

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

即使写得好,仅仅试图理解其他人的代码也可能很困难,但如果写得不好,我想几乎是不可能的。您所描述的内容听起来很难扩展。

于 2008-09-28T01:21:41.360 回答
0

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

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

于 2008-09-28T01:36:03.993 回答
0

每当我看到关于重构的讨论时,我都会发布这本书。每个人都应该阅读 Michael Feathers 的“有效地使用遗留代码”。我发现它是一本很棒的书——如果没有别的,它读起来很有趣,而且很励志。

于 2008-09-30T03:07:47.780 回答
0

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

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

改变我的主意了。

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

于 2009-03-16T20:48:32.317 回答