# Ошибки

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

## Отлов ошибок

Но одно дело натыкаться на эти ошибки во время разработки, а другое дело, когда эти ошбки видит наш конечный пользователь. Для того, чтобы нам защититься от неожиданного поведения программы существует понятие `отлов ошибок`. В Ruby для этого мы можем воспользоваться конструкцией `begin ... end`.

```ruby
user_input = gets.chomp.to_i
puts 10 % user_input
```

простейшая программа, которая получает пользовательский ввод и делит 10 на полученное число. Но вот не задача, пользователь вводит 0, а как мы знаем, делить на 0 нельзя. В итоге получим ошибку `ZeroDivisionError`. Давайте исправим ситуацию:

```ruby
user_input = gets.chomp.to_i
# все что находится между begin и rescue будет проверяться на ошибки
# если ошибка срабатывает, то начинает работать логика между rescue и end 
begin
  puts 10 % user_input
# после rescue мы указываем ошибку, которую мытаемся отловить
# err - объект класса ошибки которую отлавливаем
rescue ZeroDivisionError => error
  # Важно в случае ошибок не просто обработать и дать корректный ответ
  # но и не потерять информацию об ошибке, например написать ее в консоль
  p error
  puts 0
# количество отлавлиемых ошибок может быть сколько угодно
# Если ошибка не является объектом класса первой отлавливаемой ошибки, значит проверяется следующий rescue  тд.
rescue ArgumentError => error
    puts "Ошибка: Некорректный формат введенных данных. #{error.inspect}"
end
# если мы отловили ошибку, то после кода внутри rescue программа начем работать дальше
puts "finish"
```

В целом `begin ... rescue ... end` по своему виду очень похож на `if ... elsif ... else ... end`, только в одном случае мы проверяем условия, а в другом тип ошибки.

## Создание собственных

Как Ruby выкидывает нам свою ошибку, так и мы можем выкидывать свои собственные ошибки.

Для начала создадим класс ошибки и унаследуем его от `StandardError`:

```ruby
class MyError < StandardError
  def initialize(message)
    # любая ошибка имеет внутри себя сообщение об ошибке, которое в случае чего показывается в консоли
    # например ZeroDivisionError имеет текст "divided by 0"
    super(message)
  end
end
```

Теперь нам нужно выбросить эту самую ошибку

```ruby
def example(arg)
  if arg > 10
    # выброс ошибки отличается от создание объекта
    # нсачала пишем raise, потом класс ошибки, потом текст, который будет передан в метод initialize
    raise MyError, "helpful description for error"
  else
    puts "It is argument - #{arg}"
  end
end

begin 
  # Сначала в консоль распечатается 'It is argument - 1'
  example(1)
  # Здесь уже сработает наша ошибка
  example(20)
# Отловили нашу ошибку
rescue MyError => error
  # Теперь в консоль напечаталось 'It is MyError - helpful description for error'
  puts "It is MyError - #{error.message}"
end
```

## Дополнительный материал

* [Ошибки в Ruby](https://rollbar.com/guides/ruby/how-to-handle-exceptions-in-ruby/)
* [Все встроенные ошибки Ruby](https://www.honeybadger.io/blog/understanding-the-ruby-exception-hierarchy/)
* [Создание собственных ошибок](https://www.honeybadger.io/blog/ruby-custom-exceptions/s)
