質問

Assume I have an API that indicates failure by throwing an error (because errors are not expected). In this situation, how should retry code be written?

My first thought was to use something like this:

    int retry_count = 5;
    do {
        try {
            --retry_count;
            m_remote.communicate();
            break;
        }
        catch (const comm_ex& e) {
            LOG(warn) << "communicate() failed";
            if(retry_count == 0) throw;
            continue;
        }
    } while(retry_count > 0);

this can of course be wrapped easily converted to include a preset number of retries. But this uses exceptions as control flow, which is seen as an anti-pattern.

The best option would be to change the API of m_remote to include a try_communicate() method which indicates the error in a different way. But what if I do not control the API? How to proceed then?

Note: I've added the C++ tag since that's what I'm using, but believe this to be relevant to other languages too.

役に立ちましたか?

解決

Using exceptions as control flow is an antipattern if you choose to do it. If you work with external APIs, no degree of community consensus can override the stark necessity of getting the job done, so don't worry about it.

What you can do if you're concerned about the impression that your codebase gives off is write an anti-corruption layer that encapsulates this ugliness tightly and invisibly, so that the sight of it is as restricted as possible.

他のヒント

You got your anti-pattern wrong. The anti-pattern is: “Don’t throw exceptions for flow control”. It’s not your fault, if errors happen so often that you expect and handle them, then it’s the author of the API causing the problem. You do your best to handle it; your code is fine.

I think one should not take recommendations like "do not use exceptions for control flow" as a religious dogma. IMHO, a better recommendation could be "do not use exceptions for control flow, except for the control flow which is necessary for error handling".

And I think that is exactly what your code does.

If the API is in somebody else's control I suggest to use a apiException2MyError catch-all exceptions helper function which takes a lambda and returns an error code.

template<typename F>
MyErrorCode apiException2MyError(F f)
{
    try { f(); }
    catch /*.... */ { return MyErrorCode...} 
    // more catch
}

Then this function can be used in your loop.

The function can be specific to the needs.

Or general with a catch(...) { return false;} return true;

Here's a purely-human thought . . .

"Whoa! Why did the OP (Original Programmer ...) decide to write this in this way?"

Was (s)he maybe* hiding *something, "just to get it to 'work?'"

Hey – it's a very serious technical consideration. Such that, if you seriously believe that such an approach is "technically justified," you should very definitely describe your thinking ... both in the immediately-accompanying source code comments and in your project documentation.

Remember(!): In any code such as this, "your code represents one side of 'two external actors.'" Either of the two 'actors' could be at fault ... (in the awful bug that maybe two teams of programmers, neither of which are familiar at all with the code in question, are now trying to figure out, years later). Therefore, "'eat' another party's problems" only rarely. (Someday, "the other party's team" will thank you.)

ライセンス: CC-BY-SA帰属
所属していません softwareengineering.stackexchange
scroll top