خطاها رخ میدهند. در Elixir، هرچند اغلب میگویند «بگذارید کرش کند»، گاهی لازم است فراخوانی تابع را نجات دهیم و به وضعیت مطلوب شناختهشدهای برگردانیم تا یک قرارداد نرمافزاری برآورده شود. در برخی زبانها، از خطاها به عنوان روشی برای کنترل جریان برنامه استفاده میشود، اما در 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، روی اسم ماژول خطای ایجادشده تطابق الگو میدهیم
->:
e با ساختار خطا تطبیق داده میشود.in یک کلیدواژه است.RuntimeError خطایی است که میخواهیم از آن نجات پیدا کنیم.
_ استفاده کنیم یا کلیدواژهی in را کاملاً حذف کنیم.خطاهایی (که گاهی «استثنا» هم نامیده میشوند) که به این شکل از آنها نجات پیدا میکنید، ساختار هستند. نجات دادن از خطاها در Elixir بهندرت انجام میشود. معمولاً خطای نجاتیافته ثبت میشود یا به یک سرویس پایش خارجی فرستاده میشود و سپس دوباره ایجاد میگردد. یعنی معمولاً به ساختار درونی آن خطای مشخص اهمیتی نمیدهیم.
در مفهوم Exceptions دربارهی ساختارهای خطا بیشتر یاد میگیرید، از جمله اینکه چطور خطای سفارشی خودتان را تعریف کنید.
در حین کار در 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"}