Грешки
В Elixir
, грешките са предназначени за ситуации, които никога не би трябвало да се случат
при нормални обстоятелства. Това ще рече - ситуации при които зависим от нещо външно и то спре да работи,
зависим от някакви файлове, а тях ги няма, като цяло когато имаме проблем с ресурсите и конфигурацията от които нашата програма
зависи.
Да речем грешен input
от потребител не е такава ситуация.
‘Вдигане’ на грешка
Грешка се ‘вдига’ с raise
:
raise "Ужаст!"
# (RuntimeError) Ужаст!
По подразбиране, ако не подадем тип на грешката на raise
, тя е RuntimeError
.
Можем да вдигнем грешка и с тип, без съобщение:
raise RuntimeError
# (RuntimeError) runtime error
Както и с тип на грешката и съобщение:
raise ArgumentError, message: "Грешка, брато!"
# (ArgumentError) Грешка, брато!
Elixir
идва с набор от грешки за различни случаи, които можете да видите в
документацията под ‘EXCEPTIONS’.
Грешките в Elixir
не са препоръчителни за употреба.
Имат славата на GOTO
/спагети програмиране и наистина е хубаво да помислим дали има нужда от тях в дадена ситуация.
Прието е функции, при които има проблем, да връщат {:error, <проблем>}
, а ако се изпълняват с успех и
имат резултат - {:ok, <резултат>}
.
Имената на функции, които биха могли да ‘вдигнат’ грешка, обикновено завършват на !
.
Да речем ако имаме SomeModule.some_function/1
, която връща {:ok, result}
или {:error, reason}
,
ако искаме да дефинираме същата, но при успех връщаща result
, а при неуспех, ‘вдигаща’ грешка,
ще я кръстим SomeModule.some_function!/1
. Когато говорим за вход-изход ще видим, че функциите идващи с езика
следват тази конвенция.
Прихващане на грешка
Можем да прихванем грешка, използвайки try/rescue
блок:
try do
1 / 0
rescue
[RuntimeError, ArgumentError] ->
IO.puts("Няма да стигнем до тук.")
error in [ArithmeticError] ->
IO.puts("На нула не се дели, #{error.message}")
any_other_error ->
IO.puts("Лошаво... #{any_other_error.message}")
else
IO.puts("Няма грешка.")
after
IO.puts("Finally!")
end
Примерът по горе показва няколко вида прихващане. Прихващането пък си е match
-ване.
Първият пример е чрез списък от тип грешки, докато във втория, виждаме как от този списък
да си вземем грешката в променлива.
Накрая хващаме всички типове, които не сме описали досега в променливата any_other_error
.
Имаме и after
клауза, която винаги ще се изпълни след като try
функцията завърши, няма значение,
дали е имало грешка или не. Другата интересна клауза в примера е else
- тялото ѝ се изпълнява само
ако не е възникнала грешка в тялото на try
.
Създаване на нови типове грешки
Можем да създадем и нови типове грешки. Подобно на структурите, нова грешка се дефинира като част от модул:
defmodule VeryBadError do
defexception message: "Лошо!!!"
end
Сега можем да я ‘вдигнем’:
try do
raise VeryBadError
rescue
error in VeryBadError ->
IO.puts(inspect(error, structs: false))
end
# %{__exception__: true, __struct__: VeryBadError, message: "Лошо!!!"}
Както виждате, грешките са структури с още едно тайно поле - __exception__
.
То има стойност true
. Нищо особено.
Throw/Catch
Тези две конструкции НЕ ТРЯБВА да се ползват. Има библиотеки, които поради някакво стечение на обстоятелствата е възможно да ги ползват, но ги избягвайте. Ние ви ги показваме за да ви кажем - не ги ползвайте.
С throw
‘подхвърляме’ стойност, която може да се ‘хване’ по-късно:
try do
throw 5
catch
x -> IO.puts(x)
end
# 5
Показахме ви ги. Сега - забравете за тях и не ги ползвайте.
Още няколко думи
Тази статия е доста кратка. Идеята ѝ е да ни запознае с грешките в elixir
и да ни
каже - ‘не ги ползвайте’. Два факта:
- В кода на
mix
няма прихващане на грешки. - В кода на компилатора на
Elixir
има точно пет прихващания. Но все пак това е компилатор и там се случват доста магически неща.
Ако започнете да създавате нов тип за грешки се замислете.
В Erlang/Elixir
кодът върви в специални изолирани процеси.
Идеологията е - остави го да се счупи.
Това е така защото тези процеси са наистина изолирани и не споделят състояние.
Ако един падне, друг ще бъде вдигнат на негово място и така ако е имало проблем, възникнал runtime
,
той ще се изчисти. Няма защо ние да правим това.
Ще си говорим за тези процеси в следващи статии, засега е важно да запомните, че писането на нови типове грешки
е нещо, което Elixir
програмистите като цяло НЕ правят. Същото се отнася и за ‘прихващането’ им.
И без никакви throw/catch
-ове!