Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

API design: is "fault tolerance" a good thing?

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.

like image 624
kizzx2 Avatar asked Jul 21 '10 13:07

kizzx2


4 Answers

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.

like image 71
Tomas Aschan Avatar answered Sep 28 '22 00:09

Tomas Aschan


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.

like image 25
Larry Watanabe Avatar answered Sep 27 '22 22:09

Larry Watanabe


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.

like image 39
Björn Pollex Avatar answered Sep 27 '22 22:09

Björn Pollex


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).

like image 20
supercat Avatar answered Sep 27 '22 23:09

supercat