Обработка исключений с учетом потока выполнения

Редактировать:

Для тех, кто заинтересован в более чистом способе реализации этого, взгляните на этот ответ < / а>.


В моей работе мне часто приходится использовать сторонний 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 моим. Затем каждый сеттер упаковывается в метод, который выполняет только одно: блок 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);
    }
}

Что ты думаешь об этом? Что вы думаете о накладных расходах на обработку исключений, которые это подразумевает? ...: /

Что ж, вопрос открыт, мне нужна новая идея, чтобы избавиться от ограничений библиотеки!


person yves Baumes    schedule 04.04.2009    source источник


Ответы (5)


Если вам действительно нужно вызвать много методов, вы можете сократить код, используя библиотека отражения, создав всего один метод для вызова и обработки исключений и передав имя метода / свойства для вызова / установки в качестве аргумента. У вас по-прежнему будет такое же количество вызовов try / catch, но код будет проще, и вы уже знаете имя метода, который потерпел неудачу.

В качестве альтернативы, в зависимости от типа объекта исключения, который они отбрасывают, он может содержать информацию о стеке, или вы можете использовать другую библиотеку для просмотра трассировки стека, чтобы получить имя последнего метода, в котором произошел сбой. Это зависит от используемой вами платформы.

person John Ellinwood    schedule 04.04.2009
comment
Библиотека отражений - хороший момент, близкий к предложению Нила (?). По поводу информации о стеке: это действительно хороший момент. Хотя я не могу изменить библиотеку, которую использую, знаете ли вы, как получить эту информацию при перехвате исключений, которые она мне возвращает? - person yves Baumes; 04.04.2009

Я всегда предпочитаю обертку, когда использую стороннюю библиотеку. Это позволяет мне определить собственный механизм обработки исключений, чтобы пользователи моего класса не знали о внешней библиотеке. Кроме того, если позже третья сторона изменит обработку исключений на коды возврата, это не повлияет на моих пользователей.

Но вместо того, чтобы возвращать исключение моим пользователям, я бы реализовал коды ошибок. Что-то вроде этого:

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
    }
}
person aJ.    schedule 04.04.2009
comment
Мне было интересно сделать это вместо двойного / вложенного блока try / catch, как это делает мое решение. Но обратный чек будет производиться каждый раз и будет стоить немного. почему бы не полагаться на исключение, внешняя библиотека заставляет меня использовать ее. И код станет чище, не правда ли? - person yves Baumes; 04.04.2009
comment
Фактически, вместо кода сделайте его строкой или классом. При возникновении ошибки метод MySetFunc добавляет ошибку в строку. Затем вам нужно только проверить, есть ли что-нибудь в строке в конце. С кодом он будет перезаписываться при каждом вызове, и вам будут нужны если после каждого вызова. - person jmucchiello; 04.04.2009
comment
@yves Baumes, Я стремился не кидать еще одно исключение. К тому же код возврата нужно проверять только в случае сбоя. - person aJ.; 04.04.2009

Думаю, в этом случае я решился бы на макрос. Что-то вроде (не проверено, обратная косая черта опущена):

#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" );

Вы уловили идею.

person Community    schedule 04.04.2009
comment
это хороший момент, это на самом деле то, что я реализовал ;-) - person yves Baumes; 04.04.2009

Хотя на самом деле это не тот ответ, который вы ищете, но я думаю, что ваша внешняя библиотека или ее использование каким-то образом злоупотребляют исключениями. Исключение не следует использовать для изменения общего потока процесса. Если это общий случай, когда ввод не соответствует спецификации, ваше приложение должно проверить параметр перед передачей его во внешнюю библиотеку. Исключения следует выдавать только в случае «исключительного» случая, и я думаю, что всякий раз, когда дело доходит до того, что нужно сделать с вводом пользователя, вам обычно приходится иметь дело со всем, а не полагаться на то, что «пользователь должен предоставить правильные данные, в противном случае мы обрабатываем это с исключениями ».

тем не менее, альтернативой предложениям Нила может быть использование boost :: lambda, если вы хотите избежать макросов.

person Lars    schedule 04.04.2009
comment
Да вы правы, обычно в системе писали, мы избегаем исключений. Но здесь библиотека сделана такой, какая она есть ... Так что мне нужно включить исключение - это моя программа, и в конечном итоге обработать их. Благодарю за повышение :: лямбда-трюк. Я посмотрю поподробнее! - person yves Baumes; 04.04.2009

В вашей первой версии вы могли сообщать о количестве успешных операций при условии, что SetXXX функции возвращают какое-то значение. Вы также можете сохранить счетчик (который увеличивается после каждого SetXXX вызова в этом try блоке), чтобы отмечать, какие все вызовы были выполнены успешно, и на основе этого значения счетчика возвращать соответствующее сообщение об ошибке.

Основная проблема с проверкой каждого шага в системе реального времени - вы, вероятно, вносите слишком большую задержку.

В противном случае ваш второй вариант выглядит единственным выходом. Теперь, если вам нужно написать оболочку для каждой библиотечной функции и почему бы не добавить логику проверки, если вы можете, вместо того, чтобы выполнять фактический вызов указанной библиотеки? Этот ИМО более эффективен.

person dirkgently    schedule 04.04.2009
comment
Насчет решения счетчика вы правы, но мне не нравится наличие счетчика, который нельзя использовать после успешного выполнения этого запроса. Но, тем не менее, накладные расходы небольшие по сравнению со всем остальным. Спасибо! - person yves Baumes; 04.04.2009
comment
О логической проверке (своего рода предварительная проверка). Фактически состояние развивается в режиме реального времени (например, диапазон цен), и мне нужно полагаться на эту библиотеку (взаимодействующую с удаленной системой AFAIK), чтобы не иметь ложноотрицательных результатов (запрос отклонен, хотя все может быть в порядке) - person yves Baumes; 04.04.2009
comment
Я вижу, откуда вы. Я имел в виду только те проверки, которые могут быть надежно выполнены на стороне пользователей, для других вы должны разговаривать с вашим сервером, например: пользователь может иметь небольшую учетную запись и не может разместить заказ на сумму x $. Это инвариант. - person dirkgently; 04.04.2009
comment
Еще одна вещь, связанная с проверкой, - вам потребуется синхронизация онлайн / офлайн (и периодическое обновление данных пользователя). - person dirkgently; 04.04.2009