تعلّم البرمجة بلغة كوتلن (41): الإستدعاءات الآمنة والمعامل Elvis

تعلّم البرمجة بلغة كوتلن (41): الإستدعاءات الآمنة والمعامل Elvis
أستمع الى المقال

تحدثنا في الدرس السابق، عن ما هو الـ null، وكيفية أسناده إلى متغير، واستخدامه بطريقة آمنة لا تعرض برنامجنا للتحطم أثناء تشغيله. في هذا الدرس، سنواصل رحلتنا في شرح المزيد من الطرق الآمنة، والتي تسمح لنا بتجنب أخطاء الـ null، بالإضافة إلى زيادة قابلية قراءة الشفرة لضمان سهولة الاستخدام.

سلسلة من الإستدعاءات الآمنة:

كما قلنا في الدرس السابق، أن نظام الأنواع في كوتلن يتكون من نوعين: نوع لا يقبل القيمة الفارغة Non-Nullable Type ونوع يقبلها Nullable Type. ولأنه يتم أيضًا، وضع الأصناف الخاصة بنا في النوعين تلقائيًا بعد أن نُنشئها، يمكننا الإستفادة من ذلك، بإنشاء أصناف تقبل القيم الفارغة.

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

 لتجنب ذلك، سننشئ أصناف ومتغيرات تقبل الـ null، باستخدام علامة الاستفهام (?):

لدينا في الشفرة ثلاث أصناف تمثل: صنف User مع خاصيّتي الاسم والعنوان، وصنف العنوان Address مع خاصيّة المدينة فقط، وصنف معلومات Info، يحوي خاصيّة User فقط. نتوقع أن كل هذه الخاصيَات، يمكن أن تأتيها قيم فارغة null، لذا أضفنا إليها علامة الاستفهام (?).

في دالة ()main، بدأنا الشفرة بقراءة اسم المستخدم name والمدينة city باستخدام دالة ()readln. ولأن هذه الدالة ستعيد نص فارغ بين علامتي تنصيص “” وليس null، عندما لا يدخل المستخدم أي شئ ويضغط فقط على Enter، أنشأنا دالة ملحقة بالصنف String وأسميناها ()emptyToNull. والتي سيكون عملها تحويل أي مدخلات فارغة بين علامتي تنصيص “” إلى null.

وبعد أن نُنشئ كائنات من الأصناف الثلاث حسب مُدخلات المستخدم، نتأكد من أن المُدخلات هي قيم فعلية وليست null، عبر استخدام كتلة if، حتى لا تحدث مشكلة (NullPointerException (NPE. لذا نتأكد أولًا، من قيمة الكائن user في الصنف Info، ثم خاصيّة الاسم في كائن user الذي يحتويه الكائن Info، ثم الخاصيّة address والخاصيّة city.

بعد التأكد من أن كل ذلك لا يساوي null، نطبع معلومات المستخدم عبر استخدام الكائن info. أمّا إذا كان أيًا منها يساوي null، فسيتم طباعة: User info is null. ستقوم الشفرة بالمطلوب منها:

ولكن نلاحظ أننا كتبنا 12 سطر لكتلة if، للتأكد من قيمة مُدخلين فقط: الاسم والمدينة. لإختصار الشفرة، يمكننا استبدال كتلة if، بعامل الإستدعاء الآمن (.?). والذي سنضعه، بعد كل كائن أو خاصيّة نتوقع أن تكون null:

هذا السطر فقط، سيقوم بمهمة كل الـ 12 سطر السابقة. وهذا يوضح أيضًا، كيف يمكننا أن نستخدم عامل الإستدعاء الآمن، في سلسلة من الإستدعاءات، دون الخوف من حدوث مشاكل. ما اختلف هو أن الشفرة السابقة كانت تطبع: User info is null، عندما يكون أي كائن أو خاصيّة يساوي null. هذه المرة سيتم طباعة: null lives in null، وهي رسالة غير صحيحة ويجب أن نغيرها.

 هذا حدث، لأن العامل (.?) سيعيد النتيجة null عندما تكون قيمة المتغير null. وهو بذلك يمنع تحطم البرنامج في الحقيقة. ولكن في برنامجنا هذا، نحتاج إلى رسالة أكثر وضحًا ليفهمها المستخدم. لذا، سنستخدم عامل ثاني يُعرف بـ Elvis Operator.

العامل (:?) Elvis Operator:

نستخدم هذا العامل، حينما نريد تخصيص رسالة أو فعل شئ مخصص، عندما تكون نتيجة التعبير يسار هذا العامل null:

val platform: String? = null

println(platform?.length ?: 0)

ستطبع دالة ()println، نتيجة أحد التعبيرين. إمّا التعبير يسار العامل (:?) إذا لم تكن قيمة platform هي null، أو نتيجة التعبير يمين العامل وهو الرقم صفر. ولأن المتغير platform هو متغير null، لذا سيتم طباعة الصفر.

أمّا عند وجود قيمة في المتغير platform، فسيتم طباعة طولها وتجاهل الصفر:

val platform: String? = “ExVar”

println(platform?.length ?: 0)

تعديل البرنامج السابق:

بعد أن تعرّفنا على طريقة عمل العامل Elvis، دعونا نستخدمه في تعديل برنامجنا السابق:

نلاحظ أننا عملنا تضمين import لصنف Exception من مكتبة كوتلن القياسية، في أعلى الملف، لأننا سنستخدمه داخل الشفرة. وأنشأنا متغيرين آخرين للإسم userName والمدينة userCity، لكتابة رسالة مخصصة لكل منهما. نحن نعرف من درس الاستثناءات، أن حدوث استثناء يعني توقف البرنامج بالكامل من العمل. ولأنه ليس من المنطقي أن يواصل البرنامج في حالة أي مدخلات تساوي null، سنستفيد من هذا الأمر، برمي throw استثناء Exception عندما يكون التعبير يسار العامل (:?) عبارة عن null.  وحتى يكون سبب الاستثناء مفهومًا، وضعنا بداخله رسالة توضح السبب.

فمثلًا، عندما تكون القيمة التي نحاول إسنادها إلى المتغير userName عبارة عن null، سيتم رمي الاستثناء مباشرة متضمنًا الرسالة: User name is null وإيقاف البرنامج بالكامل. أمّا إذا تخطى البرنامج هذا السطر، فهذا يعني أن هناك قيمة تم إسنادها للمتغير userName. لذا، نستخدم هذه القيمة في رسالة الاستثناء الثانية، التي تتبع المتغير userCity.

عند تنفيذ الشفرة، سينتج لنا:

في حالة عدم وضع قيمة للاسم:

في حالة عدم وضع قيمة للمدينة:

العامل (!!) Non-Null Assertions:

نستخدم هذا العامل، لنؤكد لمترجم كوتلن، أن قيمة متغيرًا ما لن تكون فارغة. أي نجبر المترجم على تجاهل حقيقة أن المتغير لا يُمثل قيمة، والعمل على تجميع وترجمة البرنامج رغم الخطأ:

كما نرى في الصورة، ان المتغير language هو عبارة عن متغير null. لذا، لطباعة طوله باستخدام الخاصيّة length، يجب أن نستخدم معه عامل الاستدعاء الآمن (.?) لطباعة قيمة الطول أو null، أو أن المترجم لن يقوم بترجمة هذه الشفرة من الأساس.

لإجبار المترجم على ترجمة الشفرة، يجب أن نؤكد له أن قيمة هذا المتغير لن تكون null عند الطباعة، باستخدام العامل (!!). ولكن، إذا كنا على خطأ، سيتم رمي الاستثناء المخيف (NullPointerException (NPE، ويتحطم البرنامج في يد المستخدم:

أمّا عندما يكون هناك قيمة في المتغير language، سيتم طباعة طولها:

إذًا، عند استخدام العامل (!!), يجب أن نكون حذرين جدًا ومتأكدين 100% أن القيمة لن تكون فارغة. حينها فقط سيفيدنا هذا العامل في إجبار المترجم بتجاهل التحذير، وتنفيذ البرنامج.

ينصح فريق كوتلن، بتجنب استخدام هذا العامل بقدر الإمكان، واستخدام الـ Safe Calls عبر العامل (.?)  وعامل Elvis (:?) أو استخدام كتل if، كما فعلنا في بداية هذا الدرس. تم تقديم عامل التأكيد (!!) في الأساس، لتمكين التفاعل بين لغتي كوتلن و جافا، وللحالات النادرة عندما لا يكون مترجم كوتلن ذكيًا بما يكفي لضمان إجراء الفحوصات اللازمة.

هذا الدرس هو جزء من سلسلة تعليم مبادئ البرمجة بلغة كوتلن. لمُتابعة الدروس منذ البداية ومُشاهدة فهرس المحتويات يمكنك الانتقال إلى الدرس الأول من هنا.

هل أعجبك المحتوى وتريد المزيد منه يصل إلى صندوق بريدك الإلكتروني بشكلٍ دوري؟
انضم إلى قائمة من يقدّرون محتوى إكسڤار واشترك بنشرتنا البريدية.