Подтвердить что ты не робот

Какова наилучшая стратегия обработки исключений и ошибок в Rails?

Мне было интересно, смогут ли люди поделиться своими передовыми методами/стратегиями по обработке исключений и ошибок. Теперь я не прошу, когда нужно исключать исключение (здесь было сказано: SO: Когда нужно выбросить исключение). И я не использую это для потока приложений, но есть законные исключения, которые происходят все время. Например, самым популярным будет ActiveRecord:: RecordNotFound. Какой был бы лучший способ справиться с этим? Сухой способ?

Сейчас я делаю много проверки внутри своего контроллера, поэтому, если Post.find(5) возвращает Nil - я проверяю это и высылаю флеш-сообщение. Однако, хотя это очень гранулировано - это немного громоздко в некотором смысле, что мне нужно проверять такие исключения на каждом контроллере, в то время как большинство из них по существу одинаковы и связаны с записью, не найденной или связанной с ней, не найденной - такой поскольку либо 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/if или case/when (и я иногда припоминаю начало/спасение) и проверку на нуль? или пустой?, и т.д., но там должен быть лучший способ.

ОТВЕТЫ: ​​

@Milan: Привет Милан Спасибо за ответ. Я согласен с тем, что вы сказали, и я думаю, что неправильно использовал слово исключение. Я имел в виду, что сейчас я делаю много вещей, таких как:

if Post.exists?(params[:post_id])
    @p = Post.find(params[:post_id])
else
    flash[:error] = " Can't find Blog Post"
end

И я делаю много такого "обработки исключений", я стараюсь избегать использования start/rescue. Но мне кажется, что это достаточно общий результат/проверка/ситуация, что должен быть DRYER-способ сделать это, не так ли? Как бы вы делали такую ​​проверку?

И как бы справиться с этим в этом случае? Предположим, вы хотите отобразить дату создания комментария в своем представлении:

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? %>

Вы можете проверить контроллер. И т.д. Есть несколько способов сделать это. Но что такое "лучший" способ справиться с этим?

4b9b3361

Ответ 1

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

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

Ваш второй пример (Post.find(5).comments [0].created_at) также не является исключительным. Некоторые сообщения просто не имеют комментариев, и вы знаете это заранее. Так почему же это должно быть исключение?

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

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

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

Итак, как насчет исключений? Ну, первое правило действительно: используйте их как можно более редко.

Если вам действительно нужно их использовать, есть два вида исключений вообще (как я вижу):

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

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

Я склонен использовать метод rescue_from в ApplicationController только для последнего, так как для первого типа есть более подходящие места для первого типа, а ApplicationController - как самый верхний из классов контроллера, кажется, подходящее место для возврата к в таких обстоятельствах (хотя в настоящее время какое-то промежуточное ПО Rack может быть еще более подходящим местом для размещения такой вещи).

- EDIT -

Конструктивная часть:

Что касается первого вопроса, я бы посоветовал начать использовать find_by_id вместо find, так как он не генерирует исключение, но возвращает nil, если он не увенчался успехом. Тогда ваш код будет выглядеть примерно так:

unless @p = Post.find_by_id(params[:id])
  flash[:error] = "Can't find Blog Post"
end

который намного менее разговорчив.

Еще одна распространенная идиома для СУШКИ такого рода ситуаций - использовать контроллер 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 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, но, возможно, с некоторыми рефакторингами, такими как эти, вы обнаружите, что большая потребность в какой-то стратегии для обработки исключений/ошибок просто исчезает.

Ответ 2

Исключения для исключительных обстоятельств. Плохой ввод пользователя, как правило, не является исключительным; если угодно, то это довольно часто. Когда у вас есть исключительное обстоятельство, вы хотите предоставить себе как можно больше информации. По моему опыту, лучший способ сделать это - религиозно улучшить обработку исключений на основе опыта отладки. Когда вы сталкиваетесь с исключением, самое первое, что вам нужно сделать, это написать unit test для него. Второе, что вам нужно сделать, это определить, есть ли дополнительная информация, которая может быть добавлена ​​в исключение. Более подробная информация в этом случае обычно принимает форму ловить исключение выше стека и обрабатывать его или бросать новое, более информативное исключение, которое имеет преимущество дополнительного контекста. Мое личное правило состоит в том, что мне не нравится перехватывать исключения из более чем трех уровней в стеке. Если исключение должно перемещаться дальше, вы должны его поймать раньше.

Что касается выявления ошибок в пользовательском интерфейсе, операторы 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