
مرحبا يا هبر!
الجميع يحب استثناءات وقت التشغيل. لا توجد طريقة أفضل لمعرفة أن شيئًا ما لم يؤخذ بعين الاعتبار عند كتابة الكود. خاصة - إذا كانت الاستثناءات تسقط التطبيق بين ملايين المستخدمين ، ويأتي هذا الخبر في رسالة بريد إلكتروني ذعر من بوابة التحليلات. صباح السبت. عندما تكون في رحلة بلد.
بعد ذلك ، تفكر جديا في معالجة الأخطاء - وما هي الاحتمالات التي توفرها لنا Kotlin؟
أول من يتبادر إلى الذهن هو تجربة الصيد. بالنسبة لي - خيار كبير ، ولكن لديه مشكلتين:
- هذا بعد كل شيء رمز إضافي (لا يحتوي مجمّع القسري حول الرمز على إمكانية القراءة بأفضل طريقة).
- ليس من الممكن دائمًا (وخاصة عند استخدام مكتبات الجهات الخارجية) من كتلة 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 ->
يمكنك استخدام وظيفة الاسترداد - سيتم تجاهل محتوياتها تمامًا إذا حاول إرجاع النجاح.
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: الأمر يستحق ذلك :)