API设计:是“容错”的好事吗?

时间:2010-07-21 13:11:29

标签: idioms api-design

我已经整合了许多有用的答案,并想出了我自己的answer below


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

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

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

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

  1. InitLibrary内,我assert一些静态变量,它会责怪我的来电者两次初始化。
  2. InitLibrary内,我检查一些静态变量,如果我的lib已经初始化,则默默地中止。
  3. 方法#1显然是明确的,而方法#2使其更加用户友好。我认为方法#2可能有一个缺点,即我的调用者不会意识到InitLibrary不能被调用两次的事实。

    每种方法的优缺点是什么?是否有更聪明的方法来颠覆所有这些?

    修改

    我知道这里的例子非常人为。正如@daemon指出的那样,我应该初始化自己而不是打扰来电者。但实际上,有些地方我需要更多信息才能正确初始化自己(注意使用我的变量名someMagicInputRequiredAtRuntime)。这不仅限于初始化/终止,而是存在困境的其他情况,无论我是否应该选择引用并引用“故障过失”或失败。

9 个答案:

答案 0 :(得分:9)

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

另一方面,

静默失败将导致用户相信第二次调用成功(没有错误消息,没有异常),因此他们会期望设置新值。因此,当他们尝试使用Foo执行其他操作时,他们无法获得预期的结果。如果他们无法访问您的源代码,那么几乎无法弄清楚原因。

答案 1 :(得分:4)

Serenity Prayer(针对界面进行了修改)

     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

如果故障在环境中,那么您应该尝试让代码处理它。如果开发人员可以通过修复代码来防止这种情况,则应该生成异常。

答案 2 :(得分:3)

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

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

答案 3 :(得分:2)

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

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

顺便说一句,提供可能引发异常的方法的“Try”版本通常很有帮助。例如,将Control.TryBeginInvoke用于更新例程之类的事情会很好(如果线程安全控件属性发生更改,则属性处理程序希望控件在更新时仍然存在,但不会真正更新介意如果控件被释放;如果控件在其属性被更新时被关闭,则有点令人厌烦,无法避免第一次机会异常。)

答案 4 :(得分:1)

在您的班级中拥有私人静态计数器变量。如果它为0,则在Init中执行逻辑并递增计数器,如果它大于0,则只需递增计数器。在Term中相反,递减直到它为0然后执行逻辑。

另一种方法是使用Singleton pattern,这是C ++中的一个示例。

答案 5 :(得分:1)

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

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

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

我认为容错在大多数情况下都是一件好事,只是当API用户伸展角落情况时很难做到正确。我可以说两个世界中最好的是提供某种“严格模式”,这样当事情不“正常”时,用户就可以轻松地解决问题。

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

答案 6 :(得分:0)

如果您的语言不允许此错误以静态方式显示,则错误很可能仅在运行时出现。根据库的使用情况,这意味着错误不会在开发的后期出现。可能只在发货时(再次,取决于很多)。

如果没有悄悄地吃错误的危险(这不是一个真正的错误,因为你在发生危险之前就抓住了它),那么我会说你应该默默地吃掉它。这使它更加用户友好。

但是,如果someMagicInputRequiredAtRuntime因调用而变化,我会尽可能地提出错误,或者假设库不能按预期运行(“我初始化了值为42的lib,但它表现为如果我用11开始!?“。”。

答案 7 :(得分:0)

如果此库是静态类((没有状态的库类型),为什么不在类型初始值设定项中调用Init?如果它是可实例化的类型,则将调用放在构造函数中,或者放在处理实例化的工厂方法中。
不要允许公共访问Init功能。

答案 8 :(得分:0)

我认为你的界面有点太技术性了。没有程序员想要了解您在设计API时使用的概念。程序员希望解决他们的实际问题,并且不想学习如何使用API​​。没有人想要初始化你的API,这是API应该尽可能在后台处理的东西。找到一个好的抽象,可以保护开发人员尽可能少的低级技术。这意味着,API应该是容错的。