التخلص من Null في كوتلن

المقالة تستعرض الطرق التي اتت بها لغة الكوتلن لمعالجة تفاقم مشكلة المليون دولار والحد منها قدر المستطاع الا وهي مشكلة الـ Null Pointer Exception.

Mohammad Laifمنذ 5 سنوات

بسم الله الرحمن الرحيم

السلام عليكم ورحمة الله وبركاته

هذه المقالة تستعرض الطرق التي اتت بها لغة الكوتلن لمعالجة تفاقم مشكلة المليون دولار والحد منها قدر المستطاع الا وهي مشكلة الـ Null Pointer Exception. قمت بتقسيم هذه المقالة على عدة اجزاء كل جزئ منها يتناول طريقة للقضاء على الـ Null.

 

سوف تقرئ:

الخصائص وطريقة الاستخدام لكل طريقة اتت بها لغة الكوتلن للقضاء على الـ Null. وهذه الطرق عبارة عن كلمات Keywords وبعض الدوال Functions ونستطيع دمجهم معاً لتشكل Best Practice قدر المستطاع مع امثله برمجيه بسيطه جداً, الطرق هي:

  1. الـ NotNullAssertion.
  2. الـ Safe Call Operator.
  3. الـ Elvis.
  4. الـ Let.
  5. الـ FilterNotNull.
  6. الـ Safe Casting.
  7. الـ Late Initialization.
  8. الـ Mutable List.
  9. الـ Lazy Initialization.

 

قبل الشروع في القرائة

  • يجب عليك ان تكون بعض الشئ ملماً بالخطئ: NullPointerException ومن منا لايعرفه!
  • يجب عليك ايضاً ان تكون ملماً بلغة الكوتلن مثل ماجاء فيها من:
    • الانواع الغير قابلة للتغيير اي الـ Immutable وتنشئ بالـ val.
    • الانواع القابلة للتغيير اي الـ Mutable وتنشئ بالـ var.
    • الانواع الغير القابلة لإستخدام قيم Null اي الـ Non-Nullable (وهي كل نوع ننشئة بإستخدام val او var افتراضياً).
    • الانواع القابلة لإستخدام قيم Null اي الـ Nullable (وهنا سنقوم بتحويل الانواع الى هذا النوع).

 

١. استخدام الـ Not Null Assertion

استخدام الـ Not Null Assertion ويعني التأكيد من قبل المبرمج الى المترجم (الـ Compiler) ان قيمة العنصر لن تكون Null مطلقاً. ويرمز لها بعلامة التعجب مرتان !!. واذا خالف المبرمج هذا الوعد فان الخطئ الـ NullPointerException سيقذف في وجه المستخدم.

الخصائص

  • يرمز لها برمز التعجب مرتان !!.
  • تقوم بإرجاع فيمة Null وتسبب في تعطل البرنامج.
  • تقذف الخطئ الخاص بالكوتلن ويسمى بالـ KotlinNullPointerException.
  • تستخدم فقط عندما تكون متأكد ١٠٠٪ ان القيمة لن تكون Null.

طريقة الاستخدام

لنفرض اننا نريد إنشاء خاصية ما (اي Properties مايعادل الـ Fields في لغة الجافا) في الوقت الراهن, ولاحقاً سنقوم بإسناد قيمة لها كالتالي:

var name: String

 

لن يقبل المترجم (الـ Compiler) بذلك, اذا لنجرب اسناد لها قيمة null حالياً (كما كنا نفعل في الجافا سابقاً):

var name: String = null

 

لن يقبل كذلك! وسيظهر لنا الخطئ التالي:

null can not be a value of a non-null type

 

ففي لغة الكوتلن يمنع استخدام قيمة null للإنواع الـ Non-Nullable (وكل نوع افتراضياً يكون من هذا النوع). ولحل هذه المشكلة نستطيع استخدام الـ Not Null Assertion (لتحويلهم الى نوع يقبل قيمة null اي Nullable) كالتالي:

var name: String = null!!

 

وهكذا اكدنا للمترجم ان العنصر لن يكون null وان المبرمج ملتزم بذلك الوعد واذا خالف الوعد كالتالي:

var name: String = null!!
val nameLength: Int = name.length
Log.d(TAG, nameLength.toString())

سيقذف بالـ KotlinNullPointerException.

 

عند تحويل الشفرة البرمجية من لغة الجافا الى لغة الكوتلن بإستخدام محول الشفرات المدمج في الاندرويد ستديو سترى الكثير من هذا النوع الـ !! فعليك حينها تغييره الى ماتراه مناسب و التقليل منه.

فهذا الـ !! يعتبر ضار وحاول التقليل من استخدامه او استخدام طرق اخرى قدر المستطاع. الا اذا كنت متأكد ١٠٠٪ ان قيمة العنصر لن تكون Null (هل تضمن اتصال الشبكه؟ وجود الملف في ذاكرة الهاتف؟ وجود قيمة العنصر في قاعدة البيانات؟ الخ…).

 

٢. استخدام الـ Safe Call Operator

استخدام الـ Safe Call Operator ويعني النداء الآمن ويرمز له برمز الاستفهام. ففي الفقره السابقه قمنا بإستخدام الـ !! لتحويل عنصر لايقبل قيمة Null الى عنصر يقبل رغماً عن المترجم (الـ Compiler) ولكن هذا ربما يؤدي الى رمي الخطئ KotlinNullPointerException وايقاف تشغيل التطبيق في وجه المستخدم. ولكننا لانريد ايقاف التطبيق في وجه المستخدم حتى ولو كانت القيمة Null اليس كذلك. لذا الحل الامثل في هذا الصدد هو استخدام علامة النداء الآمن.

الخصائص

  • يرمز لها برمز الاستفهام؟.
  • تقوم بإرجاع قيمة Null ولاتتسبب في تعطل البرنامج.
  • لاتقذف خطئ الـ KotlinNullPointerException.

طريقة الاستخدام

لنفرض اننا نريد إنشاء خاصية ما (اي Properties مايعادل الـ Fields في لغة الجافا) في الوقت الراهن, ولاحقاً سنقوم بإسناد قيمة لها كالتالي:

var name: String = null

 

ولإستخدام النداء الآمن يصبح كالتالي:

var name: String? = null

 

واذا خالف المبرمج وقام بإستخدام العنصر كالتالي:

var name: String? = null
var nameLength: Int? = name?.length
Log.d(TAG, nameLength.toString())

فلن يتعطل البرنامج, بل سيطبع قيمة Null.

 

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

 

٣. استخدام الـ Elvis

استخدام الـ Elvis يستطيع ارجاع قيمة ما اذا كانت القيمة Null. ويرمز له بالرمز ?:. في الفقرة السابقة رأينا كيفية ان استخدام الـ ؟ لايعطل عمل البرنامج كلياً. فهو يمنع قذف الخطئ KotlinNullPointerException اذا كانت قيمة العنصر Null, وللإسف يقوم بإرجاع قيمة null كـ String. ففي هذه الفقره سنرى طريقة استخدام الـ ؟: والذي يعتبر مشابه جداً للسابق, ولكن الفرق هو ان هذا يمكنه ان يقوم بإرجاع قيمة نحددها نحن اذا كانت قيمة العنصر Null.

الخصائص

  • برمز له برمز الاستفهام ونقطتان رأسيتان ?:.
  • يقوم بإرجاع قيمة يختارها المبرمج عوضاً عن ارجاع قيمة نص Null.
  • لايسبب في تعطل البرنامج.
  • لايقذف خطئ الـ KotlinNullPointerException.
  • يستطيع المبرمج التحكم في القيمة المرجعه.

طريقة الاستخدام

لنفرض اننا نريد إنشاء خاصية ما (اي Properties مايعادل الـ Fields في لغة الجافا) في الوقت الراهن, ولاحقاً سنقوم بإسناد قيمة لها كالتالي:

var name: String = null

 

ولإستخدام الـ Elvis يصبح كالتالي:

var name: String? = null
var nameLength: Int? = name?.length ?: -1
Log.d(TAG, nameLength.toString())

 

قمنا بإستخدام العلام ؟: مع علامة الـ ؟. وذلك حتى اذا كانت القيمة Null سيقوم بإرجاع قيمة -1. وهكذا نستطيع وضع منطق معين لاحقاً في البرنامج بحيث اذا كانت القيمة بالناقص تظهر رساله ما تخبر بخلل للمستخدم او بإتخاذ اجراء اخر, بدون الصمت المطلق كما كان بالفقرة السابقة.

 

يعتبر استخدام الـ Elvis نوعا ما ملازم لإستخدام النداء اللآمن.

 

٤. استخدام الـ Let او اخواتها

استخدام كلمة الـ Let. تعتبر من الـ Scope Functions. اي هذه الكلمة تصنع Block خاص بها لكتابة الشفرة البرمجية على شكل Lambda ونقوم بإستخدامها على العناصر من نوع Nullable اي اللتي تقبل قيم Null. فهي اساساً تستخدم لربط الدوال معاً كمتسلسلة او بما يعرف Chaining Functions ولاكن هنا نحن بصدد استخدامها لمحاولة التغلب على ظهور مشكلة الـ Null في البرنامج.

الخصائص

  • تستخدم لربط الدوال مع بعضها البعض كسلسلة دوال.
  • تستخدم لمحاولة التغلب على ظهور الـ Null.
  • تستطيع ارجاع قيمة ما (بعكس بعض اخواتها Run و With و Apply و Also).
  • ترمز للمدخل (الـ Argment) لها بالرمز it. وتستطيع تغييره الى مايناسبك بإستخدام رمز السهم ->.

طريقة الاستخدام

لدينا قائمة من نوع Nullable اي تقبل الـ null (بإستخدام ؟) وتعتبر mutable (وذلك بإستخدام mutableListOf كما سيأتي لاحقاً) للإسماء كالتالي:

var names = mutableListOf<String?>("Mohammad", "Lila", null, "Khalid")

 

نريد استخدام محتوياتها في For Loop كالتالي:

var names = mutableListOf<String?>("Mohammad", "Lila", null, "Khalid")
for (name in names) {

}

الان اذا اردنا طباعة محتوياتها كالتالي:

Log.d(TAG, "Name: $name: Length: ${name.length}")

 

لن يرضى المترجم (الـ Compiler) وسيطلب منا تحويل العنصر name الى نوع Nullable ويالحسن الحظ الامر بسيط سنظيف فقط ؟ فهي افضل من استخدام التعجبين كما عرفنا سابقاً لتصبح الشفرة البرمجية كالتالي:

Log.d(TAG, "Name: $name: Length: ${name?.length}")

 

والنتيجة ستكون:

Name: Mohammad: Length: 8
Lila: Length: 4
null: Length: null
Khalid: Length: 6

 

المعضلة: لانريد طبااااعة Null ان احتوت قائمة الاسماء عليهم. الحلول المقترحة الاخرى: استخدام علامتي التعجب؟ لا هذا سيعطل البرنامج. اذا استخدام علامة الاستفهام والنقطتين الرأسيتان؟ لا فهذا سيطلب منا كتابة قيم لكل قيمة Null غير موجودة. اذن مالحال؟

هو استخدام Let كالتالي:

name?.let {
Log.d(TAG, "Name: $it: Length: ${it.length}")
}

 

لتصبح الشفرة البرمجية كاملة على النحو:

var names = mutableListOf<String?>("Mohammad", "Lila", null, "Khalid")

    for (name in names) {
        name?.let {
            Log.d(TAG, "Name: $it: Length: ${it.length}")
        }
    }

 

وعند التشغيل ستكون النتيجة:

Name: Mohammad: Length: 8
Name: Lila: Length: 4
Name: Khalid: Length: 6

 

وهكذا لم نحصل على اي قيمة Null ولم يتوقف البرنامج. ولكن في الفقرة التالية سنتعرف على وسيلة اسهل لتصفية القائمة من الـ Null.

 

٥. استخدام دالة الـ Filter Not Null

استخدام الـ FilterNotNull لتصفية قائمة ما من قيم الـ Null. في الفقرة السابقة راينا كيفية استخدام كلمة Let على عنصر من نوع قائمة لتصفية عناصر الـ Null منها. ففي هذه الفقرة سنقوم بإستخدام شئ اسهل وهي دالة معدة لعمل ذلك.

طريقة الاستخدام

لدينا قائمة من نوع Nullable اي تقبل الـ null (بإستخدام ؟) وتعتبر mutable (وذلك بإستخدام mutableListOf كما سيأتي لاحقاً) للإسماء كالتالي:

 

لنفرض ان لدينا قائمة من نوع Nullable (اي تقبل قيم Null) تحتوي على اسماء كالتالي:

var names = mutableListOf<String?>("Mohmmad", "Lila", null, "Khalid")

 

والان قبل استخدامها نستطيع حذف جميع قيم الـ Null منها, حتى نستطيع استخدامها بشكل سليم لاحقاً في البرنامج كالتالي:

val namesNoNull = names.filterNotNull()

 

وعند الاستخدام:

Log.d(TAG, namesNoNull.size.toString())

ستكون النتيجه فقط ٣ اسماء, وكل قيم الـ Null سيتم القضاء عليها.

 

٦. استخدام الـ Safe Casting

استخدام الـ Safe Casting اي التحويل الآمن. في لغة الكوتلن نستخدم الكلمة as لعملية التحويل (الـ Casting) من نوع الى نوع اخر. ويرمز له بالرمز as?. ولكن في بعض الاحيان عندما يفشل المترجم (الـ Compiler) سيرمي الخطئ: TypeCastException وسيتوقف عمل البرنامج. ولهذا يجب علينا دائماً استخدام التحويل الآمن اذا لم نكن متأكدين من اتمام عملية التحويل بشكل سليم.

الخصائص

يرمز له بالرمز as?.

طريقة الاستخدام

لدينا عنصر من نوع String نريد تحويلة الى نوع Int كما بالشفرة البرمجية التالية:

var inputSalary: String = "4000"

 

عملية التحويل الآمنة:

var salary: Int? = inputSalary as? Int

 

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

Log.d(TAG, "Total is: $salary")

ستكون النتيجة انه سيقوم بطباعة null فقط. ولن يؤدي الى فشل البرنامج ولا ظهور الخطئ TypeCastException.

 

٧. استخدام الـ Late Initialization

استخدام الـ Late Initialization اي الإنشاء المتأخر. وهي كلمة في لغة الكوتلن تكتب برمجياً lateinit.

الخصائص

  • تعمل مع الانواع الـ Mutable (القابلة للتعديل والتغيير) اي التي تعرف بإستخدام الكلمة var.

طريقة الاستخدام

سابقاً في الجافا اذا كان لدينا حقل Field ونريد تعرفية لاحقاً فإننا نستطيع عدم اسناد له قيمة او نسند له قيمة null في الوقت الراهن وفي دالة الـ onCreate نقوم بعمل Initialization له. اما في الكوتلن لانستطيع ذلك كما رأينا في الفقرات السابقة. فالشفرات البرمجية التالية جميعها غير جيدة ولاتخلو من مشاكل بعض الاحيان:

var textView: TextView
var textView: TextView = null
var textView: TextView = null!!
var textView: TextView? = null

 

والحل هو استخدام الإنشاء المتأخر الا الـ lateinit كالتالي:

lateinit var textView: TextView

 

ولاننسا ان نعمل له Initialization لاحقاً هكذا:

textView = findViewById(R.id.textView)

 

عند تعريف اي View في الاندرويد من الافضل استخدام بلاجن الكوتلن kotlin-android-extensions, كما هو موضح بالتغريدة التالية:

 

عرض الصورة على تويتر

عرض الصورة على تويتر

Mohammad Laif@mohammadlaif

وداعاً للـ FindViewById و الـ ButterKnife والـ Data Binding مع إضافات الكوتلن 💘​.
- إضف بلاجن الكوتلن لملف الجريدل.
- اعمل import للـ xml الخاصة بالـ activity في كلاسك.
- قم بإستخدام الـ views كما سميتهم بشكل مباشر في كلاسك.#اندرويد #كوتلن

٨

٦:٢٦ م - ٩ يونيو ٢٠١٩

 

٨. استخدام الـ Mutable List للقوائم

استخدام الـ Mutable List لإنشاء قائمة مؤقته في الوقت الراهي وعمل Initialization لها لاحقاً. ففي الجافا سابقاً نقوم بإسناد قيمة الـ Null لها اما في الكوتلن لانستطيع ذلك ولحل هذه المشكلة نستطيع استخدام بعض من الطرق السابقة, ولكن من افضل الطرق لإنشاء القوائم هي هذه الطريقة.

طريقة الاستخدام

نريد إنشاء قائمة فارغة مؤقتاً كحقل, حتى تحتوي على العناصر لاحقاً عندما نستخرجهم من قاعدة البيانات او نأتي بهم من احد الـ API. فبدلاً من استخدام الطرق السابقة نستطيع ذلك وبسهوله هكذا:

private var todoList: List<TodoEntity> = mutableListOf()

وهذا يغنينا عن استخدام كلاً من الـ !! او ? او حتى الـ lateinit عندما يتعلق الامر بإنشاء القوائم.

 

٩. استخدام الـ Lazy Initialization

استخدام الـ Lazy Initialization اي الإنشاء الكسول. وذلك بإستخدام الكلمة by lazy. ففي بعض الاحيان نريد تجهيز عنصر ولكننا لانريد عمل Initialization له حالياً, بل نريد عمل ذلك عندما نقوم بإستخدامه لإول مره.

الخصائص

  • تعمل مع الانواع الـ Immutable (غير قابلة للتغير او التعديل) اي التي تعرف بإستخدام الكلمة val.
  • تقوم على مبدأ الـ Delegation.
  • لايتم عمل Initialization الا عندما تقوم بإستخدام العنصر لإول مره.
  • عند استخدام العنصر مره اخرى لايقوم المترجم (الـ Compiler) بعمل Initialization له بل يعطيك اياه من الذاكرة.

طريقة الاستخدام

لنفرض ان لدينا عنصر يحمل اسم المستخدم هكذا:

val name: String

 

ونريد عمل له إنشاء كسول, اي فقط عندما نقوم بإستخدامه لاحقاً, فسيكون الوضع هكذا:

val name: String by lazy { initName() }

 

والدالة المسؤلة عن الانشاء ستكون هكذا:

fun initName(): String {
    if (userName == user) {
        return user.name
        } else {
            return "unknown"
        }
    }
}

 

وحتى هكذا:

val name: String by lazy { "Mohammad" }

 

في مثال حقيقي من المفضل استخدام الإنشاء الكسول مع الـ ViewModel.

 

في النهاية هذه هي الطرق التي اتت بها لغة الكوتلن للقضاء على الـ Null. اذا صادفتك طريقه اخرى تستطيع مشاركتها معي من خلال التعليق واذا احببت تستطيع متابعتي على حسابي بالتويتر: @mohammadlaif. وشكراً على قرائتك.

كلمات دليلية: اندرويد كوتلن
0
إعجاب
4250
مشاهدات
0
مشاركة
1
متابع

التعليقات (0)

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !