أنماط كتابة الشيفرات المُتعارَف عليها (Coding Conventions)

من موسوعة حسوب
مراجعة 04:53، 23 فبراير 2018 بواسطة Nourtam (نقاش | مساهمات) (تجميع عدة صفحات بصفحة واحدة)

تناقش هذه الصفحة أنماط كتابة الشيفرات المُتَّفق عليها من قِبل مبرمجي لغة Kotlin ضمن العناوين العامّة الآتية:

  • تنظيم الشيفرة المصدرية source code
  • قواعد التسمية
  • التنسيق
  • التعليقات التوثيقيَّة
  • تجنُّب البُنى الزائدة
  • الاستخدام الاصطلاحيُّ لمزايا اللغة
  • المكتبات

تطبيق دليل التنسيق

لضبط التنسيق بما يتوافق مع هذا الدليل يُنصَح بتثبيت إضافة Kotlin بالإصدار 1.2.20 (أو أي إصدار أحدث) ومن ثم ضبط المّحرِّر من خلال الذهاب إلى الإعدادات (Settings) ثمّ المُحرِّر (Editor) ثم نمط الشيفرة (Code Style) ثم Kotlin واضغط على "ضبط من..." Set from…"‎" في الزاوية اليمنى العُلويَّة واختر الخيار Predefined style/Kotlin style guide من القائمة.

وللتأكد من تنسيق الشيفرة وفقًا لدليل Kotlin انتقل لإعدادت التدقيق (inspection settings) وفعِّل الخاصية الموجودة في Kotlin ثم Style issues ثمّ File is not formatted according to project setting (أيّ أنَّ تنسيق الملف ليس خاضعًا لإعدادات المشروع)، أمّا التدقيقات الإضافيّة والتي تتبع لدليل الأنماط المتعارَف عليه (مثل قواعد التسمية) فهي مفعَّلة بالحالة الافتراضيَّة.

تنظيم الشيفرة المصدريّة source code

بُنية المجلدات (Directory Structure)

في المشاريع البرمجيّة المكتوبة بعدة لغات برمجة يجب أن توضع الملفات المصدريّة (source files) الخاصّة بلغة Kotlin بنفس الجذر الذي يحتوي على الملفات المصدريّة بلغة Java، وأن تتبع نفس بنية الأدلّة (يجب أن يُخزَّن كل ملف (file) في دليلٍ بحسب توصيف الحزمة (package) الوارد فيه).

أمّا في المشاريع البرمجيّة المكتوبة كليًّا بلغة Kotlin فيُنصَح باتِّباع بنية الحزم (package structure) مع حذف حزمة الجذر المشتركة، فإذا كانت كلُّ الشيفرات في المشروع موجودةً في الحزمة "org.example.kotlin" مثلًا أو في أيّ من الحزم الفرعية الموجودة فيها، فإنّ الملفات الموجودة في الحزمة "org.example.kotlinn" يجب أن تُوضَع مباشرةً تحت الجذر الأصل، أمّا الملفات في الحزمة "org.example.kotlin.foo.bar" فيجب أن توضع في الدليل الفرعي "foo/bar" من الجذر الأصل دون ذكر الحزمة المشتركة "org.example.kotlin.foo.bar".

أسماء الملفات المصدريّة (Source File Names)

إذا كان الملف يحتوي على صنفٍ (class) واحدٍ فقط (وقد يكون مرتبطًا بتصريحات (declarations) بمستوىً أعلى) فإنّ تسميته يجب أن تكون بنفس تسمية هذا الصنف مضافًا إليها اللاحقة ‎.kt. أمّا إن كان الملف يحتوي على أكثر من صنف أو على تصريحات بمستوىً أعلى فقط  فيجب اختيار اسم يعبِّر عن محتويات هذا الملف وتسميته به باعتماد نمط التسمية camel humps بالبدء بحرفٍ كبيرٍ لكلِّ كلمةٍ مثل: ProcessDeclarations.kt.

يجب أن يكون اسم الملف ذا دلالةٍ لما تقوم به الشيفرة التي يحتوي عليها، ويُنصح بتجنُّب الأسماء الاعتباطية غير المُعبِّرة مثل "Util".

تنظيم الملفات المصدريّة (Source File Organization)

من الجيد وضع التصريحات (declarations) المتعدِّدة (كالأصناف (classes) أو الدوال بمستوى أعلى (top-level functions) أو الخاصّيات (properties)) في نفس الملف المصدريّ طالما أن هذه التصريحات مرتبطةٌ فيما بينها، وما دام حجم الملف الإجماليّ مقبولًا (دون أن يتجاوز عدد أسطره عدة مئات).

أما في الحالة الخاصّة لدى تعريف الدوال الإضافيّة (extension functions) من أجل صنفٍ ما، والتي ترتبط بكافّة عملاء (clients) هذا الصنف فمن الأفضل أن تُوضَع بنفس الملف الذي يُعرَّف فيه الصنف ذاته، أمّا عندما تكون مخصَّصةً لعميلٍ واحدٍ فيُحبَّذ وضعها بجوار شيفرة هذا العميل، دون إنشاء ملفات خاصّة بها.

مخطط الصنف (Class Layout)

تُرتَّب عناصر الصنف بشكل عامٍّ وفقًا للترتيب الآتي:

  • تصريحات الخاصّيات (properties) وأجزاء التهيئة الأولية (initializer blocks)
  • البواني الثانوية (secondary constructors)
  • تصريحات التوابع (methods)
  • الكائنات المرافقة (companion object)

وليس من الشائع ترتيب تصريحات الطرق بطريقة هجائية أو بحسب إمكانية الوصول لها، ولا تٌفصَل الطرق العادية عن التوابع الإضافية (extension methods)، بل تُوضَع العناصر المرتبطة ببعضها سويّةً بحيث تساعد مَن يقرأ الشيفرة بتتبُّع التسلسل المنطقيِّ لها لتوضيح ما تقوم به، ويجب الالتزام بنفس الترتيب (من العناصر الواقعة في المستوى الأعلى للعناصر الواقعة في العناصر الأخفض أو بالعكس) بكل الشيفرات.

ويُفضَّل وضع الأصناف المتداخلة (nested classes) بجوار الشيفرة التي تستخدِم هذه الأصناف، وفي حال وجود أصناف مُعدَّة لاستخدامها خارجيًّا دون الإشارة لها داخل الصنف، فتُوضَع في النهاية بعد الكائنات المرافقة.

مخطط تعريف استخدام الواجهة (Interface Implementation Layout)

من المُساعد لدى إجراء الواجهة المحافظةُ على نفس ترتيب العناصر الوارد في الواجهة (وقد تتخلَّلها بعض الطرق الخاصَّة (private) المُستخدَمة بعملية تعريف الاستخدام (implementation)).

مخطط التحميل الزائد (Overload Layout)

من المتعارَف عليه أن تُوضَع عمليات التحميل الزائد (overload) متجاورةّ دائمًا في الصنف.

قواعد التسمية

تتبع لغة Kotlin قواعد التسمية في لغة Java نفسها؛ إذ يجب أن تكون أسماء الحزم (packages) بأحرف صغيرة دائمًا دون استخدام الشرطة السفلية (underscore) (مثل التسمية org.example.myproject)، ولا يُفضَّل اعتماد التسمية بكلمات متعدِّدة، ولكن في حال الحاجة لها يُستخدم نمط التسمية camel humps بالابتداء بحرفٍ صغيرٍ (مثل org.example.myProject).

أمّا أسماء الأصناف (classes) والكائنات (objects) فتبدأ بحرفٍ كبير ثم تتبعُ نمط التسمية camel humps، مثل:

open class DeclarationProcessor { ... }

object EmptyDeclarationProcessor : DeclarationProcessor() { ... }

أسماء الدوال (Function Names)

تبدأ أسماء كلٍّ من الدوال (functions) والخاصّيات (properties) والمتحوِّلات المحليّة (local variables) بحرفٍ صغيرٍ وباعتماد نمط التسمية camel humps دون استخدام الشرطة السفليّة (underscore)، مثل:

fun processDeclarations() { ... }

var declarationCount = ...

وتُستثنى من ذلك الدوال المُنتِجة (factory functions) والمستخدَمة لإنشاء نسخةٍ عن الأصناف إذ تكون لها نفس تسمية الصنف المتعلِّقة به، مثل:

abstract class Foo { ... }

class FooImpl : Foo { ... }

fun Foo(): Foo { return FooImpl(...) }

أسماء توابع الاختبار (Test Methods Names)

يُسمَح في تسمية توابع الاختبار (والاختبار فقط) استخدامُ المسافات spaces بشرط أن تُوضَع ما بين إشارتي backticks (الرمز `)، ومن الجدير بالذكر هنا أنّ هذه التسمية غير مدعومة في نظام Android، كما ويُسمح باستخدام الشرطة السفليّة في شيفرات الاختبار، مثل:

class MyTestCase {
    @Test fun `ensure everything works`() {
    }

    @Test fun ensureEverythingWorks_onAndroid() {
    }
}

أسماء الخاصّيات Properties Names

أمّا الثوابت (constants) (وهي الخاصّيات المُعرَّفة من نوع const أو خاصّيات المتحولات val التابعة للكائنات (objects) أو بمستوىً أعلى (top-level) دون وجود دالةٍ للحصول عليها get والتي تحتوي فعليَّا على بيانات ثابتة)، فهي تُسمى بأحرفٍ كبيرة إجمالًا ويفصِل بين الكلمات شرطةٌ سفلية (underscore)، مثل:

const val MAX_COUNT = 8

val USER_NAME_FIELD = "UserName"

أما خاصّيات الكائنات (objects) أو الخاصّيات بمستوىً أعلىً (top-level) والتي تحتوي على الكائنات وعمليّاتها أو بيانات قابلةً للتعديل فإنها تُسمى بنمط التسمية camel humps، مثل:

val mutableCollection: MutableSet<String> = HashSet()

أما تسمية الخاصّيات التي تُشير إلى كائنات singleton فهي تتبع لنفس نمط التصريحات عن الكائنات objects، مثل:

val PersonComparator: Comparator<Person> = ...

أمّا عند تسمية الثوابت المتعدِّدة (enumeration) فهناك طريقتان: إما تسميتها بأحرفٍ كبيرةٍ إجمالًا يُفصَل بين كلماتها بالشرطة السفليّة (underscore) مثل: enum class Color { RED, GREEN }‎ . أو بطريقة التسمية الاعتياديّة camel humps ابتداءً بحرفٍ كبيرٍ، ويكون اختيار نوع التسمية بحسب استخدامها.

أسماء الخاصّيات المساعدة (Backing Properties Names)

إذا احتوى الصنف (class) على خاصَّتَين متشابهتَين؛ إحداهما جزءٌ من واجهة API عامّة والأخرى من تفاصيل تعريف الاستخدام (implementation)، حينها تُستخدم الشرطة السفليّة كبادئةٍ قبل اسم الخاصّة (property) التي يكون نوعها خاصًّا (private)، مثل:

class C {
   private val _elementList = mutableListOf<Element>()
   val elementList: List<Element>
        get() = _elementList
}

الاختيار الجيد للتسمية

غالبًا ما يكون اسم الصنف (class) اسمًا أو عبارةً اسميّة تعبِّر عن ماهيّة هذا الصنف، مثل: List أو PersonReader، أما تسمية التوابع (method) فغالبًا ما تكون فعلًا أو عبارة فعليّة تدلُّ على مهمة هذه التابع، مثل: close أو readPersons، وكما يجب أن يدلُّ الاسم عمّا إذا كانت التابع تحدِث تغييرًا في الكائن (object) أو تعيد كائنًا آخر، فمثلًا، تعبِّر التسمية sort عن تبديل ترتيب العناصر الواقعة في المجموعة نفسها، أمّا التسمية sorted فهي تعبِّر عن التابع الذي يعيد مجموعةً (نسخة) مرتبةً من المجموعة؛ إذ يجب أن تُوضِّح الأسماء الهدفَ من الكيان البرمجي المُسمى، ومن الأفضل تجنُّب الأسماء بدون معنىً مثل: Manager أو Wrapper أو… إلخ.

وعند وجود اختصارات (acronym) بالتسمية فتجب كتابة الاسم كاملًا بأحرفٍ كبيرةٍ إن كان مؤلفًا من حرفين فقط مثل: IOStream ، ويبدأ بحرفٍ كبيرٍ ويستمرُّ بأحرفٍ صغيرةٍ إن كان بأكثر من حرفين، مثل: XmlFormatter و HttpInputStream.

التنسيق

التعليقات التوثيقيَّة

عند كتابة التعليقات التوثيقيّة الطويلة فمن المُتفق عليه وضع نجمة إضافيّة لتصبح بداية التعليق بالشكل ‎/**‎ في سطرٍ منفصلٍ وابتداء كل سطرٍ جديدٍ برمز النجمة كما هو واضح في التعليق الآتي:

/**
* هنا سطر
* وهنا سطر آخر
*/

أمّا التعليقات القصيرة فتُوضع في سطرٍ واحدٍ بالشكل الآتي:

/** هذا تعليق سطريّ قصير */

ومن الأفضل تجنُّب استخدام الوسمَين (tags)‏ ‎@param‎ و‎@return‎ ، والاستعاضة عنهما بما يعبِّر عنهما في سياق التعليق التوثيقيّ  وقد تُضاف روابط المتحوّلات المستخدمة عند ذكرها، أمّا إن كان التعليق توصيفيًا طويلًا ومن الصعب صياغته فيُسمَح باستخدام الوسمين السابقين. فعلى سبيل المثال؛ بدلًا من كتابة التعليق بالشكل:

/**
* Returns the absolute value of the given number.
* @param number The number to return the absolute value for.
* @return The absolute value.
*/

fun abs(number: Int) = ...

الأفضل أن يُكتب بالشكل:

/**
* Returns the absolute value of the given [number].
*/

fun abs(number: Int) = ...

تجنُّب البُنى الزائدة

إنّ من المساعِد حذفُ أيّ بنيةٍ زائدةٍ إن كان وجودها اختياريًّا أو حُدِّدَت في بيئة العمل (IDE) بوصفها إضافيّة لا داعي لها؛ لا تُضِف أي عناصر زائدة في الشيفرة بهدف جعلها أكثر وضوحًا وحسب.

النوع Unit

عندما تعيد الدالة النوع Unit فلا داعي لذكره في الشيفرة، مثل:

fun foo() { // حُذفت هنا كلمة النوع Unit
    ...
}

الفاصلة المنقوطة Semicolon (;)

استغنِ عن الفاصلة المنقوطة ما أمكن.

قوالب السلاسل النصية (String Templates)

لا داعي لاستخدام أقواس المجموعة { } عند إدخال متحوِّلٍ بسيطٍ ضمن قالب السلسلة النصيّة، إذ إنها تُستخدَم لتعابير أطول، مثل:

println("$name has ${children.size} children")

الاستخدام الاصطلاحيُّ لمزايا اللغة

المكتبات

من المُستحسَن عند كتابة المكتبات الالتزامُ ببعض القواعد العامّة لضمان استقرار واجهات API، وهي:

  • تحديد إمكانية الوصول (visibilty) للعناصر بشكل صريح، لئلا تُعرَّف -عن طريق الخطأ- من نوعٍ عامٍّ (public).
  • التصريح عن أنواع القيم المُعادَة في الدوال (functions) وكذلك أنواع الخاصّيات (properties)، لتجنُّب حدوث خطأٍ في النوع عند تغيير تعريف الاستخدام (implementation).
  • التزويد بالتعليقات التوثيقيَّة لكافَّة العناصر العامَّة (public) باستثناء عمليات إعادة تعريف الدوال (override) التي لا تتطلَّب أيَّ توثيقٍ جديد، وذلك لدعم إنشاء التوثيق للمكتبة.

مصادر