مقدمة: لماذا حتى المطورون الأذكياء يتعثرون في Kotlin
\n\nKotlin هي لغة أنيقة وقوية فازت بقلوب الملايين من المطورين. لكن إيجازها ومرونتها يخفيان فخاخًا، خاصة للمبتدئين القادمين من Java أو لغات أخرى. يعتقد الكثيرون أنه بما أن Kotlin "أوضح" من Java، فستكون الأخطاء أقل. في الواقع، العكس هو الصحيح: عدم فهم فلسفة اللغة يؤدي إلى أخطاء يصعب اكتشافها.
\n\nفي هذه المقالة، سنحلل أهم أخطاء مطوري Kotlin المبتدئين - من تجاهل null safety البسيط إلى الاستخدام الخاطئ للـ coroutines. سيتم توضيح كل خطأ بمثال كود ونصيحة عملية لتجنبه.
1. Null Safety: متى ينقذ "?" ومتى لا
\n\n1.1. تجاهل الأنواع القابلة للصفر (nullable types)
\nالخطأ الأكثر شيوعًا هو استخدام !! (تأكيد عدم الصفر) حيث يمكن استخدام استدعاءات آمنة. غالبًا ما يكتب المبتدئون:
fun printLength(str: String?) {\n println(str!!.length) // سيؤدي إلى NPE إذا كان str == null\n}\n\nالنهج الصحيح هو استخدام ?.let أو عامل الإلفيس ?::
fun printLength(str: String?) {\n str?.let { println(it.length) } ?: println("السلسلة فارغة")\n}\n\n1.2. نسيان التحويل الذكي (Smart Cast)
\nيقوم Kotlin تلقائيًا بتحويل الأنواع بعد التحقق من الصفر، لكن المبتدئين لا يلاحظون ذلك:
\n\nfun process(value: Any?) {\n if (value is String) {\n // value أصبح String بالفعل، لا حاجة للتحويل!\n println(value.length) // يعمل بدون (value as String).length\n }\n}\n\nنصيحة: ثق بالمترجم. استخدم !! فقط إذا كنت متأكدًا 100% أن القيمة ليست null، ومستعد للانهيار عند الخطأ.
2. Sealed Class و When: حفر الشمولية
\n\n2.1. استخدام فرع else مع sealed class
\nتم إنشاء sealed class ليتحقق المترجم من معالجة جميع الخيارات. لكن المبتدئين غالبًا ما يضيفون else، مما يقتل السحر:
sealed class Result {\n data class Success(val data: String) : Result()\n data class Error(val message: String) : Result()\n}\n\nfun handleResult(result: Result) {\n when (result) {\n is Result.Success -> println(result.data)\n is Result.Error -> println(result.message)\n else -> {} // خطأ! إذا أضفت فئة فرعية جديدة، لن يحذرك المترجم\n }\n}\n\nالصحيح هو عدم استخدام else:
\n\nfun handleResult(result: Result) {\n when (result) {\n is Result.Success -> println(result.data)\n is Result.Error -> println(result.message)\n } // سيعطي المترجم خطأ إذا لم يتم تغطية جميع الفروع\n}\n\n2.2. الخلط بين sealed class و enum
\nغالبًا ما يختار المبتدئون enum حيث يحتاجون sealed class. تذكر: enum لمجموعة محدودة من الثوابت، sealed class لتسلسل هرمي ببيانات مختلفة في كل فئة فرعية.
3. Scope Functions: apply, let, run, with, also — كيف لا تحتار
\n\n3.1. خلط السياق: this مقابل it
\nأكثر الالتباس شيوعًا هو عندما يستخدم المبتدئ apply (السياق — this) لعمليات تحتاج let (السياق — it):
data class User(var name: String, var age: Int)\n\nfun initUser(): User {\n return User("مجهول", 0).apply {\n // this هو User\n name = "إيفان"\n age = 25\n }\n}\n\nfun printUserInfo(user: User?) {\n user?.let { \n // it هو user\n println("الاسم: ${it.name}, العمر: ${it.age}")\n }\n}\n\n3.2. سلاسل scope functions بدون معنى
\nيحب المبتدئون كتابة سلاسل مثل:
\n\nlist?.let { it.filter { it > 5 }.also { println(it) } }
المدونات
توصيات الكتب
اقرأ أيضًا