Диспетчеризация исключений в C ++ - PullRequest
3 голосов
/ 23 декабря 2008

Как следует отправлять исключения, чтобы обработка ошибок и диагностика могли обрабатываться централизованно, удобно для пользователя?

Например:

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

Правильная стратегия обработки ошибок зависит от типа исключения и выполняемой операции. (При прерывистом сигнале повторите попытку X раз, затем сообщите пользователю; при ошибке драйвера зарегистрируйте ошибку и перезапустите драйвер; и т. Д.) Как следует вызывать эту стратегию обработки ошибок?

  • Кодирование ошибок восстановления в каждом классе исключений: это приведет к тому, что классы исключений будут довольно большими и содержат высокоуровневый пользовательский интерфейс и код управления системой. Это кажется плохим.
  • Предоставление отдельного блока catch для каждого типа исключения: поскольку класс DataHW вызывается из разных мест, каждый блок catch должен дублироваться на каждом сайте вызова. Это кажется плохим.
  • Использование одного блока catch, который вызывает некоторую функцию ExceptionDispatch с гигантским оператором switch на основе RTTI: RTTI и switch обычно указывают на невозможность применения ОО-дизайна, но это кажется наименее плохой альтернативой.

Ответы [ 4 ]

6 голосов
/ 23 декабря 2008

Избегайте дублирования блоков перехвата на каждом сайте вызова, перехватывая (...) и вызывая функцию общего обработчика, которая перебрасывает и отправляет:

f()
{
    try
    {
        // something
    }
    catch (...)
    {
        handle();
    }
}

void handle()
{
    try
    {
        throw;
    }
    catch (const Foo& e)
    {
        // handle Foo
    }
    catch (const Bar& e)
    {
        // handle Bar
    }
    // etc
}
2 голосов
/ 23 декабря 2008

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

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

У меня вопрос: если много кода для дублирования, почему бы не превратить его в функцию?

Я думаю по линии ...

void SendData(DataHW* data, Destination *dest)
{
    try {
        data->send(dest);
    } catch (CRCError) {
        //log error

        //retransmit:
        data->send(dest);
    } catch (UnrecoverableError) {
        throw GivingUp;
    }
}

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

Конечно, эта функция чрезмерно упрощена - вам может понадобиться целый класс-оболочка для DataHW; но я хочу сказать, что было бы неплохо иметь централизованную точку, вокруг которой обрабатываются все исключения DataHW - если методы, которыми их обрабатывают разные пользователи класса, одинаковы.

1 голос
/ 23 декабря 2008

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

Как сказал Батлер Лэмпсон: все проблемы в информатике могут быть решены с помощью другого уровня косвенности

1 голос
/ 23 декабря 2008

Есть три способа решить эту проблему.

Написание функций-оболочек

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

Использование функциональных объектов

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

class DataHW {
public:
    template<typename Function>
    bool executeAndHandle(Function f) {
        for(int tries = 0; ; tries++) {
            try {
                f(this);
                return true;
            }
            catch(CrcError & e) {
                // handle crc error
            }
            catch(IntermittentSignalError & e) {
                // handle intermittent signal
                if(tries < 3) {
                    continue;
                } else {
                    logError("Signal interruption after 3 tries.");
                } 
            }
            catch(DriverError & e) {
                // restart
            }
            return false;
        }
    }

    void sendData(char const *data, std::size_t len);
    void readData(char *data, std::size_t len);
};

Теперь, если вы хотите что-то сделать, вы можете просто сделать это:

void doit() {
    char buf[] = "hello world";
    hw.executeAndHandle(boost::bind(&DataHW::sendData, _1, buf, sizeof buf));
}

Поскольку вы предоставляете функциональные объекты, вы также можете управлять состоянием. Допустим, sendData обновляет len, чтобы он знал, сколько байтов было прочитано. Затем вы можете написать функциональные объекты, которые читают и пишут, и ведут подсчет количества прочитанных символов.

Недостатком этого второго подхода является то, что вы не можете получить доступ к результирующим значениям бросающих функций, так как они вызываются из оболочек объекта функции. Нет простого способа получить тип результата связующего объекта функции. Одним из обходных путей является написание объекта функции результата, который вызывается executeAndHandle после успешного выполнения объекта функции. Но если мы вложим слишком много работы в этот второй подход только для того, чтобы выполнить всю работу по ведению домашнего хозяйства, это больше не будет стоить результатов.

Объединение двух

Есть и третий вариант. Мы можем объединить два решения (обертка и функциональные объекты).

class DataHW {
public:
    template<typename R, typename Function>
    R executeAndHandle(Function f) {
        for(int tries = 0; ; tries++) {
            try {
                return f(this);
            }
            catch(CrcError & e) {
                // handle crc error
            }
            catch(IntermittentSignalError & e) {
                // handle intermittent signal
                if(tries < 3) {
                    continue;
                } else {
                    logError("Signal interruption after 3 tries.");
                } 
            }
            catch(DriverError & e) {
                // restart
            }
            // return a sensible default. for bool, that's false. for other integer
            // types, it's zero.
            return R();
        }
    }

    T sendData(char const *data, std::size_t len) {
        return executeAndHandle<T>(
            boost::bind(&DataHW::doSendData, _1, data, len));
    }

    // say it returns something for this example
    T doSendData(char const *data, std::size_t len);
    T doReadData(char *data, std::size_t len);
};

Хитрость - это return f(); шаблон. Мы можем вернуться, даже когда f возвращает void. В конечном итоге это будет моим любимым, поскольку оно позволяет хранить центральный код дескриптора в одном месте, но также позволяет выполнять специальную обработку в функциях-оболочках. Вы можете решить, лучше ли разделить это и создать собственный класс, который имеет эту функцию-обработчик ошибок и оболочки. Вероятно, это было бы более чистым решением (я думаю о Разделение проблем здесь. Одна из них - это базовая функциональность DataHW, а другая - обработка ошибок).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...