Kotlin / السلوك القابل للاستخدام والحدود المنمطة / الدرس 9
جعل النتائج البديلة صريحة
اختر enum للخيارات المحدودة ذات الشكل الموحد، وتسلسلا مغلقا لنتائج تحمل بيانات مختلفة.
في هذه الصفحة
أهداف الدرس
- نمذج النجاح والرفض والانتظار دون حقول خالية تتيح حالات متناقضة.
- استخدم when شاملة، وافهم كلفة التوافق عند إضافة بديل جديد.
مثّل حالة مسموحة واحدة في كل مرة
يتيح سجل يحوي success: Boolean وreservationId: String? وerror: String? تراكيب متناقضة، مثل نجاح بلا معرف أو فشل بلا معرف ولا خطأ. يستطيع الناتج المغلق إعطاء كل بديل البيانات التي يتطلبها: يحمل التأكيد معرف الحجز، والرفض سببه، والانتظار معرف المهمة. ينقل ذلك جزءا من نموذج الحالة إلى الأنواع، لكن المنشئات وتحليل البيانات الخارجية يظلان بحاجة إلى تحقق. لا تقلل الأنواع التراكيب غير الصالحة إلا بقدر ما يعكس النموذج نفسه معنى المجال على نحو صحيح.
عالج كل نتيجة معروفة بوضوح
يقيّد التسلسل المغلق الوراثة المباشرة وفقا لقواعد الوحدة والحزمة والمنصة المعمول بها في Kotlin؛ وليس مجرد صنف لم يحدث أن امتد منه أحد بعد. تستطيع when الشاملة فرض معالجة البدائل المعروفة، فتساعد القائم بالصيانة على اكتشاف نقاط القرار المتأثرة ببديل جديد. تجنب إضافة else تلقائيا عندما تكون المعالجة الداخلية الكاملة مفيدة، فقد يخفي بديل قديم نتيجة أضيفت حديثا. أما البيانات الخارجية فقد تحمل قيما غير معروفة، وتحتاج سياسة مقصودة عند حد استقبالها.
sealed interface BookingOutcome
data class Confirmed(val reservationId: String) : BookingOutcome
data class Rejected(val reason: String) : BookingOutcome
data class Pending(val jobId: String) : BookingOutcome
fun label(outcome: BookingOutcome): String = when (outcome) {
is Confirmed -> "confirmed:${outcome.reservationId}"
is Rejected -> "rejected:${outcome.reason}"
is Pending -> "pending:${outcome.jobId}"
}
fun main() {
println(label(Confirmed("r-2")))
println(label(Rejected("full")))
println(label(Pending("j-5")))
}
confirmed:r-2
rejected:full
pending:j-5
الأسطر المتوقعة هي confirmed:r-2 ثم rejected:full ثم pending:j-5. يحصل كل فرع على البيانات التابعة لبديله. لم يُترجم المصدر هنا، وهو يحتاج أدوات Kotlin تدعم الواجهات المغلقة.
استخدم enum عندما تشترك البدائل في البنية
يحدد enum مجموعة منتهية من مثيلات صنف تعداد واحد، ويناسب اختيارا صغيرا موحد الشكل كلغة العرض أو اتجاه الفرز. وقد تملك الثوابت خصائص وسلوكا. لا تحفظ ordinal كمعرف عمل دائم، لأن إعادة ترتيب الثوابت تغير هذا الموضع العددي. فضّل رمزا مستقرا صريحا، وتحقق من الرموز الخارجية المجهولة. ولا يفرض enum وحده الانتقالات المسموحة؛ فقد تقفز خاصية حالة من ملغى إلى مؤكد ما لم يمنع السلوك المالك ذلك. افصل التسميات الممكنة عن آلة الحالة التي تضبط الانتقال بينها.
ترجم الرموز الخارجية عند الحد
يحفظ المثال رمزا مستقرا صراحة، ويحلله ببحث قابل للخلو بدلا من valueOf التي ترمي عند اسم مجهول. يفيد ذلك عندما يحتمل أن يحمل المدخل الخارجي رمزا غير مدعوم. لا يجب أن تطابق تسمية العرض واسم الثابت في المصدر الرمز المحفوظ في بروتوكول النقل. وإذا تطلب التوافق الاحتفاظ بالقيم المجهولة لإعادتها إلى الخارج، فقد يكون غلاف مغلق بحالة رمز مجهول أنسب من إرجاع null. اختر هذه السياسة عمدا بدلا من فقد المعلومة عرضا أثناء التحليل.
enum class SortDirection(val code: String) {
ASCENDING("asc"), DESCENDING("desc")
}
fun parseDirection(code: String): SortDirection? =
SortDirection.entries.firstOrNull { it.code == code }
fun main() {
println(parseDirection("asc")?.name)
println(parseDirection("other") ?: "unsupported")
}
ASCENDING
unsupported
المخرج المتوقع ASCENDING ثم unsupported. تحتاج entries إلى نسخة Kotlin توفّر هذه الخاصية، وعلى المشروع استخدام سلسلة أدواته المعلنة المتوافقة. لم يُترجم البرنامج في بيئة التأليف.
يؤثر تغيير البدائل في المستهلكين
قد تُلزم إضافة نوع فرعي مغلق المستهلكين في المصدر بتحديث تعبيرات when الشاملة، وهو مفيد كثيرا داخل وحدة واحدة منسقة. لكن التوافق الثنائي وتسلسل البيانات مسألتان منفصلتان تعتمدان على الأدوات والبروتوكول الفعليين. وقد تفاجئ قيمة enum جديدة عميلا يفترض أن المجموعة مغلقة. استخدم تطورا متوافقا لتمثيل النقل واختبارات نسخ مختلطة عندما تصدر التطبيقات مستقلة. لا تحوّل نوعا داخليا غنيا تلقائيا إلى عقد عام مسلسَل دون تحديد أسماء حقول مستقرة ومعالجة المجهول ومعنى كل نتيجة.
الأخطاء الشائعة وإصلاحها
يحمل سجل النجاح حالة نجاح دون بياناتها.
- لماذا يحدث؟
- سمحت الحقول الخالية المستقلة بتراكيب حالة متناقضة.
- كيف تصلحه؟
- نمذج البدائل بحيث يحمل كل بديل بياناته المطلوبة.
- كيف تتجنب تكراره؟
- راجع جميع التراكيب التي يسمح بها النوع، لا الأمثلة المقصودة فقط.
يتغير معنى أرقام enum المحفوظة بعد إعادة الترتيب.
- لماذا يحدث؟
- استُخدم ordinal كرمز خارجي دائم.
- كيف تصلحه؟
- قدّم رموزا مستقرة صريحة مع انتقال مضبوط للبيانات القائمة.
- كيف تتجنب تكراره؟
- افصل ترتيب العرض عن هوية القيمة.
يخفي else واسع نتيجة أضيفت حديثا.
- لماذا يحدث؟
- تخلت المعالجة عن فائدة فحص الشمول الداخلي.
- كيف تصلحه؟
- عالج البدائل الداخلية صراحة حيث يكون ذلك مناسبا.
- كيف تتجنب تكراره؟
- راجع جميع مستهلكي النتائج عندما يتطور النموذج.
اختبر فهمك
1. هل يحسن enum مع حمولة خالية لكل حالة السجل المتناقض الأصلي؟
اعرض الحل المشروح
قد يحدد اسم الحالة، لكنه يظل يسمح بحمولات ناقصة أو غير مناسبة. يجعل البديل المغلق للتأكيد معرف الحجز مطلوبا، وللرفض سببا مطلوبا، دون جعل كل حقل اختياريا. استخدم enum للاختيارات الموحدة، وتسلسلا أغنى عندما تختلف متطلبات البيانات بين الحالات.
2. لماذا قد يحتاج نموذج شامل داخليا إلى معالجة قيمة مجهولة عند حد API؟
اعرض الحل المشروح
قد يعمل المنتج الخارجي بنسخة أحدث، أو يرسل بيانات مشوهة، أو يستخدم مجموعة رموز مختلفة. تحقق من المدخل وترجمه إلى التسلسل الداخلي وفقا لسياسة توافق صريحة. لا تتحقق معرفة المترجم بالأصناف المحلية الحالية من البايتات البعيدة، ولا تنسق الإصدارات المستقلة للتطبيقات.
أهم ما تعلمته
- تستطيع الأنواع استبعاد الحالات المتناقضة عندما يحمل كل بديل بياناته المطلوبة.
- يحتاج الشمول الداخلي والتوافق الخارجي إلى تصميمين منفصلين.