Saturday, 26 September 2020

Lippincott Pattern


1. Intro

In one of my recent C++ projects I spotted some giant macro named HANDLE_ALL_EXCEPTIONS() (or so), immediately noted how ugly it was and that a better solution to that problem exists. 

My coworkers at the then-customer were nice and open minded people so they didn't bridle at that, on the contrary, they were eager to change it and asked for advice. I simply said that they should google for the "Lippincot Pattern" and thought the matter were settled.

To my surprise my buddy returned reporting that there's nothing like that on the Internets! As to remedy that sore state of affairs I decided to make a short write-up of that really cool technique.

2. On Naming

An astute reader might have noticed that the whole problem is an artificial one: there is some (admittedly still too little!) information on the "Lippincot function" out there*. And Jason Turner (aka @lefticus) made an entire episode of C++ Weekly about it. So why I'm insisting on a different name?

Well, to be honest, it's mainly because I learned it by this name! Sadly, I cannot find any article on the web, so I cannot present you with any proof, but I assure it that it is true. Has anybody besides me heard of this technique under the name Lippincott Pattern instead of Lippincott Function? Please leave a comment if you did, I'm really curious about it!

But besides of my inability to change my habits there indeed is a genuine argument for that name. I mean, it is a known technique solving a common programming problem and that's the definition of a programming pattern if I'm not mistaken! And it's of no importance if we are using a function or a set of classes to solve the problem.

And of course, it sounds much better, and, without a doubt this, counts as a third reason. 🙂

As we are in a section called "On Naming" you might ask why this technique is called Lippincott function/pattern? Elementary, Dear Watson - it's beacuse it was invented by Lisa Lippincott, a well-known programmer and (as of recent) a C++ committee member with a faible for maths:
3. The Pattern

But enough of the idle banter, and ad rem! I maybe should have mentioned that this technique is also sometimes called "Exception Dispatcher". Now you probably can already imagine how it works.
   try
   {
      throw;  
   }
   catch (const MyNetworkException& ex)
   {
      MY_LOG_ERROR(tags::Networking, QString("Exception occurred: ") + ex.what());
   }
   catch (const std::exception & ex)
   {
      MY_LOG_ERROR(tags::Networking, QString("Unexpected exception occurred: ") + ex.what());
   }
   catch (...)
   {
      MY_LOG_ERROR(tags::Networking, "Unknown exception occurred.");
   }
As you see, we rethrow the current exception (notice that we assume we are in an exception handler!!!) and can write reusable exception handling logic without macros! We use it simply like this:
   try
   {
      a_function_that_may_throw();  
   }
   catch (...)
   {
      traceException(); // Lippincott!
   }
   
Here we just log the error and do nothing, but we could as well translate exceptions in error codes, as it is shown in the already mentioned blogpost*, and then use it like this;
   try
   {
      a_function_that_may_throw();  
   }
   catch (...)
   {
      return translateExceptionToErrno(); // Lippincott!
   }
However, that's not all - we can also here more complex logic. Let us have a look at this exception handler function from my recent HTTP project**:
  int CasablancaRestClient::handleException() const
  {
    QString errText;
    int errorCode;

    try
    {
      throw; // Lippincott pattern 
    }
    catch (const web::http::http_exception& e) 
    {
      // probably TCP conn. error!
      //  - notify conn. status change, trigger HTTP fallback if needed
      return HandleHttpError(e);
    }
    catch (const web::uri_exception& e)
    {
      QWriteLocker guard(&_serverAliveLock);

      if (!_serverAlive)
      {
        // probalbly server's URL not yet set
        errorCode = EC_NO_CONNECTION;
      }
      else
      {
        errText = tr("Internal error, bad URL: %1.").arg(e.what());
        errorCode = EC_BAD_URL;
      }
    }
    catch (const web::json::json_exception& e)
    {
      errText = tr("Internal error, bad JSON data: %1.").arg(e.what());
      errorCode = EC_JSON_FORMAT;
    }
    catch (const std::exception& e)
    {
      errText = tr("Internal error, reason: %1.").arg(e.what());
      errorCode = EC_INTERNAL;
    }
    catch (...)
    {
      errText = tr("Internal error in CCasablancaRestClientComp!!!!");
      errorCode = EC_INTERNAL;
    }

    WIN_DEBUG_CLIENT_ERR(errText.ToStdString());

    // send the error to GUI context
    emit connectionErrorMessage(errText);
  
    return errorCode;
  }
We see, we are just doing the basic translation from exception types to error codes, but also initiate a reconnection or fallback to a non-secure connection in some cases!

I was using it in following manner:
  try
  {
    sendHttpRequest(uri, data, headers);
    return EC_OK;
  }
  catch (...)
  {
    return handleException(); // Lippincott!
  }  
The beauty of this lies in its conciseness - no copy-pasted code, no macros, just a natural function call somehow obliterating the ugliness of the try-catch blocks.

4. "Modern" Lippincott variants

What we have seen above was the plain, basic, C++98-esque usage. But C++ wouldn't be itself, if we couldn't complicate things in name of progress and fashionable gimmicks 😇.

Just have a look at this code taken from the already mentioned article*:
  foo_Result lippincott()
  try
  {
     try
     {
        if (std::exception_ptr eptr = std::current_exception())
        {
           std::rethrow_exception(eptr);
        }
        else
        {
           return FOO_UNKNOWN;
        }
     }
     catch (const MyException1&)
     {
        return FOO_ERROR1;
     }
     catch (const MyException2&)
     {
        return FOO_ERROR2;
     }
     catch (...)
     {
        return FOO_UNKNOWN;
     }
  }
  catch (...)
  {
     return FOO_UNKNOWN;
  }
First irritating thing here is the function-scope try block. C++ allows you to wrap function body in a try/catch clause like this:
  ErrCode getSomeData()
  try
  {
     // do sth....
  }
  catch (...)
  {
    return PANIC_ERR;
  }
What is the purpose of this fature, you might ask? As cppreference.com states:
"The primary purpose of function-try-blocks is to respond to an exception thrown from the member initializer list in a constructor by logging and rethrowing, modifying the exception object and rethrowing, throwing a different exception instead, or terminating the program"
So there aren't any corner cases to justify its usage, and our example we already handle exceptions inside of the function  OK, now we can shed this syntax noise off. The second oddness is the usage of std::current_exception() and std::rethrow_exception() - what it is for? 

When you call std::current_exception() from within your function you can chek if there currently is an exception being handled and if it returns a nullptr then there is no exception active. Thus in the discussed code, we, in a somehow paranoid manner, are making sure that the exception dispatching function was called from the exception context - so to say implementing a "safe Lippincott" pattern. But ask yourself - would a programmer use a Lippincott function outside of a catch() block?  Well, maybe.

However, not all modern features are bad - far from that! Look at this elegant technique that uses lambdas:
  extern "C" errno_t my_amazing_c_function()
  {
    return translateException(
[&]{ // ... C++ code that may throw ... }); }
Here we wrap code that may throw in a lambda and pass it to a  generalized exception translation function which can be implemented like that:
  template<typename Callable>
    ErrorCode translateException(Callable&& f)
  {
    try
    {
      f();
      return NO_ERR;
     }
     catch (...)
     {
      return translateExceptionToErrno(); // Lippincott!
     }
  }
Cool, innit?

P.S.: "Ceterum censeo exceptiones delendam sunt..." - M. Portius Cato

--

* for example here: http://cppsecrets.blogspot.com/2013/12/using-lippincott-function-for.html plus many mentions of this article on Stack Overflow.

** Look up the Casablanca post: http://ib-krajewski.blogspot.com/2015/09/casablanca-c-rest-framework-one-year.html

1 comment:

  1. You can wrap the catching as a one-liner macrowhen in turn calls your Lippincott method. The macro/lippincot can also collect additional and often useful information such as:
    * __FILENAME__ __FUNCTION__ and __LINE__. I define this as FFL.
    * Collect any additional parameters you wish to log (or attach to an exception) using __VA_ARGS__ in conjuction with std::vector
    * Collect stacktraces (easy on gcc)





    #define __FILENAME__ Poco::replace(std::string(strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__), ".cpp", "")
    #define FFL std::string(__FILENAME__) + "::" + std::string(__FUNCTION__) + "() at line " + std::to_string(__LINE__) + ": "



    void Lippincott(std::string file_function_line, MyParameterCollection parameters = {}){

    /*
    here you can
    * log
    * rethrow
    * wrap in nested exception along with stacktrace+++ and rethrow
    * etc.
    */
    }



    #define CATCH_AND_LOG(file_function_line, ...)\
    catch(...){ Lippincott(file_function_line, {__VA_ARGS__}); }


    void someMethod(string name, int age, double weight){
    try{
    //Just throw some error here....
    throw logic_error("Oh no");
    }
    CATCH_AND_LOG(FFL, name, age, weight)
    }

    ReplyDelete