راه آهن برای دستیابی به خطای بهتر

من در حال نوشتن یک پست جدید در مورد جایگزین کردن استثنائات در Tsonnet با یک رویکرد monadic بودم و بحث اخیراً با دوستان نیز مطرح شد ، بنابراین من معتقدم که رسیدگی به خطای یکپارچه سزاوار توضیحات خاص خود است.
بگذارید در مورد رسیدگی به خطای monadic و اینکه چرا استثنائات به طور کلی ایده خوبی نیستند ، در مقایسه با این کار ، به طور کلی ایده خوبی نیستند.
رسیدگی به خطای monadic چیست؟
رسیدگی به خطای monadic رویکردی است که در آن خطاها به عنوان مقادیر معمولی پیچیده شده در یک نوع (مانند نتیجه در OCAML) که نشان دهنده موفقیت یا عدم موفقیت است ، رفتار می شود. این نوع راهی برای عملیات زنجیره ای فراهم می کند (bind
یا >>=
) این ممکن است شکست بخورد و اجازه می دهد خطاها از طریق محاسبات به روشی کنترل شده و سازنده پخش شوند.
در اینجا یک مثال سریع در OCAML آورده شده است:
type ('a, 'e) result = Ok of 'a | Error of 'e
(* Chain operations that might fail *)
let process_data input =
validate input (* Returns Result *)
|> Result.bind process (* Only runs if validate succeeds *)
|> Result.bind save (* Only runs if process succeeds *)
من به استفاده از OCAML برای نمونه های کد در این پست ادامه خواهم داد ، اما دلسرد نشوید ، ایده های موجود در اینجا مربوط به هر زبان برنامه نویسی است که به ما امکان می دهد خطاها را به عنوان مقادیر رفتار کنیم.
این سبک با خطا همچنین به عنوان برنامه نویسی راه آهن شناخته می شود ، جایی که محاسبات به عنوان قطارهایی که در دو مسیر اجرا می شوند-یکی برای موفقیت و دیگری برای شکست دیده می شود.
بیایید از نزدیک نگاه کنیم که چگونه استثنائات مقایسه می شوند.
چرا استثنا نیست؟
بیایید بررسی کنیم که چرا استثنائات ممکن است بهترین انتخاب برای رسیدگی به خطا نباشد.
یک مسئله مهم این است که استثنائات شفافیت مرجع را شکستند – یک اصل اصلی که در آن می توان یک تماس عملکردی را بدون تغییر رفتار برنامه با نتیجه خود جایگزین کرد. بیایید این را از طریق یک مثال ببینیم:
(* Using exceptions *)
let divide x y =
if y = 0 then raise Division_by_zero
else x / y
(* Using Result *)
let divide x y =
if y = 0 then Error "Division by zero"
else Ok (x / y)
هنگام استفاده از استثنائات ، امضای نوع عملکرد به ما نمی گوید ممکن است شکست بخورد:
val divide : int -> int -> int
اما با نتیجه ، احتمال شکست بخشی از نوع می شود:
val divide : int -> int -> (int, string) result
این خطای صریح از طریق انواع مزایای مختلفی را به همراه دارد. اول ، این موارد خطا را در سیستم نوع قابل مشاهده می کند و توسعه دهندگان را وادار می کند تا در هنگام تدوین ، آنها را در نظر بگیرند و اداره کنند. وقتی یک نوع نتیجه را می بینید ، بلافاصله می دانید که باید هر دو مورد موفقیت و شکست را کنترل کنید.
مزایای اصلی رویکرد monadic:
- جریان خطای صریح
- ترکیب بهتر
- خطای حفظ زمینه
- بدون وقفه در کنترل
- ادغام تطبیق الگوی
بیایید یک مثال واقعی را کشف کنیم و ببینیم که چگونه می توان آن را به صورت بی اهمیت کمتر اعمال کرد.
رویکرد monadic که برای یک سیستم توزیع شده اعمال می شود
بیایید نمونه ای از زندگی واقعی از سیستم های توزیع شده را کشف کنیم. ثبت نام کاربر یک عمل مشترک است که شامل چندین مرحله است که در آن می تواند اشتباه انجام شود – مناسب برای نشان دادن رویکردهای رسیدگی به خطا.
در اینجا کد اجرای هر دو رویکرد ، با نظرات برای برجسته کردن قسمت های مهم وجود دارد:
(* Exception-based approach *)
val validate_email : string -> string (* raises Invalid_argument *)
val validate_age : int -> int (* raises Invalid_argument *)
val create_user : int -> string -> int -> user (* raises Invalid_argument *)
(* Notice how error handling becomes nested and complex with multiple operations *)
let process_user data =
try
let user = create_user data.id data.email data.age in
try
save_to_db user; (* Could raise Database_error *)
try
notify_user user (* Could raise Network_error *)
with Network_error msg ->
log_error ("Notification failed: " ^ msg);
raise (Process_error "Notification step failed")
with Database_error msg ->
log_error ("Database error: " ^ msg);
raise (Process_error "Database step failed")
with
| Invalid_argument msg ->
log_error ("Validation error: " ^ msg);
raise (Process_error "Validation failed")
| Process_error msg as e ->
log_error msg;
raise e
(* Monadic Result approach *)
val validate_email : string -> (string, error) result
val validate_age : int -> (int, error) result
val create_user : int -> string -> int -> (user, error) result
(* Error handling flows naturally with the data transformation *)
let process_user data =
let open Result in
match
create_user data.id data.email data.age
|> bind save_to_db (* Different error types compose naturally *)
|> bind notify_user (* Chain operations without nesting *)
with
| Ok _ -> log_success "User processed"
| Error err ->
match err with
| Validation_error msg -> log_error ("Validation: " ^ msg)
| Database_error msg -> log_error ("Database: " ^ msg)
| Network_error msg -> log_error ("Network: " ^ msg)
چند نکته کلیدی:
- امضاهای نتیجه آن را صریح می کند که توابع می توانند شکست بخورند و در زمان کامپایل ، کنترل خطا را انجام دهند. با استثناء ، خرابی ها در سیستم نوع نامرئی هستند.
- توجه داشته باشید که چگونه استفاده از استثناء به بلوک های کچک های آزمایشی تو در تو نیاز دارد زیرا خطاها ضرب می شوند ، در حالی که نسخه نتیجه به صورت خطی با آن آهنگسازی می کند
bind
؟ - در نسخه نتیجه ، مسیر خطا به همان اندازه مسیر موفقیت مشخص است. رسیدگی به استثناء جریان طبیعی داده ها را با نقاط خروج چندگانه مبهم می کند.
- انواع مختلف خطای را می توان به طور طبیعی در نسخه نتیجه تشکیل داد. با استثنائات ، شما اغلب نیاز به گرفتن و تبدیل بین انواع استثناء دارید و منجر به از بین رفتن اطلاعات خطا می شود.
- اضافه کردن یک عملیات جدید که ممکن است در نسخه نتیجه شکست بخورد فقط به معنای اضافه کردن دیگری است
bind
بشر در نسخه استثنا ، برای حفظ صحیح خطای ، باید بلوک های امتحان را با دقت بازسازی کنید.
رویکرد نتیجه monadic به ویژه با سیستم نوع OCAML و سبک برنامه نویسی عملکردی متناسب است. این فکر را در مورد موارد خطا به عنوان تبدیل داده ها به جای وقفه های کنترل ، تشویق می کند و منجر به کد حفظ و قابل پیش بینی تر می شود.
چه موقع از استثنائات استفاده کنیم؟
سناریوهای خاصی وجود دارد که استثنائات ممکن است مناسب تر از رسیدگی به خطای monadic باشد:
- موارد واقعاً استثنایی: سرریز پشته یا خارج از حافظه موارد استثنایی است.
- موارد مهم و مهم: هنگامی که بیش از حد بسیاری از تخصیص نتیجه می توانند مجازات شوند.
- هنگامی که برنامه نمی تواند ادامه یابد: اگر یک شکست به این معنی است که شما نمی توانید ادامه دهید ، یک استثناء کشنده قابل قبول است.
- نمونه برداری یا اسکریپت های سریع: چه کسی اهمیت می دهد ، درست است؟ قابلیت حفظ معمولاً در سناریوهایی که بعداً کد دور می شود نگرانی نیست.
با این حال ، این استثنائات (هدف در نظر گرفته شده) برای این قانون است. برای بیشتر منطق تجارت و پردازش داده ها ، رویکرد monadic انتخاب بهتری برای ترکیب و ایمنی نوع آن باقی مانده است.
پایان
رسیدگی به خطای Monadic یک رویکرد اصولی تر برای مقابله با خطاها در کد شما ارائه می دهد. در حالی که استثنائات جایگاه خود را در سناریوهای خاص دارند ، درمان خطاها به عنوان مقادیر از طریق نوع نتیجه منجر به حفظ کد حفظ ، ترکیب و نوع ایمن تر می شود. ماهیت صریح این رویکرد به توسعه دهندگان کمک می کند تا در مورد موارد خطا استدلال کنند و به طور مناسب آنها را برطرف کنند ، و آن را به یک انتخاب عالی برای اکثر منطق تجارت و نیازهای پردازش داده تبدیل می کند.
با تشکر از شما برای خواندن بیت شاید عاقلانه! برای دریافت پست های آینده پیچیده شده در یک سازنده OK براق ، که مستقیماً به صندوق ورودی شما تحویل داده می شود ، مشترک شوید.