什么时候是好的(如果有的话)废弃生产代码并重新开始?
我被要求做一个代码审查,并向我们的新产品中添加一个新function的可行性进行报告,这是我迄今为止还没有亲自处理的。 我知道挑剔别人的代码很容易,但是我会说这是糟糕的forms(尽可能客观)。 我的代码审查的一些亮点:
-
线程滥用:
QueueUserWorkItem
和线程通常被使用很多 ,线程池委托具有非信息名称,例如PoolStart
和PoolStart2
。 线程之间也没有适当的同步,尤其是访问UI线程以外的线程上的UI对象。 -
魔术数字和魔术string :一些
Const
和Enum
是在代码中定义的,但是很多代码依赖于字面值。 -
全局variables :许多variables被声明为全局variables,可能会或可能不会被初始化,这取决于遵循什么代码path以及事件发生的顺序。当代码也在线程之间跳转时,这会变得非常混乱。
-
编译器警告 :主要的解决scheme文件包含500多个警告,总数是我不知道的。 我从Visual Studio得到一个警告,它不能再显示任何警告。
-
一半的课程 :代码被编译并添加到了这里和那里,我认为这导致人们忘记之前做了什么,所以有几个看似半完成的课程和空的存根。
-
这里没有发明 :产品重复其他产品使用的公用库中已经存在的function,例如数据访问帮助器,错误日志帮助器和用户界面帮助器。
-
关注点分离 :我认为当读到典型的“UI->业务层 – >数据访问层”三层体系结构时,有人将本书颠倒过来。 在这个代码库中,UI层直接访问数据库,因为业务层部分实现,但由于未充分充实而被忽略,数据访问层控制UI层。 大部分底层数据库和networking方法都是对主表单进行全局引用,直接显示,隐藏和修改表单。 在实际使用较薄的业务层的情况下 ,它也倾向于直接控制UI。 大多数低级代码也使用
MessageBox.Show
在发生exception时显示错误消息,并且大部分吞下原始exception。 当然,在尝试重构它之前,开始编写unit testing来validation程序的function会稍微复杂一些。
我只是在这里expression一下,但是我的问题很简单:花时间重构现有的代码库,一次只关注一个问题,还是考虑从头开始重写整个事情?
编辑 :澄清一点,我们确实有项目的原始要求,这就是为什么重新开始可能是一个选项。 另一种解释我的问题的方法是:代码能否达到维护代码的成本高于倾销代价并重新开始的成本?
如果没有任何违规行为,重新编写代码库的决定是新手软件开发人员常见的,严重的pipe理错误。
要警惕许多缺点。
- 重写停止新function冷开发几个月/年。 很less有公司能够长期坚持下去。
- 大多数发展计划很难钉。 这个重写也不会例外。 放大以前的观点,现在,延迟发展。
- 通过痛苦的经验在现有代码库中修复的错误将被重新引入。 Joel Spolsky在本文中有更多的例子。
- 第二个系统效应的受害者的危险 – 总之,“在设法做所有他们”上次没有做的事情“之前devise了一次东西的人们,加载所有的在第一版中他们推迟了,即使他们中的大部分也应该在第二版中推迟。
- 一旦这个昂贵的,繁重的重写工作完成,下一个inheritance新代码库的团队可能会用同样的借口来进行另一个重写。 程序员讨厌学习别人的代码。 没有人写完美的代码,因为完美是如此主观。 find我的任何真实世界的应用程序,我可以给你一个令人生厌的起诉和理由,从头开始重写。
无论您是否从头开始重写,现在开始重构阶段是真正坐下来了解问题的好方法,以便在真正需要的时候重写会更加顺畅,并且给现有的代码库一个诚实的外观真正看到是否需要重写。
要真的报废并重新开始?
当前的代码没有做你想做的事情,并且成本是不能改变的。
我相信现在有人会把Joel关于Netscape的文章连接起来,把它们的代码扔掉,怎么这么糟糕,以及一个巨大的错误。 我不想详细讨论这个问题,但是如果你把它链接到文章上,在这之前,请考虑一下:IE引擎,允许MS快速发布IE4,5,5.5和6的引擎inheritance,完全摧毁Netscape的IE引擎…这是新的。 三叉戟是一个新的引擎,他们扔掉了IE 3引擎, 因为它没有提供一个适合他们未来发展工作的基础 。 MS做了Joel说你永远不要做的事情,这是因为 MS做了这样一个浏览器,使他们能够完全消除Netscape。 所以请…在连接Joel之前,只要沉思这个想法,然后说“哦,你永远不应该这样做,这是一个可怕的想法”。
如果它需要更多的时间来阅读和理解代码(如果甚至可能的话)比重写整个应用程序更重要,那么我就说废弃并重新开始。
对此非常谨慎:
- 你确定你不只是懒惰,而且不打扰阅读代码
- 你对自己写的伟大代码和其他人生产的垃圾比较傲慢吗?
- 记住经过testing的代码比想象中的未被编写的代码价值更多
用我们的主人和霸主的话来说, 乔尔 – 你永远不应该做的事情 ,
放弃工作守则并不总是错误的 – 但你必须确定原因。
一个经验法则我发现有用的是,如果给定一个代码库,如果我必须重新编写超过25%的代码来使其工作或根据新的要求进行修改,那么最好重写它从头开始。
理由是你目前只能修补一段代码; 超出某个特定的时点,结束就会更快。
有一个潜在的假设,你有一个机制(如彻底的单位和/或系统testing),会告诉你,你的重写版本是否在function上等同(它需要)作为原件。
我看到一个应用程序在其引入生产的两年内重新架构,而另一些应用不同的技术重新编写(一个是C ++ – 现在是Java)。 在我看来,这两个努力都没有成功。
我更喜欢更坏的软件进化的方法。 如果你可以“组装”你的旧应用程序,这样你就可以引入你的新需求和与旧代码的接口,你可以放松自己进入新的环境,而不必“出售”零价值(从商业angular度)的投资重写。
build议的方法 – 为你想要接口的function编写unit testing1)确保代码的行为与你期望的一样; 2)为你可能希望在旧的基础上进行的任何重构提供一个安全网。
坏的代码是常态。 我认为,IT人员因为赞成重写/重新devise等而受到了不好的说唱。 他们付钱,“信任”我们(作为一个行业)提供可靠的,可扩展的代码。 可悲的是,商业压力经常导致代码不可维护的捷径。 有时候这是糟糕的程序员,有时候是不好的情况。
要回答你的重新提出的问题…可以编码维护成本超过重写成本…答案显然是的。 我没有看到你的例子中的任何东西,但是,这导致我相信这是你的情况。 我认为这些问题可以通过testing和重构来解决。
就商业价值而言,我认为极其罕见的情况是,只能通过代码的内部状态来进行重写。 如果产品面向顾客,并且正在生活并带来金钱(即不是一种无瑕疵的或未发行的产品),那么请考虑:
- 您已经有客户使用它。 他们熟悉它,可能已经在它周围build立了一些自己的资产。 (其他与之连接的系统;基于它的产品;他们必须改变的stream程;他们可能不得不重新培训的员工)。 所有这些花费了客户的钱。
- 重写它可能花费较less的长期,而不是做出困难的变化和修复。 但是,除非你的应用程序不像Hello World那么复杂,否则你还无法量化。 重写意味着重新testing和重新部署,可能是客户的升级途径。
- 谁说重写会更好? 你能诚实地说你的公司正在写shiny的代码吗? 有没有把原来的代码转化为意大利面条的做法得到纠正? (即使主要的罪魁祸首是一个开发者,他的同事和pipe理层在哪里,通过审查,testing等来确保质量?)
就技术原因而言,如果原文有一些技术上的依赖关系已经成为问题,那么我build议可以重新进行重写。 例如现在不支持的第三方依赖关系等等。
一般来说,我认为最明智的做法是一个一个地重构(如果真的很糟糕的话,很小的部分),逐步改善内部架构,而不是一个大的下降。
这一个思路的两个思路:你有原始的要求吗? 你有信心,原来的要求是准确的? 那么testing计划或unit testing呢? 如果你有这些东西,可能会更容易。
戴上我的顾客帽子,系统工作还是不稳定? 如果你有一些不稳定的事情,你就有一个变化的理由。 否则你是最好的重构一点点。
我认为在沙子上的线路是当基本维护时间比应该长25% – 50%时。 有一段时间,维护遗留代码变得太昂贵了。 许多因素有助于最终的决定。 时间和成本是我认为最重要的因素。
如果有干净的界面,并且可以清楚地描述模块边界,那么可能需要通过模块或逐层重构模块,以便将现有客户转移到更清洁,更稳定的代码库中,随着时间的推移,对每个模块进行了重构,你将会重写所有的东西。
但是,基于codereview,听起来不会有任何清晰的界限。
我想知道投票弃权和重新投票的人是否曾经成功地重构过一个大型项目,或者至less看到一个糟糕的大项目他们认为可以使用重构?
如果有的话,我犯了另一个错误:我看到4个大的项目是一团糟,我主张重构而不是重写。 在一对夫妇身上,几乎没有一行原始代码存在,主要界面也发生了重大变化,但整个过程从来没有涉及整个项目的运作,甚至超过一个星期。 (而且,树干顶部从未被破坏)。
也许某个项目如此严重的破坏,试图重构它将注定要失败,或者也许以前重构的项目之一会被更好的“干净重写”服务,但我不确定我会知道如何识别它。
我同意马丁。 您真的需要权衡从头开始编写应用程序的工作与应用程序的当前状态,以及有多less人使用它,他们是否喜欢它等等。通常我们可能要从头开始,但是成本远远超过收益。 我总是碰到难看的代码,但是我很快意识到这些“丑陋”的一些区域确实是bug修复,并使程序正常工作。
我会尝试考虑系统的体系结构,看看是否可以废弃和重写特定的明确定义的组件,而无需从头开始。
通常会发生的是,你可以这样做(然后把它卖给客户/pipe理层),或者你发现代码是如此可怕和混乱的混乱,你甚至更加确信你需要重写有更多令人信服的论据(包括:“如果我们devise正确的话,我们将永远不需要废弃整个事情并做第三次重写)。
缓慢的维护最终会导致那些稍后会使重写更加昂贵的架构漂移。
如果你完全知道你的应用程序是如何工作的,那么你只能给出一个肯定的答案(完全是我的意思,不仅仅是对它应该如何工作有一个大概的了解),而且你或多或less知道如何做到这一点更好。 任何其他情况,这是在黑暗中的一枪,这取决于太多的东西。 如果可能的话,逐渐重构可能会更安全。
早期和经常废旧的代码。 如有疑问,请将其扔掉。 困难的部分是说服非技术人员的维护成本。
只要派生的价值似乎大于运营和维护的成本,软件仍然有正面价值。 围绕重写这个问题的问题是:“我们能从重写中获得更多的价值吗?” 或者,“重写多less价值?” 您将节省多less个人小时的维护时间?
请记住,重写投资只是一次。 重写投资的回报永远持续下去。 永远 。
把价值问题重点放在具体问题上。 你列出了上面的一堆。 坚持下去。
-
“通过减less我们不使用的垃圾,但仍然需要通过降低成本来降低成本,我们会获得更多的价值吗?
-
“丢掉不可靠的垃圾,我们会得到更多的价值吗?”
-
“如果我们理解它,我们会获得更多的价值吗?不是通过logging,而是通过取代我们作为一个团队build立的东西?”
你做作业了吗? 你将不得不面对以下显示停止者。 这些将从您的执行食物链中的某个地方发起,
-
“它坏了吗?” 当你说“这不是碰撞 ”时,他们会说:“这不是坏的 – 不要修理它”。
-
“你已经完成了代码分析,你明白了,你不再需要修复它。”
你对他们的回答是什么?
这只是第一个障碍。 这是最糟糕的情况。 这并不总是会发生,但确实发生在惊人的频率上。
你的执行食品链中有人会有这样的想法:
- “重写不会产生足够的价值,而不是简单地重写,而是扩展它。 理由是,通过创造足够的价值,用户更有可能购买到重写。
范围扩大 – 人为地增加价值的项目通常是注定要失败的。
相反,做最小的重写,你可以取代织补的事情。 然后扩大以适应实际需要并增加价值。
如果可能的话,当我需要重构一个基线时,我通常宁愿随时重写代码的较小部分。 通常有很多小问题,例如幻数,糟糕的评论等等,这些问题往往会使代码看起来比实际情况糟糕。 所以,除非基线很糟糕,否则在维护代码的同时保持代码并进行改进。
如果重构需要大量的工作,我build议devise一个小的重新devise计划/待办事项清单,给你一个事情的清单,以便你可以使基线更好的状态。 从头开始总是一个冒险的举动,你不能保证当你完成代码会更好。 使用这种技术,您将始终有一个随时间推移而改进的工作系统。
具有过高循环复杂度的代码(如在大量模块中超过100个)是一个很好的线索。 另外,它有多less错误/ KLOC? 错误有多严重? 在进行错误修复时,引入错误的频率如何? 如果你的答案很多 (我现在不记得规范),那么重写是有保证的。
越早越好。 每当你预感到你的代码正在慢慢地变成一个丑陋的野兽,这很可能会消耗你的灵魂,让你头痛,并且你知道问题在于代码的底层结构(所以任何修复都是黑客,例如引入一个全局variables),那么是时候重新开始了。
由于某些原因,人们不喜欢扔掉宝贵的代码,但如果你觉得自己重新开始,你可能是对的。 相信自己的本能,记住这不是浪费时间,它教会了你一个不接近问题的方法。 你可以(应该)总是使用一个版本控制系统,所以你的宝宝永远不会迷路。
我自己没有任何使用指标的经验,但是“软件可维护性度量标准模型在实践中”这篇文章或多或less地讨论了他们所做的两个案例研究。 它从以下编者注开始:
在过去,当一个维护者接收到新的代码来维护时,经验法则是“如果你必须改变其他代码的40%以上,那么你就把它抛出去,重新开始。 这里提到的可维护性指数[MI]给出了一个更加可量化的方法来确定何时“抛出并重新开始”。 这项工作是由美国空军信息战中心和美国能源部(DOE),爱达荷州现场办公室,美国能源部合同编号DE-AC07-94ID13223。
我认为规则是…
- 第一个版本总是扔掉
所以,如果你学到了自己的课程,或者他/她的课程,那么现在就可以继续写下来,并且能够更好地理解你的问题领域。
不是说没有可以/应该保留的部分。 testing过的代码是最有价值的代码,所以如果除了风格以外没有任何实际的缺陷,没有理由把它全部折腾出来。
什么时候是好的(如果有的话)废弃生产代码并重新开始?
从来没有这样做过,但是逻辑决定了(对我来说),一旦你通过了转折点,你花费更多的时间重新修复和修复现有的代码库中的错误,而不是增加新的function,是时候垃圾了旧的东西,并重新开始。
如果它需要更多的时间来阅读和理解代码(如果甚至可能的话)比重写整个应用程序更重要,那么我就说废弃并重新开始。
我从来没有完全抛出代码。 即使从foxpro系统转到ac#系统。
如果旧的系统工作,那么为什么要把它扔掉?
我遇到了一些非常糟糕的系统。 在不需要的地方使用线程。 可怕的inheritance和滥用接口。
最好了解旧代码在做什么以及为什么这样做。 然后改变它,以免混淆。
当然,如果旧的代码不起作用。 我的意思是甚至不能编译。 那么你可能有理由刚刚开始。 但实际发生的频率如何?
是的,这完全可以发生。 我看到这样做可以节省钱。
这不是一个技术决定,这是一个商业决定。 代码重写是长期收益,而“如果没有完全破碎”是短期收益。 如果你是一年级的创业公司,那么专注于将产品推出市场,答案通常就是与之相适应。 如果你在一家成熟的公司,或者现有系统的错误导致了更多的工作量,那么公司的资金就会越多。
尽可能向GM提出问题,尽可能使用美元价值。 “我不喜欢处理它”意味着什么。 “这需要花费两倍的时间才能完成,直到这个问题得到解决”,这意味着很多。
我认为这里有很多问题很大程度上取决于你在哪里。
从客户的angular度来看,软件是否运行良好? (如果是非常小心的变化)。 如果系统正在运行,除非扩展了function集,否则我认为将会有一点重新考虑。 你打算扩大软件的function和客户群吗? 如果是这样,那么你有更多的理由来改变。
尽pipe只是试图理解别人的代码,即使编写得很好也是很困难的,但是如果写得不好,我会想象几乎不可能。 你所描述的东西听起来像是很难扩展的东西。
我会考虑,如果应用程序做它打算做什么,是需要你做出修改,并且你有信心,该应用程序已被彻底testing过的所有情况下,它将被用于。
如果应用程序不需要更改,请不要投入时间。 但是,如果它不能满足您的需求,您需要控制投入修改的时间和时间,请将其废弃并重新写入您的团队可以支持的标准。 没有什么比可怕的代码更糟,你必须支持/解码,但仍然必须忍受。 请记住,墨菲定律说,在晚上你必须做出事情的时候,这是不会有生产力的。
生产代码总是有一定的价值。 如果我们确定知识产权是不可逆转地受到污染的话,那么我唯一真正把它全部抛出并重新开始的情况就是如此。 例如,如果有人从以前的雇主那里拿到大量的代码,或者很大一部分代码是从GPLd代码库中剥离的。
每当我看到关于重构的讨论,我都会发布这本书。 每个人都应该阅读Michael Feathers写的“遗产代码有效地工作”。 我发现它是一本很好的书 – 如果没有别的,这是一个有趣的阅读和动机。
当代码已经达到了不可维护或可扩展的一个点。 充满了短期的哈克修复。 它有很多的耦合。 它有很长的(100 +行)方法。 它在UI中具有数据库访问权限。 它会产生大量的随机错误,无法debugging。
底线:维护它比重写它更昂贵(即需要更长的时间)。
我曾经相信只是从头重写,但是这是错误的。
http://www.joelonsoftware.com/articles/fog0000000069.html
改变主意。
我会build议找出一种方法来正确地重构代码。 保持所有现有的function,并随时testing。 我们都看到了可怕的代码库,但是随着时间的推移,保持应用程序的知识非常重要。