API设计:就是“容错”是一件好事吗?

发布于 2024-09-10 18:20:46 字数 977 浏览 8 评论 0原文

我整合了许多有用的答案,并提出了我自己的答案如下


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

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

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

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

  1. 在 InitLibrary 内部,我断言一些静态变量,该变量会责怪我的调用者初始化两次。
  2. InitLibrary 内,我检查一些静态变量,如果我的库已经初始化,则默默中止。

方法#1 显然是显式的,而方法#2 则使其更加用户友好。我认为方法 #2 可能有一个缺点,即我的调用者不会意识到 InitLibrary 不应该被调用两次。

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

编辑

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

I've consolidated many of the useful answers and came up with my own answer below


For example, I am writing a an API Foo which needs explicit initialization and termination. (Should be language agnostic but I'm using C++ here)

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

Apparently, our library doesn't care about multi-threading, reentrancy or whatnot. Let's suppose our Init function should only be called once, calling it again with any other input would wreak havoc.

What's the best way to communicate this to my caller? I can think of two ways:

  1. Inside InitLibrary, I assert some static variable which will blame my caller for init'ing twice.
  2. Inside InitLibrary, I check some static variable and silently aborts if my lib has already been initialized.

Method #1 obviously is explicit, while method #2 makes it more user friendly. I am thinking that method #2 probably has the disadvantage that my caller wouldn't be aware of the fact that InitLibrary shouln't be called twice.

What would be the pros/cons of each approach? Is there a cleverer way to subvert all these?

Edit

I know that the example here is very contrived. As @daemon pointed out, I should initialized myself and not bother the caller. Practically however, there are places where I need more information to properly initialize myself (note the use of my variable name someMagicInputRequiredAtRuntime). This is not restricted to initialization/termination but other instances where the dilemma exists whether I should choose to be quote-and-quote "fault tolorent" or fail lousily.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

绿萝 2024-09-17 18:20:46

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

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

I would definitely go for approach 1, along with an easy-to-understand exception and good documentation that explains why this fails. This will force the caller to be aware that this can happen, and the calling class can easily wrap the call in a try-catch statement if needed.

Failing silently, on the other hand, will lead your users to believe that the second call was successful (no error message, no exception) and thus they will expect that the new values are set. So when they try to do something else with Foo, they don't get the expected results. And it's darn near impossible to figure out why if they don't have access to your source code.

囍孤女 2024-09-17 18:20:46

宁静祈祷(针对接口进行了修改)

     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

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

Serenity Prayer (modified for interfaces)

     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

If the fault is in the environment, then you should try and make your code deal with it. If it is something that the developer can prevent by fixing their code, it should generate an exception.

孤独患者 2024-09-17 18:20:46

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

如果该库只能有一个活动实例,请让工厂检查现有实例。这将有效地使您的库对象成为单例

A good approach would be to have a factory that creates an intialized library object (this would require you to wrap your library in a class). Multiple create-calls to the factory would create different objects. This way, the initialize-method would then not be a part of the public interface of the library, and the factory would manage initialization.

If there can be only one instance of the library active, make the factory check for existing instances. This would effectively make your library-object a singleton.

流年已逝 2024-09-17 18:20:46

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

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

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

I would suggest that you should flag an exception if your routine cannot achieve the expected post-condition. If someone calls your init routine twice, and the system state after calling it the second time will be the same would be the same as if it had just been called once, then it is probably not necessary to throw an exception. If the system state after the second call would not match the caller's expectation, then an exception should be thrown.

In general, I think it's more helpful to think in terms of state than in terms of action. To use an analogy, an attempt to open as "write new" a file that is already open should either fail or result in a close-erase-reopen. It should not simply perform a no-op, since the program will be expecting to be writing into an empty file whose creation time matches the current time. On the other hand, trying to close a file that's already closed should generally not be considered an error, because the desire is that the file be closed.

BTW, it's often helpful to have available a "Try" version of a method that might throw an exception. It would be nice, for example, to have a Control.TryBeginInvoke available for things like update routines (if a thread-safe control property changes, the property handler would like the control to be updated if it still exists, but won't really mind if the control gets disposed; it's a little irksome not being able to avoid a first-chance exception if a control gets closed when its property is being updated).

檐上三寸雪 2024-09-17 18:20:46

在你的类中有一个私有静态计数器变量。如果为 0,则执行 Init 中的逻辑并递增计数器,如果大于 0,则只需递增计数器。在 Term 中则相反,递减直至为 0,然后执行逻辑。

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

Have a private static counter variable in your class. If it is 0 then do the logic in Init and increment the counter, If it is more than 0 then simply increment the counter. In Term do the opposite, decrement until it is 0 then do the logic.

Another way is to use a Singleton pattern, here is a sample in C++.

好菇凉咱不稀罕他 2024-09-17 18:20:46

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

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

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

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

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

I guess one way to subvert this dilemma is to fulfill both camps. Ruby has the -w warning switch, it is custom for gcc users to -Wall or even -Weffc++ and Perl has taint mode. By default, these "just work," but the more careful programmer can turn on these strict settings themselves.

One example against the "always complain the slightest error" approach is HTML. Imagine how frustrated the world would be if all browsers would bark at any CSS hacks (such as drawing elements at negative coordinates).

After considering many excellent answers, I've come to this conclusion for myself: When someone sits down, my API should ideally "just work." Of course, for anyone to be involved in any domain, he needs to work at one or two level of abstractions lower than the problem he is trying to solve, which means my user must learn about my internals sooner or later. If he uses my API for long enough, he will begin to stretch the limits and too much efforts to "hide" or "encapsulate" the inner workings will only become nuisance.

I guess fault tolerance is most of the time a good thing, it's just that it's difficult to get right when the API user is stretching corner cases. I could say the best of both worlds is to provide some kind of "strict mode" so that when things don't "just work," the user can easily dissect the problem.

Of course, doing this is a lot of extra work, so I may be just talking ideals here. Practically it all comes down to the specific case and the programmer's decision.

蒗幽 2024-09-17 18:20:46

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

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

然而,如果 someMagicInputRequiredAtRuntime 因调用而异,我会尽可能地引发错误,或者推测该库将无法按预期运行(“我用值 42 初始化了该库,但它的行为是如果我以 11 开始!?”)。

If your language doesn't allow this error to surface statically, chances are good the error will surface only at runtime. Depending on the use of your library, this means the error won't surface until much later in development. Possibly only when shipped (again, depends on alot).

If there's no danger in silently eating an error (which isn't a real error anyway, since you catch it before anything dangerous happens), then I'd say you should silently eat it. This makes it more user friendly.

If however someMagicInputRequiredAtRuntime varies from calling to calling, I'd raise the error whenever possible, or presumably the library will not function as expected ("I init'ed the lib with value 42, but it's behaving as if I initted with 11!?").

尽揽少女心 2024-09-17 18:20:46

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

If this Library is a static class, (a library type with no state), why not put the call to Init in the type initializer? If it is an instantiatable type, then put the call in the constructor, or in the factory method that handles instantiation.
Don;t allow public access to the Init function at all.

又怨 2024-09-17 18:20:46

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

I think your interface is a bit too technical. No programmer want to learn what concept you have used while designing the API. Programmers want solutions for their actual problems and don't want to learn how to use an API. Nobody wants to init your API, that is something that the API should handle in the background as far as possible. Find a good abstraction that shields the developer from as much low-level technical stuff as possible. That implies, that the API should be fault tolerant.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文