Помилки трапляються. В Elixir, хоч часто кажуть «let it crash», бувають випадки, коли нам потрібно врятувати виклик функції до відомого робочого стану, щоб виконати контракт програмного забезпечення. У деяких мовах помилки використовують як засіб керування потоком виконання, але в Elixir цей підхід не схвалюють. Часто функції, які можуть спричинити помилку, можна розпізнати вже за їхньою назвою: функції, які спричиняють помилки, мають ! у кінці своєї назви. Це на противагу функціям, які повертають {:ok, value} або :error. Погляньмо на ці приклади з бібліотеки:
Map.fetch(%{a: 1}, :b)
# => :error
Map.fetch!(%{a: 1}, :b)
# => raises KeyError
Elixir надає конструкцію для рятування від помилок за допомогою try .. rescue
try do
raise RuntimeError, "error"
rescue
e in RuntimeError -> :error
end
Розгляньмо цю конструкцію:
try.raise/2.rescue ми зіставляємо зі зразком за назвою Module помилки, яку спричинено
->:
e зіставляється зі структурою помилки.in - це ключове слово.RuntimeError - це помилка, від якої ми хочемо врятуватися.
_ замість назви модуля або взагалі опустити ключове слово in.Помилки (які іноді також називають «винятками»), від яких ми рятуємося в такий спосіб, є структурами. Рятування від помилок в Elixir трапляється дуже рідко. Зазвичай врятовану помилку записують у журнал або надсилають до зовнішнього сервісу моніторингу, а потім спричиняють її знову. Це означає, що зазвичай нас не цікавить внутрішня структура конкретної структури помилки.
У концепції «Винятки» ми дізнаємося більше про структури помилок, зокрема про те, як визначити власну помилку.
Працюючи в Instruments of Texas, ми отримуємо завдання попрацювати над експериментальним калькулятором зі зворотним польським записом [RPN], написаним на Elixir. У нашої команди проблема: деякі операції спричиняють помилки й призводять до аварійного завершення процесу. Наше завдання - написати функцію, яка обгортає функцію операції, щоб помилки можна було обробляти елегантніше, ідіоматичним кодом Elixir.
Реалізуйте функцію calculate!/2, щоб викликати функцію операції, передаючи стек як єдиний аргумент. Функція операції визначена деінде, але відомо, що вона може або завершитися успішно, або спричинити помилку.
stack = []
operation = fn _ -> :ok end
RPNCalculator.calculate!(stack, operation)
# => :ok
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate!(stack, operation)
# => ** (ArgumentError) An error occurred
Імена функцій, що закінчуються на
!, попереджають програмістів про те, що така функція може спричинити помилку
Під час додаткового дослідження ми помічаємо, що багато функцій використовують атоми й кортежі, щоб позначити свій успіх або невдачу. Реалізуйте calculate/2 за цією стратегією.
stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate(stack, operation)
# => {:ok, "operation completed"}
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate(stack, operation)
# => :error
Деякі помилки містять важливу інформацію, яка потрібна колегам, щоб забезпечити правильну роботу системи. Реалізуйте calculate_verbose/2, щоб передати повідомлення про помилку. Помилка - це структура, яка має поле :message.
stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:ok, "operation completed"}
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:error, "An error occurred"}
Зареєструйтеся на Exercism, щоб вивчати й опановувати Elixir, а також 58 концепцій168 вправ та справжнє наставництво від людей, і все це безкоштовно.