Исключения и Ruby
Андрей Колешко
@ka8725
ka8725@gmail.com
Общие проблемы подхода к
разработке
Программируйте не на языке, а с помощью
языка.
С. Макконнелл
Проблемы исключений
Использование исключений в
неисключительных ситуациях
Пример
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 => :new
end
Правильный подход
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
end
end
Где использовать методы,
генерирующие исключения
• Могут быть полезны в тестах для
проверки валидаций
• В транзакциях для эмуляции отката
Где не использовать методы,
генерирующие исключения
• По возможности везде!
• Так или иначе, нам не избавиться от
случаев, когда без исключений не
обойтись
За что мы любим Ruby?
File.open('testfile') do |file|
while line = file.readline
puts line
end
rescue EOFError
return
end
Любили бы мы Ruby за это?
За что мы любим Ruby?
File.open('testfile') do |file|
while line = file.gets
puts line
end
end
Мы любим Ruby за это:
Примеры исключительных
ситуаций
• Потеря соединения с базой данных
• Переполнение памяти
• Ошибка чтения/записи сокета
Случаи, возникающие вследствии пользовательского
ввода НЕ являются исключительными ситуациями!
Должны ли мы отлавливать
исключительные ситуации?
• Все зависит от типа приложения и его
требований
• В большинстве случаев нет
• Любая строчка в коде может вызвать
исключение. Отлавливать все?
Не ловите все
user.address.street rescue ‘’
Не ловите все
user.address.street rescue ‘’
# а как насчет этого:
user.address.bla_bla_bla rescue ‘’
Будьте выразительнее
if user.address
user.address.street
end
Где может быть полезен
inline rescue?
val_or_error = {}.fetch(:name) rescue $!
val_or_error
#=> #<KeyError: key not found: :name>
Ловим исключение и анализируем его:
Не перехватывайте
Exception
class TaskProcessor
def perform(id)
task = Task.find(id)
task.process
rescue Exception => e
task.log(e.message)
raise
end
end
Результат
NoMethodError: undefined method `log' for nil:NilClass
Действительный
Ожидаемый
ActiveRecord::RecordNotFound: Couldn't find Task with
id=1000
Фикс
class TaskProcessor
def perform(id)
task = Task.find(id)
task.process
rescue ProcessError => e
task.log(e.message)
raise
end
end
Пути обхода
• Предоставляйте стратегию отступления
• Применяйте паттерны проектирования
• Пишите тесты
Сложность написания
теста укажет на плохо
спроектированную
систему
Hash#fetch
h = {}
h.fetch(:name) # =>KeyError: key not found: :name
h.fetch(:name) { 'ka8725' } # => "ka8725"
Обратная связь
def run
on_success do
# обработка успеха
end
on_failure do
# обработка ошибки
end
end
http://goo.gl/lchi1K
Throw/catch
def invoke
@res = catch(:halt) { yield }
# дальше анализ res
end
invoke { 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/
Вопросы?
Андрей Колешко
@ka8725
ka8725@gmail.com

Ruby exceptions