#cpp #ооп #шаблоны_проектирования
В разрабатываемом приложении часть классов спроектированы таким образом, что статус выполнения функции либо возвращается, либо сохраняется в соответствующем поле. Эти классы связаны с обработкой файлов, xml-документов и т.п. После выполнения каждой операции приходится проверять статус выполнения, например: if (xmlParser.hasError()) { log(...); return <ошибка выполнения>; // завершаем выполнение текущей функции, поскольку продолжать не имеет смысла. } Таких проверок в теле функции может набежать довольно много. Собственно вопрос: есть ли возможность уменьшить код, направленный на проверку статуса выполнения, и сосредоточиться на непосредственно логике самой функции. Возможно, лучше будет использовать тогда исключения? Либо текущее решение вполне жизнеспособно?
Ответы
Ответ 1
Используйте исключения, они специально придуманы для этой цели. Либо, при возникновении ошибки переводите xmlParser в состояние, когда он ничего не делает, и приверяйте ошибку один раз после всех операций с ним, как это сделано в стандартных потоках ввода-вывода: void f(std::istream& s, int& a, int& b) { s >> a >> b; bool eof = !s; ... Текущий подход к проверке ошибок плох тем, что он никак не поощряет и не контролирует то, что все ошибки будут проверены. Очень легко не написать этот if (xmlParser.hasError()), или например по ошибке написать if (!xmlParser.hasError()). Если использовать возврат ошибок, то лучше возвращать ошибку из самой функции, которая выполняет действие приводящее к ошибке: void parse(error_code& ec) { if (xmlParser.parse(ec)) return; } либо можно использовать монады (в т.ч. optional), что является более современным подходом, но к сожалению пока мало библиотек реализующих типы Result, std::expected и т.п. Resultresult = xmlParser.parse(); if (!result) return result.error(); Ответ 2
Исключения. Если библиотека не умеет исключения, то пишем враппер для всех вызовов или что-то вроде: templatevoid check_error(T &clazz, void (T::*method)(Args...), Args... args) { (clazz.*method)(args...); if (clazz.hasError()) throw runtime_error("Some error"); } И звать как-то так: check_error(f, &Foo::foo1); check_error(f, &Foo::foo2, 31337); У себя в проекте использовал std::error_code, переменная для его сохранения была опциональной, по умолчанию - специальное значение, если оно, то бросается исключение, если отлично - то сохраняется код ошибки. При таком подходе лень стимулирует не проверять код, но если что навернётся, сразу выпадет исключение. Выглядит как-то так: void setCodec(const Codec &codec, bool resetDefaults, std::error_code &ec = throws()); ... void CodecContext::setCodec(const Codec &codec, bool resetDefaults, error_code &ec) { clear_if(ec); // очистка предыдущего кода ошибки if (!m_raw) { fflog(AV_LOG_WARNING, "Codec context does not allocated\n"); throws_if(ec, Errors::Unallocated); return; } ... } Но эта библиотека, сама по себе враппер. Детали throws(), clear_if(), throws_if() можно посмотреть тут и тут. Ответ 3
Данное решение вполне жизнеспособно, не раз встречалось мне в разного масштаба проектах. Ближайший пример библиотека работы с zip от Nokia - там десяток (если не больше) подобных вызовов на различных стадиях распаковки/чтения файлов/zip заголовков.Ответ 4
Вы могли бы разнести данную часть кода if (xmlParser.hasError()) { log(...); return <ошибка выполнения>; // завершаем выполнение текущей функции, поскольку продолжать не имеет смысла. } по отдельным фрагментам функций. Например, вызов log(...); можно было бы поместить в те функции, которые выполняют соответствующие операции. Эту запись в журнал событий можно сделать зависимой от наличия заданных в начале работы программы условий. В самом же классе сделать явный оператор приведения к типу bool, значение объекта которого было бы равно наличию или отсутствию ошибки. Например, explicit operator bool() const { return !hasError(); } Тогда ваш код мог бы выглядеть следующим образом while ( xmlParser ) { // выполнение обработки } И, соответственно, ответственность за запись ошибок в журнал лежал бы на вызываемых функциях, а не на клиентском коде. Также вы могли бы включить установку, которая сообщает, нужно ли выбрасывать исключение при возникновении ошибки. То есть я вам хочу предложить тот же самый подход, который реализован в стандартных потоках ввода-вывода.
Комментариев нет:
Отправить комментарий