На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
Страницы: (6) « Первая ... 2 3 [4] 5 6  все  ( Перейти к последнему сообщению )  
> "обработка исключений" vs "обработка кодов возврата"
    Ну а по теме ... запилил две синтетические реализации паттерна "Фасад":

    на кодах возврата

    ExpandedWrap disabled
      #include <iostream>
       
      class SubsystemA {
        public:
          int operationA() noexcept {
            // Имитация успешной операции
            return 0; // 0 - успех
          }
      };
       
      class SubsystemB noexcept {
        public:
          int operationB() {
            // Имитация ошибки
            return -1; // -1 - ошибка
          }
      };
       
      class Facade {
        private:
          SubsystemA subsystemA;
          SubsystemB subsystemB;
       
        public:
          int performOperations() noexcept {
            if (subsystemA.operationA() != 0) {
              return -1; // Ошибка в A
            }
            if (subsystemB.operationB() != 0) {
              return -2; // Ошибка в B
            }
            return 0; // Успех
          }
      };
       
      int main() {
        Facade facade;
        int result = facade.performOperations();
        if (result == 0) {
          std::cout << "The operation was successful." << std::endl;
        } else {
          std::cout << "An error occurred: " << result << std::endl;
        }
        return 0;
      }

    на обработках исключений

    ExpandedWrap disabled
      #include <iostream>
      #include <stdexcept>
       
      class SubsystemA {
        public:
          void operationA() {
            // Имитация успешной операции
            // Ничего не происходит
          }
      };
       
      class SubsystemB {
        public:
          void operationB() {
            // Имитация ошибки
            throw std::runtime_error("Error in SubsystemB");
          }
      };
       
      class Facade {
        private:
          SubsystemA subsystemA;
          SubsystemB subsystemB;
       
        public:
          void performOperations() {
            subsystemA.operationA(); // Успех
            subsystemB.operationB(); // Может выбросить исключение
          }
      };
       
      int main() {
        Facade facade;
        try {
          facade.performOperations();
          std::cout << "The operation was successful." << std::endl;
        } catch (const std::runtime_error& e) {
          std::cout << "An error occurred: " << e.what() << std::endl;
        }
        return 0;
      }

    Благодаря тому, что оба варианта примитивно-простые, особого преимущества второго варианта перед первым я не наблюдаю. По компактности и читаемости оба варианта примерно равны.

    Вопрос знатокам ;)

    Какие можно внести правки для усложнения, чтобы показать, что вариант с обработкой исключений стал более красивым (компактным, читаемым, эффективным) по сравнению с вариантом на кодах возврата?
    Да и, собственно, просьба - внесите и покажите!
      Цитата Majestio @
      Какие можно внести правки для усложнения, чтобы показать, что вариант с обработкой исключений стал более красивым (компактным, читаемым, эффективным) по сравнению с вариантом на кодах возврата?
      Да и, собственно, просьба - внесите и покажите!

      1) Увеличение глубины вложенности классов. Как в виде наследования, так и в виде просто дополнительных объектов классов. (Это к удобству использования второго варианта)
      2) Варианты исправления ошибки и перезапуска операции (допустим ошибка сигнализирует о timeout/retry). (Это к применимости первого варианта)
      3) Как в первом случае найти точное место возникновения ошибки (не к минусам, а к необходимости усложнения обработки: не равноценные примеры)?
      Сообщение отредактировано: macomics -
        Цитата macomics @
        3) Как в первом случае найти точное место возникновения ошибки (не к минусам, а к необходимости усложнения обработки: не равноценные примеры)?

        Ну тут просто. Создается enum со всеми видами ошибок. Другое дело, если один и тот же вид ошибок может быть в разных местах. Но и это решается - вместо числового кода возврата возвращается структура, включающая сам код, и макро __FILE__, __LINE__. Что-то типа:

        ExpandedWrap disabled
          #include <iostream>
          #include <optional>
          #include <string>
           
          struct ErrorInfo {
            int errorCode;
            std::string fileName;
            int lineNumber;
          };
           
          std::optional<ErrorInfo> performOperation(bool shouldFail) {
            if (shouldFail) {
              return ErrorInfo{404, __FILE__, __LINE__}; // Код ошибки, имя файла и номер строки
            }
            return std::nullopt; // Успешное выполнение, ошибки нет
          }
           
          int main() {
            auto result = performOperation(true); // Изменяем на false для успешного выполнения
            if (result) {
              std::cout << "Error: Code " << result->errorCode
                        << ", File: " << result->fileName
                        << ", Line: " << result->lineNumber << std::endl;
            } else {
              std::cout << "The operation was successful." << std::endl;
            }
            return 0;
          }

        Другое дело, что без стека вызовов, тут часто будет недостаточно инфы для однозначного определения причины. Но это уже другая тема.

        В принципе это же касается и варианта с использованием исключений, а-ля:

        ExpandedWrap disabled
          #include <iostream>
          #include <optional>
          #include <string>
          #include <stdexcept>
           
          struct ErrorInfo {
            int code;
            std::string file;
            int line;
          };
           
          class CustomException : public std::runtime_error {
            public:
              CustomException(const std::string& message, const std::string& file, int line)
                : std::runtime_error(message), errorInfo{1, file, line} {}
           
              const ErrorInfo& getErrorInfo() const {
                return errorInfo;
              }
           
            private:
              ErrorInfo errorInfo;
          };
           
          std::optional<ErrorInfo> riskyOperation() {
            // Симулируем ошибку и выбрасываем исключение с информацией о месте выброса
            throw CustomException("Something went wrong!", __FILE__, __LINE__);
          }
           
          int main() {
            try {
              riskyOperation();
            } catch (const CustomException& e) {
              // Обработка ошибки
              std::optional<ErrorInfo> error = e.getErrorInfo();
              if (error) {
                std::cout << "Error Code: " << error->code;
                std::cout << ", File: " << error->file;
                std::cout << ", Line: " << error->line;
                std::cout << ", Message: " << e.what() << std::endl;
              }
            }
            return 0;
          }
          Цитата Majestio @
          Какие можно внести правки для усложнения, чтобы показать, что вариант с обработкой исключений стал более красивым (компактным, читаемым, эффективным) по сравнению с вариантом на кодах возврата?
          Да и, собственно, просьба - внесите и покажите!
          А оно надо? Ты ж не забывай о главном правиле дизайна библиотек: если нет никаких причин предпочитать одно решение остальным, его следует отдать на откуп пользователям библиотеки.
            Цитата Qraizer @
            А оно надо?

            Надо! Мы тут синтетические примеры решаем. И пытаемся (одно из) определить почему Qt почти отказались от обработки исключений в угоду другим способам обработки ошибок.

            Цитата Qraizer @
            А оно надо?

            И второе, что О-Ч-Е-Н-Ь надо ... таки решить или обозначить тему топика. Где когда и что лучше?!! Дружище, давай "разговаривать и аргументировать" кодом. Пустые слова тут мало-мало ничего не решают по вопросу.

            Цитата Qraizer @
            на откуп пользователям библиотеки

            А нам, пользователям библиотек, не безразлично! Особенно простой интерес - откуда у вас ноги растут, и почему оттуда? :-)
              Цитата Majestio @
              Ну тут просто. Создается enum со всеми видами ошибок.

              Кажется, что ты опять игнорируешь std::expected :) Он таки получше std::optional будет для подобного.

              Добавлено
              Цитата Majestio @
              И пытаемся (одно из) определить почему Qt почти отказались от обработки исключений в угоду другим способам обработки ошибок.

              Этому решению очень много лет. Тогда исключения были достаточно медленными и мне кажется плохо ложились на дизайн qt и его кодогенрацию и объектную модель. Т.е. считай исторические причины.
              ИМХО.

              Добавлено
              Цитата Qraizer @
              Ты ж не забывай о главном правиле дизайна библиотек: если нет никаких причин предпочитать одно решение остальным, его следует отдать на откуп пользователям библиотеки.

              Ну да, можно пойти по пути asio с его функциями (для синхронного API), которые принимают опционально ссылку на код ошибки, а если не передаешь, то будет исключение.
                Цитата D_KEY @
                Кажется, что ты опять игнорируешь std::expected Он таки получше std::optional будет для подобного.

                Ты прав. Я пока не дошёл до чтения фич std::expected. Но я исправлюсь, скорее всего завтра.

                Цитата D_KEY @
                Этому решению очень много лет. Тогда исключения были достаточно медленными и мне кажется плохо ложились на дизайн qt и его кодогенрацию и объектную модель. Т.е. считай исторические причины.
                ИМХО.

                Похоже на то. Если я не ошибаюсь? Qt свой старт начал даже задолго до C++11. Т.е. еще на "ламповой" версии С++.
                  Цитата D_KEY @
                  std::expected

                  Мой первый блин комом - статья на Хабре. Читал сперва вдумчиво. Потом, в какой-то момент, мысль "поплыла", потому как параллельно возникла вторая - "начали городить огород". Дальше читал еще чуть бегло. Ну потом как обычно, какие-то лешие вышли из леса, русалки на ветвях уселись... Шютка :lol: Возникло уточнение второй мысли "начали городить огород могильной оградкой". В результате, как говорится, в сухом остатке - я понял, что чел начал типизировать ошибки просто по беспределу! Ну и что с того, что С++ поддерживает шаблонизацию? Это не значит, что нужно плодить сущности-ошибки просто от балды! >:( И законный вопрос - на-хре-на?!

                  Любая проектируемая система обладает своим набором уникальных состояний. Соответственно обладает своим набором и типовых ошибок. Да, часть множества ошибок и состояний может пересекаться от системе-к-системе. По типу "файл не найден", "ошибка записи" & etc. Но это не означает их идентичность! Везде же есть свой контекст. Что чел хотел сделать в статье, а тем более где-то там в глубинах статьи он еще присобачил динамический полиморфизм! :wacko: Карл, динамический полиморфизм, для каста типов ошибок! :wacko:

                  Возможно мой "старт" стартанул некузяво. Но такая кухня мне явно не надо! Мой принцип давно и надолго - Бритва Оккама. А тут ни бритвы не видно, и Оккама давно не сидел.

                  ... попробую поискать что-то более вменяемое :-?
                    Ну, человек поставил себе задачу, человек её решил и поделился решением. Решение наиуниверсальнейшее, так что почему бы и нет. Я бы вот не стал настолько заморачиваться, но положа руку на сердце, ведь именно за то мы и любим Плюсы, что можем наваять либу так, что пользоваться ею можно будет так же, как будто это языковое средство.
                    Скажем так: если тебе интересно, как оно устроено, то любая либа внутри выглядит страшно, но если тебе интересно лишь, как оно работает, то юзаешь и не паришься.
                      А тебе не кажется, что это немотивированное усложнение гораздо простого вопроса? Ну если честно, Только если честно?

                      Добавлено
                      Цитата Qraizer @
                      Решение наиуниверсальнейшее, так что почему бы и нет.

                      Стоп! Ты тоже считаешь - что ошибки нужно типизировать? И что это реально даст профит?!!! :blink:
                        Majestio, ну первый же пример из статьи понятен?

                        ExpandedWrap disabled
                          enum class MathError : unsigned char
                          {
                              ZeroDivision,
                              NegativeNotAllowed
                          };
                           
                          std::expected<int, MathError> Bar(int a, int b)
                          {
                            if (b == 0)
                              return std::unexpected(MathError::ZeroDivision);
                            if (a < 0 || b < 0)
                              return std::unexpected(MathError::NegativeNotAllowed);
                            
                            return a / b;
                          }


                        Существенная разница с optional тут в том, что ты не забываешь вид ошибки. Это близко к Rust'овскому Result<T, E>.

                        А дальше он в статье о чем-то другом уже начал :)
                          Цитата D_KEY @
                          Существенная разница с optional тут в том, что ты не забываешь вид ошибки.

                          Я вид/тип ошибки могу успешно не забывать и кодом ошибки? И это считаю (пока) самым простым и изящным решением. Убеди меня что мне ну вот очень-очень нужно ошибки "типизировать" :lol: Ну убеди меня! :lol:
                            Цитата Majestio @
                            Я вид/тип ошибки могу успешно не забывать и кодом ошибки?

                            В optional не сможешь :)

                            Цитата Majestio @
                            Убеди меня что мне ну вот очень-очень нужно ошибки "типизировать" :lol: Ну убеди меня! :lol:

                            Ну можешь использовать std::expected<T, int> и использовать свои int'овые коды на здоровье :)

                            Суть-то в том, что "обычный" код ошибки ты можешь проигнорировать случайно и т.п., а тут придется обработать. Собственно, мотивация Result<T, E> в Rust такая же.
                              Цитата D_KEY @
                              что ты не забываешь вид ошибки

                              Искусственное усложнение, детектед! Есть набор ошибок - этого для обработки хватает. А вот для анализа ... но это уже потом ... собирай их в группы как хочется. В свои эти "виды". Но зачем эту шляпу внедрять в код? Хватит номеров ошибок, не?

                              Добавлено
                              Цитата D_KEY @
                              Суть-то в том, что "обычный" код ошибки ты можешь проигнорировать случайно и т.п., а тут придется обработать. Собственно, мотивация Result<T, E> в Rust такая же.

                              Т.е. я объявляю секцию catch или if-фю код возврата и ... сразу "случайно" забываю нахрена я это делаю?!! :wacko: Мама, роди меня обратно.
                                Цитата Majestio @
                                или if-фю код возврата

                                или не объявляешь :) А компилятор молчит, к сожалению. Плюс даже если объявил, сможешь случайно обратиться к результату. А это исключено в std::expected или std::optional.

                                Добавлено
                                И я тебя не уговариваю, используй что хочешь. Страдать тебе потом самому :D
                                1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                                0 пользователей:
                                Страницы: (6) « Первая ... 2 3 [4] 5 6  все


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0527 ]   [ 15 queries used ]   [ Generated: 16.06.25, 20:16 GMT ]