views:

135

answers:

9

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.

+9  A: 

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.

Tomas Lycken
Errors should be thrown when doing A will break B. In this case, calling init() twice will break nothing. The second call is simply unnecessary and has no effect. If anything, a warning should be shown. We shouldn't invent fatal errors for conditions that are non-fatal.
Chris S
@Chris: How do you suggest this warning is shown, if not with an exception? Without actually executing the code, there's no way the compiler can determine whether multiple calls are being made or not.
Tomas Lycken
@Tomas: You're right, if the need is for a compile-time message, that would be difficult, if not impossible. However, the author doesn't specify the need for compile-time errors or warnings. I'm implying the warning be shown on the standard error stream during runtime.
Chris S
A: 

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!?").

Svend
There is a danger if you eat the error that the programmer has a call in the code that he thinks does something, that isn't doing anything. This can be immensely frustrating and time-wasting ('I keep changing the code and nothing happens' is a particularly maddening problem).
Brian Hooper
@Brian: "I keep changing the code and nothing happens" -- I guess everyone should have had this problem :p
kizzx2
I'm an expert on code problems because I have so many of them.
Brian Hooper
+1  A: 

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

Romain Hippeau
+3  A: 

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.

Space_C0wb0y
Isn't the Singleton pattern more or less approach #2?
kizzx2
A: 

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.

Charles Bretana
If it had no state, it wouldn't need to be initialized, right?
Space_C0wb0y
@Space_Cowboy, Even a static class that has no state (state defined as data which is specific to or defines an instance of the type), might need to be initialized with external data elements that controlled or configured how it's static methods behave - Which database do I use? What format do I use to output results? What the tax rate tables do I use today? etc. etc.
Charles Bretana
@Charles -- As edited `Init` is a very contrived example. Let's pretend for a moment that our design is actually needed :p The point was there are places where I can choose to "eat the error" or just "always complain". (Or you could respond with some clever trick which subvert all these dilemmas in all circumstances)
kizzx2
+4  A: 

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.

Larry Watanabe
A: 

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.

deamon
+1  A: 

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

supercat
Excellent answer! I really like your examples. IMO this is a really nice design philosophy which is also practical.
kizzx2
I guess this can be summed up as "a library should be fault tolerant as long as the contract is not broken."
kizzx2
@kizzx2: To this I would add that contracts should be written to allow libraries and interfaces to behave in useful fashion when practical. For example, I dislike the iEnumerable contract's requirement that enumerators throw exceptions any time the underlying object is modified. It would have been much nicer to require that they throw exceptions if they were incapable of continuing "sensibly" (subject to certain requirements) but encourage enumeration authors to allow enumeration to continue if data structures could maintain obey contracts (returning unchanged items exactly once, etc.)
supercat
A: 

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.

kizzx2