【问题标题】:Exception handling aware of execution flow异常处理感知执行流程
【发布时间】:2009-04-04 12:39:46
【问题描述】:

编辑:

对于有兴趣以更清洁的方式实现它的人,请查看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
       }
   }

每个库的设置器都会检查最终用户提供的值,并且当用户不符合远程系统需求时可能会抛出异常。例如,可能不允许特定用户发送太大的音量。这是一个例子,实际上很多次用户尝试不遵守:没有长期有效的工具,价格超出限制等,等等。 因此,我们的最终用户需要明确的错误消息来告诉他在其请求中修改什么以获得第二次机会来编写一个有效的请求。我必须向他提供这样的提示

无论如何,外部库的异常(大部分)从不指定哪个字段是源 中止请求。

您认为处理这些异常的最佳方法是什么?

我第一次尝试处理这些异常是用我的“包装”Request 类。然后将每个 setter 包装在一个只做一件事的方法中:一个 try/catch 块。然后 catch 块会抛出我的一个新异常:my_out_of_range_volume 或 my_out_of_range_price,具体取决于设置器。例如 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 限制!

【问题讨论】:

    标签: c++ exception-handling


    【解决方案1】:

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

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

    【讨论】:

    • reflection 库是一个好点,并且接近 Neil 的命题(?)。关于堆栈信息:这确实是一个好点。虽然我无法修改我使用的库,但您知道在捕获异常时如何获取这些信息吗?
    【解决方案2】:

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

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

    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
        }
    }
    

    【讨论】:

    • 我想这样做,而不是像我的解决方案那样使用双重/嵌套的 try/catch 块。但是退货检查将在每一次完成并且花费一点点。为什么不依赖异常外部库强迫我使用它。它让代码更清晰,你不觉得吗?
    • 其实不是代码,而是字符串或者类。当出现错误时,MySetFunc 方法会将错误附加到字符串中。然后你只需要检查字符串最后是否包含一些东西。使用代码,它会在每次调用时被覆盖,并且每次调用后都需要 ifs。
    • @yves Baumes,我的意图是避免抛出另一个异常。此外,只有在失败的情况下才需要检查返回码。
    【解决方案3】:

    我想在这种情况下我可能会敢于使用宏。类似的东西(未测试,省略反斜杠):

    #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】:

    虽然这并不是您真正想要的答案,但我认为您的外部库或您对它的使用会以某种方式滥用异常。不应使用异常来改变一般流程。如果是一般情况,即输入与规范不匹配,则由您的应用程序在将参数传递给外部库之前验证参数。仅当发生“异常”情况时才应抛出异常,我认为每当涉及到用户输入时,您通常必须处理所有事情,而不是依赖于“用户必须提供正确的数据,否则我们会处理例外”。

    不过,如果您想避免使用宏,则可以使用 boost::lambda 来替代 Neil 的建议。

    【讨论】:

    • 是的,你是对的,通常在我们编写的系统中,我们会避免异常。但是这里的库是按原样制作的......所以我需要启用异常是我的程序,并最终处理它们。谢谢你的 boost::lambda 技巧,我会深入了解一下!
    【解决方案5】:

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

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

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

    【讨论】:

    • 关于“计数器”解决方案,您是对的,但我不喜欢的是维护一个在此请求成功后无法使用的计数器。但是,与周围的所有东西相比,开销并不大。谢谢!
    • 关于逻辑验证(一种预检查)。 Acutally 条件实时演变(例如价格范围),我需要依靠这个库(与远程系统 AFAIK 通信)不会有误报(请求被拒绝,但它可能没问题)
    • 我可以看到你来自哪里。我的意思是只有那些可以在用户端可靠完成的验证应该在那里完成,对于其他人你必须与你的服务器交谈,例如:用户可能有一个小帐户并且不能放置价值 x$ 的订单。这是一个不变量。
    • 验证的另一件事是您需要在线/离线同步(以及定期更新用户数据)。
    猜你喜欢
    • 2012-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-10
    • 2017-06-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多