ملحقات في Kotlin. atavism الخطرة أو أداة مفيدة؟



لا تزال Kotlin لغة شابة ، لكنها دخلت بالفعل في حياتنا. لهذا السبب ، ليس من الواضح دائمًا كيفية تنفيذ هذا أو ذاك الوظيفي بشكل صحيح وأي ممارسة أفضل لتطبيق.

هناك صعوبة خاصة في حالة ميزات لغة ليست في Java. واحدة من هذه العقبات تم التوسع .

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

الامتدادات إلى DTO - انتهاك قالب كائن نقل البيانات


على سبيل المثال ، هناك مستخدم فئة

class User(val name: String, val age: Int, val sex: String) 

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

 if (user.age >= 18) { ... } 

ولكن نظرًا لوجود العديد من هذه الأماكن بشكل تعسفي ، فمن المنطقي وضع هذا الاختيار في الطريقة.
هناك ثلاثة خيارات هنا:

  1. fun fun isAdult (المستخدم: User) - تتكون فئات الأدوات المساعدة عادة من هذه الوظائف.
  2. ضع وظيفة isAdult داخل فئة المستخدم

     class User(val name: String, val age: Int, val sex: String) { fun isAdult() = age >= 18 } 

  3. اكتب برنامج تغليف للمستخدم سيحتوي على وظائف مماثلة.

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

لا ينتهك الخيار الثالث إما مبادئ OOP أو القوالب ، ولكن في كل مرة يتعين عليك إنشاء برنامج تغليف إذا كنت تريد استخدام وظائف مماثلة. هذا الخيار هو أيضا ليست مثل جدا. في النهاية ، اتضح أنه لا يزال يتعين عليك تقديم تضحيات.

في رأيي ، من الأسهل التضحية بقالب DTO. أولاً ، لم أجد تفسيرًا واحدًا لسبب عدم إمكانية إنشاء الوظائف (باستثناء الحروف والأدوات) في DTO. وثانياً ، من حيث المعنى ، فإن مثل هذا الرمز مناسب ليكون بجوار البيانات التي نعمل عليها.

لكن ليس من الممكن دائمًا وضع مثل هذا الكود داخل شيك DTO ، نظرًا لأن المطور ليس لديه دائمًا القدرة على تحرير الفئات التي يعمل بها. على سبيل المثال ، قد تكون هذه فئات تم إنشاؤها من xsd. بالإضافة إلى ذلك ، قد يكون من غير المعتاد وغير المريح كتابة مثل هذا الرمز في فئات البيانات. تقدم Kotlin حلاً لمثل هذه الحالات في شكل وظائف وحقول ملحق:

 fun User.isAdult() = age >= 18 

يمكن استخدام هذا الرمز كما لو كان قد تم الإعلان عنه داخل فئة المستخدم:

 if(user.isAdult()) {...} 

والنتيجة هي حل دقيق إلى حد ما يلبي احتياجاتنا بأقل الحلول التوفيقية. إذا تحدثنا عن حقيقة أن قالب DTO قد تم انتهاكه ، فعندئذٍ نريد أن نتذكر أنه في Java سيكون طريقة ثابتة ثابتة للنموذج:

 public static final boolean isAdult(@NotNull User receiver) 

كما ترون ، بشكل رسمي ، حتى القالب لا ينتهك. يبدو استخدام هذه الوظيفة كما لو كان قد تم الإعلان عنه في User و Idea ستعرضه عند الإكمال التلقائي. انها مريحة جدا.

ملحقات محددة. لا يمكنك معرفة وجودها والخلط بين أساليب وحقول الكيان مع الامتدادات


الفكرة هي أن المطور جاء للمشروع ، وحول الكود الذي تم تنفيذه في الامتدادات ، ليس من الواضح ما هي الطريقة الأصلية وما هي طريقة التمديد.

هذه ليست مشكلة ، لأن Idea تساعد المطور في هذه المسألة وتسلط الضوء على هذه الوظائف. على الرغم من الإنصاف ، يجب القول إن الفرق ملحوظ بشكل أفضل في موضوع داركولا. إذا غيرته إلى Light ، يصبح كل شيء أقل وضوحًا ويختلف الامتداد فقط بالخط المائل.

أدناه نرى مثالاً على استدعاء طريقتين: isAdult هي طريقة التمديد ، isMale هي الطريقة المعتادة داخل فئة المستخدم. لقطة الشاشة على اليسار هي سمة Darcula ، على اليمين هي سمة Light المعتادة.



الأمور أسوأ إلى حد ما مع الحقول. على سبيل المثال ، إذا قررنا تطبيق isAdult كحقل ملحق ، فلا يمكننا تمييزه إلا عن حقل منتظم حسب نوع الخط. في هذا المثال ، الاسم هو حقل منتظم. ينتج حقل الامتداد خطًا مائلًا فقط.



تساعدك بيئة تطوير Idea على تحديد الطريقة التي هي امتداد وما هي الأصل عند الإكمال التلقائي. هذا مريح.



الوضع مشابه للحقول.



"بالنسبة للمستخدم في <الجذر>" يعني أنه امتداد.

بالإضافة إلى ذلك ، فإن حقيقة أن Idea "تربط" امتدادًا للكيان القابل للامتداد تساعد كثيرًا في التطوير ، حيث يتم اقتراح أساليب الإرشاد والحقول لإكمالها تلقائيًا.

تنتشر الامتدادات في جميع أنحاء المشروع ، وتشكل علبة قمامة


ليس لدينا مثل هذه المشكلة في المشاريع ، حيث أننا لا نضع الإضافات بشكل تعسفي ونخرج الشفرة ذات الإضافات العامة في ملفات أو حزم منفصلة.

على سبيل المثال ، قد تظهر وظيفة isAdult من المثال أعلاه في ملف المستخدم الخاص بحزمة الامتدادات. إذا لم تكن الحزمة كافية وتريد فقط عدم الخلط بين مكان الفصل ومكان وجود ملف الوظيفة ، يمكنك تسمية ذلك ، على سبيل المثال ، _User.kt. وكذلك فعل المطورين في JetBrains للمجموعات. أو ، إذا كان الضمير يحظر بدء الملف بتسطير أسفل السطر ، فيمكنك الاتصال بـ user.kt. في الواقع ، لا يوجد فرق في طريقة الاستخدام ، الشيء الرئيسي هو أن هناك توحيدًا يلتزم به الفريق بأكمله.

منشئو اللغة ، عند تطوير طرق الامتداد للمجموعات ، وضعوها في ملف _Collections.kt .

هذه عمومًا مسألة تنظيم الكود ، وليس مشكلة الامتدادات. يمكن أن تنتشر الوظائف الثابتة في Java ، وليس فقط تلك الثابتة ، بشكل عشوائي لا يقل عن الامتدادات.

لا تغفل وظائف التمديد أثناء اختبار الوحدة


في رأيي ، ليست هناك حاجة إلى تبليل وظائف الامتدادات ، تمامًا كما لا توجد حاجة إلى تبليل الأساليب الثابتة. في وظيفة التوسيع ، يجب أن تضع منطق العمل مع البيانات الموجودة. على سبيل المثال ، في حالة وظيفة isAdult لفئة المستخدم ، كل ما تحتاجه موجود في adAdult. لا تحتاج إلى الرطب.

النظر في مثال أكثر تعقيدا قليلا. يوجد مكون معين يعمل على الحصول على مستخدمين من نظام خارجي - UserComponent. تسمى طريقة الحصول على المستخدمين getUsers. افترض أن هناك حاجة للحصول على جميع المستخدمين النشطين وقرر إضافة منطق التصفية في شكل وظيفة - امتداد. نتيجة لذلك ، حصلنا على الوظيفة:

 fun UserComponent.getActiveUsers(): List<Users> = this.getUsers().filter{it.status == “Active”} 

قد يبدو أنه هنا - موقف عندما تحتاج إلى محاكاة وهمية للتوسع. ولكن إذا كنت تتذكر أن getActiveUsers هو مجرد طريقة ثابتة ، فقد تبين أن الصورة الوهمية ليست ضرورية. يجب أن يكون تراجع الأساليب والوظائف التي تسمى في الملحق ، ولا شيء أكثر من ذلك.

من الممكن أن تتداخل وظيفة الامتداد مع وظيفة تحمل نفس الاسم الموجود داخل الفئة الموسعة


سننظر في هذه الحالة باستخدام المثال الوارد في الفقرة الأولى. افترض أن هناك ملحق وظيفة isAdult ، والذي يتحقق مما إذا كان المستخدم شخصًا بالغًا:

 fun User.isAdult() = age >= 18 

بعد ذلك ، ننفذ وظيفة بنفس الاسم داخل المستخدم:

 class User(val name: String, val age: Int, val sex: String){ fun isAdult() = age >= 21 } 

عندما يتم استدعاء user.isAdult () ، سيتم استدعاء وظيفة من الفصل ، على الرغم من وجود ملحق بنفس الاسم ووظيفة مناسبة. قد تكون هذه الحالة مربكة ، لأن المستخدمين الذين ليسوا على دراية بالوظيفة المعلنة داخل الفصل سينتظرون إكمال وظيفة الامتداد. هذا وضع غير سارة يمكن أن يكون له عواقب وخيمة للغاية. في هذه الحالة ، لا نتحدث عن الإزعاج المحتمل لمخالفة المراجعة أو القالب ، ولكن عن السلوك الخاطئ المحتمل للشفرة.

يوضح الموقف الموضح أعلاه أنه عند استخدام وظائف الامتداد ، يمكن أن تنشأ مشاكل حقيقية.

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

يرتبط الملحق بفئة ، وليس كائن ، وهذا يمكن أن يسبب الارتباك


على سبيل المثال ، ضع في الاعتبار فئة المستخدم من الفقرة الأولى. لنجعلها مفتوحة وخلق خلفها الطالب:

 class Student(name: String, age: Int, sex: String): User(name, age, sex) 

نحدد وظيفة التمديد للطالب ، والتي ستحدد أيضًا ما إذا كان الطالب بالغًا أم لا. للطالب فقط نقوم بتغيير الحالة:

 fun Student.isAdult() = this.age >= 16 

والآن نكتب الكود التالي:

 val user: User = Student("", 17, "M") 

ما سيعود user.isAdult ())؟
يبدو أن كائن من نوع الطالب والدالة يجب أن يعود صحيحاً. لكنها ليست بهذه البساطة. يتم إرفاق الامتدادات بالفئة ، وليس بالكائن ، وستكون النتيجة خاطئة.

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

بدلا من الإخراج


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

في رأيي ، تعد الإضافات أداة قوية ومريحة يمكنها تحسين جودة الشفرة وإمكانية قراءتها ، ولا تتطلب أي شيء تقريبًا في المقابل. لهذا السبب أنا أحبهم:

  • تتيح لك الامتدادات كتابة منطق محدد لسياق فئة قابلة للمد. بفضل هذا ، تتم قراءة الحقول وطرق الامتداد كما لو كانت موجودة دائمًا في الكيان الموسع ، مما يحسن بدوره فهم المستوى الأعلى للرمز. في Java ، للأسف ، لا يمكن القيام بذلك. علاوة على ذلك ، فإن الملحقات لها نفس معدلات الوصول مثل الوظائف العادية. يتيح لك ذلك كتابة تعليمات برمجية مماثلة مع النطاق الضروري حقًا لوظيفة معينة.
  • من الملائم استخدام وظائف الامتداد للتعيين ، والتي يجب أن تراها كثيرًا عند حل المهام اليومية. على سبيل المثال ، في المشروع هناك فئة UserFromExternalSystem ، والتي يتم استخدامها عند استدعاء نظام خارجي ، وسيكون من الرائع وضع التعيين في وظيفة الامتداد ، ونسيانها واستخدامها كما لو كانت في الأصل في المستخدم.

     callExternalSystem(user.getUserFromExternalSystem()) 

    بالطبع ، يمكن القيام بالشيء نفسه بالطريقة المعتادة ، ولكن هذا الخيار أقل قابلية للقراءة:

     callExternalSystem(getUserFromExternalSystem(user)) 

    أو مثل هذا الخيار:

     val externalUser = getUserFromExternalSystem(user) callExternalSystem(externalUser) 

    في الواقع ، لا يحدث أي سحر ، ولكن بفضل هذه التافهات ، من الممتع العمل مع الكود.
  • فكرة ودعم الإكمال التلقائي. بخلاف الأساليب الموجودة في فئات الأدوات المساعدة ، يتم دعم الملحقات بشكل جيد بواسطة بيئة التطوير. مع الإكمال التلقائي ، يتم تقديم الامتدادات بواسطة البيئة كوظائف وحقول "أصلية". هذا يسمح بزيادة جيدة في إنتاجية المطور.
  • لصالح الامتدادات هو حقيقة أن جزءًا كبيرًا من مكتبات Kotlin تتم كتابتها كملحقات. العديد من الطرق المريحة والمفضلة للعمل مع المجموعات هي الملحقات (تصفية ، خريطة ، وما إلى ذلك). يمكنك التحقق من ذلك عن طريق فحص ملف _Collections.kt .

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

فيما يلي روابط لمواد تم استخدامها لإعداد هذه المقالة:

  1. proandroiddev.com/kotlin-extension-functions-more-than-sugar-1f04ca7189ff - من هنا يتم أخذ أفكار مثيرة للاهتمام حول حقيقة أننا ، باستخدام الامتدادات ، نعمل بشكل وثيق مع السياق.
  2. www.nikialeksey.com 2017 / 11 / 14 / kotlin - is - bad.html - هنا يعارض المؤلف الامتدادات ويعطي مثالًا مثيرًا للاهتمام ، والذي تمت مناقشته في إحدى النقاط أعلاه.
  3. medium.com/@elizarov/i-do-not-see-much-reason-to-mock-extension-functions-7f24d88a188a - رأي رومان إليزاروف حول ترطيب طرق التمديد.

أود أيضًا أن أتقدم بالشكر الجزيل للزملاء الذين ساعدوا في حالات وأفكار مثيرة للاهتمام بشأن هذه المادة.

Source: https://habr.com/ru/post/ar485760/


All Articles