异常处理意识到执行流程

时间:2009-04-04 12:39:46

标签: c++ exception-handling

编辑:

对于有兴趣以更清洁的方式实施的人,请查看answer


在我的工作中,我经常需要使用第三代API来访问远程系统。 例如,创建请求并将其发送到远程系统:

   #include "external_lib.h"
   void SendRequest(UserRequest user_request)
   {
       try
       {
           external_lib::Request my_request;
           my_request.SetPrice(user_request.price);
           my_request.SetVolume(user_request.quantity);
           my_request.SetVisibleVolume(user_request.quantity);
           my_request.SetReference(user_request.instrument);
           my_request.SetUserID(user_request.user_name);
           my_request.SetUserPassword(user_request.user_name);
           // Meny other member affectations ...
       }
       catch(external_lib::out_of_range_error& e)
       {
           // Price , volume ????
       }
       catch(external_lib::error_t& e)
       {
           // Here I need to tell the user what was going wrong
       }
   }

每个lib的setter都会检查最终用户提供的值,并在用户不符合远程系统需求时发出异常。例如,可能不允许特定用户发送过大的卷。这是一个例子,实际上很多次用户尝试都不符合:没有长期有效的工具,价格超出限制等等。 因此,我们的最终用户需要一条明确的错误消息,告诉他在请求中要修改哪些内容以获得第二次撰写有效请求的机会。我必须提供hiim这样的提示

无论如何,外部lib的例外(大多数)从不指定哪个字段是源 中止请求。

据你所知,处理这些例外的最佳方式是什么?

我处理这些异常的第一个尝试就是用我的“包装”Request类。然后将每个setter包装在一个只执行一件事的方法中:try / catch块。然后catch块会抛出我的新例外:my_out_of_range_volume或my_out_of_range_price,具体取决于setter。例如,SetVolume()将以这种方式包装:

My_Request::SetVolume(const int volume) 
{
    try
    {
        m_Request.SetVolume(volume);
    }
    catch(external_lib::out_range_error& e)
    {
        throw my_out_of_range_volume(volume, e);
    }
}
你怎么看?您如何看待它所暗示的异常处理开销? ......:/

那么问题是开放的,我需要新的想法来摆脱那个lib约束!

5 个答案:

答案 0 :(得分:1)

如果确实需要调用很多方法,可以使用reflection library减少代码,只需创建一个方法来执行调用和异常处理,并传入名称要调用/设置为参数的方法/属性。你仍然有相同数量的try / catch调用,但代码会更简单,你已经知道失败的方法的名称。

或者,根据它们返回的异常对象的类型,它可能包含堆栈信息,或者您可以使用另一个库来遍历堆栈跟踪以获取失败的最后一个方法的名称。这取决于您正在使用的平台。

答案 1 :(得分:1)

每当我使用第三方库时,我总是更喜欢包装器。 它允许我定义自己的异常处理机制,避免我的类用户知道外部库。 此外,如果稍后第三方更改异常处理以返回代码,则我的用户不需要受到影响。

但是,不是将异常抛回给我的用户,而是实现错误代码。像这样:

class MyRequest
{
    enum RequestErrorCode
    {
        PRICE_OUT_OF_LIMIT,
        VOLUME_OUT_OF_LIMIT,
        ...
        ...
        ...
    };

    bool SetPrice(const int price , RequestErrorCode& ErrorCode_out);

    ...

private:

    external_lib::Request mRequest;


};

bool MyRequest::SetPrice(const int price , RequestErrorCode& ErrorCode_out)
{
    bool bReturn = true;
    try
    {

        bReturn = mRequest.SetPrice(price);
    }
    catch(external_lib::out_of_range_error& e)
    {

        ErrorCode_out = PRICE_OUT_OF_LIMIT;
        bReturn = false;
    }
    return bReturn;
}



      bool SendRequest(UserRequest user_request)
{
    MyRequest my_request;
    MyRequest::RequestErrorCode anErrorCode;
    bool bReturn = my_request.SetPrice(user_request.price, anErrorCode);
    if( false == bReturn)
    {
        //Get the error code and process
        //ex:PRICE_OUT_OF_LIMIT
    }
}

答案 2 :(得分:0)

在第一个版本中,如果SetXXX函数返回某个值,您可以报告成功的操作数。您还可以保留一个计数器(在SetXXX块中的每次try调用后增加)以记录所有调用成功并基于该计数器值返回相应的错误消息。

验证每一步的主要问题是,在实时系统中,您可能会引入太多的延迟。

否则,您的第二个选项看起来是唯一的方式。现在,如果你必须为每个库函数编写一个包装器,为什么不添加验证逻辑,如果可以,而不是实际调用所述库?这个IMO更有效率。

答案 3 :(得分:0)

我认为在这种情况下我可能会敢于一个宏。像(没有测试,反斜杠省略):

#define SET( ins, setfun, value, msg )                           
  try {
    ins.setfun( value );
  }
  catch( external::error & )  {
    throw my_explanation( msg, value );
  }

并在使用中:

Instrument i;
SET( i, SetExpiry, "01-01-2010", "Invalid expiry date" );
SET( i, SetPeriod, 6, "Period out of range" );

你明白了。

答案 4 :(得分:0)

虽然这不是你正在寻找的答案,但我认为你的外部lib或者你使用它会以某种方式滥用异常。不应使用异常来改变一般流程。如果是一般情况,输入与规范不匹配,则应用程序在将参数传递给外部lib之前验证参数。只有在出现“例外”情况时才会抛出异常,我认为无论何时用户输入做什么,你通常都要处理所有事情,而不是依赖“用户必须提供正确的数据,否则我们处理它有例外'。

但是,如果你想避免使用宏,可以使用boost :: lambda替代Neil的建议。