您已经找到了一些看起来多余的代码,而编译器没有注意到这一点。您要做什么以确保(或尽可能接近地确保)删除此代码不会导致回归。
想到两个主意。
根据代码是否看起来应该执行来“简单地”使用推论。但是,有时这可能是一项复杂,耗时的工作,并且风险很小(您的评估容易出错),并且没有实质性的业务回报。
将日志记录在该代码部分中,并查看实际输入的频率。执行完足够的代码后,您应该有足够的信心删除代码是安全的。
是否有更好的主意或类似的规范方法?
您已经找到了一些看起来多余的代码,而编译器没有注意到这一点。您要做什么以确保(或尽可能接近地确保)删除此代码不会导致回归。
想到两个主意。
根据代码是否看起来应该执行来“简单地”使用推论。但是,有时这可能是一项复杂,耗时的工作,并且风险很小(您的评估容易出错),并且没有实质性的业务回报。
将日志记录在该代码部分中,并查看实际输入的频率。执行完足够的代码后,您应该有足够的信心删除代码是安全的。
是否有更好的主意或类似的规范方法?
Answers:
在我拥有100%单元测试覆盖率的完美幻想世界中,我只是删除它,运行我的单元测试,并且当没有任何测试变为红色时,我将其提交。
但是不幸的是,我必须每天早晨醒来,面对这样一个残酷的现实:许多代码要么没有单元测试,要么没有代码可以覆盖所有可能的边缘情况。因此,我将考虑风险/回报,并得出结论,这样做根本不值得:
此过程分为两半。第一个是确认代码确实已死。第二个是理解错误的代价,并确保它们得到了适当的减轻。
这里的许多答案对前半部分都有很好的解决方案。诸如静态分析器之类的工具非常适合识别死代码。 grep
在某些情况下可以成为您的朋友。我经常采取的一个不寻常的步骤是尝试确定代码的原始用途。争论“ X不再是我们产品的功能,并且代码段Y旨在支持功能X”要比说“我看不到代码段Y有任何目的”要容易得多。
后半部分是打破是否应该删除代码的僵局的关键步骤。您需要了解错误答案的含义。如果您因错误的答案而要死掉,请注意!也许是时候接受代码随时间推移而发展的好时机,而不是尝试自己不要编写更多的东西。如果人们不会死,请问问自己自己用户的宽容程度。如果您破坏了某些东西并保持了客户关系,是否可以向他们发送修复程序?您是否有付费的问答团队来查找此类问题?这些类型的问题对于理解在按下Delete键之前必须具有的确定性至关重要。
在评论中,rmun指出了删除代码之前理解代码原始目的这一概念的绝佳措辞。现在的报价为切斯特顿篱笆。虽然太大了,无法在评论中直接引用,但我认为应该在此处正确引用:
在改造事物时,不同于使事物变形,有一个简单明了的原则;可能被称为悖论的原理 在这种情况下,存在某种制度或法律;为了简单起见,我们可以说是在道路上竖起的栅栏或大门。较现代的重整器对此轻描淡写地说道:“我看不到有这种用途。让我们清除它。”哪一种更聪明的重整器将很好地回答:“如果您看不到它的用途,我当然不会让您清除它。走开思考。然后,当您可以回来告诉我您确实看到它的用途时,我可以允许您销毁它。
我也倾向于grep
使用代码中的函数/类名称,这可以提供代码分析器可能没有的其他好处,例如,如果在注释,文档文件或脚本中提到了该名称。我在源代码树中的文件上运行grep并将结果存储在文件中;通常,结果会给出一个简短的信息:文件名/路径,行号以及遇到该名称的行,这可以提供线索,指出在没有任何语义的情况下调用或提及函数/类的位置(与代码分析器相反) ),并且不考虑文件扩展名。绝对不是最终的解决方案,而是对分析的一个很好的补充。
$object->$variableMethod($arg1, $arg2);
grep
。包含代码部分的函数可以提供有关如何使用函数以及其内容的线索?
除了提到的现有答案外,您还可以迭代地删除多个版本的代码。
在初始版本中,您可以在代码块仍起作用的情况下发出弃用警告。在此后的版本中,您可以删除代码块,但会留下一条错误消息,使用户不赞成使用该功能,并且该功能不再可用。在最终版本中,您将删除代码块和所有消息。
这可能有助于识别不可预见的功能,而不会警告最终用户。在最好的情况下,该代码实际上什么也不做,并且所发生的一切只是在删除之前不需要的代码保留了多个版本。
许多人建议“安全”的事情是在无法证明未使用代码的情况下保留代码。
但是代码不是资产,而是责任。
除非您能解释为什么它很重要并指出其测试,否则“更安全”的选择可能就是删除它。
如果仍然不确定,则至少确保添加测试以执行僵尸代码。
您可以使用功能切换来更改软件的执行路径,以完全忽略相关代码。
这样,您可以在不使用代码的情况下安全地部署您的更改,然后将其关闭。如果您发现一些与代码有关的主要错误,请重新打开切换开关,并调查可能的代码路径。
如果您在长时间内未发现任何问题,并且可以通过实时部署将其重新启用,则此方法应该使您充满信心。但是,更好的方法是在相关区域周围应用额外的日志记录和测试覆盖范围,这将为是否使用该区域提供更多证据。
在此处阅读有关切换的更多信息:https : //martinfowler.com/articles/feature-toggles.html
当然可以进行静态分析……而且,好处是,您不需要任何新工具。您的编译器具有所需的所有静态分析工具。
只需更改方法的名称(例如更改DoSomething
为DoSomethingX
),然后运行构建即可。
如果构建成功,则该方法显然不会被任何人使用。安全删除。
如果构建失败,请隔离调用它的代码行,并检查if
该调用周围有哪些语句以确定如何触发它。如果找不到任何触发它的数据用例,则可以安全地将其删除。
如果您真的担心删除它,请考虑保留代码,但使用ObsoleteAttribute(或与您的语言等效的标记)对其进行标记。像这样发布一个版本,然后在没有任何问题的情况下删除代码。
删除无法访问的代码
在有原则的静态类型语言中,您应该始终知道代码实际上是否可以访问:将其删除,编译,如果没有错误,则无法访问。
不幸的是,并非所有的语言都是静态类型的,并且并非所有的静态类型语言都是有原则的。可能出错的事情包括(1)反射和(2)无原则的重载。
如果您使用动态语言或具有足够强大的反射能力的语言,使得可以在运行时通过反射来访问受审查的代码段,那么您就不能依赖编译器。这些语言包括Python,Ruby或Java。
如果您使用的语言没有意想不到的重载,那么仅删除重载就可以简单地将重载分辨率切换为另一个重载。某些这样的语言允许您编写与代码使用相关的编译时警告/错误,否则您将不能依赖编译器。这样的语言包括Java(使用@Deprecated
)或C ++(使用[[deprecated]]
或= delete
)。
因此,除非您非常幸运能够使用严格的语言(想到Rust),否则您可能真的会信任编译器,从而陷入困境。不幸的是,测试套件通常不完整,因此也没有太多帮助。
提示下一节...
删除可能未使用的代码
实际上,该代码实际上是被引用的,但是您怀疑实际上引用该代码的分支从未被采用。
在这种情况下,无论使用哪种语言,都可以证明代码是可访问的,并且只能使用运行时工具。
过去,我已经成功地使用了3个阶段的方法来删除此类代码:
什么周期?这是代码使用的周期。例如,对于财务应用程序,我希望每月周期较短(月底支付工资),而每年周期较长。在这种情况下,您必须等待至少一年,以验证没有发出任何警告,因为年终清单可以使用否则不会使用的代码路径。
希望大多数应用程序的周期较短。
我建议在TODO注释中加上日期,以建议何时进行下一步。以及日历中的提醒。
[[deprecated]]
以标识调用该版本的站点。那么您可以检查它们,看看它们的行为是否会改变。或者,将重载定义为= delete
-在这种情况下,您将需要显式转换参数以使用其他版本之一。
从生产中删除代码就像打扫房子。当您从阁楼上扔掉一个物体时,您的妻子第二天就会杀死您,原因是她从1923年去世的邻居那里丢掉了曾祖母的第三位侄子的返乡礼物。
认真地说,在使用每个人都提到过的各种工具进行粗略分析之后,再使用已经提到的逐步弃用方法之后,请记住,在实际删除工作时,您可能会离开公司。让代码保留下来并记录其执行,并确保将有关其执行的任何警报明确地传达给您(或您的后继者和分配者)是至关重要的。
如果您不遵循这种方法,很可能会被妻子杀死。如果您保留代码(就像每一份纪念品一样),那么您可以放心,因为摩尔定律对您有所帮助,并且代码所使用的垃圾磁盘空间的成本感觉就像“一块石头砸了我”。年,您不必冒险成为多个饮水机八卦的中心,而在走廊上看起来也很怪异。
PS:为回应评论而澄清。.最初的问题是“您如何安全删除..”当然,我的回答中假设使用了版本控制。就像在垃圾桶中寻找有价值的东西一样。任何机构都不应抛弃代码,而版本控制也不应该成为每个开发人员的严格要求。
问题在于可能多余的代码。除非我们可以保证100%的执行路径都不会到达,否则我们永远无法知道一段代码是多余的。并且假定这是一个足够大的软件,因此几乎无法保证。除非我读错了这个问题,否则整个对话的整个过程只有在生产期间可能会调用被删除的代码的情况下才有意义,因此我们遇到了运行时/生产问题。版本控制不会因生产故障而挽救任何人,因此关于“版本控制”的评论与问题或我的初衷无关,即,如果确实需要,则在非常长的时间内适当弃用,否则不要
恕我直言,该评论是多余的,可以删除。
也许编译器确实注意到了,只是没有大惊小怪。
使用针对大小的完整编译器优化来运行构建。然后删除可疑代码,然后再次运行构建。
比较二进制文件。如果它们相同,则编译器已经注意到并默默地删除了代码。您可以安全地从源中删除它。
如果二进制文件不同...则没有定论。可能还有其他变化。一些编译器甚至在二进制文件中包含了编译日期和时间(也许可以将其配置掉!)
实际上,我最近使用一种称为“ deleteFoo”的方法遇到了这种确切情况。在整个代码库中,除了方法声明之外,找不到该字符串,但是我明智地在方法顶部写了一条日志行。
PHP:
public function deleteFoo()
{
error_log("In deleteFoo()", 3, "/path/to/log");
}
原来,代码已被使用!某些AJAX方法调用“ method:delete,elem = Foo”,然后将其连接并使用call_user_func_array()进行调用。
如有疑问,请登录!如果花了足够的时间而看不到日志已满,请考虑删除代码。但是,即使您删除了代码,也请保留日志和带日期的注释,以使需要维护它的人更容易在Git中查找提交!
使用grep
或首先使用可用的任何工具,您可能会找到对代码的引用。(最初不是我的指示/建议。)
然后决定是否要冒删除代码的风险,或者是否可以使用我的建议:
您可以修改功能/代码块以将其用于日志文件。如果在日志文件中没有“记录过”这样的消息,那么您可以删除日志记录代码。
两个问题:
从其他用户获取日志。 也许您可以设置一个全局标志,该标志将在退出时使程序崩溃,并请用户向您发送错误日志。
跟踪呼叫者。 在某些高级语言(例如Python)中,您可以轻松地获得追溯。但是,在编译语言中,您必须将返回地址保存到日志中,并在退出时强制进行核心转储(第1点)。
在C语言中,这应该非常简单:使用条件块(例如#ifdef ... #endif)仅在知道它会工作(并已经测试过)时才使用它,并只需读取返回地址从堆栈中(可能需要也可能不需要内联汇编语言)。
将参数保存在日志文件中可能有用也可能没有用。
如果您知道它周围的代码是做什么的,请对其进行测试以查看其是否损坏。这是重构您正在处理的代码的最简单,最具成本效益的方法,无论如何,首先应该对正在处理的代码进行任何更改,都应该这样做。
另一方面,如果您在不知道代码做什么的情况下重构了一段代码,请不要删除它。首先了解它,然后在您确定它安全的前提下对其进行重构(并且仅当您可以确定重构将适当地利用您的时间时)。
现在,在某些情况下(我知道我自己也遇到了这种情况),“死”的代码实际上没有连接任何东西。例如由承包商开发的代码库,这些代码库实际上从未在任何地方实现,因为它们在应用交付后立即变得过时了(为什么是的,我确实有一个具体的例子,您怎么知道?)。
我本人确实删除了所有这些代码,但是我不建议轻描淡写,这是一个巨大的风险-取决于此更改可能会影响多少代码(因为您始终有可能忽略了某些东西)应该格外小心,并进行积极的单元测试,以确定删除此古老的旧代码是否会破坏您的应用程序。
现在所有这些都说,尝试删除这样的代码可能不值得您花费时间或理智。除非它已经成为您的应用程序中的一个严重问题(例如,由于应用程序膨胀导致无法完成安全扫描...为什么是,我仍然想着一个例子),否则,除非您达到目标,否则我不建议您这样做这种情况下,代码确实开始以一种有影响的方式腐烂了。
简而言之-如果您知道代码的作用,请先对其进行测试。如果您不这样做,则可以不理会它。如果您知道自己不能孤单,请花时间在实施更改之前积极测试更改。
我的方法在这种情况下一直被认为是行业标准,但迄今为止却没有被提及:
再换一双眼睛。
有不同类型的“未使用的代码”,在某些情况下,删除是适当的,在其他情况下,您应该对其进行重构,而在其他情况下,则尽可能地逃避并且永远不要回头。您需要弄清楚是哪一个,并且这样做最好与经验丰富的第二个配对!-开发人员,非常熟悉代码库。这显着降低了发生不必要错误的风险,并允许您进行必要的改进以将代码库保持在可维护的状态。而且,如果您不这样做,那么在某些时候您将耗尽对代码库非常熟悉的开发人员。
我也已经看到了日志记录方法-更确切地说,我已经看到了日志记录代码:还有更多死代码被保留了10年。如果您正在谈论删除整个功能,那么日志记录方法是相当不错的,但是如果您只是删除一小部分无效代码,则不是。
如果相关代码的开发人员仍然可用,请与他一起检查删除代码。另外,请阅读代码中的注释。
您将得到正确的解释,为什么还要添加此代码以及该代码的作用。它可以轻松地支持特定的用例,或者您甚至可能没有足够的专业知识来判断是否真的不需要它。在极端情况下,可能会从C程序中删除所有空闲语句,而不会发现任何错误(可能需要几分钟才能用完内存)。
当代码的作者坐在您旁边时,这确实是一种糟糕的文化,但是您看不出要说话的原因,因为您确定他只是编写了不好的代码,而且您的代码是如此完美。而且,当然,他只是在评论中随意写一些单词,而无需浪费时间阅读。
在代码中写注释的最重要原因之一就是解释为什么它是通过这种方式实现的。您可能不同意该解决方案,但是您需要考虑该问题。
与作者的讨论也可能表明该代码是已删除或从未完成的内容的历史遗留物,因此可以安全删除。
我非常同意Markus Lausberg的第一点:绝对应该借助工具来分析代码。猿人的大脑不能被这种任务所信赖!
大多数标准编程语言都可以在IDE中使用,并且值得调用的每个IDE都具有“针对所有用途的查找”功能,而这正是这种情况的正确工具。(例如,在Eclipse中为Ctrl + Shift + G)
当然:静态分析器工具并不完美。必须意识到,在更复杂的情况下,仅在运行时才决定调用该方法的决定,而不是更快(通过反射调用,以脚本语言调用“ eval”函数等)。
两种可能性: