【问题标题】:Fix common library functions, or abandon then?修复常用的库函数,还是放弃呢?
【发布时间】:2010-03-19 02:54:42
【问题描述】:

想象一下我有一个带有错误的函数:

伪代码:

void Foo(LPVOID o)
{ 
   //implementation details omitted
}

问题是用户通过null:

Object bar = null;

...

Foo(bar);

那么函数可能由于访问冲突而崩溃;但它也可能会正常工作。错误是函数应该一直在检查传递null的无效情况,但它从来没有这样做过。这从来不是问题,因为相信开发人员知道他们在做什么。

如果我现在将函数更改为:

伪代码:

void Foo(LPVOID o)
{
   if (o == null) throw new EArgumentNullException("o");

   //implementation details omitted
}

然后那些愉快地使用该功能的人,碰巧但没有遇到访问冲突,现在突然会开始看到EArgumentNullException

我是否继续让人们不正确地使用该功能,并创建该功能的新版本?或者我是否修复了该功能以包含它最初应该具有的功能?


所以现在是道德困境。您是否曾经向现有代码添加新的健全性检查、安全检查、断言?还是说放弃旧功能,换新功能?


考虑一个如此常见的错误,以至于微软不得不为开发人员修复它:

 MessageBox(GetDesktopWindow, ...);

您永远、永远、永远都不想在桌面上制作一个窗口模型。你会锁定系统。您是否继续让开发人员锁定用户的计算机?还是把函数改成:

 MessageBox(HWND hWndParent, ...)
 {
    if (hWndParent == GetDesktopWindow)
       throw new Exception("hWndParent cannot be the desktop window. Use NULL instead.");

    ...
 }

实际上微软更改了窗口管理器以自动修复错误参数:

 MessageBox(HWND hWndParent, ...)
 {
    if (hWndParent == GetDesktopWindow)
       hWndParent = 0;

    ...
 }

在我编造的例子中,没有办法修补函数 - 如果没有给我一个对象,我就不能做我需要做的事情。

您是否冒着通过添加参数验证来破坏现有代码的风险?您是否让现有代码继续出错,得到不正确的结果?

【问题讨论】:

    标签: language-agnostic


    【解决方案1】:

    问题在于,您不仅要修复错误,而且还通过引入错误案例来更改方法的语义签名。

    从软件工程的角度来看,我建议您尝试尽可能地指定方法(例如使用前置条件和后置条件),但是一旦方法出现,规范更改是不行的(或至少您必须检查该方法的所有出现)并且新方法会更好。

    【讨论】:

    • 您是在鼓吹我们让人们错误地使用现有功能,从而产生错误的结果?什么是强制所有人放弃功能的最佳机制?
    • @Ian:这就是过时的目的。在 C# 中,这将是过时的属性。
    • @Ian:我会提倡它。可能人们已经开始假设您的函数本质上是使用逗号连接的。由于您没有具体说明该方法应该做什么,因此很难确定人们是否以任何方式滥用它。 Jacob 建议在 Java 中过时或 @deprecated 可能是逐步摆脱所有这些不正确使用的最佳方法。
    • @Christopher:该功能旨在将城市和州转换为“适当的”格式。人们提供了无效的参数(他们没有提供城市参数中的城市)。然后我开始添加更严格的参数检查。没有人可以争辩说他们错误地使用了该功能。是否应该允许开发人员添加额外的参数验证和单元测试来匹配新发现的边缘情况?
    • +1:VB3 附带了大量的错误,开发人员很快就会依赖这些错误,例如我 - 似乎 - 记得当使用箭头键向下导航列表框时,甚至触发了单击而不是选择更改事件。然而,围绕它编写了如此多的生产代码,以至于它成为了一个“功能”。当时微软在 MSDN 中的官方说法是“这种行为是设计使然,这种设计正在审查中”。这句话一直伴随着我,我经常在聚会上推出它:p
    【解决方案2】:

    我会保留旧功能,只是让它创建一个警告,通知您每次(可能)错误使用,然后我会踢错使用它的开发人员,直到他正确使用它为止。

    你无法抓住一切。如果有人写了“MakeLocation(“Ian Boyd”,“很愚蠢”);”怎么办?你会创建一个新函数还是改变函数来捕捉它?不,你会解雇开发者(或至少惩罚他)。

    当然,这需要您记录您的函数需要什么作为输入。

    【讨论】:

    • 该函数确实记录了它的输入。 “城市”和“国家”。 “愚蠢”不是一个州,“伊恩·博伊德”也不是一个城市。我知道这一点,因为我是一个人。
    【解决方案3】:

    这就是自动化测试 [单元测试、集成测试、自动化功能测试] 的好处所在,它们使您能够自信地更改现有代码。

    在进行此类更改时,我建议找到所有用法并确保它们的行为符合您的预期。

    我自己会对现有功能进行错误修复,而不是在 99% 的时间里复制它们。如果它改变了很多行为并且有很多对该函数的调用,您需要非常确定您的更改。

    所以继续进行更改,运行单元测试,然后进行自动化功能测试。修复任何错误和你的黄金!

    【讨论】:

    • 在我的实际情况下,单元测试不会发现问题。因为继续前进,一切都按原样进行。问题可能在于以特定方式与旧数据交互。
    【解决方案4】:

    如果您的代码中存在错误,您应该在报告任何错误时执行通常的操作。其中之一是评估修复和不修复的影响。有时,处理错误的正确做法是不修复它,因为它暴露的行为已被接受。有时修复它的成本,或者在正常发布周期之外发布修复的便利性,会阻止你发布修复的错误一段时间。这不是道德困境,而是成本和收益的经济问题。如果您对发布的代码中存在已知错误感到不安,请发布 known-bugs 列表。

    其他受访者似乎都没有建议的一个选项是将有问题的函数包装在另一个函数中,该函数会强制执行您需要的新行为。在函数可以运行到多行的世界中,有时不太可能引入新的错误来保留 99% 正确的代码片段并在不修改现有代码的情况下解决更改。当然,这并不总是可能的

    【讨论】:

    • 我要解决的另一个问题是如何通知开发人员他们错误地使用了一个函数。他们传递了无效的参数,这是他们的错。如果我有一台时间机器来添加我最初没有想到的所有这些边缘情况 - 我会的。
    【解决方案5】:

    两个选择:

    • 为错误检查版本指定一个新名称并弃用旧版本(稍后的一个版本让它开始发出警告(如果可能,编译时间,必要时运行时间),以后的两个版本将其删除)。
    • [并非总是可能] 将新引入的错误检查设置为仅在未修改版本崩溃或产生未定义行为时触发。 (这样在代码中小心翼翼的用户就不会遇到任何令人不快的意外。)

    【讨论】:

      【解决方案6】:

      这完全取决于您、您的代码库和您的用户。

      如果您是 Microsoft,并且您的 API 中存在被全球数百万开发人员使用的错误,那么您可能只想创建一个新函数并更新旧函数的文档。如果可以,您还想更新编译器以发出警告。 (尽管即便如此,您也可以更改现有系统;请记住,当 MS 将 VC 切换到 C++ 标准时,您必须更新所有 #include iostreams 并添加 using stds 以使简单的现有控制台应用程序再次运行?)

      这基本上取决于功能是什么。如果它是基本的东西,会产生巨大的连锁反应,那么它可能会破坏很多代码。如果它只是一个辅助功能,那么你也可以修复它。当然,如果您是 Microsoft 并且您的其他代码依赖于您的某个函数中的错误,那么您可能应该修复它,因为这只是简单的令人尴尬的保留。如果其他开发人员依赖于错误(您创建的),那么您可能对用户有义务不破坏您导致错误的代码。


      如果您是小公司或独立开发人员,那么请继续修复该功能。如果您只需要更新自己或几个人的新用法,那么修复它是最好的解决方案,特别是因为它甚至不是什么大问题,因为它真正需要的只是在文档中添加注释以了解该功能。例如do not pass NULLan exception is thrown if hWnd is the desktop

      另一种折衷方案是创建一个包装函数。您可以创建一个小的内联函数来检查参数,然后调用现有函数。这样一来,您实际上不必在短期内做太多事情,最终当人们迁移到新的时,您可以弃用甚至删除旧的,在检查之间将代码移动到新的一次。


      在大多数情况下,最好修复有缺陷的函数,特别是如果您只是添加参数检查而不是完全改变函数的行为。仅仅因为它会破坏一些现有代码(特别是如果代码是免费的!)就促进(阅读鼓励)糟糕的编码并不是一个好主意,想想看:如果有人正在创建一个新程序,那么他们可以做对从一开始,而不是依赖一个错误。如果他们正在重新编译依赖于错误的旧程序,那么他们可以只更新代码。同样,这取决于代码的混乱程度、有多少人受到影响以及他们是否付钱给你,但必须更新旧代码以例如初始化手头没有的变量是很常见的,或者检查错误代码等。

      总而言之,在您的具体示例中(鉴于提供的信息),您应该修复它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-10-22
        • 2012-07-18
        • 1970-01-01
        • 2016-06-12
        • 2013-09-07
        • 2014-03-08
        • 1970-01-01
        相关资源
        最近更新 更多