我正在处理一个相当大的代码库,并且给了我几个月时间来重构现有代码。需要进行重构过程,因为很快我们将需要在产品中添加许多新功能,而就目前而言,我们将无法再添加任何功能而不会破坏其他功能。简而言之:我们许多人在他们的职业生涯中都看到过的凌乱,庞大,错误的代码。

在重构期间,我不时遇到类,方法或代码行,它们的注释如下: br />

超时设置为模块A提供了一些时间来做一些事情。如果它的计时不正确,它将损坏。





请勿更改此设置。相信我,您会遇到麻烦。





我知道使用setTimeout不是一个好习惯,但是在这种情况下,我不得不使用它


我的问题是:当我遇到来自作者的此类警告时,我应该重构代码吗(不,我无法与作者联系)?

评论

恭喜你!您现在是代码的作者。

您必须先知道要做什么和实际要做什么,然后才能解决它(您需要了解后者才能了解更改的全部结果。)问题似乎是同步的,并消除了这些问题。应该重构工作#1,否则每次更改都会使情况变得更糟。您应该问的问题是如何做到这一点。在这个问题上有相当多的语言和平台依赖性。例如:stackoverflow.com/questions/3099794/finding-threading-bugs

跟进@sdenham的评论:如果您还没有可以执行代码的自动化单元测试,我建议您花时间创建此类单元测试,而不是在AFAICT上浪费时间进行“重构女巫搜索”,没有明确的目标心里。一旦有了一套可以彻底锻炼代码基础的单元测试,您将有一种客观的方式来知道您的代码是否/何时需要重写它的代码是否仍然可以正常工作。祝你好运。

如果作者如此偏执,以至于有人喘不过气来,代码就会被破坏,那么它可能已经被破坏了,未定义的行为恰好可以解决他们目前想要的方式。特别是第一个听起来好像他们已经竞争了比赛条件,所以大多数时候都可以奏效。斯登纳姆说对了。弄清楚应该做什么,而不要假设它实际上是什么。

@Ethan可能不是那么简单。更改代码可能会以不立即可见的方式破坏代码。有时,在您忘记当前的重构可能导致它之后,它会中断很长时间,因此您所拥有的只是一个带有神秘原因的“新”错误。与时间相关的情况尤其如此。

#1 楼

似乎您在“以防万一”的情况下进行重构,而又不确切知道在进行新功能开发时将详细更改代码库的哪些部分。否则,您将知道是否确实需要重构脆弱的模块,或者是否可以保留它们。

简单地说:我认为这是注定的重构策略。您正在将时间和金钱投入到公司中,而没人知道它是否真的会带来收益,并且您正在通过将错误引入工作代码来使事情变得更糟。

更好的策略:利用您的时间向有风险的模块添加自动测试(可能不是单元测试,而是集成测试)。尤其是您提到的那些易碎模块,在其中进行任何更改之前都需要完整的测试套件。
仅重构将测试就位所需的位。尽量减少任何必要的更改。唯一的例外是当您的测试发现错误时-立即修复它们(并重构到您需要安全地进行重构的程度)。当团队开始添加新功能(或修复错误)时,他们应该改进和重构恰好需要更改的代码库部分,而不是少一点也不多。

因此,当您确定需要更改和重构脆弱的模块时(由于新功能开发或测试,添加到第1步中会发现一些错误),然后您和您的团队就可以重构模块了,或多或少可以安全地忽略这些警告注释。作为对一些评论的答复:公平地说,如果一个人定期怀疑现有产品中的某个模块是引起问题的原因,尤其是标记为“请勿触摸”的模块,我同意所有您。应该在该过程中对其进行检查,调试和重构(在我提到的测试的支持下,不一定按此顺序进行)。错误是变更的有力依据,通常比新功能更强大。但是,这是个案决定。必须非常仔细地检查是否值得在标记为“请勿触摸”的模块中更改某些东西的麻烦。

评论


我很想拒绝投票,但我会避免。我已经看到了在多个项目上的这种心态会导致更糟糕的结果。一旦故障最终发生,它们往往是灾难性的,并且由于其根深蒂固而难以修复。我会定期修复所见到的污泥,似乎最坏的情况是所修复的问题数量最多,或者最多不会造成已知问题。总体而言,这是一个巨大的优势。然后,代码将更好地用于将来的开发。一针一针可节省九针,但被忽略的问题会变得更糟。

–亚伦
17-4-5在16:50



我认为,任何标有注释的代码“超时都会给模块A一些时间来做东西。如果它不像这样定时,那么它将崩溃”已经存在一个错误。没碰到bug真是幸运。

– 26之17
17年4月5日在17:38

@ 17of26:不一定是错误。有时,当您使用第三方图书馆时,您会被迫做出各种“高雅”让步,以使它正常工作。

–whatsisname
17年4月5日在19:47

@ 17of26:如果怀疑其中的模块存在错误,那么在进行任何类型的重构之前添加测试就更加重要。当这些测试揭示了该错误时,您就需要更改该模块,因此有必要立即对其进行重构。如果测试没有发现任何错误,最好将代码保持原样。

–布朗博士
17-4-5在19:50



@亚伦:我不明白为什么您认为添加测试或遵循童子军原则会降低产品质量。

–布朗博士
17-4-5在19:54



#2 楼

是的,您应该在添加其他功能之前重构代码。

此类注释的麻烦之处在于它们取决于运行代码库的环境的特定情况。在特定时间点编程的超时可能在编程时确实确实有必要。 ,您可以为典型数据量中的更改命名。无法保证在今天仍然有必要,或者仍然有足够的保证(原本应该保护的东西的完整性可能已经被破坏了很长时间-如果没有适当的回归测试,您可能永远不会注意到)。这就是为什么编程固定的延迟以允许另一个组件终止几乎总是不正确并且只能偶然地起作用的原因。原始作者。 (大概您也没有适当的回归/集成测试,或者您可以继续进行测试,告诉您是否破坏了某些内容。)出于谨慎但您说无论如何都必须进行重大更改,因此已经存在破坏该位置以前实现的微妙平衡的危险。当您唯一要做的是重构时,现在让苹果购物车不高兴,并且要确定如果事情中断是导致重构的重构,那总比等到您同时进行其他更改且永远不要可以。

评论


最佳答案就在这里;太糟糕了,这是一个失败者。上面接受的答案不是很好的建议,尤其是对“虚假”的强调。去年,我花了最大的精力(仅是我的部分,工作量约为10 ^ 6LOC),这是我见过的最遗留的,最糟糕的代码,我习惯于修理掉我看到的火车残骸时间,即使它不是我正在处理的组件的一部分。这样做后,我发现并修复了开发团队甚至不知道的错误(尽管我们的最终用户可能存在)。实际上,我被指示去做。结果产品得到改善。

–亚伦
17年4月5日在16:46

不过,我不会将其称为重构,而是将其修复。

–PaŭloEbermann
17年4月5日在18:23

重构代码库的设计就是重构。如果重新设计揭示了可以修复的错误,那就是错误修复。我的观点是,第一个通常会导致第二个,而第二个往往很难没有第一个。

–克拉米
17年4月5日在18:36

@Aaron,您很幸运在此方面得到管理人员/开发人员的支持。在大多数环境中,由不良的设计和代码导致的已知和未知错误被视为事物的自然顺序。

–旧帐户
17年4月5日在19:05

@nocomprende我会说,如果您对系统不够了解,无法为此编写一些测试,则您无须更改其工作方式。

–帕特里克M
17年4月6日在14:56

#3 楼


我的问题是:当遇到来自作者的警告时,我应该重构代码吗?


否,或者至少现在还没有。您暗示自动化测试的水平很低。您需要进行测试才能放心地重构。


现在,我们再也不能添加任何功能而不会破坏其他功能了


现在您需要专注于提高稳定性,而不是重构。您可能会在提高稳定性的同时进行重构,但这是实现真正目标的工具-稳定的代码库。稍有不同。

首先添加表征测试。不用担心任何规格,只需添加一个可断言当前行为的测试即可。这将有助于防止新工作破坏现有功能。

每次修复错误时,请添加一个证明该错误已修复的测试用例。这样可以防止直接回归。

添加新功能时,请至少添加一些新功能可以正常工作的基本测试。

可能会获得“有效工作”的副本。使用旧版代码”?


我花了几个月的时间来重构现有代码。


首先要提高测试范围。从最困难的区域开始。从变化最大的区域开始。然后,一旦确定了不良的设计,就将它们一个接一个地替换。

您几乎永远不会做一个大型重构,而是每周不断进行小型重构。一个大的重构具有破坏事物的巨大风险,并且很难很好地进行测试。

评论


+1用于添加表征测试。在修改未经测试的旧代码时,这几乎是最小化回归的唯一方法。如果您开始进行更改后测试开始失败,则可以根据给定的规范检查以前的行为是否确实正确,或者新更改的行为是否正确。

–狮子座
17年4月5日在18:51

对。或者,如果没有可用的规范检查,则购买该软件或对该软件感兴趣的人们是否确实需要先前的行为。

–bdsl
17年4月6日在9:10



也许可以获得“有效使用旧版代码”的副本?他读那本书需要几个星期?什么时候:在工作场所的工作时间,晚上每个人都睡觉?

– Billal Begueradj
17-4-8在12:13



#4 楼

切记切斯特顿(GK Chesterton)的篱笆:在您了解修建道路的原因之前,不要拆下阻碍道路的篱笆。他们获得了解。您可以查看提交消息,电子邮件线程或文档(如果存在)。然后,您将能够重构标记的片段,或者在注释中写下您的知识,以便让维护该代码的下一个人员可以做出更明智的决定。

您的目标是可以理解代码的功能,以及为什么当时要用警告标记该代码,以及如果忽略该警告会发生什么情况。

在此之前,我不会触摸标记的代码“请勿触摸”。

评论


OP表示“不,我无法与作者联系”。

– FrustratedWithFormsDesigner
17年4月5日在14:45

我无法与作者联系,也没有任何文档。

–kukis
17年4月5日在15:12

@kukis:您无法联系作者,任何领域专家,也找不到与所涉及代码有关的任何电子邮件/ Wiki /文档/测试案例?好吧,那是一个完整的软件考古研究项目,而不是简单的重构任务。

– 9000
17-4-5在18:55



代码很旧并且作者离开了,这并不少见。如果他们最终为竞争对手工作,那么讨论它可能会违反某人的NDA。在某些情况下,作者永远无法访问:您无法向Phil Katz询问PKzip,因为他几年前去世了。

– pjc50
17年4月6日在8:57

如果将“查找作者”替换为“了解代码解决了什么问题”,那么这是最佳答案。有时,这些代码是对花费数周或数月才能发现,跟踪和处理的错误的最可怕的解决方案,这些错误通常是普通类型的单元测试无法找到的。 (尽管有时它们是程序员不知道自己在做什么的结果。您的首要任务是了解它的本质。)

– grahamparks
17年4月11日在18:17

#5 楼

带有注释(如您显示的注释)的代码将在以下情况下成为我重构的事情的重中之重,前提是我有任何理由这样做。关键是,代码的臭味非常严重,您甚至可以通过注释闻到它的味道。尝试将任何新功能添加到此代码中是没有意义的,此类代码需要在需要更改时就立即消失。

还请注意,这些注释至少没有帮助:它们仅给出警告。也没有理由是的,它们是鲨鱼缸周围的警告标志。但是,如果您想在附近做任何事情,那就没有什么可以尝试与鲨鱼一起游泳了。恕我直言,您应该首先摆脱那些鲨鱼。一旦有了这些测试用例,就一定要了解您所更改的每一点,以确保您确实没有更改行为。保留代码的所有行为特殊性是您的首要任务,直到您可以证明它们没有任何作用。请记住,您在处理鲨鱼-必须非常小心。

因此,在超时的情况下:将其保留直到您确切地了解代码正在等待什么,然后修复

还要先确定超时的原因。
此外,请确保您的老板了解您所从事的工作是什么,以及为什么需要这样做。如果他们拒绝,请不要这样做。您绝对需要他们的支持。

评论


有时,代码最终变得混乱不堪,因为它必须在实际行为与文档不符的硅生产前样本上正确运行。根据变通办法的性质,如果代码永远不需要在越野车上运行,替换代码可能是个好主意,但是如果没有新代码所需的工程分析级别来更改代码,则可能不是一个好主意。理念。

–超级猫
17年4月6日在19:20

@supercat我的观点之一是,重构必须完全保留行为。如果它不能保留行为,那么它不只是在我的书中进行重构(而且在处理此类旧代码时非常危险)。

–cmaster-恢复莫妮卡
17年4月7日在6:20

#6 楼

如果一切正常,使用警告来重构代码可能不是一个好主意。但是如果必须重构...

首先,编写一些单元测试和集成测试,以测试警告提示您的条件。尽量模仿生产条件。这意味着将生产数据库镜像到测试服务器,在计算机上运行相同的其他服务,等等。。。。然后尝试进行重构(当然,在隔离的分支上)。然后在新代码上运行测试,以查看是否可以获得与原始代码相同的结果。如果可以,那么可以在生产中实施重构。但是如果出现问题,请准备好回滚更改。

#7 楼

我说继续进行更改。信不信由你,不是每个编码员都是天才,并且这样的警告意味着它可能是一个改进的地方。如果您发现作者是正确的,则可以(略)记录或解释警告的原因。

#8 楼

我将评论扩展为答案,因为我认为特定问题的某些方面或者被忽略了,或者被用来得出错误的结论。

此时,是否重构的问题还为时过早。 (即使它可能会以特定的“是”形式回答)。条件或其他并发/同步问题,例如此处讨论的内容。由于几个原因,这些都是特别困难的问题。首先,正如您所发现的,看似无关的更改可能会引发问题(其他错误也可能会产生这种影响,但并发错误几乎总是会发生。)其次,它们很难诊断:错误通常会在一个时间或代码与原因相距太远,您所做的任何诊断原因都可能导致其消失(Heisenbugs)。第三,并发错误很难在测试中找到。部分原因是由于组合爆炸:对于顺序代码来说已经很糟糕了,但是添加并发执行的可能交织将其炸毁,使得顺序问题在比较中变得无关紧要。此外,即使是一个好的测试用例,也只会偶尔触发问题-Nancy Leveson计算出Therac 25中的一个致命bug发生在大约350次运行中的1个中,但是如果您不知道该bug是什么,甚至有一个,您不知道有多少次重复可以进行有效的测试。此外,在这种规模下,只有自动化测试才可行,并且测试驱动程序可能会施加细微的时序约束,以致于它永远不会真正触发bug(再次是Heisenbugs)。

在某些环境中,有一些用于并发测试的工具,例如Helgrind,用于使用POSIX pthreads的代码,但是我们在这里不知道具体细节。如果有适合您的环境的工具,则应该在测试中添加静态分析(或者是相反的方法)。

为了增加难度,编译器(甚至处理器)在运行时)通常可以自由地以某种方式重新组织代码,这些方式有时使对线程安全性的推理非常违反直觉(也许,最著名的情况是双重检查的锁定习惯用法,尽管某些环境(Java,C ++ ...)具有进行了修改,以改善它。)

此代码可能有一个导致所有症状的简单问题,但是您更可能遇到系统性问题,可能会导致计划向其中添加新功能停下来。我希望我已经说服了您,您可能会遇到严重的问题,甚至可能对产品造成生存威胁,而要做的第一件事就是找出正在发生的事情。如果这确实揭示了并发问题,我强烈建议您先修复它们,甚至在询问是否应该执行更多常规重构的问题之前,以及在尝试添加更多功能之前。

评论


您在评论的哪里看到“竞赛条件”?甚至暗示在哪里?您在这里做了很多技术假设,甚至看不到代码的好处。

–罗伯特·哈维(Robert Harvey)
17年4月7日在16:22

@RobertHarvey“超时设置为模块A提供了一些时间来做一些事情。” -几乎是竞争条件的定义,并且超时不是处理它们的严格方法。我并不是唯一得出这一推论的人。如果这里没有问题,那很好,但是发问者需要知道,因为这些注释是处理不佳同步的危险信号。

–斯登纳姆
17年4月7日在17:03

#9 楼


我正在处理一个相当大的代码库,并且给了我几个月的时间来重构现有代码。重构过程是必需的,因为很快我们将需要在产品中添加许多新功能。在重构期间,我时不时会遇到类,方法或代码行,它们的注释如[“不要碰这个!“]

是的,您应该特别重构那些部分。这些警告是由以前的作者放置在此处的,它的意思是“不要无所事事地篡改这些东西,它非常错综复杂,并且有很多意外的交互作用”。当您的公司计划进一步开发该软件并添加许多功能时,他们专门责成您清理这些内容。因此,您不会无所适从地对其进行篡改,而是要负责清理它。
找出那些棘手的模块在做什么,然后将其分解为较小的问题(原始作者应该做的事情) )。为了使可维护的代码变得一团糟,需要对好的部分进行重构,而对不好的部分进行重写。

#10 楼

这个问题是关于何时/如何重构和/或清除代码的争论的另一个变体,带有一些“如何继承代码”。我们在具有不同团队和文化的不同组织中都有不同的经验和工作,因此,除了“做您认为需要做的事情,并且以不会被解雇的方式做”之外,没有任何对与错的答案。

我认为,没有很多组织会因为业务支持程序需要代码清理或重构而乐于接受业务流程。

在这种特定情况下,代码注释引起了不应该更改这些代码部分的标记。因此,如果继续进行,而业务确实倒退了,不仅您没有任何要证明的事情来支持您的行动,实际上还有一个工件会影响您的决定。

在通常情况下,只有在了解了要更改的各个方面之后,您才应该谨慎进行并进行更改,并找到测试方法,并特别注意容量,性能和时间,因为编码。

但是,即使如此,您的管理层仍需要了解您所做的工作所固有的风险,并同意您所做的事情具有超过风险的商业价值,并且您已经做了可以做到的事情。减轻这种风险。

现在,让我们大家回到自己的TODO上,如果只有更多的时间,我们知道的事情可以在我们自己的代码创建中得到改善。

#11 楼

是的,一点没错。这些清楚的迹象表明,编写此代码的人对代码不满意,可能会戳它,直到他们使它起作用为止。他们很可能不了解真正的问题,或者更糟的是,他们理解了这些问题并且懒得解决它们。

但是,这警告我们需要付出很多努力才能解决它们,并且此类修复程序将具有与之相关的风险。

理想情况下,您将能够找出问题所在并进行正确修复。例如:


超时设置为模块A提供了一些时间来完成任务。如果它的计时不正确,它将损坏。


这强烈表明模块A不能正确指示何时可以使用或何时完成处理。也许写这篇文章的人不想打扰修复模块A或由于某种原因而无法做。这看起来像一场灾难,等待发生,因为它表明计时相关性是靠运气而不是适当的顺序来处理的。如果看到此消息,我将非常希望对其进行修复。


请勿更改此设置。相信我,你会破坏事情的。


这不会告诉你太多。这将取决于代码在做什么。这可能意味着它似乎有明显的优化,出于某种原因,它实际上会破坏代码。例如,循环可能碰巧将变量保留为另一代码所依赖的特定值。否则可能会在另一个线程中测试一个变量,并且更改变量更新的顺序可能会破坏其他代码。


我知道使用setTimeout并不是一个好习惯,但是在这种情况下使用它。


这看起来很简单。您应该能够看到setTimeout在做什么,也许可以找到一种更好的方法。

也就是说,如果这些修补程序不在您的重构范围之内,则表明尝试在此代码中进行重构可能会极大地增加您的工作范围。受影响的代码,并查看您是否至少可以将注释改进到更清楚地说明问题所在。这样可以避免下一个人面对您面临的相同谜团。

评论


情况可能已经改变到根本不再存在旧问题的地步,警告的注释也变得像旧地图上写着“这里就是龙”的地方。深入研究情况,或了解情况已传到历史。

–user251748
17年4月6日在14:33

在现实世界中,某些设备没有提供可靠的就绪指示,而是指定了最长的未就绪时间。可靠且有效的准备就绪指示很不错,但有时有人会卡在没有它们的情况下使用。

–超级猫
17年4月6日在19:12

@supercat也许吧,也许不是。我们无法从此处的评论中分辨出来。因此,有必要进行调查,以改善评论的细节。

– David Schwartz
17年4月6日在19:53

@DavidSchwartz:评论当然可能会更有用,但是有时不清楚程序员应该花多少时间来尝试找出设备无法遵守其规格的所有精确方法,尤其是如果尚不清楚是否预期会出现问题时,尤其如此是暂时的或永久的事情。

–超级猫
17年4月6日在21:00

#12 楼

注释的作者很可能没有完全理解代码本身。如果他们真的知道自己在做什么,他们就会写出实际有用的评论(或者首先不介绍比赛条件)。像“相信我,你会破坏事情”这样的评论。对我来说,这表明作者试图更改导致他们未完全理解的意外错误的内容。

代码可能是通过猜测和反复试验而开发的,没有完全了解实际发生的情况。

这意味着:


更改代码是有风险的。显然,这将需要时间来理解,并且它可能不遵循良好的设计原则,并且可能具有模糊的效果和依赖性。它很可能没有经过充分的测试,并且如果没有人完全理解代码的作用,那么将很难编写测试以确保不引入错误或行为改变。赛车条件(如所提到的评论)特别繁重-这是单元测试无法挽救您的地方。
不更改代码是有风险的。该代码很可能包含模糊的错误和竞赛条件。如果在代码中发现了严重的错误,或者高优先级的业务需求更改迫使您在短时间内更改此代码,那么您将陷入严重麻烦。现在,您遇到了1中列出的所有问题,但是时间紧迫!此外,代码中的此类“暗区”往往会传播并感染它所接触的代码的其他部分。

进一步的复杂之处:单元测试无法为您节省。通常,建议的解决此类旧代码的方法是先添加单元测试或集成测试,然后隔离并重构。但是赛车条件不能通过自动测试来捕捉。唯一的解决方案是坐下来仔细研究代码,直到理解为止,然后重新编写代码以避免出现竞争情况。

这意味着任务比常规的重构要苛刻得多。您必须将其安排为实际的开发任务。

您可以将受影响的代码封装为常规重构的一部分,因此至少可以隔离危险代码。

#13 楼

我要问的问题是为什么有人首先写“请勿编辑”。

我已经编写了许多代码,其中一些代码很丑陋,并且在当时的给定约束下花费了大量的时间和精力来工作。

在这种情况下,我敢打赌,这种情况确实发生了,写评论的人发现有人对其进行了更改,然后不得不重复整个练习并将其放回原处,以便得到工作的事情。此后,出于剪裁的挫败感,他们写道...请勿编辑。

换句话说,我不想再解决此问题,因为我有更好的生活习惯。

说“不要编辑”是一种可以这样说,我们知道我们现在将要知道的一切,因此在将来,我们将永远不会学习任何新知识。

关于不编辑的原因,至少应该有一条评论。就像说“请勿触摸”或“请勿输入”。为什么不碰栅栏?为什么不输入?说“电子围栏,请勿触摸!”会更好吗?或“地雷!请勿输入!”。然后很明显为什么,但您仍然可以输入,但至少要先知道后果,然后再做。更改后,代码将正确运行。围绕问题代码进行特性测试始终是第一步。有关在解决更改代码之前如何打破依赖关系并测试代码的技巧,请参阅Michael Feathers的“使用遗留代码”。限制重构并让产品以自然和有机的方式发展。

评论


在先前的9个答案中,这似乎并没有提供任何实质性的解释。

– gna
17年4月7日在8:06