1

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.

2
  • But this uses exceptions as control flow, which is seen as an anti-pattern. Well, you are not throwing/propagating exceptions to move the execution to a different and arbitrary path. If m_remote doesn't support callbacks or error handlers, there's very few to do. m_remote API is forcing you to handle errors this way. You could wrap the API but it won't remove the try/catch
    – Laiv
    Feb 4, 2021 at 11:04
  • 1
    You're not using exceptions to control non-exceptional flow here. catch is intended to handle exceptional flow the way you need it to and throw just does its legitimate job (rethrows in this case). So this very solution is not an anti-pattern.
    – bloody
    Oct 11, 2023 at 9:16

5 Answers 5

8

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.

5

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.

4

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.

1

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

template<typename CallType>
MyErrorCode apiException2MyError(CallType 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;

2
  • This is a neat function, I could potentially even return the exception itself, but there's a problem if f() returns a value I want to use. I suppose in that case this wrapper would need to return std::variant or such.
    – jaskij
    Feb 1, 2021 at 17:20
  • @JanDorniak I like the std::variant idea. Then you can add a NoError type into it. Feb 1, 2021 at 17:25
-2

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.