Концепция баррикады

  • Tutorial

Каждый программист когда-то давно, в начале своего Пути писал что-то типа вот этого:
double div( double a, double b )
{
	return a / b;
}


И был в полной уверенности, что эта функция делает именно то, что нужно — делит а на b. Но рано или поздно рядом оказывался друг или преподаватель, который объяснял, что эта функция делает еще одну важную в жизни любой программы вещь: валит её с исключением деления на ноль, если b равно нулю. После этого к будущему программисту приходило понимание необходимости проверки входных данных. Кто-то на этом решал вопрос исчерпанным, а кто-то приходил к мысли, что это только половина Дао.


В самом деле, ставить проверки на каждом шагу — практика распространённая. Этому учат в ВУЗе, до этого доходишь и своим умом. Но со временем приходит мысль, что что-то не так:
  • Проверка разных вещей занимает в Вашем коде кучу места. За проверками этот самый код вообще перестаёт быть виден.
  • Профайлер показывает, что существенная часть времени уходит именно на проверки. Программа тормозит. Пользователи жалуются.
  • Одни и те же проверки повторяются по множеству раз. Если в каком-то классе есть функции, вызывающие друг друга и работающие с одними и теми же данными — проверки написаны в каждой из них и часть из них срабатывают «вхолостую».

Концепция баррикады предлагает выход из этого положения.

О баррикаде писали в своё время Макконнелл, Фаулер и может быть кто-то еще. Суть подхода заключается в проведении аналогии между работой программы и некоторым вооруженным конфликтом (войной). На войне всегда есть Наши и Не Наши. Наши — рядом в окопах, по эту сторону баррикады. Им можно доверять, с ними можно переломить хлеб и не бояться нагнуться за мылом в бане. Не Наши — это те, кто по ту сторону баррикады. Им доверять нельзя. Среди них могут быть как враги, так и мирные жители. Но доверять всё-равно нельзя. Каждого нужно проверить и выяснить, чем дышит.

Концепция баррикады предлагает провести черту между сущностями, отделив их друг от друга этаким барьером. Под «сущностями» можно подразумевать разные вещи. В зависимости от масштабов и архитектуры Вашей системы, баррикада может проходить между методами класса, между классами, между отдельными модулями (библиотеками) и т.д.

Часть сущностей (назовем их «забаррикадные») — живут в активной и агрессивной среде, взаимодействуют с внешним миром, вызываются сторонними классами и им может быть передана любая чушь. Начинаться они должны со всех необходимых проверок и никому не доверять. Невалидность входных данных должна рассматриваться как штатный режим работы. Он не должен валить программу. В зависимости от принятой в проекте идеологии, при приходе невалидных данных можно написать сообщение в лог, выдать сообщение пользователю или вообще молча исправить данные на валидные и продолжить работу.

Следующая группа объектов — это сама «баррикада». Роль баррикады — гарантировать всем, кто находится внутри безопасность. Ничто не должно проникнуть внутрь баррикады непроверенным, несконвертированным во внутренние форматы, невалидным и т.д. Сюда входят разнообразные конверторы, валидаторы, вропперы и прочие аналогичные вещи.

И последняя часть концепции — это «внутрибаррикадные» жители. Они и есть тем, ради чего вся эта затея начиналась. Метод, находящийся внутри баррикады может не проверять входные параметры или текущее состояние объекта, к которому он принадлежит! За него это уже гарантированно сделала баррикада. Во всех внутрибаррикадных методах обязаны стоять ASSERTы, поскольку невалидность чего-нибудь является уже не проблемой взаимодействия с внешней средой, а конкретной ошибкой в баррикаде. Её в дебаг-версии нужно обнаружить и исправить. А в релизе ASSERTы будут выброшены компилятором и не будут никому мешать.

Конечно, в баррикадах есть недостатки — нужно чётко разграничивать вещи относительно баррикады по уровням доверия к ним, проверки внутри баррикады все-равно иногда нужны (ASSERTы), хоть их и значительно меньше. Всё это решает где-то полторы-две из трех описанных в начале статьи проблем, так что иногда может пригодиться.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 98

    +21
    Так и пришли к концепции Хаскеля.
      +4
      Объясните подробней, как это относится к концепции Хаскеля? Интересно.
        +5
        Здесь в статье речь шла о избыточности проверок: классы/компоненты/модули обычно устраиваются так, что они могут работать полностью независимо от остальных, — бери и включай в другой проект. Из-за своей универсальности им приходится проводить самостоятельные проверки входных данных: в одном проекте их посадят за ту самую баррикаду и будут кормить только отборными продуктами, а в другом — отправят на передовую, и мало ли, что их ожидает. Авторы же концепции предлагают, ориентируясь на конкретный проект, убирать лишние проверки.

        Хаскель вспомнился потому, что там строгое разделение чистой функциональной части от операций с побочными действиями, которые являются потенциально опасными. Пока мы в функциональной части, вокруг нас кристальной чистоты мир, радуга и прочие соответствующие атрибуты. А при выходе наружу — IO и др. — нас уже могут покусать злые волки.
        Вообще, может быть, сравнение не совсем корректное, но что-то общее есть.
      0
      Развейте мысль, пожалуйста?
        +3
        Легко.
        Начнём со связи с внешнем миром. В хаскеле для этого используется полиморфный тип IO, который прозрачно намекает что все обращения к такому типу могут сопровождаться побочными эффектами.

        Соответсвенно любое общение с внешним миром (считывание файла, получение системной даты, отправка сообщений по HTTP, получение кода нажатой клавиши) возвращают тип, завёрнутый в IO, и выйти из этого контейнера штатными средствами нельзя.

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

        Дальше идёт следующий встроенный барьерный механизм: например класс Functor. Он позволяет совершать операции над полиморфным типом: то есть для типа f a (а — параметр) и функции (a -> b)
        получать тип f b:
        fmap :: Functor f => (a -> b) -> f a -> f b

        Это позволяет, в частности обрабатывать чистой логикой небезопасные значения. Примерно так

        let input = readFile "input.file"
        let result = fmap clearLogic input
        output result


        Вообще система типов хаскеля достаточно сложна и позволяет для внутренней части создать типы не допускающий неверных подходов.

        Мне кажется, что именно поэтому автор сказал про хаскель. Баррикады там, пожалуй, единственный возможный способ сделать что-то действительно рабочее и не слишком сложное для понимания.
          +1
          Чёрт, я написал столько слов про хаскель и не сказал магического слова «монада».
          Восполню недостаток:

          любая монада на самом деле является аппликативным функтором. Только в силу исторических причин
          объявление монады не означает объявления функтора.

          Соответственно, монада IO, про которую написано на каждом столбе, является функтором и позволяется безопасно перерабатывать значения внутри себя не опасаясь любых побочных эффектов. Это куда лучше, там традиционный монадный синтаксис:
          >>= :: Monad m => (a -> m b) -> m a -> m b

          Поскольку такая запись предполагает, что каждый участок вычислений [a -> m b] чистые данные превращает в какую-то гадость (то есть порождает внешние эффекты, ведь m = IO)
      0
      Хорошо продуманная архитектура проекта не приведет к лишним проверкам. Например при написании игрового сервера следует разделять игровые модули и ядро, отвечающее за работу команд, синхронизацию и прочее. Ядро должно отсеивать и учитывать основной поток возможных багов, прокидывая в случае не возможности адекватной реакции необходимые эксепшены в логи. Игровые же модули в основном должны контролировать баги которые возможно может вызывать читер. Ну тут лишних перепроверок быть и не может особо.
        +28
        ой, да все знают «как надо», но почему-то на выходе получается другое. и сразу же оп и все знают почему так вышло
          0
          Нет ну понятное дело, что сразу написать красиво не получится :-) Но все же 70% проблем решит, да и отладка будет намного быстрей и эффективней.

          Количество багов на выходе, имхо, зависит от следующих факторов:
          1) Профессионализм и грамотность кодера;
          2) Правильная и гибкая архитектура проекта;
          3) Четкое не изменяемое глобально ТЗ;
          4) Тестирование (нагрузочное и живое).
          Конечно все это в идеале, но жить помогает заметно :-)
        +68
        Примеры кода где? Системы, которые построены по такому принципу где? Требую кода и зрелищ!
          +2
          Любая программа на Haskell, взаимодействующая с внешним миром через монады.
            0
            Макконнелл, «Совершенный код», страницы 200-220 печатного издания. Копипастить сюда 20 килобайт текста не хочу.
              –2
              Обычно достаточно сделать выжимку из самых ярких примеров данного подхода и опубликовать ее тут. Никому не интересно читать 20 страниц ради понимания того, что же это за зверь такой новомодный, баррикада…
            –4
            «защита от дурака»
              0
              Вот ссылок на соответствующие изречения Макконнелла и Фаулера явно не хватает.
                +2
                Дык раз уж знаете, что такие есть — так и сослались бы…
                  +2
                  В посте написано: "О баррикаде писали в своё время Макконнелл, Фаулер и может быть кто-то еще."
                  Вот я и хочу, чтобы ссылки были не голословными, а реальными.
                  0
                  Отписал выше.
                  +7
                  Создать защиту от дурака практически невозможно, ведь дураки так гениальны :)
                    +2
                    Я бы сказал — совершенно непредсказуемые, на счёт гениальности — не факт.
                      +2
                      «Никогда не стоит недооценивать непредсказуемость тупизны» (Большой Куш в переводе Пучкова)
                        +6
                        Вспомнилось изречение Бисмарка

                        «Никогда не замышляйте ничего против России! На любую вашу хитрость Россия ответит своей непредсказуемой глупостью»
                    +1
                    В своё время тоже задавался таким вопросом. Правда в разрезе: должны ли protected и private методы проверять входные аргументы как это делают public методы или можно ограничиться Assert'ом. На практике пришлось задуматься о рефакторинге — теперь это непросто замена модификатора доступа, а переписывание всех Assert'ов на нормальные проверки. В итоге победила лень — практически не использую Assert'ы)
                      0
                      у меня местами ассерты по объему сравнимы с кодом
                      0
                      Подход интересный ) Кстати, в web-приложениях как правило есть очевидное место для построения баррикады — прием данных из запросов — и там она обычно и формируется)
                        0
                        Плюс не очевидное для проектировщика (или программиста) поведение пользователя: нажал кнопку Назад или обновил страницу «не вовремя» или внезапно несколько раз и прочее.
                          0
                          Ну да, т.е. проверка состояния тоже важна.
                          0
                          Я бы не сказал, что настолько оно очевидное. В MVC приложениях куда логичнее, по-моему, проверять данные при помещении в модель.
                            0
                            По сути это примерно тоже самое, что я и имел ввиду.
                              0
                              Или я вас неверно понял, или сам не точно выразился :) Я имел в виду, что сами методы проверки находятся в классах модели (или тесно с ними связаны). Что контроллер анализом запроса на корректность не занимается, а просто преобразует HTTP-запрос в понятный для модели вид, в параметры методов и передаёт ей, а уж модель может сообщить контроллеру, что данные некорректны. Не
                              if ($_GET['param'] > 0) {
                                $model->setParam($_GET['param']);
                                render('Success');
                              }
                              else {
                                render('Error', 'Param is not positive');
                              }
                              
                              , а, например,
                              if ($model->setParam($_GET['param'])) {
                                render('Success');
                              }
                              else {
                                render('Error', $model->getErrorMessage());
                              }

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

                              P.S. Кстати, «баррикада» — ещё один довод в пользу использования сеттеров/геттеров в модели, а не public свойств.
                            –2
                            А если у вас три классических слоя и каждый разрабатывает независимый человек,
                            или еще интереснее, во втором слое 10 сервисов, каждый из которых разрабатывает независимый человек (который еще и работает на субподряде и контракт у него заканчивается) где тут провести баррикадную черту?

                            Если не проверять параметры траблшутинг превратится в настоящий ночной кошмар.

                            «Профайлер показывает, что существенная часть времени уходит именно на проверки. Программа тормозит. Пользователи жалуются.»

                            Что может быть проще и быстрее проверки на null, или на >0. Если от такого ваша «программа» тормозит у вас серьёзные проблемы ;)
                              +5
                              >Что может быть проще и быстрее проверки на null, или на >0. Если от такого ваша «программа» тормозит у вас серьёзные проблемы ;)

                              а две проверки?
                              а если функция вызывается в цикле?
                              а в цикле по битмап-буферу?
                              –17
                              Можно делить float на float, любой на любой. В случае N/0.0 получается ± INF, если N != 0.
                              Сколько можно эту чушь про исключения писать.
                                +10
                                а ничего, что кроме JS есть другие языки?

                                In [2]: float(0)/float(0)
                                ---------------------------------------------------------------------------
                                ZeroDivisionError Traceback (most recent call last)

                                /home/muromec/ in ()

                                ZeroDivisionError: float division
                                  0
                                  Я на C++ проверял.
                                  Впрочем я немного погорячился. Деление на ноль выставляет флаги, которые можно проверить и бросить исключение. Возможно некоторые языки так делают.
                                    0
                                    Что за язык кстати?
                                      0
                                      Python.
                                    +7
                                    Статья не про деление на ноль, а про валидацию данных.
                                      0
                                      Да, оффтоп конечно. Но после таких примеров люди вставляют проверки там, где они не нужны.
                                        0
                                        Что значит не нужны? В подавляющем большинстве случаев деление на нуль — не валидная операция.
                                          0
                                          если формально подходить, то 9mm прав, функция делает ровно то, что от нее ожидают — делит. :) вот только пример был чисто для абстракции, а в этом оффтопе комменты уже до 5 уровня дошли.
                                            0
                                            А по-моему, пример как раз очень удачный. Ведь если функция ничего кроме деления не делает, то почему бы не использовать стандартный оператор деления?
                                            А вот если функция понадобилась, значит, это не просто деление, а и дополнительные проверки, которые, если их делать каждый раз при выполнении операции деления в коде, — получится куча дублированного кода.
                                            Например, при делении на нуль хочется всегда выкидывать какой-то свой exception, потому что в нашем приложении такая операция не валидна.
                                            0
                                            В подавляющем большинстве случаев как раз валидная.

                                            Невалидной она становится если важен порядок бесконечности, что редкость.
                                            Собственно «оперирование двоичными числами почти так-же как простыми вещественными» и было одний из целей стандарта IEEE 754.

                                      • UFO just landed and posted this here
                                        +2
                                        Сразу вспомнилась цитата из книги Programming Perl:

                                        The trend over the last 25 years or so has been to design computer languages that enforce a state of paranoia. You're expected to program every module as if it were in a state of siege.


                                        Я уж надеялся, что в конце статьи будет какой-то полезный совет, как избежать превращения кода в баррикаду. Жаль.
                                          +4
                                          Такое чувство, что мы с вами читали разные статьи. Вся статья один большой совет: чтобы избежать превращения всего кода в баррикаду разделите код на обособленную баррикаду и код, которые работает с гарантированно безопасными данными.
                                            +1
                                            … гарантированно безопасными данными, которые все равно проверяются ASSERT'ами потому, что ошибка может быть в самой баррикаде. Я так и не понял, чем это отличается от повсеместного баррикадирования. Если вы об экономии нескольких процессорных инструкций с помощью использования препроцессора, то это применимо только к семейству C, в большинстве же остальных языков разницы не будет никакой.
                                              0
                                              отличие в том, что все ошибки и некорректности в потоках данных внутри баррикады должны быть выявлены на этапах тестирования, после этого все ошибки это исключительные ситуации, требующие исправления
                                                –1
                                                Тогда уж лучше сразу воспользоваться модульным тестированием:
                                                1. Тестовый код будет заведомо отделен от логики приложения в любом языке
                                                2. Проверка результатов будет на выходе тестируемой функции, а не внутри вызывающей ее — если функция вызывается в 10 местах, не нужно писать 10 ассертов
                                                3. Одинаковый формат тестирования что для «внешних», что для «внутренних» функций
                                                +1
                                                Вы правы, «Во всех внутрибаррикадных методах обязаны стоять ASSERTы» — очевидно эта фраза не подходит для каждого языка и не совсем корректна, правильнее будет сказать, что единственная проверка корректности данных в «безопасной» зоне должна осуществляются в процессе отладки «баррикады». В глобальном смысле на саму концепцию это не влияет на мой взгляд.
                                            0
                                            поправьте пожалуйста если не прав,
                                            MVC является реализацией данной концепции, если абстрагироваться от назначения компонентов?
                                              0
                                              Нет, дело не в этом.
                                              Вы можете проверять во View, пришли ли правильные данные, а можете построить баррикаду в контроллере или модели и во View не делать никаких проверок.
                                                0
                                                Client-side и server-side валидации как раз вписываются в эту концепцию — во View по-быстрому проверяем данные на JavaScript'е, в контроллере уже по-взрослому, ну а всякие сервисы/бизнес-логика/репозитории — это уже на «нашей» стороне баррикады.
                                                +3
                                                Вспомнил цитату из одного руководства:
                                                Ограничивайте, отклоняйте и очищайте пользовательский ввод. Иначе говоря, предполагайте, что весь пользовательский ввод является злонамеренным. Проводите проверку длины, диапазона, формата и типа вводимых данных.
                                                Всегда проверяйте ввод на наличие неверных или злонамеренных данных на стороне сервера,
                                                поскольку проверка на стороне клиента может быть без труда подделана.

                                                По-моему нужно просто определить границу доверия, где разделяются внутренние данные и внешние данные. Основная баррикада должна стоять именно на этом уровне. А делать проверку внутренних данных в релизной версии нет необходимости.
                                                  +2
                                                  Невалидность входных данных должна рассматриваться как штатный режим работы. Он не должен валить программу. В зависимости от принятой в проекте идеологии, при приходе невалидных данных можно написать сообщение в лог, выдать сообщение пользователю или вообще молча исправить данные на валидные и продолжить работу.

                                                  Здесь хотелось бы немного дополнить. Не для опровержения ваших слов, а для более полного раскрытия темы.

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

                                                  Всегда уведомляйте пользователя, что что-то пошло не так, давая нужные ему подробности и советы по исправлению входных данных.
                                                    +1
                                                    В свою очередь хочу добавить: я по возможности стараюсь делать так — сам барьер работает на стороне сервера, но он особо не заморачивается с объяснениями в чем именно проблема, его задача — проверить данные на 100% и при этом потратить минимум ресурсов (соответственно, за этим барьером проверки уже практически не производятся). А вот на клиентской стороне делаются дублирующие проверки, но более конкретные, которые подскажут что именно не так: то ли длина строки, то ли неверный первый символ, то ли недопустимый символ во всей строке в принципе и т.п.

                                                    Таким образом мы снижаем нагрузку на сервер/барьер, сохраняем безопасность, но при этом повышаем информирование пользователя до максимума.

                                                    На практике конечно есть свои сложности, особенно в отдельных ситуацих (в идеале нужно обеспечить полное соответствие проверок клиента проверкам сервера/барьера, иначе нужно описывать поведение клиента для ошибки, которую клиент пропустил, но поймал барьер, плюс изменение в спецификации требует изменений в двух местах, плюс не все проверки можно сделать на стороне клиента и т.п.), но в большинстве случаев все отлично работает.
                                                    0
                                                    try… catch
                                                      0
                                                      Ну если без фанатизма.
                                                      Потому что конструкция on error resume next высмеивает весь этот пост!
                                                        0
                                                        Я проводя собеседования на вакансию программера С++ сильно удивился, что про исключения знает менее чем каждый второй соискатель.
                                                          0
                                                          эмм. А как эти соискатели говорят, как обычно они управляют обработкой ошибок?
                                                            0
                                                            вопрос звучал так: как без анализа содержимого делителя обработать ошибку деления на ноль:

                                                            предполагается ответ с try...catch

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

                                                              Множество раз замечал, как собеседуемые не отвечают на простейшие вопросы только потому, что считают: «здесь серьёзные люди и серьёзное собеседование — значит вопрос с подвохом. Ну не могут же они такую элементарную вещь спрашивать»…

                                                              Но это уже тема отдельной статьи…
                                                                +5
                                                                вопрос звучал так: как без анализа содержимого делителя обработать ошибку деления на ноль:

                                                                предполагается ответ с try...catch


                                                                Неправильный ответ. Правильный: средствами C++ это сделать невозможно.
                                                                ISO/IEC 14882, глава 5 Expressions [expr], параграф 4 говорит нам следующее:
                                                                If during the evaluation of an expression, the result is not mathematically defined or not in the range of
                                                                representable values for its type, the behavior is undefined. [ Note: most existing implementations of C++
                                                                ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all
                                                                floating point exceptions vary among machines, and is usually adjustable by a library function. —end note ]


                                                                Это UB.

                                                                В разных РЕАЛИЗАЦИЯХ C++ это можно сделать при помощи implementation-specific функционала. Более того, такие ошибки практически никогда не транслируются в C++ исключения автоматом.

                                                                В Windows будет сгенерировано SEH исключение, которое можно словить через __try/__except либо с помощью _set_se_translator сконвертировать в C++ исключение. Кроме того, если включить обработку асинхронных исключений catch-all блок будет ловить и SEH исключения (примечательно, что именно divide-by-zero использовано в качестве примера в статье про разные типы обработки исключений).

                                                                В POSIX системах будет сгенерирован сигнал (SIGFPE), который можно обработать предварительной установкой хендлера.

                                                                В общем, задавать на собеседовании вопросы про UB можно только если принимаете на какую нибудь senior/principal должность. Новички этого знать не обязаны — им бы с defined поведением разобраться.
                                                                  +1
                                                                  спасибо за развернутый комментарий. К сожалению НИ ОДИН из соискателей не упомянул, что с исключениями не все так гладко. Либо пишут try… catch, либо пишут какую-то ерунду.

                                                                  Вообще, открою секрет, тест изначально создавался для соискателей на Delphi, там с исключениями все на порядок прозрачнее.
                                                                  +1
                                                                  Вопрос «как без анализа содержимого делителя обработать ошибку деления на ноль:
                                                                  предполагается ответ с try...catch» совершенно не подразумевает, что «про исключения знает менее чем каждый второй соискатель.»

                                                                  Я думаю, что если бы вы захотели поговорить конкретно об исключениях, их преимуществах и недостатках, то процент ответивших подскочил бы, скажем, до 90%.

                                                                  A так вы задали вопрос, на который можно ответить, например так:
                                                                  double foo (int a, int b)
                                                                  {
                                                                  if (a — b == a)
                                                                  {
                                                                  ProcessError(«ololo»);
                                                                  return 0;
                                                                  }
                                                                  return a/b;
                                                                  }
                                                                  и это тоже будет являться приемлимым решением, но неверным с вашей точки зрения.

                                                                  Вообще, реально напрягают интервьеверы, которые задают вопрос не,- «Как ты использовал исключения в своем последнем проекте», а в стиле,- «Ну-ка, попробуй прочитать мои мысли, сынок!»
                                                                    +1
                                                                    Какие бы преобразования Вы ни делали над b — это все равно остается «анализом делителя».
                                                                      –1
                                                                      обработка исключения при делении на ноль описана почти в каждой книжке по программированию как академический пример
                                                                        0
                                                                        Обработка исключения при делении на ноль — это ОТВРАТИТЕЛЬНО. Это как в Java unchecked exception — ошибка программирования. Это все равно что контроль выхода за границы массива перехватом ArrayIndexOutOfBoundsException.
                                                                          0
                                                                          А если у вас десяток параметров, которые на ноль надо проверить? тоже каждый анализировать? бред
                                                                            0
                                                                            А если у вас десяток коллекций, по которым надо итерировать, вы тоже не пишете цикл от 0 до collection.size(), а ждете исключения?
                                                                              0
                                                                              не понимаю, что общего у деления на ноль и OutOfBounds?

                                                                              PS: кстати цикл до .size()-1
                                                                                0
                                                                                Ну в Java например, традиционно считается, что runtime (unchecked) exceptions типа обращения по нулевой ссылке, арифметические ошибки типа деления на ноль, выход за границы массива перехватывать не надо — это дурной тон использовать исключения для управления логикой программы. Эти исключения — ошибки программирования. Если вы обращаетесь файла, а его там нет или у вас нет прав — тут можно выбросить исключение, т.к. вы не можете никогда заранее знать, есть файл или нет, и это нельзя заранее проверить.

                                                                                Проверить, что переменная равна null / 0, что счетчик не выходит за границу массива, и прочее можно легко предварительно — и это более логично.

                                                                                Я уж даже не говорю о стоимости выбрасывания исключения (это для тех, кто считает что проверки у них занимают много времени).
                                                                                  0
                                                                                  >> Если вы обращаетесь файла, а его там нет или у вас нет прав
                                                                                  Наличие файла тоже можно безопасно проверить. Да и права при желании. Вобщем-то все практически можно проверить.
                                                                                  Зачем тогда исключения? WinAPI вон практически без исключений написан. а все потому что не умели им пользоваться некоторые товарищи.
                                                                                  Проверять на каждом шаге ResultCode не всегда хорошая идея, если при неудаче все равно вываливаемся в одно и то же место с выдачей ошибки юзеру.
                                                                      +2
                                                                      > предполагается ответ с try...catch

                                                                      Это неправильный ответ. Для usage-ошибок не надо использовать try...catch.

                                                                      > более половины пишут что всегда анализируют делитель

                                                                      И правильно делают. Это не от незнания про исключения, а от умения ими не пользоваться в неподходящих местах.

                                                                      > Я проводя собеседования на вакансию программера С++ сильно удивился, что про исключения знает менее чем каждый второй соискатель.

                                                                      Вы и сами бы не прошли собеседования на джуниора.

                                                                      > предполагается ответ с try...catch

                                                                      Правильный ответ (с учётом рассуждений про баррикады) может быть примерно таким (на C#).

                                                                      Вызываемый код:
                                                                      private double DivUnsafe(double a, double b)
                                                                      {
                                                                      Debug.Assert(!/*корректное сравнение b с 0.0*/);
                                                                      return a / b;
                                                                      }

                                                                      public double Div(double a, double b)
                                                                      {
                                                                      // Не допускается выбрасывание из библиотеки ZeroDivisionException,
                                                                      // NullReferenceException и тому подобных исключений.
                                                                      if (/*корректное сравнение b с 0.0*/)
                                                                      {
                                                                      throw new ArgumentException(...);
                                                                      }
                                                                      return DivUnsafe(a, b);
                                                                      }

                                                                      В библиотечном коде (по «нашу сторону» баррикад) всегда вызываем DivUnsafe, контекст вызова всегда можем контролировать. Пользовательскому коду (на корректность которого мы не можем прямо влиять) предоставляется Div.
                                                                      Неправильный вызывающий код:
                                                                      try
                                                                      {
                                                                      c = Div(a, b);
                                                                      }
                                                                      cactch (ArgumentException ex)
                                                                      {
                                                                      // Обработка ошибки, например:
                                                                      throw new MyBusinessLogicException(..., a, b, ex);
                                                                      }

                                                                      Правильный вызывающий код:
                                                                      if (/*корректное сравнение b с 0.0*/)
                                                                      {
                                                                      // Обработка ошибки, например:
                                                                      throw new MyBusinessLogicException(..., a, b);
                                                                      }
                                                                      с = Div(a, b);
                                                                        0
                                                                        статью не хотите написать на эту тему?
                                                                          0
                                                                          Да просто вопросы соискателям нормальные задавать надо.
                                                              –5
                                                              Позвольте пофантазировать в php на эту тему:
                                                               class ArgumentException extends Exception {
                                                                public function __construct($message, $code, $previous) {
                                                                 parent::__construct($message, $code, $previous);
                                                                }
                                                                
                                                                public function __toString() {
                                                                 parent::__toString();
                                                                }
                                                               }
                                                               
                                                               interface Ifoo {
                                                                /**
                                                                 *
                                                                 * @param mixed $a
                                                                 * @param mixed $b
                                                                 * @return double
                                                                 */
                                                                public function Test($a, $b);
                                                               }

                                                               class fooProtected implements Ifoo {
                                                                
                                                                /**
                                                                 *
                                                                 * @param mixed $a
                                                                 * @param mixed $b
                                                                 * @return double
                                                                 */
                                                                public function Test($a, $b){
                                                                 return $this->_Test();
                                                                }
                                                                
                                                                /**
                                                                 *
                                                                 * @access protected
                                                                 * @param double $a
                                                                 * @param double $b
                                                                 * @throws divide
                                                                 * @return double
                                                                 */
                                                                protected function _Test($a, $b) {
                                                                 return $a / $b;
                                                                }
                                                               }
                                                               
                                                               class barUnProtected extends fooProtected {
                                                                
                                                                /**
                                                                 *
                                                                 * @access protected
                                                                 * @param mixed $a
                                                                 * @param mixed $b
                                                                 * @return double
                                                                 */
                                                                protected function _Test($a, $b) {
                                                                 
                                                                 //valid
                                                                 if(!is_double($a) || !is_double($b))
                                                                  throw new ArgumentException('Some variables not convertible to double!');
                                                                 
                                                                 //user rule
                                                                 if($b == 0)
                                                                  return 0.0;
                                                                 
                                                                 return parent::_Test($a, $b);
                                                                }
                                                               }
                                                               
                                                               /**
                                                                *
                                                                * @param Ifoo $dividor
                                                                * @param mixed $a
                                                                * @param mixed $b
                                                                * @throws ArgumentException on un protected
                                                                * @throws Exception
                                                                * @return double
                                                                */
                                                               function dividePleaseTwoStrangeParams($dividor, $a, $b) {
                                                                try
                                                                {
                                                                 if($dividor instanceof Ifoo)
                                                                 {
                                                                  return $dividor->Test($a, $b);
                                                                 }
                                                                }
                                                                catch (ArgumentException $exc)
                                                                {
                                                                 echo $exc->getTraceAsString();
                                                                 return 0.0;
                                                                }
                                                                catch(Exception $exc)
                                                                {
                                                                 echo $exc->getTraceAsString();
                                                                 return 0.0;
                                                                }
                                                               }


                                                              * This source code was highlighted with Source Code Highlighter.


                                                              Зачем так сложно? Пытался придумать, как написать много асбтрактной писанины сначала, при минимизации кода в точке использования.

                                                              По хорошему придется делать dividePleaseTwoStrangeParams() методом класса, и в нем хранить ссылки на Ifoo обьекты и выбирать по ключу, опять же переданному в качестве аргумента функции… Замкнутый круг какой-то получается…

                                                              Подскажите, как научить dividePleaseTwoStrangeParams() самому определять, в каком контексте (в смысле с каким $dividor) выполняться?
                                                                0
                                                                Какую проблему вы хотите решить, используя этот класс?
                                                                  0
                                                                  Разделение доступа к методу (читать самого метода) для «Своих» и «Чужих».
                                                                  Для «Чужих» осуществляются различного рода козни проверок, для «Своих» — сразу результат.
                                                                  *Пример притянут для функций, пользуются которыми по обе стороны*

                                                                  Почитав комментарии другое направление: вначале отфильтровали, внутрь пустили только валидные данные.
                                                                  +2
                                                                  а раньше он бы свернулся…
                                                                  +6
                                                                  Поздравляю вас, граданин!
                                                                  Вы придумали «чистое программирование». Теперь осталось декларировать иммутабельность и реализрватть саму бариикаду на монадах.

                                                                  Ждем новых, потрясаающих идей -)
                                                                    0
                                                                    Я себе чужих достижений не присваивал и честно признался, что вычитал идею в книгах других авторов. Просто там, чтобы её поймать нужно прочитать 2 книги по 800 страниц, а тут — 3 абзаца по 100 слов. Кому-то может сэкономить время.
                                                                      0
                                                                      Достаточно прочитать статью в википедии о Функциональном Программировании: там даже больше, шире, лучше и разнообразнее.

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

                                                                      Но вы в любом случае делаете хорошее дело, просвещая общественность -)
                                                                        0
                                                                        я согласен про википедию, но не согласен, что «кому надо — знают». На Хабре много студентов, которые еще учатся. Если не сама статья, то мои ссылки на Макконнелла и Ваши на википедию могут быть полезными.
                                                                    0
                                                                    Есть другой подход, который мне видится более удобный и перспективным.
                                                                    Можно назвать его «Let It Crash».

                                                                    Любая сущность обрабатывающая данные может рухнуть при обработке данных когда хочет. Собственно это и будет сообщением о ошибке, ибо ее падение будет отслежено и залогинованно. Сущность будет перезапущена при получении новых данных, или по мониторингу… Остальные сущности (того же типа) опять же будут продолжать работать параллельно.

                                                                    Собственно подход Erlang, название оттуда же.
                                                                      +2
                                                                      Работает только в языках, где гарантируется отсутствие side-эффектов от выпадания.
                                                                        0
                                                                        LetItCrash не спасет от SQL инъекции или XSS уязвимости например…
                                                                        +1
                                                                        Данная проблема, как мне кажется, решается не очень сложно.
                                                                        Пишем код с использованием контрактов вроде Code Contracts. Компилятор проверяет, что внутри библиотеки/баррикады контракты не нарушены. После этого, можно [будет] сказать компилятору убрать код проверок, для которых доказано, что они всегда верны. Таким образом, реальные проверки останутся только по периметру баррикады, а корректность баррикады гарантируется компилятором, анализирующим контракты.

                                                                        В Code Contracts можно задать (Requires) условия для аргументов метода. Также можно пообещать (Ensures) что для при выходе из метода будут выполнены определённые условия для результата и конечных значений аргументов (да аргументы могут меняться внутри процедуры). Также можно пообещать, что аргументы не изменятся если в методе возникнет исключение. Инварианты пишутся в отдельном методе.
                                                                        При компиляции с соответствующими опциями, все утверждения проверяются и выводятся ворнинги, причём довольно осмысленные. Например, увидев код вроде int a = 1 << (arg1.Prop — 8), компилятор предложил добавить проверки на то, чтоm 7 < arg1.Prop < 39.
                                                                          0
                                                                          а как доказать, что контракты всегда соблюдаются и соответствующие проверки можно убрать? просто проанализировать граф вызовов и сопоставить requires и ensures — мало…
                                                                            +1
                                                                            Компилятор анализирует сам код. Если ты написал Ensures, то он проверит, что ты в самом коде метода не нарушил это условие. Многие стандартные функции помечены как [Pure] — это означает, что они никак не меняют состояние объекта. Ещё есть случай совсем внешних библиотек. У них нет контрактов и система, отдавая им переменную, или принимая от них значение, не знает, что произойдёт. Для таких случаев есть Assume. Assume похож на Requires, но если Requires находится в начале метода и проверяет добросовестность вызывающего метода, то Assume просто делает недоказуемое утверждение (сопровождающееся проверкой в Debug версии). Вообще, задача, конечно, непростая. Как я понимаю, Code Contracts дествуют по принципу «виновен, пока не доказано обратное».

                                                                            У меня был совершенно глупый случай, когда я не мог понять почему контракт считается недоказаным. У меня в программе был набор списков. Был метод, позволяющий добавить новый список в набор. При этом было условие, что длина нового списка должна быть такая же, как и длины уже существующих (проверялось при помощи Requires). Но инвариант, который постулировал, что длины всех списков равны, оказывался недоказуем. Причина банальна: тот, кто передал мне список для добавления, может всего добавить в него элемент, изменив длину и нарушив мой инвариант.
                                                                              +1
                                                                              Если будет интересно, то основной сайт сейчас — research.microsoft.com/en-us/projects/contracts/
                                                                              Вот тут (http://rise4fun.com/CodeContracts и pexforfun.com/) можно поиграться прямо в браузере — пишется код функции, а Code Contracts и PEX проверяют валидность (первая ссылка) и находят аргументы при которых возникают проблемы (вторая ссылка).
                                                                            0
                                                                            Они и есть темто, ради чего вся эта затея начиналась.

                                                                              0
                                                                              Все это красивые слова и прекрасно в теории, но сделать хорошую работающую программу по такому принципы задача очень сложная. К тому же за счет баррикады можно нехило потерять в производительности.
                                                                                0
                                                                                так утверждается же что наоборот, «баррикада» как раз уменьшает количество мест, где понатыканы тормозящие проверки?
                                                                                0
                                                                                найти бы теперь руководство, в котором учат где конкретно в коде лучше выстраивать эту «баррикаду» :)
                                                                                  0
                                                                                  В случае Java многие уже используют аннотации типа @NotNull и @Nullable, а IDE проверяет, чтобы это было корректно. В итоге — делаем в одном месте проверку, возвращаем @NotNull — и в остальных местах уже можно не проверять.
                                                                                  Я думаю, через пару-тройку лет подобная фича будет реализована на уровне синтаксиса, причем не только для not-null, а также для range-check и тому подобных условий.

                                                                                  Only users with full accounts can post comments. Log in, please.