Грешки

В 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-ове!