Учебники

Эликсир — Обработка ошибок

У Elixir есть три механизма ошибок: ошибки, броски и выходы. Давайте рассмотрим каждый механизм подробно.

ошибка

Ошибки (или исключения) используются, когда в коде происходят исключительные вещи. Пример ошибки можно получить, если попытаться добавить число в строку —

IO.puts(1 + "Hello")

Когда вышеуказанная программа запущена, она выдает следующую ошибку:

** (ArithmeticError) bad argument in arithmetic expression
   :erlang.+(1, "Hello")

Это была встроенная ошибка образца.

Повышение ошибок

Мы можем поднять ошибки, используя функции повышения. Давайте рассмотрим пример, чтобы понять то же самое —

#Runtime Error with just a message
raise "oops"  # ** (RuntimeError) oops

Другие ошибки могут быть вызваны с помощью параметра allow / 2, передающего имя ошибки и список ключевых аргументов.

#Other error type with a message
raise ArgumentError, message: "invalid argument foo"

Вы также можете определить свои собственные ошибки и поднять их. Рассмотрим следующий пример —

defmodule MyError do
   defexception message: "default message"
end

raise MyError  # Raises error with default message
raise MyError, message: "custom message"  # Raises error with custom message

Спасая ошибки

Мы не хотим, чтобы наши программы внезапно закрывались, скорее, ошибки должны тщательно обрабатываться. Для этого мы используем обработку ошибок. Мы спасаем ошибки, используя конструкцию try / rescue . Давайте рассмотрим следующий пример, чтобы понять то же самое —

Live Demo

err = try do
   raise "oops"
rescue
   e in RuntimeError -> e
end

IO.puts(err.message)

Когда вышеуказанная программа запущена, она дает следующий результат —

oops

Мы обработали ошибки в операторе спасения, используя сопоставление с образцом. Если мы не используем ошибку и просто хотим использовать ее для идентификации, мы также можем использовать форму:

Live Demo

err = try do
   1 + "Hello"
rescue
   RuntimeError -> "You've got a runtime error!"
   ArithmeticError -> "You've got a Argument error!"
end

IO.puts(err)

При запуске вышеуказанной программы, она дает следующий результат —

You've got a Argument error!

ПРИМЕЧАНИЕ. — Большинство функций в стандартной библиотеке Elixir реализованы дважды: один раз возвращает кортежи, а другой — ошибки. Например, File.read и File.read! функции. Первый вернул кортеж, если файл был успешно прочитан, и если произошла ошибка, этот кортеж использовался, чтобы указать причину ошибки. Второй вызвал ошибку, если возникла ошибка.

Если мы используем подход первой функции, то нам нужно использовать case для сопоставления с шаблоном ошибки и действовать в соответствии с этим. Во втором случае мы используем подход try rescue для подверженного ошибкам кода и соответственно обрабатываем ошибки.

Броски

В эликсире значение может быть выброшено, а затем поймано. Throw и Catch зарезервированы для ситуаций, когда невозможно извлечь значение, если не использовать throw и catch.

Практические примеры довольно необычны, за исключением случаев взаимодействия с библиотеками. Например, давайте теперь предположим, что модуль Enum не предоставил никакого API для поиска значения и что нам нужно было найти первое кратное 13 в списке чисел —

Live Demo

val = try do
   Enum.each 20..100, fn(x) ->
      if rem(x, 13) == 0, do: throw(x)
   end
   "Got nothing"
catch
   x -> "Got #{x}"
end

IO.puts(val)

Когда вышеуказанная программа запущена, она дает следующий результат —

Got 26

Выход

Когда процесс умирает по «естественным причинам» (например, необработанные исключения), он посылает сигнал выхода. Процесс также может умереть, явно отправив сигнал на выход. Давайте рассмотрим следующий пример —

spawn_link fn -> exit(1) end

В приведенном выше примере связанный процесс умер, отправив сигнал выхода со значением 1. Обратите внимание, что выход также можно «перехватить» с помощью try / catch. Например —

Live Demo

val = try do
   exit "I am exiting"
catch
   :exit, _ -> "not really"
end

IO.puts(val)

Когда вышеуказанная программа запущена, она дает следующий результат —

not really

После

Иногда необходимо убедиться, что ресурс очищен после некоторого действия, которое потенциально может вызвать ошибку. Конструкция try / after позволяет вам это сделать. Например, мы можем открыть файл и использовать предложение after, чтобы закрыть его, даже если что-то пойдет не так.

{:ok, file} = File.open "sample", [:utf8, :write]
try do
   IO.write file, "olá"
   raise "oops, something went wrong"
after
   File.close(file)
end

Когда мы запустим эту программу, она выдаст нам ошибку. Но оператор after гарантирует, что дескриптор файла будет закрыт при любом таком событии.