Статьи

Круглый стол № 1: Должны ли когда-либо использоваться исключения для управления потоком?

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

Круглый стол? Вы когда-нибудь выходили на ужин с друзьями-разработчиками и участвовали в долгих обсуждениях / дебатах по конкретным программам? Хорошо, теперь мы переносим этот формат в Nettuts +. Группа друзей, случайно обсуждающих данную тему.

Чаба Паткос

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

Паван Подилла

Существует три широких категории «ненормальных» состояний в программном обеспечении, как они определены Бертраном Мейером: отказ, исключение и ошибка.

Отказ — неспособность программного элемента выполнять свою функцию. Исключением является ненормальное состояние программного обеспечения. Ошибки происходят из-за неудовлетворенного ожидания / спецификации.

Ошибки вызывают сбои и распространяются через исключения.


Джонатан Каттрелл

Мне нравится идея объяснения того, что мы считаем исключениями. Моя идея исключения — именованный класс ошибок, который может быть обработан в операторе try-catch.

Так:

1
2
3
4
5
try {
  something();
} catch(SomeErrorType e){
  return respondTo(e);
}

Ловля исключений, как упоминал Чаба, спроектирована как непредвиденный случай. Они часто являются результатом неправильного ввода или неудачной передачи данных.

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


Паван Подилла

Таким образом, по сути, исключения являются «абстракцией» исключительно для моделирования ненормальности. Существует естественный поток, определяемый большинством языковых сред выполнения, начинающийся с языка «C», близкого к металлическому, или чего-то столь же динамического, как «JavaScript» или «Ruby», который заключается в полном выходе из среды выполнения, если не обрабатывается, через «исключение». обработчик «.

Чаба Паткос

Хорошее научное определение Павана. Джонатан, я согласен. Часть кода / программы / модуля, которая генерирует исключение, должна остановить его выполнение.

Клиент этого кода может использовать try-catch, чтобы предотвратить автоматическое распространение исключения (так как большинство языков распространяет его). Однако я считаю, что try-catch следует использовать только для логики, которая требуется для обработки этих неожиданных ситуаций. Например, прерванная передача данных может быть повторена при возникновении исключения. Или, некоторые исключения не влияют на результат (например, когда вы хотите создать каталог, который уже существует). В этом случае это может быть просто подавлено.


Габриэль Манрикс

Так в каком случае использования мы говорим о том, где он используется для потока управления?

Джонатан Катрелл

Может ли кто-то, кто выступает за исключения как поток управления, предоставить нам пример кода, который концептуально является приемлемым сценарием для вашего мнения?

Аарон Куземчак

Вот пример, который породил пламя войны с Reddit, что привело к этой дискуссии.

Габриэль Манрикс

Я думаю, что использование там кажется вполне допустимым. Является ли аргумент против этого, что это не так «оптимизировано». Потому что это намного более многословно, когда дело доходит до чтения вашего кода, и вам не нужно иметь дело с числами или константами в операторе switch.

Паван Подилла

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

Аарон Куземчак

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

Паван Подилла

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

Габриэль Манрикс

Это мое мнение, как хорошо. Исключения не должны относиться к приложению, как кажется, люди предлагали ранее. Приложение не должно заканчиваться. Это ошибка, связанная с конкретным классом.

Чаба Паткос

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

Габриэль Манрикс

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

Паван Подилла

Возвращаясь к первоначальной теме этого обсуждения, следует ли использовать исключения для обработки «потока управления»?

Чаба Паткос

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

Аарон Куземчак

Чаба, согласился. Хотя в веб-приложении мне бы хотелось иметь глобальный обработчик исключений, чтобы в качестве запасного варианта отображалась некоторая стилизованная страница «ошибки». И в PHP вы бы отключили display_errors в работе.

Паван Подилла

Поскольку у нас, похоже, есть общая основа для того, когда исключение имеет смысл (то есть для уведомления о плохих ситуациях), для нормального потока управления это не первое готовое решение. Проще сопоставить счастливый путь с помощью простых управляющих конструкций (if / else, while, switch / case и т. Д.).

Чаба Паткос

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

Аарон, точно. То, что вы сказали, не противоречит тому, что я сказал о ловле ошибок. Спасибо за указание на более подробное решение.


Габриэль Манрикс

Вот почему я думаю, что нам нужен пример, когда вы думаете, что исключения неправильны. Было бы весело, если бы мы все согласились, и это вопрос определения.

Джонатан Катрелл

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

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

Это также абстракция самой функции аутентификации.

Приемлемой альтернативой может быть возвращение объекта входа, над которым вы можете запустить элемент управления if / else.

1
2
3
4
5
6
$attempt = Sentry::authenticate($credentials);
if ($attempt->status == «success»){
   $user = $attempt->user;
} else if ($attempt->status == «no_password») {
 // etc
}

Паван Подилла

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

Джонатан Катрелл

На самом деле, дело не в том, являются ли исключения «неправильными» в данном сценарии. Это о том, что лучше для работы.

Габриэль Манрикс

Но Джонатан, конечно, если бы у вас был большой список случаев, как в примере с аутентификацией, наличие зарегистрированного обработчика ошибок было бы быстрее и более подробным, чем просеивание каждого параметра?

Чаба Паткос

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

Например, в форме аутентификации, если имя пользователя должно быть адресом электронной почты, а пользователь вводит что-то еще, это может быть исключением.

Но если пользователь правильно заполняет форму и просто комбинация пользователь / пароль не совпадает, это скорее всего другой случай, а не исключение.


Джонатан Катрелл

Тогда, возможно, возвращаемый объект несет сообщение и статус.

1
2
3
4
5
6
if ($attempt->status == «success»){
   $user = $attempt->user;
} else if ($attempt->status == «failure»){
   echo $attempt->message;
} else {
   echo «Something unexpected happened. Please try again.»

Чаба Паткос

Джонатан, или ты мог выбросить ошибку в последнем!

Паван Подилла

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

NodeJS сделал себе имя в этом отношении, и, в этом отношении, любую «управляемую событиями» среду выполнения, такую ​​как EventMachine или Twisted.


Габриэль Манрикс

Так ты за это, Паван?

Чаба Паткос

Паван, я не очень знаком с асинхронным кодом. Можете ли вы объяснить, как исключения работают там более подробно?

Паван Подилла

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

Csaba, вот классический пример асинхронного кода:

Допустим, вы пытаетесь прочитать файл: /usr/local/app.log . То, как вы делаете это в Node:

1
2
3
4
5
6
7
8
var fs = require(‘fs’);
 
fs.readFile(‘/usr/local/app.log’, function(err, doc){
 
if (err) { console.log(‘failed to read’);
 
// process file
});

Из-за обратного вызова вы не ставите try / catch вокруг вызова. Вместо этого вы используете стиль обратного вызова для обработки результата. Я надеюсь, что это проясняет. В общем, любая операция, которая не может быть выполнена синхронно, будет иметь API стиля обратного вызова для обработки результатов.


Габриэль Манрикс

Я все еще не думаю, что кто-то упомянул пример, где исключения используются неправильно. Я не совсем понимаю встречный пример.

Аарон Куземчак

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

Джонатан Катрелл

Паван, очень верно. Я думаю, концептуально идея та же самая, однако, когда мы говорим о таких вещах, как реализация AJAX в jQuery, в которой вы можете определить .error() , .success() и т. Д. Это абстракция от того, что действительно происходит ( проверка объекта XHR).

1
2
3
4
5
$.get(«http://example.com/something/remote.json»).success(function(data){
   // do something with data
}).error(function(res){
  // do something different;
});

Хотя это и не исключение, а if / else, оно все же решает проблему: обрабатывает как успешные, так и неуспешные запросы AJAX. Однако сама реализация jQuery не является try / catch, потому что она асинхронная.


Паван Подилла

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
var auth = require(‘authenticator’);
var eventbus = EventBus.global();
 
auth.login(«pavan», «pwd», function(err, result) {
   if (err) {
      var details = {
         username: «pavan»,
         error: err
      };
 
      eventbus.put(«Authentication failed», details);
 
      return;
   }
 
   eventbus.put(«Authentication successful!!»);
});

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


Джонатан Катрелл

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

Габриэль Манрикс

Разве этот код не проходит мимо? Где-то должен быть какой-то код, который выполняет небезопасную операцию и должен обрабатывать исключение и устанавливать ошибку.

Паван Подилла

Габриэль, это именно то, что происходит и является примером потока управления через различные границы программного обеспечения.

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

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


Габриэль Манрикс

Поправьте меня, если я ошибаюсь, но я понимаю, что вы, ребята, говорите, что следующее плохо:

01
02
03
04
05
06
07
08
09
10
11
12
try
{
    $conn = connectToDatabase($credentials);
}
catch (NoDbAtThatUriException $e)
{
    //handle it
}
catch (LoginException $e)
{
    //handle it
}

Паван Подилла

Габриэль, если вызов connectToDatabase является синхронным, исключения будут работать. В противном случае вам нужны обратные вызовы. Также может быть несколько форм сбоев (разные классы исключений). Вас волнует, что это за сбой, особенно если вы где-то его регистрируете?

Джонатан Катрелл

Кроме того, как насчет неисследованных исключений в этом примере? (Не уверен, что они будут в данный момент, но не должен ли быть способ поймать все?) В блоке if / else финал else обрабатывает все оставшиеся случаи.

Паван Подилла

Кроме того, более полезно понимать, как вы распространяете ошибки в программном стеке. Я предполагаю, что LoginException должен быть уведомлен пользователю?

Габриэль Манрикс

Потенциально все они могут следовать протоколу и иметь такую ​​функцию, как $e->handleIt() .

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

1
2
3
4
5
catch (NoDbAtThatUriException $e)
{
    $credentials->uri .= «:3065»;
    //recall original function here
}

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


Аарон Куземчак

В PHP вы можете использовать set_exception_handler() для обработки любых необработанных исключений. Вы также можете использовать блок catch (Exception $e) в качестве последнего оператора catch в потоке управления, поскольку все исключения в PHP расширяют собственный класс Exception .

Габриэль Манрикс

Я думаю, что одним из преимуществ использования исключений вместо оператора if является то, что вы получите подробную ошибку, если вы забудете ее учесть, тогда как в if оператора if это будет просто по умолчанию для else .

Чаба Паткос

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

Аарон Куземчак

Все еще не хороший пример, но вот демонстрация обработки различных сценариев:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
try {
    $user = $auth->login($credentials);
} catch (InvalidUsernameException $e) {
    // Redirect to login page with error message
    // Could even use $e->getMessage()
} catch (InvalidPasswordException $e) {
    // If this is the 5th attempt, redirect to reset password page
    // Otherwise, redirect to login page with error message
} catch (AccountLockedException $e) {
    // Redirect to special error screen explaining why they aren’t allowed
} catch (Exception $e) {
    // Fallback for everything else
    // Log that we had an unexpected exception
    // Redirect to error page or something
}

Паван Подилла

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

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

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


Чаба Паткос

С другой стороны, как отметил Габриэль, исключения — это отличный способ подробно рассказать о проблемах. Если вы решите вообще их не использовать и просто используете return в своих функциях для передачи как хороших, так и плохих сценариев, вы можете и с некоторыми весьма непредсказуемыми функциями. Это в основном проблема с динамически типизированными языками, такими как PHP, где даже некоторые встроенные функции имеют эту проблему. По какой-то причине люди, которые их сделали, решили, что, например, функция возвращает строку или массив в случае успеха, и false или -1 в случае неудачи.

Аарон Куземчак

Pavan, PHP допускает вложенные исключения; так что вы могли бы бросить другое и сослаться на предыдущее исключение, что позволит более детально рассмотреть происходящее.

Чаба Паткос

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

Габриэль Манрикс

Csaba, но зачем вам вкладывать контрольные операторы в исключения? В то время как вы на это, было бы лучше просто обрабатывать каждый в отдельности (если, конечно, они имеют одинаковый результат)?

Паван Подилла

Я думаю, что я был немного неясен об обработке исключений в моем предыдущем посте. Я пытался подчеркнуть, что обработчики исключений распространяются на несколько уровней вашего программного стека. В этом случае исключение на уровне вашей БД, которое в конечном итоге необходимо распространить на пользователя в виде сообщения «не удалось войти / подключиться».

Должно быть несколько обработчиков исключений, прежде чем он достигнет пользователя, начиная с уровня вашей БД до вашего веб-уровня и, наконец, отображая его на клиенте. Если вы заметили поток управления, который в настоящее время распределен по многим слоям, это может быть трудно для плавного обращения. Какой у вас был опыт в этом отношении?


Габриэль Манрикс

Я определенно не позволю исключать исключения из класса. Вот где переменная состояния может пригодиться. Это то, что я имел в виду ранее, когда я упомянул всю вещь » попусту «. Я согласен с тем, что в этот момент важно использовать операторы управления и переменные состояния. Но, внутренне, исключения хороши для потока кода.

Чаба Паткос

Габриэль, я хотел указать, что я буду генерировать исключения только тогда, когда происходит что-то исключительное. Пользователь, который не смог правильно указать свое имя пользователя и пароль, не является исключением в моих глазах. Это просто другая ситуация, с которой приходится сталкиваться моей программе.

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

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

1
2
3
4
5
try {
  $filesystemHandler->createDirectory(‘/tmp/dirname’);
} catch (DirectoryExistsException $e) {
  return true;
}

Конечно, будут распространяться другие исключения, такие как NoPermissionToCreateDirectory . Я думаю, что это хороший пример управления потоком на основе исключения.


Паван Подилла

Единственный поток управления, который мы видели до сих пор, — это случай сбоя. Будут ли исключения по-прежнему использоваться для нормального потока управления? Например, пакетный процесс, который применяет некоторые преобразования к изображению. Для конвейера может быть несколько преобразований изображения. Я начинаю со 100 изображений, и после пакетного процесса получается 100 преобразованных изображений. Как вы можете смоделировать эту программу?

Габриэль Манрикс

Паван, куда бы ты положил контрольное заявление? Вы говорите об изображении в -> процессе -> изображение вне?

Паван Подилла

Преобразование на изображении может быть одним из, скажем, десяти различных типов. Каждое изображение имеет спецификацию о серии преобразований, которые должны быть выполнены, и в каком порядке. Изображение также имеет путь, указывающий на файл. Блок «process» принимает по одному изображению за раз и применяет эти преобразования по порядку. В конце преобразованное изображение собирается в некотором выходном местоположении.

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

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


Чаба Паткос

Упрощенно, не со всеми указанными вами деталями, вот мой первый взгляд на проблему обработки изображений:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class ImageTransformer {
 
   private $images = [];
   private $transformer;
   private $failedTransforms = [];
 
   function __construct($images) {
      $this->images = $images;
      $this->transformer = new TransformOneImage();
   }
 
   function transformAll() {
      foreach ($this->images as $image) {
         try {
            $this->transformer->transform($image);
         } catch (CanNotTranformImageException $e) {
            $this->failedTransforms[] = $e->getMessage();
         }
      }
 
      if (!emptyArray($this->failedTransforms)) {
         // code to notify user here
         // and finally
         return false;
      }
      return true;
   }
 
}
 
class TransformOneImage {
 
   function transform($image) {
      $transformedImage = // do image processing here
 
      if (!$transformedImage) {
         throw new CanNotTranformImageException($image);
      }
 
      return $tranformedImage;
   }
 
}

Габриэль Манрикс

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

Джонатан Катрелл

Это довольно интересно, Габриэль. Я думаю, что одной опасностью является повторение цикла без какого-либо участия пользователя, но я предполагаю, что это всего лишь пример, а не реальная практика.

Реальный вопрос здесь действительно о том, какие случаи считаются исключениями. Мы могли бы легко переписать это, чтобы локализовать ошибки, что уменьшило бы время, необходимое для выявления источника ошибок. Конечно, в этом примере не составит труда определить источник.


Габриэль Манрикс

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

Бен Корлетт

Будучи одним из главных авторов для Sentry, я подумал, что я добавлю свои два цента о том, почему мы сделали то, что мы сделали в Sentry, и мои мысли об Исключениях в потоке управления.

При аутентификации пользователя, по сути, есть два основных способа, которыми мы могли бы его решить:

  1. попробуй поймать
  2. если еще

Позвольте мне объяснить способы, которыми мы могли бы заняться, если / еще:

1
2
3
4
5
6
7
8
if (Sentry::authenticate($args))
{
    // Great, go ahead
}
else
{
    // Right, something went wrong, but what?
}

Но что произойдет, если мы захотим узнать немного больше информации, чем простое « Нет, вам не разрешено »? Возвращение объекта — отличный подход:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$response = Sentry::authenticate($args);
 
if ($response->hasError())
{
    switch ($response->getError())
    {
        case ‘login_required’:
            $message = ‘You didn\’t enter any login details.’;
            break;
        case ‘password_required’:
            $message = ‘You didn\’t enter a password.’;
            break;
        case ‘user_not_found’:
            $message = ‘No user was found with those credentials.’;
            break;
 
        // And so on…
    }
 
    // Let’s pretend we’re working in L4
    return Redirect::route(‘login’)->withErrors([$message]);
}
else
{
    return Redirect::route(‘profile’);
}

Это имеет некоторые преимущества, в первую очередь из-за оператора switch:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
if ($response->hasError())
{
    switch ($response->getError())
    {
        // Consolidate errors
        case ‘login_required’:
        case ‘password_required’:
        case ‘user_not_found’:
            $message = ‘No user was found with those credentials.’;
            break;
 
        // And so on…
    }
 
    return Redirect::route(‘login’)->withErrors([$message]);
}
else
{
    return Redirect::route(‘profile’);
}

Однако исключения дают гораздо больший контроль, поскольку вы можете разрешить их обработку на любом уровне в вашем приложении. Исключения также могут расширять друг друга, в то время как все расширяют базовый класс Exception (очевидно, здесь речь идет о PHP).

Недостатком используемых исключений ( особенно в Sentry ) является их многословие. Это связано с тем, что мы разделили различные компоненты в Sentry (groups / users / throttling), чтобы вы могли взять нужные компоненты и создать полностью потрясающую систему аутентификации. Таким образом, все, что принадлежит компоненту ‘users’ в Sentry, находится в пространстве имен Cartalyst\Sentry\Users . use Cartalyst\Sentry\Users\LoginRequiredException; простой способ уменьшить многословность — use ключевое слово use Cartalyst\Sentry\Users\LoginRequiredException; : use Cartalyst\Sentry\Users\LoginRequiredException; , Или, конечно, вы можете пойти дальше и добавить class_alias() для глобального псевдонима класса. Внезапно мы приводим детализацию (и с некоторыми практическими примерами):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
try
{
    // Set login credentials
    $credentials = array(
        ’email’ => ‘[email protected]’,
        ‘password’ => ‘test’,
    );
 
    // Try to authenticate the user
    $user = Sentry::authenticate($credentials);
}
catch (LoginRequiredException $e)
{
    // Or a «goto», take your pick
    return $this->userFail();
}
catch (PasswordRequiredException $e)
{
    return $this->userFail();
}
catch (UserNotFoundException $e)
{
    return $this->userFail();
}
catch (UserNotActivatedException $e)
{
    // Take to a page where the user can resend their activation email
    return Redirect::to(‘users/activate’);
}
catch (UserSuspendedException $e)
{
    return Redirect::to(‘naughty’);
}
catch (UserBannedException $e)
{
    return Redirect::to(‘naughty’);
}
catch (Exception $e)
{
    // Show a 500 page or something?
}
 
return Redirect::route(‘profile’);

Многословие — это один недостаток, который можно попробовать / поймать, но его можно уменьшить с помощью use (плохая формулировка там, верно?) И псевдонимов классов.

Давайте рассмотрим положительные стороны:

  • Логика может быть обработана на любом уровне приложения или через пользовательский зарегистрированный обработчик (по крайней мере, в PHP).
  • try / catch — это «низкий уровень». Я имею в виду это в том смысле, что они действительно не меняются. В PHP всегда есть $ e-> getMessage () и $ e-> getCode () (из-за наследования от «Exception»). Если я возвращаю объект (например, $ response-> hasError ()), разработчику необходимо знать предоставляемый API для этого объекта. Также объект может измениться в будущем. try / catch — это синтаксис, который не меняется. Это интуитивно понятно.
  • Единственная реальная альтернатива наличию нескольких уловов (с уловом) — это переключатель. Но многословие переключателя statemtn во многом совпадает с try / catch.
  • Смешивание true / false и try / catch в одном и том же утверждении является путаницей. Как хорошо сказал @philsturgeon: «Например, с помощью почтовой программы при отправке электронной почты может произойти много ошибок, поэтому вы хотите выбросить исключения, если электронная почта не может связаться с SMTP-сервером, если она не содержит адрес, если он не может найти установку sendmail, что угодно. Что, если у него нет действительного адреса электронной почты? Это исключение или он должен вернуть false и заставить вас искать код ошибки? Почему половина и половина? «
  • В PHP нет такой (реальной) вещи, как асинхронный. Прежде, чем вы все прыгнете мне на спину по поводу процессов порождения и всего этого, PHP действительно не поддерживает это. Я не понимаю, как использование обратного вызова может реально улучшить приложение (напоминание: я говорю на PHP) или взаимодействие с пользователем, так как вы можете добиться одинакового «прогресса» обратной связи (через приложение, опрашивающее ваш скрипт) на протяжении всего процесса что бы ни происходило в блоке try. 10 баллов за худшее объяснение, но я думаю, что точка все еще сталкивается. С помощью try / catch вы можете делать все то же самое, что и обратный вызов на однопоточном языке.

Я думаю, в конце концов, дело доходит до разных вариантов использования. Некоторые могут утверждать, что это то же самое, что «табуляция против пробелов», но я не верю, что это так. Я думаю, что есть сценарии, когда уместно if / else, но, если есть более одного возможного неуспешного результата, я считаю, что попытка / отлов обычно является лучшим подходом.

Вот еще два примера, которые мы могли бы обсудить:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function create_user($name)
{
    if (strlen($name) === 0) return false;
 
    if (user_exists($name)) return false;
 
    // This believe it or not returns a user 😛
    return super_magic_create_method($name);
}
 
// EXAMPLE ONLY, DON’T SHOOT ME
if ($user = create_user($_POST[‘name’]))
{
    // Hooray
}
else
{
    // What went wrong?!
}
 
function create_user($name)
{
    if (strlen($name) === 0) throw new InvalidArgumentException(«You have not provided a valid name.»);
 
    if (user_exists($name)) throw new RuntimeException(«User with name [$name] exists.»);
 
    // This believe it or not returns a user 😛
    return super_magic_create_method($name);
}
 
try
{
    $user = create_user($_POST[‘name’]);
}
catch (InvalidArgumentException $e)
{
    // Tell the user that some sort of validation failed
}
// Yep, catch any exception
catch (Exception $e)
{
 
}

И, для другого примера, в Laravel 4, есть класс проверки. Это работает так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
$rules = [‘name’ => ‘required’, ’email’ => ‘required|email’];
$validator = Validator::make(Input::get(), $rules);
 
if ($validator->passes())
{
    // Yay
}
else
{
    foreach ($validator->errors() as $error)
    {
 
    }
}

Что, если это сработало так?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
try
{
    $rules = [‘name’ => ‘required’, ’email’ => ‘required|email’];
    $validator = Validator::make(Input::get(), $rules);
    $validator->run();
}
catch (ValidationFailedException $e)
{
    foreach ($e->getErrors() as $error)
    {
 
    }
}
catch (InvalidRulesException $e)
{
    // You made bad rules
}

Просто пища для размышлений. Первый из них читается как «красивее», а второй — оптимальным. Есть мысли по этому поводу?


Габриэль Манрикс

Я согласен с Беном; всегда есть правильный и неправильный способ что-то сделать. Это не так, как каждое утверждение if теперь должно быть заменено исключением. Но они гораздо более «точные» и читаемые, чем, например. функция, которая возвращает -1 или false .

Джонатан Катрелл

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

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

Может быть, сценарий плохого сценария использования ловит исключения для ожидаемых и хороших сценариев. Я, конечно, согласен с тем, что выложил Бен, особенно когда дело доходит до того, что я теперь (не) официально обозначил товарным знаком блок Un-Information else ™ . (Вы можете ласково называть его «UEB ™».) Это плохое «общее» не имеет ничего хорошего, чтобы рассказать нам, и, к сожалению, поражено всем плохим, практически без регресса. Какой позор.

И, таким образом, мы можем столкнуться с ответом! Держать в курсе весь наш поток управления и использовать исключения для вещей, которые действительно являются исключениями.

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


Габриэль Манрикс

Я согласен с Джонатаном. Использовать исключения для хорошего случая было бы неправильно. Например:

1
2
3
4
5
6
7
8
try
{
    loginUser($creds);
}
catch (LoginSuccess $success)
{
   //store session and cookies
}

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

Также стоит добавить, что это зависит от того, что вы делаете — выполняете ли вы действие или что-то проверяете.

В моем Gist выше у меня был метод validate, и в коде я написал что-то вроде:

1
2
3
4
5
if ($transform->validate()) {
    //process
} else {
    //throw exception
}

Я мог бы выдать исключение внутри метода validate, но это было бы разрушительным для читабельности кода. Вы бы что-то вроде:

1
2
$transform->validate();
$transform->process();

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

Он также работает с синтаксисом, потому что вы можете сказать « if (x) then y », когда вы проверяете данные, и try { x } catch (a snag) для действий. Было бы грамматически неправильно менять их.


Аарон Куземчак

Я полностью согласен с тем, что исключения никогда не должны использоваться для достижения положительного результата. Для меня это сводится к этим двум сценариям:

  • В конкретной функции / методе есть несколько точек сбоя.
  • Необходимо различать эти точки отказа и обрабатывать их более чем одним способом.

Чаба Паткос

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

Я думаю, что проблема в том, что некоторые люди считают язык препятствием для выражения своих идей. Таким образом, сочетание if / else с try / catch затрудняет понимание кода.

Проблема, на мой взгляд, прямо противоположна. Код должен отражать концепции, которые вы хотите реализовать. Использование try / catch для всего, кроме счастливого пути, скрывает множество деталей логики выполнения. Все становится белым или черным.

С другой стороны, если вы используете if / else для случаев, когда ваше приложение идет по пути, являющемуся частью его логики (например, неправильное имя пользователя — пара паролей), а затем пытаетесь / перехватывает ситуации, когда ваше приложение попадает в неожиданное состояние. / невосстановимое состояние, было бы гораздо более очевидно, каковы возможные варианты поведения и пути выполнения.


Аарон Куземчак

Первоначальная причина, почему это заставило меня задуматься прежде всего, была из-за коленного рефлекса, « НЕТ » ответов.

  • Должен ли я когда-либо использовать JavaScript в качестве языка на стороне сервера? Нет, потому что это не то, для чего он был разработан.
  • Должен ли я использовать LESS для написания CSS? Нет, потому что это может кого-то запутать.
  • Можно ли использовать статические методы? Нет, это никогда не хорошо.

Все экстремально и не конструктивно.

Я думаю, что, представив обоснованные рассуждения с обеих сторон, мы приходим к еще более важному выводу: не осуждаем других разработчиков за то, что они не видят друг с другом себя. Если кто-нибудь из вас даст мне какой-нибудь код, я не буду жаловаться на ваше решение использовать исключения или нет. Я просто хочу, чтобы вы были последовательны в вашем подходе, а вам лучше прокомментировать это! 🙂

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


Паван Подилла

Я думаю, что у нас есть общее согласие относительно использования Исключений только для обработки ошибок / сбоев. Он никогда не должен использоваться для нормального потока управления.

Стоит отметить, почему исключения особенно подходят для распространения ошибок. Все дело в контексте и в том, чтобы этот контекст передавался туда, где лучше всего обрабатывать исключение. У большинства сред выполнения есть автоматический способ всплытия исключения, что делает очень удобным внедрение обработчиков исключений в нужных местах программного стека.

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


Вот что мы должны сказать по этому вопросу. Каковы ваши взгляды? Можно ли привести аргумент в пользу использования исключений для управления потоком?