Я задавался вопросом, совместно используют ли люди свои лучшие практики / стратегии относительно обрабатывания исключений и ошибок. Теперь я не спрашиваю, когда выдать исключение (это был throroughly, которому отвечают здесь: ТАК: Когда выдать Исключение). И я не использую это для своего потока приложения - но существуют законные исключения, которые происходят все время. Например, самым популярным был бы ActiveRecord:: RecordNotFound. Каков был бы лучший способ обработать его? DRY путь?
Прямо сейчас я делаю большую проверку в моем контроллере, итак, если Post.find(5)
Ноль возвратов - я проверяю на это и бросаю сообщение "молния". Однако, в то время как это очень детализировано - это является немного громоздким в некотором смысле, что я должен проверить на исключения как этот в каждом контроллере, в то время как большинство из них является по существу тем же и имеет отношение к записи, не найденной или связанным записям, не найденным - такой как также Post.find(5)
не найденный или при попытке отобразить комментарии, связанные с сообщением, которое не существует, который выдал бы исключение (что-то как Post.find(5).comments[0].created_at
)
Я знаю, что можно сделать что-то вроде этого в ApplicationController и перезаписать его позже в конкретном контроллере/методе для получения большей детализированной поддержки, однако который был бы надлежащим способом сделать это?
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordInvalid do |exception|
render :action => (exception.record.new_record? ? :new : :edit)
end
end
Также это работало бы в случае, если Post.find(5)
не найденный, но что относительно Post.find(5).comments[0].created_at
- Я подразумевал, что не могу выдать полноценное исключение, если сообщение существует, но не имеет комментариев, правильно?
Для суммирования до сих пор, я делал большую ручную проверку с помощью if/else/unless или случай/когда (и я признаюсь, иногда начинают/спасают), и проверяющий на ноль? или пустой?, и т.д., но там получен, чтобы быть лучшим способом, которым это кажется.
ОТВЕТЫ:
@Milan: Привет Милан спасибо за ответ - я соглашаюсь с тем, что Вы сказали, и я думаю, что неправильно использовал исключение слова. То, что я имел в виду, - то, что прямо сейчас я делаю много вещей как:
if Post.exists?(params[:post_id])
@p = Post.find(params[:post_id])
else
flash[:error] = " Can't find Blog Post"
end
И я делаю много этого вида "обработки исключений", я стараюсь не использовать, начинают/спасают. Но мне кажется, что это - достаточно общий результат/проверка/ситуация, что должна быть СУШИЛКА способ сделать это, не так ли? Как Вы сделали бы этот вид проверки?
Также, как обработал бы его в этом случае? Скажем, Вы хотите отобразиться, комментарий создал дату в Вашем представлении:
Last comment for this post at : <%= @post.comments[0].created_at %>
И это сообщение не имеет никаких комментариев. Можно сделать
Last comment for this post at : <%= @post.comments.last.created_at unless @post.comments.empty? %>
Вы могли сделать регистрацию контроллера. И т.д. Существует несколько способов сделать это. Но что "лучший" путь состоит в том, чтобы обработать это?
Тот факт, что вы делаете много ручной проверки на исключения, говорит о том, что вы просто не используете их правильно. На самом деле, ни один из ваших примеров не является исключительным.
Что касается несуществующего поста - вы должны ожидать, что пользователи вашего API (например, пользователь, использующий ваш веб через браузер) будут спрашивать о несуществующих постах.
Ваш второй пример (Post.find(5).comments[0].created_at) также не является исключительным. Некоторые посты просто не имеют комментариев, и вы знаете об этом заранее. Так почему это должно вызывать исключение?
То же самое происходит и с примером ActiveRecord::RecordInvalid. Просто нет причин обрабатывать этот случай с помощью исключения. То, что пользователь вводит в форму некорректные данные, является вполне обычным делом, и в этом нет ничего исключительного.
Использование механизма исключений для подобных ситуаций может быть очень удобным в некоторых ситуациях, но это неправильно по причинам, указанным выше.
С учетом сказанного, это не означает, что вы не можете DRY код, который инкапсулирует эти ситуации. Существует довольно большой шанс, что вы сможете это сделать, по крайней мере, в некоторой степени, поскольку это довольно распространенные ситуации.
Итак, что насчет исключений? Ну, первое правило действительно таково: используйте их как можно реже.
Если вам действительно необходимо их использовать, то существует два вида исключений в целом (как я это вижу):
исключения, которые не нарушают общий рабочий процесс пользователя внутри вашего приложения (представьте себе исключение в процедуре генерации миниатюр картинки профиля), и вы можете либо скрыть их от пользователя, либо просто уведомить его о проблеме и ее последствиях, когда это необходимо
исключения, которые вообще не позволяют пользователю использовать приложение. Это последнее средство, которое следует обрабатывать через внутреннюю ошибку сервера 500 в веб-приложениях.
Я склонен использовать метод rescue_from
в ApplicationController только для последнего, поскольку для первого типа есть более подходящие места, а ApplicationController, как самый верхний из классов контроллеров, кажется правильным местом для возврата в таких обстоятельствах (хотя в настоящее время какой-нибудь Rack middleware может быть даже более подходящим местом для размещения такой вещи).
-- EDIT --
Конструктивная часть:
Что касается первого, то я бы посоветовал начать использовать find_by_id вместо find, поскольку он не выбрасывает исключение, а возвращает nil в случае неудачи. Тогда ваш код будет выглядеть примерно так:
unless @p = Post.find_by_id(params[:id])
flash[:error] = "Can't find Blog Post"
end
что гораздо менее болтливо.
Другая распространенная идиома для DRYing такого рода ситуаций - использовать контроллер before_filters для установки часто используемых переменных (например, @p в данном случае). После этого ваш контроллер может выглядеть следующим образом
controller PostsController
before_filter :set_post, :only => [:create, :show, :destroy, :update]
def show
flash[:error] = "Can't find Blog Post" unless @p
end
private
def set_post
@p = Post.find_by_id(params[:id])
end
end
Что касается второй ситуации (несуществующий последний комментарий), то одним из очевидных решений этой проблемы является перенос всего этого в хелпер:
# This is just your way of finding out the time of the last comment moved into a
# helper. I'm not saying it's the best one ;)
def last_comment_datetime(post)
comments = post.comments
if comments.empty?
"No comments, yet."
else
"Last comment for this post at: #{comments.last.created_at}"
end
end
Затем, в ваших представлениях, вы просто вызовете
<%= last_comment_datetime(post) %>
Таким образом, крайний случай (пост без комментариев) будет обрабатываться в своем собственном месте и не будет загромождать представление.
Я знаю, все это не указывает на какой-либо шаблон для обработки ошибок в Rails, но, возможно, с некоторыми рефакторингами, подобными этим, вы обнаружите, что большая часть необходимости в какой-либо стратегии для обработки исключений/ошибок просто исчезнет.
Исключения составляют исключительные обстоятельства. Плохой пользовательский ввод обычно не является исключением; во всяком случае, это довольно распространенное явление. Когда у вас действительно есть исключительные обстоятельства, вы хотите дать себе как можно больше информации. По моему опыту, лучший способ сделать это - строго улучшить обработку исключений на основе опыта отладки. Когда вы сталкиваетесь с исключением, первое, что вам нужно сделать, это написать для него модульный тест. Второе, что вам нужно сделать, это определить, есть ли дополнительная информация, которую можно добавить к исключению. Дополнительная информация в этом случае обычно принимает форму перехвата исключения выше по стеку и либо его обработки, либо создания нового, более информативного исключения, которое имеет преимущество дополнительного контекста. Мое личное правило состоит в том, что мне не нравится перехватывать исключения на более чем трех уровнях стека. Если исключение должно пройти дальше этого, вам нужно поймать его раньше.
Что касается выявления ошибок в пользовательском интерфейсе, операторы if
/ case
полностью подходят, если вы не вкладываете их слишком глубоко. Вот когда такой код становится трудно поддерживать. Вы можете абстрагироваться от этого, если это станет проблемой.
Например:
def flash_assert(conditional, message)
return true if conditional
flash[:error] = message
return false
end
flash_assert(Post.exists?(params[:post_id]), "Can't find Blog Post") or return