Исключение != ошибка

    Многие программисты почему-то считают, что исключения и ошибки — это одно и то же. Кто-то постоянно кидает exception, кто-то через errorHandler превращает ошибки в исключения. Некоторые пытаются увеличить производительность, используя исключения. Но, на самом деле, exception и ошибки — это совершенно разные механизмы. Не надо одним механизмом заменять другой. Они созданы для разных целей.

    Когда появился php5 с исключениями, а затем ZendFramework, который всегда кидает исключения — я не мог понять: чем же exception лучше моего любимого trigger_error()? Долго думал, обсуждал с коллегами и разобрался в этом вопросе. Теперь я чётко знаю, где использовать trigger_error(), а где throw new Exception().

    В чём же принципиальная разница между ними?



    Ошибки


    Ошибки — это то, что нельзя исправить, об этом можно только сообщить: записать в лог, отправить email разработчику и извинится перед пользователем. Например, если мой движок не может подключиться к БД, то это ошибка. Всё. Точка. Без БД сайт не работает, и я не могу с этим ничего сделать. Поэтому я вызываю ales_kaput() и trigger_error(), а мой errorHandler отправит мне email и покажет посетителю сообщение «Извините, сайт не работает».

    Exception


    Исключения — это не ошибки, это всего лишь особые ситуации, которые нужно как-то обработать. Например, если в калькуляторе вы попробуете разделить на ноль, то калькулятор не зависнет, не будет отсылать сообщения разработчику и извинятся перед вами. Такие ситуации можно обрабатывать обычным if-ом. Строго говоря, исключения — это конструкция языка позволяющая управлять потоком выполнения. Это конструкция, стоящая в одном ряду с if, for и return. И всё. Этот механизм ничем более не является. Только управление потоком.

    Их основное предназначение: пробрасывать по каскаду. Покажу это на примере: есть три функции, которые вызывают друг друга каскадом:
    <?php
    a();
    function a()
    {
    	b();
    }
    
    
    function b()
    {
    	c(99);
    }
    
    function c($x)
    {
    	if ($x === 0) {
    		// Некоторая особенная ситуация,
    		// которая должна остановить выполнение функций c() и b(),
    		// а функция a() должна узнать об этом
    	}
    	return 100 / $x;
    }
    


    Эту задачу можно было бы решить без механизма exception. Например, можно заставить все функции возвращать специальный тип (если ты матёрый пэхапэшник, то должен вспомнить PEAR_Error). Для простоты я обойдусь null-ом:

    <?php
    a();
    function a()
    {
    	echo 'a-begin';
    
    	$result = b();
    	if ($result === null) {
    		echo 'Делить на ноль нехорошо';
    		return;
    	}
    
    	echo 'a-stop';
    }
    
    function b()
    {
    	echo 'b-begin';
    
    	$result = c(0);
    	if ($result === null) {
    		return null;
    	}
    
    	echo 'b-stop';
    	return true;
    }
    
    function c($x)
    {
    	echo 'c-begin';
    
    	if ($x === 0) {
    		return null;
    	}
    
    	echo 'c-stop';
    	return 100 / $x;
    }
    
    


    Результат работы:

    a-begin
    b-begin
    c-begin
    Делить на ноль нехорошо


    Задача выполнена, но, обратите внимание, мне пришлось модифицировать промежуточную функцию b(), чтобы она пробрасывала результат работы нижестоящей функции выше по каскаду. А если у меня каскад из 5 или 10 функций? То мне пришлось бы модифицировать ВСЕ промежуточные функции. А если исключительная ситуация в конструкторе? То мне пришлось бы подставлять костыли.

    А теперь решение с использованием Exception:
    a();
    function a()
    {
    	echo 'a-begin';
    
    	try {
    		b();
    		echo 'a-stop';
    	} catch (Exception $e) {
    		echo $e->getMessage();
    	}
    }
    
    
    function b()
    {
    	echo 'b-begin';
    	c(0);
    	echo 'b-stop';
    }
    
    function c($x)
    {
    	echo 'c-begin';
    
    	if ($x === 0) {
    		throw new Exception('Делить на ноль нехорошо');
    	}
    
    	echo 'c-stop';
    	return 100 / $x;
    
    }
    

    Результат выполнения будет идентичен. Функция b() осталась в первоначальном виде, не тронутая. Это особенно актуально, если у вас длинные каскады. И ещё объект $e может содержать дополнительную информацию о произошедшей ситуации.

    Таким образом, получается, что ошибки и исключения — это совершенно разные инструменты для решения совершенно разных задач:
    ошибка — не поправимая ситуация;
    исключение – позволяет прервать выполнение каскада функций и пробросить некоторую информацию. Что-то вроде глобального оператора return. Если у Вас нет каскада, то вам достаточно использовать if или return.

    Ошибки не всегда являются ошибками


    Некоторые могут мне возразить: «Посмотри в Zend Framework — там всегда кидают исключения. Это best practics, и надо делать также. Даже если не удалось подключиться к БД, надо кидать исключение».

    В этой статье я как раз хочу развеять это заблуждение. Zend действительно является best practics, но программисты Зенда находятся на другой лодке и делают другие вещи. Принципиальная разница между ними и мной в том, что они пишут универсальную библиотеку, которая будет использоваться во многих проектах. И они со своей колокольни не могут сказать, что является критической ошибкой, а что является поправимой.

    Например, в вашем проекте может быть несколько MySQL серверов и вы можете переключаться между ними при падении одного из них. По этому, Zend_Db, как универсальная библиотека, кидает исключение, а что с ним делать — решайте сами. Exception это гибко — вы сами решаете на каком уровне и какой тип ситуаций ловить. Вы можете вывести сообщение об ошибке или попытаться исправить возникшую ситуацию, если знаете как. При написании универсальных библиотек необходимо всегда кидать исключения. Это делает библиотеку более гибкой.

    В итоге, могу сказать, что у обоих механизмов есть свои особенности и, самое главное, что у них есть своё предназначение и эти предназначения нисколько не пересекаются. Ошибки != исключения. Не надо использовать исключения для улучшения быстродействия или сообщения об ошибках. Не надо в классе My_Custom_Exception реализовывать какую-либо логику исправления ситуации. Этот класс должен быть пустым, он создаётся только что бы определить тип ситуации и поймать только то, что надо. Название класса 'My_Custom_Exception' это такой древовидный аналог линейному списку констант E_*** (E_NOTICE, E_WARNING, ...).

    В php давно был разработан механизм обработки ошибок, и он отлично работает. Я им отлично пользуюсь там, где это надо.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 110

      +24
      Неделя исключений на хабре?
        +13
        <?php echo Ошибки != исключения; ?>

        И ведь правда!
          +8
          У вас notice в коде…
          +1
          Да, и это, имхо, хорошо. Этот топик, имхо, очень правильно описал роль и суть исключений. Особенно уточнение про универсальные библиотеки.
          +4
          Например, если мой движок не может подключиться к БД, то это ошибка. Всё. Точка. Без БД сайт не работает, и я не могу с этим ничего сделать. Поэтому я вызываю ales_kaput() и trigger_error()

          А что вы будете делать, если в будущем вы решите добавить какую-нибудь логику в обработку конкретно этой ошибки? В какое место будете ее вставлять? Будете править логику подключения к БД?
            0
            | А если в будущем вы решите добавить какую-нибудь логику…
            | Будете править логику подключения к БД?

            Пример с БД не самый лучший, так как относится к универсальной библиотеке. Он здесь только что бы провести аналогию с ZF. Пример с калькулятором и делением на ноль гораздо показательнее.

            Если у меня появится больше серверов БД, то в любом случае придётся переписывать логику. На данный момент, отвалившийся сервер это алес_капут, по этому я делаю так:
            <?php
            try {
                $db = Zend_Db::factory(...);
                $db->getConnection();
            } catch (Zend_Db_Adapter_Exception $e) {
                trigger_error(...); // Универсальная штука,
                                    // которая отправляет мне сообщения
                                    // об ошибках с трейсами и дампами.
            }
            
              +3
              Пример с БД не самый лучший, так как относится к универсальной библиотеке.

              Тогда приведите более удачные примеры, где, по-вашему мнению, использование trigger_error предпочтительнее исключений
                0
                Пример: Zend_Db не смог подключится к БД и выкинул исключение. У вас только один сервер БД и вы не можете исправить эту ситуацию. Что делать?

                Мой ответ: уведомить об этом ответственных лиц (в моём проекте это я сам). При этом, у меня уже есть готовая налаженная и оттестированная функция myErrorHandler(). И в неё я перенаправляю исключение — так как описано в комментарии выше.

                А как вы поступаете с исключениями, которые не можете никак исправить?
                  +2
                  Ну если по аналогии с вашим кодом, то, например, так:
                  <?php
                  try {
                      Application::init()
                  } catch (Exception $e) {
                      // Универсальная штука,
                      // которая отправляет мне сообщения
                      // обо всех не отловленных эксепшенах с трейсами и дампами.
                  }
                  


                  Таким образом будут обработаны все не отловленные эксепшены в приложение. И не надо ничего никуда явно перенаправлять
                    0
                    Невозможно отказаться от myErrorHandler потому что он обрабатывает стандартные сообщения php об ошибках (Undefined variable, и т.п.). Придётся в нём продублировать функционал универсальной штуки?

                    Конечно можно внутри myErroHandler кидать исключения, что бы все ошибки превратить в exception, но зачем? Тогда получится: ошибки === исключения. А какой смысл? Вы же не собираетесь отлавливать Exception_Php_UndefinedVariable?
                      +1
                      Невозможно отказаться от myErrorHandler потому что он обрабатывает стандартные сообщения php об ошибках (Undefined variable, и т.п.). Придётся в нём продублировать функционал универсальной штуки?

                      Ну даже если ваш myErrorHandler останется (хотя по-хорошему логику логирования и отправки email'ов следует реализовывать за пределами этой функции), то все равно явный вызов trigger_error будет только в одном месте — на верхнем уровне обработки эксепшенов, который отлавливает только необработанные эксепшены. Приведите пример, где еще может понадобиться явно вызывать trigger_error вместо того, чтобы бросать эксепшены.
                        0
                        static function execueController(...) {
                          if (...) {
                             // не удалось найти класс-контроллер.
                             // Эта ситуация никогда не может
                             // возникать, если роутер настроен правильно.
                          }
                        }
                        


                        Конечно, всегда можно кидать исключения, но зачем? Что бы потом поймать на верхнем уровне и обработать как ошибку (точно так же как Undefined variable)?

                        А ещё бывает необходимость сообщить об ошибке, но не останавливать выполнение скрипта.
                        trigger_error("The " .  __METHOD__ . " method is deprecated
                        and will be dropped in 2.0.", E_USER_NOTICE);
                        

                        Мой errorHandler не прекращает выполнение программы, если приходит NOTICE.

                        Загляните в ZendFramework. Они тоже иногда выкидывают сообщения об ошибках. Я поиском нашёл 51 trigger_error (версия 1.11.8).
                          +5
                          Нотис, по правде говоря — такая же ошибка. пхп слишком много прощает.

                          Причины нотисов, к примеру: опечатка в имени переменной. В этом случае НЕЛЬЗЯ продолжать выполнение, потому что ваш скрипт не получил данных, которые должен получить.
                          Далее: обращение к несуществующему элементу массива: если ваш код обращается к элементу, то вы предполагаете, что он там есть. Если его там нет — значит скрипт выше работает неправильно. И тут тоже НЕЛЬЗЯ продолжать исполнение.

                          В каждом отдельно взятом случае нотиса может произойти потеря или повреждение данных. Поэтому нужно остановить приложение и насколько это возможно быстро исправить эту ПРОБЛЕМУ.
                            0
                            > Поэтому нужно остановить приложение и насколько это возможно быстро исправить эту ПРОБЛЕМУ.

                            Как правило, в случае нотиса эта «ПРОБЛЕМА» не настолько огромная чтобы останавливать все приложение, ИМХО, лучше дать ему отработать, а вот после исправить все, что вылезло (я про сервер разработки).

                            Если рассматривать шаблоны, то там нотис говорит о том, что где-то что-то пропущено (== будет выведено не так как нужно), какие либо серьезные проблемы маловероятны.

                            К огромному сожалению не все разработчики придерживаются мнения что нотис это ошибка, и поэтому со спокойной совестью их игнорируют. Яркий пример — IP.Board, печально наблюдать за десятками нотисов на каждой странице…
                              +3
                              Если приложению данные нужны, а их нет (опечатка в переменной, неправильно вычисленный ключ массива, ...), тогда приложение может начать работать некорректно. Да, может повезти, и это никак не отразится ни на чём. А может получиться так, что потом будешь сидеть ночь и исправлять базу ручками.

                              Без нотисов писать ведь очень просто.

                              «Не так выведено» — тоже может быть огромной ошибкой. Пускай ваше приложение печатает договоры. И один пункт взял и не пропечатался в результате нотиса. Клиент ушёл довольный, а потом вас вызывают в суд. Привет, незначительная проблема.
                                0
                                > Без нотисов писать ведь очень просто.
                                Не все об этом знают.

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

                                  Но пусть каждый сам решает, чай все уже взрослые и есть своя голова на плечах :-)
                                  0
                                  Обо всех нотисах я всегда узнаю. myErrorHandler всегда отсылает мне сообщения обо всех ошибках. А после этого решает: нужно останавливать или нет?
                                    0
                                    Как программный обработчик может решить, критичный нотис или нет?
                            0
                            > Приведите пример, где еще может понадобиться явно вызывать trigger_error вместо того, чтобы бросать эксепшены.
                            1) При обращении к несуществующим свойствам/методам класса при использовании __* — здорово помогает при обнаружении опечаток.

                            2) Предупредить о том, что метод устарел (resurection, чуть выше об этом уже сказал)
                              0
                              «Предупредить о том, что метод устарел (resurection, чуть выше об этом уже сказал) „
                              Мда. Системы статического код-анализа просто плачут в этом месте.

                              (и .net с атрибутом obsolete)
                                0
                                @deprecated можно использовать, но это больше для чужого кода — при обновлении велик шанс пропустить несколько устаревших методов, и вот тут, выброшенный нотис будет очень кстати.
                    +2
                    посмотрите на парадигму python — там всё построено на исключениях, их иерархии. то же деление на ноль — это исключение арифметической операции. после python тяжело возвращаться к php с его костылями типа @. неудобно скрещивать в приложении исключения с ошибками, которые кидают встроенные функции, в этом есть доля истины.
                  +4
                  Исключения != ошибка не только в PHP
                    0
                    Простите меня срочно, но мне хватило кармы, что бы опубликовать в разделе «разработка». Я пытался, честно.
                      +3
                      «но мНЕ хватило» — опечатка. Ещё раз срочно простите.
                        +19
                        > Простите меня срочно
                        Как-то пугает:)
                          0
                          Тсс… Ща он еще за Ваш испуг начнет извиняться
                          0
                          Пожалуй, возьму на вооружение.
                          «Простите меня немедленно!» — звучит :)
                        –8
                        Замерил время выполнения примеров.

                        Без исключений: 0.0000171661
                        С исключением: 0.0000400543

                        Исключения все же проигрываю по скорости. Хотя на мой взгляд их использовать удобней.
                          +11
                          Экономия на спичках и преждевременная оптимизация. Если у вас каскад из 10 функций, то время потраченное программистом на переписывание всего каскада будет в тысячи раз больше, чем суммарное время потраченное сервером за всё время жизни проекта. Тем более в 10 промежуточных функциях появятся дополнительные if-ы и выигрыш производительности будет менее заметен.
                            0
                            Точно. Не факт, что при переписывании будут предусмотрены все случаи, соломку везде не постелишь.
                            Зато при работе в штатном режиме, кусок без проверок обернутый try catch отработает быстрее.
                              0
                              Собственно, исключения на то и исключения, чтобы быть не самым часто используемым путём. А там эта потеря скорости некритична. А вот делать через них основную логику програмы — глупость, с какой стороны ни посмотри — и с читабельностью плохо, и со скоростью.
                          +4
                          Теперь думаю, может написать статью «Переменная != файл» или «Процессор != суслик»?!

                          Может я что-то упустил, но ведь это не новость и не ценное наблюдение. Более того, довольно сложно найти литературу в которой «Исключение = ошибка». Или читать уже совсем не модно и до всего доходим методом «ого, а так тоже можно!»?
                            +3
                            Как оказалось, многие этого не понимают
                            habrahabr.ru/blogs/development/130534/ — в комментариях холивар масштабов «табы vs пробелы».
                              +2
                              Вы сейчас критически важную тему затронули, но это не этом топике писать надо.

                              На хабре не хватает крутых технических статей, так? Вот или всякие обзоры, или перепечатка с иностранных ресурсов, или новости от ализара. А технических статей нету. Точнее как, они есть, но все какие-то для новичков. А нам бы подавай да поматерей что-нить эдакое.

                              Я не осуждаю, критика это хорошо. Но вот такая критика без конструктива это бессмысленно.
                                0
                                Я не говорю, что нужно «крутых технических статей», но не стоит же вдаваться в крайности. Давайте не делать статьи о том, что «Зимой и летом одним цветом» — это «ёлка». Я не хотел никого задеть и не говорю, что никто не найдёт для себя тут что-то новое…

                                Но не от того ли, большинство проектов находится в плачевном состоянии, что разработчики вместо того, чтобы изучить основы делает для себя открытия из серии «чтение из оперативной памяти быстрее, чем с дискет»?
                                  +2
                                  Ну я согласен насчет крайностей, просто я надеюсь что пока одни делают открытия и пишут об этом статьи, другие их читают и повышают свой уровень. :)
                                    0
                                    У нас тут не химия с литературой. Огромное количество людей хотят научиться реализовывать свои идеи и задачи средствами программирования. И спрашивают, к примеру меня, куда пойти, как научится? А ведь нет таких курсов. Может в отдельновзятой нерезиновой и есть такие курсы-классы. Большинству же приходиться уповать на Хабр и пытаться приткнуться в проект на обучение. Далеко не во всех книгах напишут все базовые тривиальные вещи
                                    Вы скажете, что надо бы вуз заканчивать. Однако программы устаревают стремительнее, чем вот такие новые простые вещи возникают. И не забывайте: программирование, как и математика — это лишь инструменты для реализации практических вещей и класть всю жизнь на изучение Кнута с Муком для того, чтобы упростить себе рутину в медицине просто не хочется.
                                –5
                                Чорт, ты раскрыл мне глаза, Кэп!!! Ошибка != Исключение, Error != Exception — гениально!
                                А я-то дурак не мог никак понять, почему в описании класса Exception нет ни слова об ошибках.
                                +19
                                > исключения — это конструкция языка позволяющая управлять потоком выполнения
                                > Их основное предназначение: пробрасывать по каскаду.
                                > ошибка — не поправимая ситуация;
                                >исключение – позволяет прервать выполнение каскада функций и пробросить некоторую информацию. Что-то
                                > вроде глобального оператора return. Если у Вас нет каскада, то вам достаточно использовать if или return.

                                Это феерично.

                                Уважаемый, вы бы хотя бы википедию посмотрели перед тем как херню нести.

                                P.S. При всем моем уважении к веб-разработчикам, но почему такое всегда в блоге PHP?
                                  +12
                                  Конструктив:
                                  Да, исключение — это не ошибка. Это, блин, механизм обработки ошибок(и только ошибок, его нельзя использовать для flow control, как минимум потому, что этот механизм довольно медленный). Или как сущность — объект несущий информацию об ошибке.

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

                                  Я так подозреваю, что в ПХП механизм исключений появился далеко не сразу и поэтому до сих пор много legacy-кода, который использует коды ошибок. Но это не повод смешивать оба подхода, и тем более использовать для управления потоком выполнения (потому что это все-таки форма goto и такое использование быстро приведет к спагетти-коду).
                                    0
                                    Иногда имеет смысл использовать механизм обработки исключений в качестве flow механизма. Например когда требуется организовать работу конструкции finally, которая в свою очередь тоже может быть весьма развесистой. Все зависит от конкретной бизнес-логики проекта
                                      0
                                      Сходу не могу представить ситуацию. Приведите пример, пожалуйста.
                                        0
                                        Пример в студию. Имхо если такое приходится вытворять — это говорит о плохом дизайне в принципе.
                                          +1
                                          В исходниках Android'а finally используется для закрытия Cursor'ов и файлов. Да и Oracle говорит, что «The finally block is a key tool for preventing resource leaks».
                                            0
                                            Если в таком ключе, то да.
                                      • UFO just landed and posted this here
                                          0
                                          Зря вы так по поводу формы goto. Переход вперед-вверх является логичной частью структурного программирования, избавляя от лапши с протаскиванием состояния вверх по стеку вызовов и снижая цикломатическую сложность.
                                          0
                                          Мне тут коллега подсказывает смешную ситуацию: представьте себе, что топикстартер «пробрасывает по каскаду» какое либо свое значение, а потом в ходе какого-либо рефакторинга кто-то покатчит исключения в середине «каскада».
                                            +5
                                            Соглашусь с retran'ом.
                                            Во-первых исключения, как конструкции в языках программирования появились уже после того, как они появились в процессоре. В некоторых языках — это адское зло, скажем Objective-C. Там прямым текстом написано: Не использовать — это дорого. В PHP который вы выбрали как основу для своей статьи, всякие trigger_error() — это пережитки прошлого, т.с. попытки уменьшить затраты на поддержку обратной совместимости.

                                            Ваши рассуждения, о том, что Exception — это что-то каскадное и так далее, то готова с вами спорить.
                                            Все мы знаем о договорённостях вызова функций, что первый аргумент функции в стеке может быть как первым так и последним. Тоже самое и здесь: если договориться о том, что все функции будут возвращать некий integer как код ошибки — вы получите такое же каскадное поведение. Вспомним тот же HRESULT в MS COM.

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

                                            Современные языки программирования позволяют вместе с исключениями передавать дополнительные данные: Call Stack, данные, сообщение итд. Подобного достичь с trigger_error() или raise_error(), return -10; — будет куда сложнее.

                                            Вообщем: читайте больше.
                                            +5
                                            Полностью не согласен с точкой зрения автора. Какие-то двойные стандарты: для библиотек, мол, всегда исключения, т.к. она должна быть гибкой, а для каких-то «не библиотек» — ошибки. Это для вермишели чтоли? Иначе как разделить код на библиотеку и не библиотеку? Если пишем максимально code reuse'абельно, то практически любой код можно считать библиотекой, которая может использоваться где-то в другом месте. И вполне возможно, что в этом другом месте та фатальная неисправимая ошибка вовсе не является фатальной и неисправимой.
                                              +1
                                              Согласен, плюсую. Но:

                                              | практически любой код можно считать библиотекой

                                              Поэтому я практически всегда кидаю исключения, но не всегда. И вот такой вопрос: что делать, если Zend_Db не смог подключится к серверу и выкинул исключение. У вас только один сервер и исправить эту ситуацию вы не можете. Что делать с исключением?
                                                –3
                                                Не хватает кармы для плюсования :(
                                                  0
                                                  Отловить исключение, показать 500-ю страничку. В какое место вы хотите trigger_error?
                                                    0
                                                    | показать 500-ю страничку
                                                    Эту страничку надо показывать ещё во многих случаях. Для этого у меня уже написана функция myErrorHandler(). По этому я ловлю исключение от Zend_Db и вызываю trigger_error().
                                                      0
                                                      Не во многих, а во всех, когда исключение не было перехвачено в контроллере. За исключением тех случаев, когда было возбуждено специальное исключение для другой страницы (404 или 403, например).
                                                        0
                                                        Для этого у меня уже написана функция myErrorHandler(). По этому я ловлю исключение от Zend_Db и вызываю trigger_error().

                                                        Если файл не получилось открыть тоже алес капут? или @?
                                                    +1
                                                    Чтобы понять, что такое рекурсия — нужно вначале понять… что такое рекурсия
                                                    • UFO just landed and posted this here
                                                    –3
                                                    Ох. Если бы на свете не было других языков программирования и я бы не знал как пользоваться исключениями из этой бы статьи точно не узнал + запутался сильней.
                                                    Особы диссонанс вызывает «Ошибки — это то, что нельзя исправить, об этом можно только сообщить: записать в лог, отправить email разработчику и извинится перед пользователем.», но при этом на своём человеческом языке мы вполне себе говорим об ошибках ввода пользователя и о том, что мы покажем сообщение и попросим ИСПРАВИТЬ.

                                                      +3
                                                      Тут есть три момента:
                                                      1. Спор о том, что лучше — исключения или коды возврата, абсолютно не нов, и PHP тут не при чем.
                                                      2. В той же яве ошибки — это разновидность исключений, сигнализирующих о неисправимой проблеме.
                                                      3. Использовать процедурный (коды возврата) или объектно-ориентированный стиль (исключения) обработки исключительных ситуаций — дело вкуса каждого.
                                                        +11
                                                        Теперь я чётко знаю, где использовать trigger_error(), а где throw new Exception()

                                                        У меня для тебя плохие новости, бро.
                                                          0
                                                          Расскажите подробнее, плз.

                                                          Автору: спасибо за статью, очень ценно.
                                                            0
                                                            Сделаю попытку сделать вывод:
                                                            Использовать коды ошибок нужно тогда, когда целевая платформа не поддерживает исключения, опять-таки objective-c. Исключения там есть, но они реализованы _программно_ а не аппаратно. А там, где есть возможность и необходимость — лучше использовать исключения. x86 исключения поддерживаются на уровне процессора, и все кто «выше» (Операционная система) уже их обрабатывают. Java, .NET — они все тоже исключения обрабатывают используя исключения операционной системы, а она (ОС) уже использует процессор.
                                                          +14
                                                          if ($x === 0) {
                                                          throw new Exception('Делить на ноль нехорошо');
                                                          }

                                                          А проверять float на строгое равенство — еще хуже.
                                                            +7
                                                            Я бы выпилил бы все эти trigger_error и ядра PHP.
                                                              +4
                                                              забавная опечатка получилась
                                                              +7
                                                              Когда-то я писал на PHP и очень удивлялся, почему же все эти ошибки не ловятся моим catch-ем. Но потом я перешёл на нормальные языки программирование, и мои волосы стали гладкими и шелковистыми.
                                                                0
                                                                На какие? :)
                                                                  +2
                                                                  Python и Javascript. В обоих даже ошибки синтаксиса — это обычные исключения
                                                                    0
                                                                    И это грамотное решение. Как же было удобно писать на шарпах, когда можно отловить исключения в нужном месте, а не париться, что произойдет, как поведет себя вм, какую ошибку и каким же таким образом кинет и т.д.
                                                                +1
                                                                > В php давно был разработан механизм обработки ошибок, и он отлично работает. Я им отлично пользуюсь там, где это надо.
                                                                А расскажите вкратце об этом?
                                                                  +6
                                                                  моё толкование:
                                                                  исключение — исключительное состояние функции, в котором при определённых входящих параметрах функция не может быть отработана. Например, невозможно создать процесс из-за нехватки памяти. Выкидывается по стеку наверх и после точки входа в алгоритм проверяется: можно ли исправить и повторить вызов функции или завершить программу с ошибкой.
                                                                  ошибка — неправильное выполнение функции при _правильных_ параметрах. Момент, где в алгоритме не предусматривается никаких других действий. И требует немедленного прекращения действий во избежание чего-нибудь нехорошего.
                                                                    +1
                                                                    Киньте соответствующее исключение, какие проблемы. Не ловите все подряд в catch, ловите только то, что сможете обработать, остальное оборачивайте и кидайте наверх. Правильность параметров не при чем, если возникла некая ошибка в работе.

                                                                    Буквально недавно была цепочка примерно такая цепочка, не могу найти: Ошибка записи в сокет (или базу) — Ошибка IO — Ошибка связи с сервером платежей — Ошибка платежа — Ошибка сохранения заказа и т.д., т.е. вы имеете семантичные ошибки, у которых у каждой известно, что ее спровоцировало, и чем глубже — тем конкретнее. Естественно, управляющий код верхнего уровня, поймавший исключение, про самые низкие типы исключений ничего знать не должен.
                                                                      0
                                                                      моё толкование не противоречит вашей реализации, даже одно следует из другого.
                                                                      есть старая хорошая статья на тему.
                                                                    –4
                                                                    Мои 2 копейки. Исключения нужны для интерфейсов с внешними системами, будь то база данных, веб сервис, или пользовательский ввод. Во остальных случаях они замутняют логику приложения.
                                                                      +4
                                                                      В объектном программировании я стараюсь руководствоваться принципом: если на выходе метода должен быть объект — то только объект или exception. Никаких 0/false/null.
                                                                        +2
                                                                        нормальный результат, если метод возвращает null или false, когда ничего не найдено. Exception — исключение, нарушение нормального хода вещей. То, что вы не продумали (smtp не доступен и сообщение не удается отправить сейчас же), или предусмотрели что здесь что-то может пойти не так (нет прав на создание директории файловым менеджером).

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

                                                                          Не уверен. Попытка найти что-то несуществующее, но очень нужное, к примеру, должна приводить к исключению, а не проверке типа (псевдокод):
                                                                          x = sample_db_query(…)
                                                                          if (x != false) … else …

                                                                          Вообще я за типизированные ответы, если от метода ожидается коллекция — пусть вернется либо пустая, либо исключение, если пустой там быть не может. Если вернуться должен ровно один объект — либо он сам, либо исключение.
                                                                            0
                                                                            Про типизацию — подразумевалось, что от метода не может приходить false или объект, а всегда только что-то определенное.
                                                                              0
                                                                              да, я согласен, что если метод возвращает коллекцию, то он должен возвращать её пустой, если найдено 0 объектов.

                                                                              Вообще это вопрос проектируемого Вами интерфейса. Иногда нужно возвращать null в случае неудачи, а когда подразумевается, что загружаемый объект _должен_ существовать — Exception.

                                                                              Согласитесь, конструкция вида
                                                                              try {
                                                                                  if ($object = $adapter->findById($objectId)) {
                                                                                  } elseif ($object = $adapter->findByName($objectId)) {
                                                                                  } else {
                                                                                      return null;
                                                                                  }
                                                                              } 
                                                                              catch (Adapter\Exception $e) {
                                                                                 throw new Exception("Adapter error", Exception::ADAPTER_ERROR, $e)
                                                                              }
                                                                              


                                                                              как-то приятнее чем

                                                                              try {
                                                                                  $object = $adapter->findById($objectId
                                                                              } 
                                                                              catch (Adapter\Exception $e) {
                                                                                  if ($e->getCode() != Adapter\Exception::NOT_FOUND) {
                                                                                      throw new Exception("Adapter error", Exception::ADAPTER_ERROR, $e)
                                                                                  }
                                                                                  else {
                                                                                      try {
                                                                                          $object = $adapter->findById($objectId
                                                                                      } 
                                                                                      catch (Adapter\Exception $e) {
                                                                                          if ($e->getCode() != Adapter\Exception::NOT_FOUND) {
                                                                                              throw new Exception("Adapter error", Exception::ADAPTER_ERROR, $e)
                                                                                          } else {
                                                                                              throw new Exception("Object not found", Exception::NOT_FOUND, $e)
                                                                                          }
                                                                                      }
                                                                                  }
                                                                                  return $object;
                                                                              }
                                                                              


                                                                              Да и работать первый вариант будет быстрее.
                                                                                +1
                                                                                Второй пример черезчур избыточен, нет?

                                                                                try {
                                                                                  try {
                                                                                    $obj = $ad->getById();
                                                                                  } catch (Adapter\Exception $e) {
                                                                                    $obj = $ad->getByName();
                                                                                  }
                                                                                } catch (Adapter\Exception $e) {
                                                                                  throw new ApplicationException('Все плохо', ApplicationException::NOT_FOUND, $e);
                                                                                }

                                                                                В принципе, если не найденный объект не является фатальной ошибкой, возврат null не так страшен, главное, чтоб не false, 0, -1 и всякие прочие вариации. Однако я считаю это плохим тоном — стоит пропустить хотя бы одну такую проверку на null в коде аля

                                                                                Some some = Some.get();
                                                                                if (some != null) {}

                                                                                и приложение упадет с NPE. В случае коллекций надо возвращать пустую, чтобы итерироваться по ней можно было вообще без проверок и это не приводило к фатальным последствиям. В случае одного эл-та по ID, к примеру, нужно каждый раз крепко задумываться. В некоторых книгах вместо null рекомендуют возвращать «объект особого случая».
                                                                                  +1
                                                                                  Martin Fowler recommends «Introduce null-object»
                                                                                  0
                                                                                  ИМХО лучше никогда null не возвращать.
                                                                                  Если же по логике вызова результата может не быть, то сделать функцию вроде TryGetResult с out-параметром для результата, возвращающую явный флаг.
                                                                                  Профит:
                                                                                  1. Возврат результата и проверка в одну строку
                                                                                  2. Случайно пропустить проверку много сложнее
                                                                              0
                                                                              Да, это больше стратегия.
                                                                              Когда уверен, что нет nullов, то не нужно писать лишние обработки if (result = null)…
                                                                              Это делает код более лаконичным, имхо.
                                                                                0
                                                                                Иногда возникают ситуации, когда нужно вернуть нул, вместо объекта… А бросать исключение нельзя, потому что как таковой ошибки нет. Не приведу пример сходу, но у меня такие ситуации возникали. Не спорю, что можно найти более правильный способ :)

                                                                                Хотя на самом деле куда приятней видеть метод, видеть в хинте возвращаемый тип MyType и @throws MyException если что не так. Сразу знаешь, чего ждать, и что может произойти. Чем каждую минуту сверяться с докой на счет возможных возвращаемых значений (false, null, undefined (js), 0… omg)
                                                                            +7
                                                                            Исключение должно порождаться лишь нарушением нормального выполнения кода. Т.е. в случае ошибки. Вашей ошибки, системной, или ошибки пользователя (т.е. в итоге — вашей), исключающей возможность нормального продолжения работы линейного кода вашего приложения.

                                                                            Более того, если Вы используете библиотеку н-р в качестве адаптера (Zend_Db) и в вашем сервисном слое она вызывается, Вы, как хороший разработчик, не должны позволять этому исключению просто так провалиться в контроллер, Вы должны перехватить это исключение, обернуть его в свое и пробросить дальше новое, которое будет содержать в себе тело исходного исключения. Потому что когда-нибудь вы захотите использовать вместо Zend_Db, например, чистый PDO или Doctrine, и чтобы Вам не перелопачивать весь код на предмет перехвата исключений Zend_Db_Exception, а ограничиться лишь теми слоями, которые у Вас непосредственно Zend_Db и используют. Именно для этого в PHP 5.3 в конструкторе исключений появился третий параметр типа Exception, который реализует некоторое подобие приемственности.

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

                                                                              +3
                                                                              Много полезного выше уже написали, добавлю еще.
                                                                              Мне всегда казалось, что в ООП исключение — это часть интерфейса того или иного метода или класса.
                                                                              Основная его ценность, что если мы знаем, как его обработать, то мы должны его обработать (например, залоггировать куда-нибудь) и продолжить выполнение кода, либо прокинуть выше, либо и то и другое вместе.

                                                                              Хотел пример написать, но даже простой пример нужно довольно серьёзно расписывать, ленюсь в общем. Лучше порекомендую почитать классическую литературу по ООП: про шаблоны проектирования, про рефакторинг и тому подобное. Неплохой список здесь: wiki.agiledev.ru/doku.php?id=books. На этом же сайте можно найти и неплохие статьи на русском. Чтение данной литературы вкупе с постоянной практикой позволит со временем раз и навсегда забыть о trigger_error и error_handler (который, тем не менее, пригодится, чтобы превратить всякие PHP_WARNING в нормальный Exception). На мой взгляд — это вообще наследие четвёртого PHP, которое необходимо только для совместимости.

                                                                              И да, не стоит каждую ошибку слать себе на email, если это не какой-то внутрикорпоративный проект. Попадет говнокод на сервер (а он обязательно попадёт :) ) и засыпет ящик спамом. Хотя, я тут уже преждевременно оптимизирую ваш способ обработки ошибок :)
                                                                                +1
                                                                                Приложение не смогло подключиться к БД и как это обработать неизвестно, сворачиваем лавочку, ок, чем в этом случае trigger_error и set_error_handler кардинально отличается от throw new Exception и set_exception_handler (try/catch верхнего уровня) за исключением того что во втором варианте исключение при необходимости можно поймать… может лучше перебдеть чем недобдеть, мм? :)
                                                                                  0
                                                                                  2-е правило при использовании Exception — не перехватывайте Exception, которые не можете обработать.

                                                                                  Например в Zend Framework все исключения перехватываются FrontController-ом и обрабатываются им (записываются в лог). Я в своих контроллерах также перехватываю целевые исключения, когда обращаюсь к сервисам (н-р Zend_Db) и обрабатываю их (н-р вывожу форму регистрации с заполненными полями), не показывая пользователю ненужной инфы об ошибках (выдавая ему сообщение «Сервис временно не доступен, попробуйте повторить регистрацию позже»)
                                                                                    +4
                                                                                    «не перехватывайте Exception, которые не можете обработать» — не совсем. Перехватывайте, оборачивайте, и кидайте дальше.
                                                                                      0
                                                                                      С одной стороны яростно плюсую — действительно одна из вещей которые забывают в применении исключений — то что их еще нужно ловить, с другой стороны не понятно что конкретно вы имеете в виду, перехватывать нужно известные коду исключения, если объект формы работает с тремя типами объектов — валидаторы, декораторы, фильтры — то и ловить он должен соот-щие три типа исключения, если вдруг к нему проскочит исключение работы с БД — оно как раз должно провалиться насквозь, так как объект к БД не обращался — и оно пришло из вышеописанных трех типов объектов, которые в идеале, раз уж работают с БД (хотя с чего-бы это им приспичило) и должны были его отлавливать и оборачивать в собственное исключение, о чем и будет проинформирован программист когда исключение провалится до верхнего уровня.
                                                                                        0
                                                                                        Перехватывайте, оборачивайте, и кидайте дальше.

                                                                                        в данном контексте я подразумевал это тоже как способ обработки :)
                                                                                        +1
                                                                                        Выше Вы это уже писали, кстати. Отчего тут-то по-другому?
                                                                                          0
                                                                                          Так а с тем что я написал это как связано? try/catch верхнего уровня с выводом «Пичалька» на экран и есть обработка исключения, и известно как его обработать — вывести ошибку, фронтальный контроллера зенда именно этим занимается.
                                                                                            0
                                                                                            к тому, что это вводит второй уровень обработки Exceptions. Уровень контроллера действия в дополнении к уровню фронтального контроллера. Сможете ли вы красиво реализовать такое с trigger_error?
                                                                                        +4
                                                                                        Нравится мне вот такая картинка. Из Java, конечно, но все равно она к месту:
                                                                                          +3
                                                                                          По вашей логике, некоторые участки кода в праве решать как вести себя всей системе (падать ли ей насмерть или, так уж и быть, позволить себя обработать).
                                                                                            0
                                                                                            Вы как раз сформулировали основной косяк подхода.
                                                                                            0
                                                                                            Что-то я не ощутил разницы между trigger_error() и throw new RuntimeException(). И там, и там идёт прерывание кода(нотисы не в счёт, с ними разговор короткий). И там, и там идёт последующая обработка: показать страницу с ошибкой, записать в лог, уведомить заинтересованных. В чём всё-таки разница?
                                                                                              0
                                                                                              Более точно суть статьи отражал бы заголовок
                                                                                              Исключение !== ошибка
                                                                                                +2
                                                                                                а потом добавляем вызов функции d после c и долго думаем, почему же она не вызывается. а потом выяснив, что какой-то умник решил «пробрасывать управление через каскад» добавляем такого рода защитный код:

                                                                                                function b()
                                                                                                {
                                                                                                try{
                                                                                                c(99);
                                                                                                } finally {
                                                                                                d(100);
                                                                                                // и остальное тело функции
                                                                                                }
                                                                                                }
                                                                                                  0
                                                                                                  Я считаю наиболее правильным, когда все ошибки являются исключениями. Как в питоне, js, c# etc…

                                                                                                  Также, я не согласен с автором статьи, что о непоправимых ситуациях нужно оповещать как-то иначе, нежели исключениями. Потому что зачастую в приложении с грамотной архитектурой как правило есть диспетчер, в котором можно отловить исключения и обработать необходимые как критические ошибки. К чему тогда какие-то иные способы — не понимаю.
                                                                                                    0
                                                                                                    > Я считаю наиболее правильным, когда *все ошибки* являются исключениями.
                                                                                                    Вы это клиенту объясните, особенно когда у него после обновления версии PHP появится несколько E_DEPRECATED и сайт станет полностью неработоспособным.
                                                                                                      0
                                                                                                      А что, в PHP при deprecated-методах код становится неработоспособным? ололо
                                                                                                        0
                                                                                                        Вы бы предыдущий комментарий перечитали внимательно, тогда, возможно вам стало бы понятно, что в случае «когда все ошибки являются исключениями» (т.е. всё превращаем в исключения) вызов любого deprecated метода и последующий E_DEPRECATED делает все приложение неработоспособным.
                                                                                                          0
                                                                                                          Я вообще к тому, что не нужно все насильно конвертировать в исключения — не опасные ошибки (STRICT, DEPRECATED) разумнее игнорировать (= складывать куда нибудь для последующего исправления).
                                                                                                            0
                                                                                                            О том, что такие «ошибки» человек тоже будет конвертировать в исключения я бы даже не подумал, если честно :) Я думаю, что все-таки автор комментария не это имел в виду (= мы сами в коде вешаем на обработчик ошибок кидалку исключений), а то, что это в самой архитектуре языка/интерпретатора так должно быть. Тогда, я искренне надеюсь, авторы PHP не делали бы такие вещи ошибками (которые стали бы исключениями), а оставили для strict, deprecated и прочих notice другой механизм, не влияющий на ход выполнения кода.

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