ruby exceptions
TRANSCRIPT
Исключения и Ruby
Андрей Колешко@[email protected]
Общие проблемы подхода к разработке
Программируйте не на языке, а с помощью языка.
С. Макконнелл
Проблемы исключений
Использование исключений в неисключительных ситуациях
Примерdef create @user = User.new params[:user] @user.save! redirect_to user_path(@user)rescue ActiveRecord::RecordNotSaved flash[:notice] = 'Unable to create user' render :action => :newend
Правильный подходdef create @user = User.new params[:user] if @user.save redirect_to user_path(@user) else flash[:notice] = 'Unable to create user' render :action => :new endend
Где использовать методы, генерирующие исключения
• Могут быть полезны в тестах для проверки валидаций
• В транзакциях для эмуляции отката
Где не использовать методы, генерирующие исключения
• По возможности везде!
• Так или иначе, нам не избавиться от случаев, когда без исключений не обойтись
За что мы любим Ruby?
File.open('testfile') do |file| while line = file.readline puts line endrescue EOFError returnend
Любили бы мы Ruby за это?
За что мы любим Ruby?
File.open('testfile') do |file| while line = file.gets puts line endend
Мы любим Ruby за это:
Примеры исключительных ситуаций• Потеря соединения с базой данных• Переполнение памяти• Ошибка чтения/записи сокетаСлучаи, возникающие вследствии пользовательского ввода НЕ являются исключительными ситуациями!
Должны ли мы отлавливать исключительные ситуации?
• Все зависит от типа приложения и его требований
• В большинстве случаев нет• Любая строчка в коде может вызвать исключение. Отлавливать все?
Не ловите все
user.address.street rescue ‘’
Не ловите все
user.address.street rescue ‘’
# а как насчет этого:user.address.bla_bla_bla rescue ‘’
Будьте выразительнее
if user.address user.address.streetend
Где может быть полезен inline rescue?
val_or_error = {}.fetch(:name) rescue $!val_or_error#=> #<KeyError: key not found: :name>
Ловим исключение и анализируем его:
Не перехватывайте Exceptionclass TaskProcessor def perform(id) task = Task.find(id) task.process rescue Exception => e task.log(e.message) raise endend
Результат
NoMethodError: undefined method `log' for nil:NilClassДействительный
ОжидаемыйActiveRecord::RecordNotFound: Couldn't find Task withid=1000
Фикс
class TaskProcessor def perform(id) task = Task.find(id) task.process rescue ProcessError => e task.log(e.message) raise endend
Пути обхода
• Предоставляйте стратегию отступления
• Применяйте паттерны проектирования
• Пишите тестыСложность написания теста укажет на плохо спроектированную систему
Hash#fetch
h = {}h.fetch(:name) # =>KeyError: key not found: :nameh.fetch(:name) { 'ka8725' } # => "ka8725"
Обратная связь
def run on_success do
# обработка успеха end on_failure do # обработка ошибки endend
http://goo.gl/lchi1K
Throw/catch
def invoke @res = catch(:halt) { yield } # дальше анализ resendinvoke { throw :halt, "response 1"}@res #=> "response 1"invoke { "response 2" }@res #=> "response 2"
http://goo.gl/qey41F - исходники sinatra
Правило выбрасывания исключений
Дейв Томас и Энди Хант, Прагматик-программист
Литература
http://exceptionalruby.com/ http://eloquentruby.com/http://pragprog.com/the-pragmatic-programmer
http://cc2e.com/