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

من موسوعة حسوب
< Kotlin
مراجعة 03:14، 6 مارس 2018 بواسطة Nourtam (نقاش | مساهمات) (←‏تنسيق الحاشية Annotation: إضافة فقرات جديدة اعتبارًا من هذه الفقرة وحتى نهاية عنوان التنسيق.)
اذهب إلى التنقل اذهب إلى البحث

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

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

لضبط تنسيق IntelliJ formatter بما يتوافق مع هذا الدليل يُنصَح بتثبيت إضافة 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.kotlin" يجب أن تُوضَع مباشرةً في المجلد الجذر للملفات المصدرية، أمّا الملفات في الحزمة "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)

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

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

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

من المُساعد لدى تطبيق (implementing) الواجهة المحافظةُ على نفس ترتيب العناصر الوارد في الواجهة (وقد تتخلَّلها بعض الطرق [methods] الخاصَّة [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.

التنسيق

تماثل لغة Kotlin في نمط التنسيق لغة Java غالبًا، مثل استخدام 4 مسافات فارغة (spaces) عند الحاشية بدلًا من استخدام tabs. وعند استخدام أقواس المجموعات { } يُوضَع قوس الافتتاح بنهاية السطر حيث سيبدأ الجزء البُنيويّ (construct)، ويُوضَع قوس الإغلاق في سطرٍ منفصلٍ بشكلٍ محاذٍ عموديًّا لبداية هذا الجزء (block)، مثل:

if (elements != null) {
    for (element in elements) {
        // ...
    }
}

ويُلاحظ أنّ إضافة الفاصلة المنقوطة (;) أمرٌ اختياريٌّ في لغة Kotlin وبالتالي فإن الفصل من خلال الانتقال لسطرٍ جديدٍ يظهر واضحًا، ويعتمد النسق العامّ هنا على الأقواس المُستخدَمة في لغة Java وقد يحدث خللٌ ما في حال الاعتماد على نسقٍ مغاير.

المسافات البيضاء الأفقيّة (Horizontal whitespace)

  • تُضاف المسافات (spaces) على جانبي المعامِلات الثنائية (binary operators) مثل: a + b، وتُستَثنى من ذلك المجالات فتكتب بالشكل: 0..i ، أما في حالة المعامِلات الأحاديّة (unary operators) فلا تُضاف أيّة مسافات مثل: a++‎ .
  • تُضاف مسافةٌ ما بين كلمات التحكم بالتدفق (مثل if و when و for و while ) والقوس الابتدائي التالي لها.
  • لا تُضاف مسافةٌ قبل القوس الابتدائي في التعريف الأساسيّ للبُنى البرمجيّة أو تعريف التوابع (methods) أو استدعائها كما هو واضحٌ في الشيفرة:
class A(val x: Int)

fun foo(x: Int) { }

fun bar() {
    foo(1)
}
  • لا تُضاف أي مسافات بعد أقواس البدء )و] أو قبل أقواس الإنهاء (و[.
  • لا تُضاف أي مسافات على جانبيّ . أو .? مثل: foo.bar().filter { it > 2 }.joinToString()‎ أو foo?.bar()‎ .
  • تُضاف مسافةٌ بعد رمز التعليق // مثل: ‎// This is a comment.
  • لا تُضاف أي مسافة على جانبيّ القوسين < و > والمستخدمة لتحديد متحوّلاتٍ من نوعٍ معيّن، مثل: class Map<K, V> { ... }‎ .
  • لا تُضاف أي مسافة على جانبيّ الرمز :: مثل: Foo::class أو String::length.
  • لا تُضاف أيّ مسافة قبل الرمز ? والمستخدم للدلالة على النوع nullable مثل: String?‎.

وبشكل عامّ، من الأفضل تجنُّب المحاذاة الأفقية مهما كان نوعها بحيث أنه إذا أُعيدت تسمية متحوّلٍ مثلًا لا يتغيَّر التنسيق العامّ للتعريف أو الاستخدام.

النقطتان الرأسيّتان Colon (:)

تُضاف مسافةٌ (space) قبل النقطتين الرأسيّتين : في الحالات الآتية:

  • عندما تستخدم للفصل ما بين نوع ونوع أعلى (supertype).
  • عند التعميم لبانٍ من صنف أعلى (superclass constructor) أو لبانٍ آخر ضمن نفس الصنف.
  • بعد الكلمة المفتاحية object.

ولا تُضاف مسافة قبلهما عندما تفصلان ما بين التصريح (declaration) ونوعه.

وتُضاف المسافة دائمًا بعدهما.

مثال:

abstract class Foo<out T : Any> : IFoo {
    abstract fun foo(a: Int): T
}

class FooImpl : Foo() {
    constructor(x: String) : this(x) {
        //...
    }
    
    val x = object : IFoo { ... } 
}

تنسيق ترويسة الصنف (Class Header)

تُمكن كتابة متحولات الباني (constructor parameters) في سطرٍ واحدٍ إن كان عددها قليلًا مثل:

class Person(id: Int, name: String)

أمّا في حالة الأصناف ذات الترويسات (headers) الطويلة فيجب تنسيقها بحيث يكون كل متحول (parameter) أساسيّ للباني على سطرٍ منفصلٍ مع الإزاحة (4 مسافات)، كما ويُوضع قوس الإنهاء في سطر آخر منفصلٍ ويُضاف له -في حالة الوراثة- استدعاء الباني من الصنف الأعلى (superclass) أو قائمة الواجهات المُعاد تعريف استخدامها (implemented interfaces)، ليصبح بالشكل:

class Person(
    id: Int,
    name: String,
    surname: String
) : Human(id, name) {

    // ...
}

وإذا ما تعدَّدت الواجهات، فيُوضع استدعاء باني الصنف الأعلى (superclass constructor) أولًا ثم توضع كل واجهةٍ بسطرٍ مختلفٍ، مثل:

class Person(
    id: Int,
    name: String,
    surname: String
) : Human(id, name),
    KotlinMaker {
    
    // ...
}

وإذا ما كانت قائمة النوع الأعلى (supertype) طويلةً فعندها يفصل سطرٌ بين النقطتين الرأسيّتين والأنواع، والتي تُكتب وفق محاذاةٍ عموديٍّة، مثل:

class MyFavouriteVeryLongClassHolder :
    MyLongHolder<MyFavouriteVeryLongClass>(),
    SomeOtherInterface,
    AndAnotherOne {

    fun foo() {}
}

وللفصل الواضح ما بين الترويسة (head) الطويلة للصنف وبُنيته (body) إما أن يُوضع سطرٌ فارغٌ بعد انتهاء الترويسة (كما هو الحال في المثال السابق) أو أن يُكتب قوس البدء في سطرٍ منفصلٍ بالشكل الآتي:

class MyFavouriteVeryLongClassHolder :
    MyLongHolder<MyFavouriteVeryLongClass>(),
    SomeOtherInterface,
    AndAnotherOne
{
    fun foo() {}
}

إذ تُضاف المحاذاة الاعتياديّة (4 مسافات) قبل متحوّلات الباني، وذلك لجعل محاذاة الخاصّيات (properties) المُعرَّفة في الباني الأساسي بنفس محاذاة الخاصّيات المُعرَّفة في بُنية الصنف (class body).

المُحدِّدات (Modifiers)

إن كان لنفس التصريح (declaration) أكثر من مُحدِّد فهي توضع بحسب الترتيب الآتي:

public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation
companion
inline
infix
operator
data

وفي حال وجود أي حاشية (annotation) فإنها تُكتب قبل المُحدِّدات، مثل:

@Named("Foo")
private val foo: Foo

ومن الأفضل حذف أي مُحدِّدات زائدة (مثل public)، ولكنّ هذا لا ينطبق في حال العمل على المكتبات (libraries).

تنسيق الحاشية (Annotation)

تُوضع الحاشية (annotation) بسطرٍ منفصلٍ غالبًا قبل التصريح (declaration) المرتبطة به وبنفس المحاذاة العموديّة، مثل:

@Target(AnnotationTarget.PROPERTY)
annotation class JsonExclude

وفي حال وجود أكثر من حاشيةٍ وبدون متحولات (arguements) فيُمكن وضعها بنفس السطر، مثل:

@JsonExclude @JvmField
var x: String

كما يُمكن وضعها إن كانت بدون متحولات بنفس السطر مع التصريح المرتبطة به، مثل:

@Test fun foo() { ... }

حاشية الملفات (File Annotation)

تُكتَب حاشية الملف (file annotation) بعد سطر التعليق (إن وُجد) وقبل تعليمة الحزمة (package) بحيث تكون منفصلة بسطر عنها (للتأكيد على أنها للملف لا للحزمة)، مثل:

/** License, copyright and whatever */
@file:JvmName("FooBar")

package foo.bar

تنسيق الدوال (Functions)

يتبع تنسيق ترويسة الدالة (function signature) للشكل الآتي إن تعذّر وضعه كاملًا بسطرٍ واحد:

fun longMethodName(
    argument: ArgumentType = defaultValue,
    argument2: AnotherArgumentType
): ReturnType {
    // بُنية الدالة هنا
}

وتستخدم الإزاحة بأربع مسافات (spaces) لمتحوٍّلات الدالة (parameters) وذلك بهدف تحقيق التشابه مع متحوِّلات الباني (constructor parameters). ومن المُفضَّل استخدام بُنية التعبير (expression body) للدوال إن كانت تحتوي على تعبيرٍ (expression) واحدٍ فقط، مثل:

fun foo(): Int {     // تنسيق سيء
    return 1 
}

fun foo() = 1        // تنسيق جيد

تنسيق بُنية التعبير (Expression Body)

إذا كانت بُنية التعبير غير مناسبةٍ في سطرٍ واحدٍ كما هو الحال في التصريح (declaration) فحينها يُمكن وضع إشارة الإسناد = في السطر الأول والإزاحة في السطر التالي بمقدار 4 مسافات (spaces) ومتابعة التعبير، مثل:

fun f(x: String) =
    x.length

تنسيق الخاصّيات (Properties)

يُعتمَد نمط التنسيق السطريّ لخاصّيات القراءة فقط (read-only properties) مثل:

val isEmpty: Boolean get() = size == 0

أما للخاصّيات الأكثر تعقيدًا فمن الأفضل استخدام الكلمتين المفتاحيّتين get و set وبأسطرٍ منفصلةٍ بالشكل:

val foo: String
    get() {
        // ...
    }

أمّا في حالة الخاصّيات بإسناد أوليّ (initializer) فإذا كان الإسناد طويلًا يُوضَع بسطرٍ جديدٍ ابتداءً ممّا يلي إشارة الإسناد = وبإزاحة 4 مسافات (spaces)، مثل:

private val defaultCharset: Charset? =
    EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)

تنسيق أوامر التحكم بالتدفق Control Flow Statements

تُستخدَم الأقواس { } لمجموعة التعليمات إذا كان التعبير في الشرط if أو في تعليمة when واقعًا بعدة أسطرٍ والتي تُزاح بمقدار 4 مسافات (spaces) عن بداية التعليمة التابعة لها، ويُوضع كلٌّ من قوس إغلاق الشرط وقوس ابتداء مجموعة التعليمات سويّة بسطرٍ منفصلٍ، مثل:

if (!component.isSyncing &&
    !hasAnyKotlinRuntimeInScope(module)
) {
    return createKotlinNotConfiguredPanel(module)
}

وبهذا يصبح الفصل واضحًا ما بين الشرط وبُنية التعليمات. وتُوضَع الكلمات المفتاحيّة else و catch و finally والكلمة المفتاحيّة while من حلقة do/while بنفس السطر الذي يحتوي على قوس الإغلاق للبُنية التي تسبقه، كما يلي:

if (condition) {
    // ...
} else {
    // ...
}

try {
    // ...
} finally {
    // ...
}

وإذا كان أحد الفروع (branches) التابعة لتعليمة when بأكثر من سطرٍ فعندها يُفصَل عن الفروع الأخرى عبر سطرٍ فارغٍ مثل:

private fun parsePropertyValue(propName: String, token: Token) {
    when (token) {
        is Token.ValueToken ->
            callback.visitValue(propName, token.value)

        Token.LBRACE -> { // ...
        }
    }
}

أما الفروع القصيرة فتُوضَع مع الشرط بنفس السطر وبدون استخدام الأقواس، مثل:

when (foo) {
    true -> bar() // تنسيق جيّد
    false -> { baz() } // تنسيق سيء
}

تنسيق استدعاء التوابع (Method Call)

إذا كانت قائمة متحولات التابع (parameters) طويلةً فعندها يُضاف سطر جديدٍ بعد قوس البدء وتُزاح المتحوّلات بمقدار 4 مسافات (spaces) مع بداية كلِّ سطرٍ، إذ يًمكن تجميع المتحولات المتقاربة من بعضها بنفس السطر، مع وضع فراغات على جانبي إشارة الإسناد = للفصل ما بين اسم المتحول وقيمته، مثل:

drawSquare(
    x = 10, y = 10,
    width = 100, height = 100,
    fill = true
)

تغليف الاستدعاء المتسلسل (Chained Call Wrapping)

عند تغليف الاستدعاء المتسلسل يُوضع الرمز . أو .? بالسطر التالي وبإزاحة بمقدار 4 مسافات (spaces) مثل:

val anchor = owner
    ?.firstChild!!
    .siblings(forward = true)
    .dropWhile { it is PsiComment || it is PsiWhiteSpace }

إذ إنّه غالبًا ما يُفصَل الاستدعاء الأول عمّا قبله بسطرٍ جديدٍ ولكن يمكن الاستغناء عن ذلك.

تنسيق Lambda

تُستخدَم المسافات (spaces) على طرفيّ الأقواس في تعابير lambda وكذلك على طرفيّ السهم الفاصل ما بين المتحولات (parameters) والبُنية (body)، وإن استلزم الاستدعاء إلى lambda واحدةٍ فقط فيجب تمريرها لخارج الأقواس ما أمكن، مثال:

list.filter { it > 10 }

ولدى إسناد تسمية (label) إلى lambda فلا تُوضع مسافة (space) ما بين التسمية وقوس البدء التالي لها، مثل:

fun foo() {
    ints.forEach lit@{
        // ...
    }
}

أمّا عند التصريح (declaration) عن أسماء المتحولات (parameters) في lambda بعدة أسطرٍ، تُكتَب التسميات بالسطر الأوّل ويُلحَق بها السطرُ التالي عبر رمز السهم، أيّ:

appendCommaSeparated(properties) { prop ->
    val propertyValue = prop.get(obj)  // ...
}

وإذا ما كانت قائمة المتحولات (parameters) طويلةً لا تتسع بسطرٍ واحدٍ فيُستخدم رمز السهم بسطرٍ منفصلٍ، مثل:

foo {
   context: Context,
   environment: Env
   ->
   context.configureEnv(environment)
}

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

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

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

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

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

ومن الأفضل تجنُّب استخدام الوسمَين (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) التي لا تتطلَّب أيَّ توثيقٍ جديد، وذلك لدعم إنشاء التوثيق للمكتبة.

مصادر