5

我已经整合了许多有用的答案,并在下面提出了我自己的答案


例如,我正在编写一个Foo需要显式初始化和终止的 API。(应该与语言无关,但我在这里使用 C++)

class Foo
{
public:
    static void InitLibrary(int someMagicInputRequiredAtRuntime);
    static void TermLibrary(int someOtherInput);
};

显然,我们的库并不关心多线程、重入或诸如此类的东西。假设我们的Init函数应该只被调用一次,再次使用任何其他输入调用它都会造成严重破坏。

将此信息传达给我的来电者的最佳方式是什么?我可以想到两种方法:

  1. 在里面InitLibrary,我assert有一些静态变量会责怪我的调用者初始化两次。
  2. 在里面InitLibrary,我检查了一些静态变量,如果我的库已经初始化,我会默默地中止。

方法#1 显然是显式的,而方法#2 使它对用户更友好。我认为方法#2 的缺点可能是我的调用者不会意识到InitLibrary不应调用两次的事实。

每种方法的优缺点是什么?有没有更聪明的方法来颠覆这一切?

编辑

我知道这里的例子很做作。正如@daemon 指出的那样,我应该初始化自己而不打扰调用者。然而实际上,有些地方我需要更多信息来正确初始化自己(注意使用我的变量名someMagicInputRequiredAtRuntime)。这不仅限于初始化/终止,而是存在困境的其他情况,无论我应该选择引用并引用“容错”还是糟糕地失败。

4

9 回答 9

9

我肯定会选择方法 1,以及一个易于理解的异常很好的文档解释为什么会失败。这将迫使调用者意识到这可能会发生,并且调用类可以在需要时轻松地将调用包装在 try-catch 语句中。

另一方面,静默失败会让您的用户相信第二次调用是成功的(没有错误消息,没有异常),因此他们会期望设置新值。因此,当他们尝试用 做其他事情时Foo,他们不会得到预期的结果。如果他们无权访问您的源代码,几乎不可能弄清楚为什么。

于 2010-07-21T13:18:16.157 回答
4

宁静祈祷(针对界面进行了修改)

     SA,  grant me the assertions 
     to accept the things devs cannot change 
     the code to except the things they can, 
     and the conditionals to detect the difference

如果故障出在环境中,那么您应该尝试让您的代码处理它。如果开发人员可以通过修复他们的代码来阻止它,它应该生成一个异常。

于 2010-07-21T13:32:19.450 回答
3

一个好的方法是拥有一个创建初始化库对象的工厂(这需要您将库包装在一个类中)。对工厂的多次创建调用将创建不同的对象。这样,initialize-方法就不会成为库的公共接口的一部分,工厂将管理初始化。

如果只有一个库实例处于活动状态,请在工厂检查现有实例。这将有效地使您的 library-object 成为singleton

于 2010-07-21T13:21:49.930 回答
2

如果您的例程无法达到预期的后置条件,我建议您标记一个异常。如果有人调用了你的 init 例程两次,并且第二次调用它后的系统状态将与刚刚调用一次一样,那么可能没有必要抛出异常。如果第二次调用后的系统状态与调用者的期望不符,则应抛出异常。

总的来说,我认为从状态的角度思考比从行动的角度思考更有帮助。打个比方,尝试以“写入新”的方式打开已经打开的文件应该要么失败,要么导致关闭-擦除-重新打开。它不应该简单地执行空操作,因为程序将期望写入一个创建时间与当前时间匹配的空文件。另一方面,尝试关闭已经关闭的文件通常不应被视为错误,因为希望关闭文​​件。

顺便说一句,拥有可能引发异常的方法的“尝试”版本通常很有帮助。例如,最好有一个 Control.TryBeginInvoke 可用于更新例程之类的事情(如果线程安全控件属性发生更改,则属性处理程序希望控件在它仍然存在时进行更新,但实际上不会请注意控件是否被释放;如果控件在更新其属性时关闭,则无法避免第一次机会异常,这有点令人讨厌)。

于 2010-07-21T15:05:47.877 回答
1

在你的类中有一个私有的静态计数器变量。如果为 0,则执行 Init 中的逻辑并递增计数器,如果大于 0,则简单地递增计数器。在 Term 中做相反的事情,递减直到它为 0,然后执行逻辑。

另一种方法是使用单例模式,这是 C++ 中的示例。

于 2010-07-21T13:21:38.207 回答
1

我想颠覆这种困境的一种方法是满足两个阵营。Ruby 有-w警告开关,它是 gcc 用户自定义的,-Wall甚至-Weffc++Perl 有污点模式。默认情况下,这些“正常工作”,但更细心的程序员可以自己打开这些严格的设置。

反对“总是抱怨最轻微的错误”方法的一个例子是 HTML。想象一下,如果所有浏览器都对任何 CSS hack(例如在负坐标处绘制元素)大喊大叫,世界将会多么沮丧。

在考虑了许多优秀的答案之后,我自己得出了这个结论:当有人坐下时,我的 API 理想情况下应该“正常工作”。当然,对于任何涉及任何领域的人来说,他都需要在比他试图解决的问题低一到两个抽象级别上工作,这意味着我的用户迟早必须了解我的内部结构。如果他使用我的 API 的时间足够长,他将开始扩大限制,而过多地“隐藏”或“封装”内部工作只会变得令人讨厌。

我想容错在大多数时候是一件好事,只是当 API 用户在扩展极端情况时很难做到正确。我可以说两全其美的是提供某种“严格模式”,以便当事情不“正常工作”时,用户可以轻松剖析问题。

当然,这样做是很多额外的工作,所以我在这里可能只是在谈论理想。实际上,这一切都取决于具体情况和程序员的决定。

于 2010-07-23T01:38:23.813 回答
0

如果您的语言不允许此错误静态出现,则该错误很有可能仅在运行时出现。根据您的库的使用情况,这意味着该错误要到开发后期才会出现。可能仅在发货时(同样,取决于很多)。

如果默默地吃错误没有危险(无论如何这都不是真正的错误,因为你在任何危险发生之前就发现了它),那么我会说你应该默默地吃它。这使其更加用户友好。

但是,如果someMagicInputRequiredAtRuntime从调用到调用不同,我会尽可能地引发错误,或者库可能无法按预期运行(“我用值 42 初始化了库,但它的行为就像我用 11 初始化一样!?” )。

于 2010-07-21T13:21:00.503 回答
0

如果这个库是一个静态类(一个没有状态的库类型),为什么不把调用放到Init类型初始化器中呢?如果是可实例化类型,则将调用放在构造函数中,或者放在处理实例化的工厂方法中。
根本不允许公众访问该Init功能。

于 2010-07-21T13:22:02.380 回答
0

我认为您的界面有点过于技术化。没有程序员想知道你在设计 API 时使用了什么概念。程序员想要解决他们实际问题的解决方案,不想学习如何使用 API。没有人想初始化你的 API,这是 API 应该尽可能在后台处理的事情。找到一个好的抽象,尽可能多地保护开发人员免受低级技术的影响。这意味着,API 应该是容错的。

于 2010-07-21T13:34:49.733 回答