معالجة خطأ وظيفي في Kotlin باستخدام Arrow

الصورة

مرحبا يا هبر!

الجميع يحب استثناءات وقت التشغيل. لا توجد طريقة أفضل لمعرفة أن شيئًا ما لم يؤخذ بعين الاعتبار عند كتابة الكود. خاصة - إذا كانت الاستثناءات تسقط التطبيق بين ملايين المستخدمين ، ويأتي هذا الخبر في رسالة بريد إلكتروني ذعر من بوابة التحليلات. صباح السبت. عندما تكون في رحلة بلد.

بعد ذلك ، تفكر جديا في معالجة الأخطاء - وما هي الاحتمالات التي توفرها لنا Kotlin؟

أول من يتبادر إلى الذهن هو تجربة الصيد. بالنسبة لي - خيار كبير ، ولكن لديه مشكلتين:

  1. هذا بعد كل شيء رمز إضافي (لا يحتوي مجمّع القسري حول الرمز على إمكانية القراءة بأفضل طريقة).
  2. ليس من الممكن دائمًا (وخاصة عند استخدام مكتبات الجهات الخارجية) من كتلة catch الحصول على رسالة إعلامية حول سبب الخطأ بالضبط.

دعونا نرى ما يتحول try-catch إلى رمز عند محاولة حل المشكلات المذكورة أعلاه.

على سبيل المثال ، أبسط وظيفة تنفيذ استعلام الشبكة

fun makeRequest(request: RequestBody): List<ResponseData>? { val response = httpClient.newCall(request).execute() return if (response.isSuccessful) { val body = response.body()?.string() val json = ObjectMapper().readValue(body, MyCustomResponse::class.java) json?.data } else { null } } 

يصبح مثل

 fun makeRequest(request: RequestBody): List<ResponseData>? { try { val response = httpClient.newCall(request).execute() return if (response.isSuccessful) { val body = response.body()?.string() val json = ObjectMapper().readValue(body, MyCustomResponse::class.java) json?.data } else { null } } catch (e: Exception) { log.error("SON YOU DISSAPOINT: ", e.message) return null } } 

"قد لا يكون الأمر سيئًا للغاية" ، كما يقول أحدهم ، "أنت وزوجتك تريدان كل رمز السكر" ، يضيف (هذا اقتباس) - وسيظل ... مرتين على صواب. لا ، لن يكون هناك holivars اليوم - الكل يقرر لنفسه. أنا شخصياً حكمت رمز محلل json المكتوب ذاتيًا ، حيث تم لف تحليل كل حقل في try-catch ، بينما كانت كل من كتل catch فارغة. إذا كان شخص ما راضيا عن هذا الوضع - العلم في يديه. أريد أن أقدم طريقة أفضل.

تقدم معظم لغات البرمجة الوظيفية المكتوبة فئتين للتعامل مع الأخطاء والاستثناءات: حاول وإما . حاول التعامل مع الاستثناءات ، وإما للتعامل مع أخطاء منطق الأعمال.

تتيح لك مكتبة Arrow استخدام هذه التجريدات مع Kotlin. وبالتالي ، يمكنك إعادة كتابة الطلب أعلاه على النحو التالي:

 fun makeRequest(request: RequestBody): Try<List<ResponseData>> = Try { val response = httpClient.newCall(request).execute() if (response.isSuccessful) { val body = response.body()?.string() val json = ObjectMapper().readValue(body, MyCustomResponse::class.java) json?.data } else { emptyList() } } 

كيف يختلف هذا النهج عن استخدام try-catch؟

بادئ ذي بدء ، سيكون بإمكان أي شخص يقرأ هذا الرمز بعدك (ومن المرجح أن يفعل ذلك) أن يفهم بالفعل من خلال التوقيع أن تنفيذ الرمز يمكن أن يؤدي إلى خطأ - وأن يكتب رمزًا لمعالجته. علاوة على ذلك ، سوف يقسم المترجم إذا لم يتم ذلك.

ثانياً ، هناك مرونة في كيفية معالجة الخطأ.

داخل المحاولة ، يتم تمثيل خطأ أو نجاح التنفيذ كصفتي الفشل والنجاح ، على التوالي. إذا كنا نريد أن تُرجع الدالة دائمًا شيئًا ما عن طريق الخطأ ، فيمكننا تعيين القيمة الافتراضية:

 makeRequest(request).getOrElse { emptyList() } 

إذا كانت معالجة الأخطاء أكثر تعقيدًا مطلوبة ، فسيتم طي عملية الإنقاذ:

 makeRequest(request).fold( {ex -> //  -       emptyList() }, { data -> /*    */ } ) 

يمكنك استخدام وظيفة الاسترداد - سيتم تجاهل محتوياتها تمامًا إذا حاول إرجاع النجاح.

 makeRequest(request).recover { emptyList() } 

يمكنك استخدام الاستيعاب (المستعارة من قِبل منشئي Arrow من Scala) إذا كنت بحاجة إلى معالجة نتيجة Success باستخدام سلسلة من الأوامر عن طريق الاتصال بمصنع .monad () على المحاولة:

 Try.monad().binding { val r = httpclient.makeRequest(request) val data = r.recoverWith { Try.pure(emptyList()) }.bind() val result: MutableList<Data> = data.toMutableList() result.add(Data()) yields(result) } 

يمكن كتابة الخيار أعلاه دون استخدام الربط ، ولكن بعد ذلك سيتم قراءته بطريقة مختلفة:

 httpcilent.makeRequest(request) .recoverWith { Try.pure(emptyList()) } .flatMap { data -> val result: MutableList<Data> = data.toMutableList() result.add(Data()) Try.pure(result) } 

في النهاية ، يمكن معالجة نتيجة الوظيفة باستخدام:

 when(response) { is Try.Success -> response.data.toString() is Try.Failure -> response.exception.message } 

وبالتالي ، باستخدام Arrow ، يمكنك الاستعاضة عن التصميم المثالي للتجربة بأشياء مرنة ومريحة للغاية. ميزة إضافية لاستخدام Arrow هي أنه على الرغم من أن المكتبة تعتبر نفسها وظيفية ، يمكنك استخدام التجريدات الفردية من هناك (على سبيل المثال ، نفس التجربة) مع الاستمرار في كتابة رمز OOP القديم الجيد. لكنني أحذرك - ربما تعجبك وتشترك ، خلال أسبوعين ستبدأ في دراسة هاسكل ، وسيتوقف زملاؤك قريبًا عن فهم منطقك حول بنية الكود.

PS: الأمر يستحق ذلك :)

Source: https://habr.com/ru/post/ar435254/


All Articles