Ads
Comments 98
+10
1. Формальный подход требует, чтобы в случае невозможности аутентификации выбрасывалось AutentificationException. С точки зрения общности и целостности восприятия кода это тоже наиболее эффективно — не надо думать, ошибка ли это, или не ошибка. Исключительная ситуация — это любая ситуация, когда поведение программы отличается от нормального. В том числе, и ввод неверного логина и пароля.

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

2. Каждая функция, которую вы пишете, должна знать обо всех исключениях, которые могут произойти из-за вызова других функций в ее теле, и должна уметь их все обрабатывать хотя бы на уровне catch(Exception $ex){$log->write($ex->Message);}. Это просто правило хорошего тона. Еще правило хорошего тона требует, чтобы запись в лог шла на самом верхнем уровне программы, когда уже точно исключение либо выбрасывается пользователю, либо просто пожирается.

Если функция не может обработать исключение — она перебрасывает его с помощью throw.

Исключения — это еще один способ возврата значения из функции, при этом подобное «значение» всегда означает, что что-то идет не так.
0
Исключительная ситуация — это любая ситуация, когда поведение программы отличается от нормального. В том числе, и ввод неверного логина и пароля.

Так всё-таки, ввод (!) логина и пароля — это поведение программы? Или пользовательское действие?

По поводу остального — спасибо, интересные мысли, постараюсь обобщить со своими.
+8
Вообще, все умные книжки вторят моему личному опыту в утверждении, что самой ненадежной частью любой программы является устройство хаотического ввода данных, расположенное между стулом и монитором.

То есть, чисто формально, конкретный наш пример следует рассматривать так:
1. Пользователь должен ввести логин и пароль.
2. Логин и пароль могут быть введены некорректно — надо предусмотреть фильтрацию.
3. Логин и пароль могут быть введены неверно — надо предусмотреть код возврата.
4. Логин и пароль могут быть не обработаны из-за серверной ошибки — надо предусмотреть исключение.

Как видите, мы имеем три частных случая — некорректный ввод, ошибка аутентификации и ошибка сервера. Какие из них следует обрабатывать с помощью исключений?

Логично, что все. Почему?

Потому что во всех случаях у нас одинаковое поведение — показать пользователю жуткое окно и редиректуть его для повторного ввода (если возможно, если сервер не упал).

Разделяя сущности, мы придем к дублированию кода. Наоборот, выкидывая исключение на каждый из типов, мы делаем поведение более общим и не теряем возможность расширения.
+1
Я с вами согласен. Вообще, по-моему, использование кодов возврата в эпоху освоения космоса и дешевого интернета — дичь. Естественно, под «кодом возврата» я подразумеваю неочевидные данные. Например, такая функция (довольно часто раньше я так писал):

bool login(username, password)…

Что нам говорит код возврата? «Логин не удался». Никакой конкретной информации. А уж конструкции вида if (! login())… еще сильней добавляют туману.

Другое дело, когда «код возврата» — суть некое смысловое значение (например, int getHttpError() ...).

Поэтому, с моей точки зрения, исключениям — быть. Единственное, что неудобно — это отсутствие в пыхе возможности спецификации исключений, наподобие ветки throw в C++:

void f() throw (exception1, exception2) {… }

Приходится спасаться тегами PHPDocumentor'а, что все-же не так удобно.
-1
это вы — дичь, а нормальные люди пишут чекеры и хранят стэки возвращаемых данных

в моих приложениях у меня просто набор правил для определенной ситуации и собственно функция, которая делает чек входных данных.

а уже внутри — ловятся ошибки, пишутся номера ошибок, номера пишутся в класс ошибок, там преобразуются в мессаги и xajax-ом аппентядтся в афтер лабелы для полей

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

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

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

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

1. Пользователь должен ввести логин и пароль — throw(«Необходимо ввести логин и пароль», E_USER_NOTICE);
2. Логин и пароль могут быть введены некорректно — throw(«Не корректный логин или пароль», E_USER_WARNING);
3. Логин и пароль могут быть введены неверно — throw(«Не верно введён логин или пароль», E_USER_WARNING);
4. Логин и пароль могут быть не обработаны из-за серверной ошибки — throw(«Ошибка на сервере», E_USER_ERROR);

Естественно, на константе нотис — просто сообщение, на константе варнинг — отмена действия и предупреждение, на константе еррор — стандартное сообщение об ошибке на сервере, просьбе не волноваться и емайл или смс админу.

Можно написать с десяток своих классов, наследником Exception и как в языке java разруливать их.

У исключений уйма применений и они достаточно удобны, когда знаешь как ими пользоваться.
0
А с каких пор сообщения типа notice и warning должны прерывать поток выполнения и сворачивать стек? исключения не могут быть использованы для таких сообщений
0
Простите, я не совсем вас понимаю.

Исключения не прерывают выполнение, обычно… Это да… Но если у вас свой обработчик «error_handler», то вы можете и прервать. Я говорил именно про этот случай.

Почему исключения не могут использоваться для этих целей?

Можно написать «class ExceptionUserError extend Exceptions {}» и использовать его для обозначения ошибок или неверно введённых данных от пользователя.

Почему нет?

PS Просто вам кажется что идеалогически исключения должны работать лишь там, где произошла действительная ошибка. Что вообщем-то правильно, но не всегда удобно.
0
Можно сделать все что угодно и как угодно. Но вы ведь придерживаетесь каких-то принципов, правил и идиом при написании программ? Так вот, исключения — для исключительных ситуаций — это принцип и правило и идиома :).

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

2. Попробуйте такой «финт ушами».
set_error_handler("UserErrorHandler");

function UserErrorHandler($errno, $errstr, $errfile, $errline){
switch ($errno) {
case E_USER_ERROR:
trigger_error("*USER ERROR* [$errno] $errstr
\n".
" User error on line $errline in file $errfile
\n");
exit(1);
case E_USER_WARNING:
print("*USER WARNING* [$errno] $errstr
\n".
" User warning on line $errline in file $errfile
\n");
break;
case E_USER_NOTICE:
print("*USER NOTICE* [$errno] $errstr
\n".
" User notice on line $errline in file $errfile
\n");
break;
}
return true;
}



PS Да, я понимаю что это идеологически не верно, но чёрт возьми, на мелких проектах это очень удобно :)
0
Главное, чтоб то, что удобно на мелких проектах не вошло в привычку.
0
Нелогично. Исключение, как средство сообщения об исключительных ситуациях должно использоваться только в последнем случае. Во-первых — потому что такова суть исключений, во-вторых, потому что некорректный ввод не является исключительной ситуацией (это вполне ожидаемая ситуация). И в-третьих — потому что поведение все таки может быть разное: самый простой пример — только в последнем случае мы пишем в лог о серверной ошибке, для того, чтобы позже с ней разобраться
+7
>перебрасывает его с помощью throw
еще надо отметить, что перебрасывать надо именно само исключение, а то некоторые говнокодят catch (Exception $e) {throw new Exception($e->getMessage());}
0
дотнетом вообще не владею.
это оно? it.toolbox.com/blogs/coding-dotnet/innerexception-14200
если я правильно понял, то это хранения одного эксепшена в другом, что можно вполне организовать и на пхп.
только не запутывание кода ли это? если есть, например, 3 уровня, 1й ловит второй, а 2й — 3й, и каждый перебрасывает свой эксепшн, то 1й уровень о 3м знать не должен, а то банально инкапсуляция нарушается
+1
Не совсем, все несколько хитрее.

Оборачивать можно внутренние эксепшены в том случае, если их выбрасывание наружу ничего не скажет о сути произошедшей ошибки. Например, если у нас в процессе логина вычисляется какой-нибудь хитрый хеш, и оттуда появляется DivisionByZeroException — мы не будем выбрасывать его наружу (какое нафиг деление на ноль в логине?) — мы обернем его в FaultAutentificationException с пояснением и выкинем.
0
а какая польза для верхнего уровня от этого? или там как-то автоматически разворачивается стектрейс?
0
Исключительно в прозрачности обработки исключительны ситуаций.
0
Если даже и нет, Вы можете создать свой класс, наследованный от Exception, который будет в конструкторе принимать вложенное исключение.
Мне вот больше интересно, а в PHP можно получить стектрейс исключения?
//
(я тоже .NET разработчик)
+4
В общем то статья не о PHP, так как стратегия обработки исключений одинакова для многих языков.
А вообще спасибо, для себя много не узнал нового, но для других будет очень полезно.
0
А как кстати с производительностью при перехвате исключений? как будет быстрее вернуть какой-то параметр и обработать его или словить исключение?
0
Быстрее вернуть параметр, естественно. Исключение — это медленно.
0
М… это в php так? я могу сказать что в питоне — если есть часто вызываемая ф-я где вы время от времени(не очень часто) бросаете исключения и ловите их, то это будет быстрее чем постоянно проверять что же вернула функция.
+1
Если не часто — то нормально. Просто следует помнить, что исключение не может быть вариантом штатного завершения функции.
+3
Если у исключения происходят настолько часто, что влияют на производительность — надо менять логику приложения.
+1
Согласен, сам недавно сталкивался с такой проблемой… главное вовремя понять что исключения не надо пихать во все щели :) с исключениями было 15-20% загрузки процессора, без — ближе к 1%. прочувствуй разницу, как говорится.
0
Абсолютно согласен, использование исключений должно быть прежде всего уместным.
+2
и ещё с форума, как раз обсуждаем
phpclub.ru/talk/showthread.php?s=&threadid=114258&rand=12
-6
Важно понимать, что механизм исключений по своей сути недалеко ушел от всеми любимого GOTO. На тему «неправильно введённые логин или пароль — это не исключительная ситуация!» — весьма спорное утверждение. Неправильно заполненная форма — это исключительная ситуация или нет?
+3
кроме как переход в другую точку кода, что ещё у них общего?
-2
А кроме перехода в другую точку кода, в чем их отличие?
0
goto определяет, куда будет передано управление. throw — нет.
с тем же успехом можно сказать, что и любая управляющая структура — тоже недалеко ушла от goto. тот же if — он тоже изменяет течение программы.
+1
Форма — это вообще не ситуация, это набор данных, введённых пользователем.

В случае, если конечно, класс/метод/функция, обрабатывающие эти данные, не способны правильно обработать неверные данные, то для них это исключительная ситуация. Правда, это довольно странно.
0
Пример:

try {

Signup(R(«username», «email», «capcha»))->validate()->register();

} catch (IJsonable $e) {

JSON(Fail($e));

}

Напишите мне это без использования исключений с проверкой состава полей, их содержимого и попыткой создания записи в БД.
-1
да легко %-)

$user->assign( array('usrname','email','captcha'), $request )->register()->log->success
and $response->content= array( 'message' => 'registered', 'user' => $user )
or $response->content= array( 'message' => 'error', 'log' => $user->log )
;
0
Пардон, а Data_Access_Exception — это какой стандарт именования функций?
0
я такого мнения что исключения нужно использовать только тогда когда в программе происходит ошибка коотрая «никогда» не должно произойти — вот это исключительная ситуация. Остальное все можно обработать на уровне кодов ошибок и тд

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

Однако если у нас помер запрос в методе query (ну вот кто-то забыл отфильтровать кавычки), то это просто внештатная ситуация, можно вернуть например false и выдать ошибку юзеру мол извините, на сервере проблемы. однако это ни в коей мере не мешает работе приложения, оно просто уйдет в другую ветку логики
+1
Согласен.
И применимо это не только для PHP, но и для других языков, работающих с исключениями.
UFO landed and left these words here
0
Лично мне удобнее классы исключений определять там, где они используются. Например, Model_Exception может выбрасываться в классе Model_User или Model_Product_Type. А именование исключений в обратном порядке вынуждает меня дублировать структуру каталогов. Мне это неудобно.
UFO landed and left these words here
0
Вот с этим я с некоторой натяжкой могу согласиться. Хотя в моей практике такого небыло. Exception всегда на нижнем уровне. Но и такого, что в каталоге только Exception, тоже небыло.
UFO landed and left these words here
UFO landed and left these words here
UFO landed and left these words here
0
Как ни крути, но вряд ли люди будут создавать неймспейсы Application, Logic, Data\Access только для того, чтобы сложить в них исключения.
UFO landed and left these words here
-3
Так ли часто мы пишем библиотеки на PHP? Столько уровней, прямо операционная система, а не сайт.
В 90%+ процентах случаев паттерна MVC с головой, а необходимые библиотеки давно написаны.

И хотя статья правильно описывает работу с исключениями, но она больше бы подошла к разработке на java а не php. Особенно, учитывая еще и тот вариант, что в PHP есть fatal_error-ы, которые не являются исключениями, не перебрасывваются никуда и тупо стопорят исполнение программы на месте. Поэтому исключения в php сейчас выглядят как костыль, который есть для галочки.
UFO landed and left these words here
0
у тебя все формы состоят из одного единственного поля, что ты кидаешься исключениями?
UFO landed and left these words here
0
ещё есть аутентификация, права, целостность данных в базе и прочие необходимые проверки. они тоже примёрживаются в одно исключение?
UFO landed and left these words here
0
«одного типа» — понятие абстрактное.
при формировании респонса нужно выдавать все ошибки (строка вместо числа, идентификатор уже занят, нет прав на использование хтмл), а не только первого попавшегося типа…
UFO landed and left these words here
0
нет прав на содание сущности — нужно выдавать форму перелогина и ошибки в данных.
UFO landed and left these words here
0
Отличный способ. Кстати говоря, в Symfony, начинаю с 1.1 валидация организована примерно так же.
UFO landed and left these words here
UFO landed and left these words here
0
ошибка подключения к базе данных является исключительной ситуацией только для метода подключения.
зачем высокоуровневому модулю знать обо всех низкоуровневых, и исключениях, которые могут там возникнуть?
+1
Гм-гм… то есть, если к БД подключиться не удалось, уровень работы с БД должен проворчать типа «блин, нет базы. Ну и фиг с ней, будем работать на файлах и вообще, у меня будет своя база с блэкджеком и шлюхами!» Так что ли?
0
да, представь себе. приложение не должно падать только от того, что одна база вдруг отвалилась.
0
А мне казалось, что это зависит, в первую очередь от контекста использования. При чтении данных такой финт еще может прокатить, но при модификации — уж лучше сразу под поезд.
0
ага, расскажи это пользователю, который 30 минут набирал текст…
+2
Эх, хороши исключения, да вот в PHP они реализованы с изъяном — библиотечные функции, а так же различные исключительные ситуации вроде 1 / 0 или null->foo() реализованы не с помощью исключений. Конечно, есть способ с set_error_handler и выкидыванием, но опять же, он почему-то не всегда работает. Например, в том же случае с null->foo обработчик, установленный с помощью set_error_handler почему-то не срабатывает. А это значит, что вместо исчерпывающего стектрейса я получаю невнятное сообщение всего лишь с одним номером строки.
-3
Статья хорошая.

Расскажу свой способ определения, когда стоит выбрасывать исключение а когда нет: когда я пишу код, то предполагаю что все методы должны возвращать только один любой тип данных (сами определяем какой: Объект определенного типа, массив, число, строку и т.д.) Если по каким-то причинам в методе нужно вернуть еще что-то, или вернуть другой тип данных — выбрасываем исключение
UFO landed and left these words here
0
Интересно, если нужно ролбэкнуть транзакцию, вы тоже плюете и заканчиваете выполенение кода? Не смешите, исключения выбрасываются для того чтобы их обработать.

По поводу «данные должны быть всегда те, которые были запрошены» — метод должен возвращать тот тип данных, который он продекларировал, а не менять его в зависимости от настроения. Если каждый метод в вашем коде возвращает разные типы данных, то лучше подумайте как этого избежать. Тогда отпадет вопрос и с исключениями
UFO landed and left these words here
-1
А с параметрами передаваемыми по ссылке или out параметрами методов вы не знакомы?
0
Спасибо, статья действительно очень познавательна, но есть вопрос:
Методы класса не должны перехватывать исключения, сгенерированные другими методами этого же класса. Библиотека вообще ничего не должна знать о том ...

Вот смотрите, у меня есть класс, который читает информацию из файла. Если, вдруг, что-то там происходит, я вызываю исключительную ситуацию. Но тут вопрос, если не обрабатывать её просто в классе, типа:
try
readData
except
print error
А если отлавливать исключение каждый раз, когда нужно прочитать какой-то параметр из файла? Как здесь быть: Оставить отловление исключений в том классе, или испортить читабельность кода?
+1
Я бы еще добавил рекомендацию написать для приложения обработчик Unhandled Exceptions, который обязательно должен вести лог необработанных исключений. Часто об этом забывают поначалу.
0
Ничего =). Зато я на него внимания обратил и действительно задумался. Спасибо.
UFO landed and left these words here
0
Небольшой но содержательный и полезный пост! Очень здорово было бы рассказать о применении эксцепшенов в «рядовых» ситуациях (ну в стиле «Избавляемся от «if(!$db) die('...');»», чтобы задать вектор мышления у людей, не пользовавшихся ими до этого.
0
Не думаю, что достаточно — хоть там и есть примеры, но…
Всё приходит с опытом, и если он есть, почему бы им не поделиться?
+2
В крупном приложении необходимо использовать свои собственные классы исключений (унаследованные от встроенного Exception)

В PHP есть не только встроенный Exception, но и еще много разных Excception'ов. В числе прочих там есть и LogicException. И свои классы исключений иногда лучше наследовать от подходящего базового класса (ну и, само собой, вовсю использовать собственно все предоставляемые «бесплатно» встроенные классы, а не только Exception).
Only those users with full accounts are able to leave comments.  , please.