دليل أسلوب Go من Google › دليل أسلوب Go 2 / 6 السابق التالي

دليل أسلوب Go

أدلة أسلوب Google | أدلة أسلوب للمشاريع مفتوحة المصدر التي أطلقتها Google

دليل أسلوب Go

https://google.github.io/styleguide/go/guide

نظرة عامة | دليل | قرارات | أفضل الممارسات

ملاحظة: هذا جزء من سلسلة من المستندات التي تحدد أسلوب Go في Google. هذا المستند معياري ومرجعي. راجع النظرة العامة لمزيد من المعلومات.

مبادئ الأسلوب

هناك بضعة مبادئ شاملة تلخص كيفية التفكير في كتابة شيفرة Go قابلة للقراءة (readability). فيما يلي سمات الشيفرة القابلة للقراءة، مرتبة حسب الأهمية:

  1. الوضوح (clarity): الغرض من الشيفرة ومبررها واضحان للقارئ.
  2. البساطة (simplicity): تحقق الشيفرة هدفها بأبسط طريقة ممكنة.
  3. الإيجاز (concision): تتمتع الشيفرة بنسبة إشارة إلى ضوضاء عالية.
  4. قابلية الصيانة (maintainability): كُتبت الشيفرة بحيث يسهل صيانتها.
  5. الاتساق (consistency): الشيفرة متسقة مع قاعدة شيفرة Google الأوسع.

الوضوح

الهدف الأساسي لقابلية القراءة هو إنتاج شيفرة واضحة للقارئ.

يتحقق الوضوح أساسًا عبر التسمية الفعّالة، والتعليقات المفيدة، والتنظيم الكفؤ للشيفرة.

ينبغي النظر إلى الوضوح من منظور القارئ، لا من منظور مؤلف الشيفرة. الأهم أن تكون الشيفرة سهلة القراءة أكثر من كونها سهلة الكتابة. للوضوح في الشيفرة وجهان متميزان:

ما الذي تفعله الشيفرة فعليًا؟

صُممت Go بحيث يكون من السهل نسبيًا رؤية ما تفعله الشيفرة. في حالات عدم اليقين أو حين يحتاج القارئ إلى معرفة مسبقة لفهم الشيفرة، يجدر استثمار الوقت لجعل غرض الشيفرة أوضح للقراء المستقبليين. على سبيل المثال، قد يساعد ما يلي:

  • استخدام أسماء متغيرات أكثر وصفًا
  • إضافة تعليقات إضافية
  • تقسيم الشيفرة بمسافات بيضاء وتعليقات
  • إعادة هيكلة الشيفرة إلى دوال/طرق منفصلة لجعلها أكثر تفكيكًا إلى وحدات

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

لماذا تفعل الشيفرة ما تفعله؟

غالبًا ما يُنقل مبرر الشيفرة بشكل كافٍ عبر أسماء المتغيرات أو الدوال أو الطرق أو الحزم. وحيث لا يحدث ذلك، من المهم إضافة تعليقات. ويكتسب سؤال "لماذا؟" أهمية خاصة عندما تحتوي الشيفرة على دقائق قد لا يكون القارئ على دراية بها، مثل:

  • دقيقة في اللغة، مثل أن يلتقط إغلاق (closure) متغير حلقة، لكن الإغلاق يبعد عدة أسطر
  • دقيقة في منطق العمل، مثل فحص للتحكم في الوصول يحتاج إلى التمييز بين المستخدم الفعلي وشخص ينتحل شخصية مستخدم

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

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

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

تحتوي المكتبة القياسية على أمثلة كثيرة على هذا المبدأ عمليًا. ومنها:

البساطة

ينبغي أن تكون شيفرة Go الخاصة بك بسيطة لمن يستخدمها ويقرأها ويصونها.

ينبغي كتابة شيفرة Go بأبسط طريقة تحقق أهدافها، من حيث السلوك والأداء معًا. في قاعدة شيفرة Go لدى Google، الشيفرة البسيطة:

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

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

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

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

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

أقل آلية ممكنة

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

  1. اهدف إلى استخدام بنية لغوية أساسية (مثل قناة أو مقطع أو خريطة أو حلقة أو بنية) عندما تكون كافية لحالة الاستخدام لديك.
  2. إذا لم توجد واحدة، ابحث عن أداة في المكتبة القياسية (مثل عميل HTTP أو محرك قوالب).
  3. أخيرًا، فكّر فيما إذا كانت هناك مكتبة أساسية في قاعدة شيفرة Google كافية قبل إدخال تبعية جديدة أو إنشاء مكتبتك الخاصة.

كمثال، تخيل شيفرة إنتاجية تحتوي على علم (flag) مرتبط بمتغير له قيمة افتراضية يجب تجاوزها في الاختبارات. ما لم يكن القصد اختبار واجهة سطر الأوامر للبرنامج نفسه (مثلًا باستخدام os/exec)، فمن الأبسط وبالتالي الأفضل تجاوز القيمة المرتبطة مباشرةً بدل استخدام flag.Set.

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

الإيجاز

تتمتع شيفرة Go الموجزة بنسبة إشارة إلى ضوضاء عالية. يسهل تمييز التفاصيل ذات الصلة، وترشد التسمية والبنية القارئ عبر هذه التفاصيل.

هناك أشياء كثيرة يمكن أن تحول دون إبراز أبرز التفاصيل في أي لحظة معينة:

  • شيفرة متكررة
  • صياغة زائدة
  • أسماء غامضة
  • تجريد غير ضروري
  • مسافات بيضاء

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

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

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

// Good:
if err := doSomething(); err != nil {
    // ...
}

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

// Good:
if err := doSomething(); err == nil { // if NO error
    // ...
}

قابلية الصيانة

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

الشيفرة القابلة للصيانة:

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

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

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

// Bad:
// The use of = instead of := can change this line completely.
if user, err = db.UserByID(userID); err != nil {
    // ...
}
// Bad:
// The ! in the middle of this line is very easy to miss.
leap := (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0))

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

// Good:
u, err := db.UserByID(userID)
if err != nil {
    return fmt.Errorf("invalid origin user: %s", err)
}
user = u
// Good:
// Gregorian leap years aren't just year%4 == 0.
// See https://en.wikipedia.org/wiki/Leap_year#Algorithm.
var (
    leap4   = year%4 == 0
    leap100 = year%100 == 0
    leap400 = year%400 == 0
)
leap := leap4 && (!leap100 || leap400)

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

الأسماء القابلة للتنبؤ سمة أخرى من سمات الشيفرة القابلة للصيانة. ينبغي أن يكون مستخدم الحزمة (package) أو مشرف قطعة من الشيفرة قادرًا على التنبؤ باسم متغير أو طريقة أو دالة في سياق معين. وعادةً ما ينبغي أن تتشارك معاملات الدوال وأسماء المستقبِل للمفاهيم المتطابقة الاسم نفسه، سواء للحفاظ على قابلية فهم التوثيق أو لتسهيل إعادة هيكلة (refactoring) الشيفرة بأقل عبء ممكن.

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

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

الاتساق

الشيفرة المتسقة هي الشيفرة التي تبدو وتُحَس وتتصرف مثل الشيفرة المشابهة في جميع أنحاء قاعدة الشيفرة الأوسع، وفي سياق فريق أو حزمة، وحتى داخل ملف واحد.

لا تلغي اعتبارات الاتساق أيًا من المبادئ المذكورة أعلاه، لكن إذا لزم حسم تعادل، فغالبًا ما يكون من المفيد حسمه لصالح الاتساق.

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

الإرشادات الأساسية

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

توسّع الإرشادات أدناه في التوصيات الواردة في Effective Go، والتي توفر خط أساس مشتركًا لشيفرة Go في المجتمع بأكمله.

التنسيق

يجب أن تتوافق كل ملفات مصدر Go مع التنسيق (formatting) الذي تنتجه أداة gofmt. ويُفرض هذا التنسيق عبر فحص قبل الإرسال في قاعدة شيفرة Google. وينبغي عمومًا تنسيق الشيفرة المولّدة أيضًا (مثلًا باستخدام format.Source)، لأنها قابلة للتصفح أيضًا في Code Search.

MixedCaps

يستخدم مصدر Go MixedCaps أو mixedCaps (حالة الجمل camel case) بدلًا من الشرطات السفلية (حالة الثعبان snake case) عند كتابة أسماء متعددة الكلمات.

وينطبق هذا حتى عندما يخالف أعراف لغات أخرى. على سبيل المثال، يكون الثابت MaxLength (وليس MAX_LENGTH) إذا كان مُصدَّرًا، وmaxLength (وليس max_length) إذا لم يكن مُصدَّرًا.

تُعتبر المتغيرات المحلية غير مُصدَّرة لأغراض اختيار الحرف الأول من حيث الكبر أو الصغر.

طول السطر

لا يوجد طول سطر ثابت لمصدر Go. إذا بدا السطر طويلًا جدًا، ففضّل إعادة الهيكلة بدل تقسيمه. وإذا كان قصيرًا بالفعل قدر ما هو عملي، فينبغي السماح للسطر بالبقاء طويلًا.

لا تقسّم سطرًا:

  • قبل تغيير في الإزاحة (مثل تعريف دالة، أو شرط)
  • لجعل نص طويل (مثل عنوان URL) يتناسب مع عدة أسطر أقصر

التسمية

التسمية (naming) فن أكثر منها علمًا. في Go، تميل الأسماء إلى أن تكون أقصر إلى حد ما مما هي عليه في لغات كثيرة أخرى، لكن تنطبق الإرشادات العامة نفسها. وينبغي للأسماء أن:

  • لا تبدو متكررة عند استخدامها
  • تراعي السياق
  • لا تكرر مفاهيم واضحة أصلًا

يمكنك العثور على إرشادات أكثر تحديدًا حول التسمية في القرارات.

الاتساق المحلي

حيث لا يقول دليل الأسلوب شيئًا عن نقطة أسلوبية معينة، يكون للمؤلفين الحرية في اختيار الأسلوب الذي يفضلونه، ما لم تكن الشيفرة القريبة (عادةً داخل الملف أو الحزمة نفسها، وأحيانًا داخل فريق أو دليل مشروع) قد اتخذت موقفًا متسقًا بشأن المسألة.

أمثلة على اعتبارات أسلوب محلي صحيحة:

  • استخدام %s أو %v لطباعة الأخطاء المنسقة
  • استخدام القنوات المخزنة مؤقتًا بدلًا من أقفال الاستبعاد المتبادل (mutexes)

أمثلة على اعتبارات أسلوب محلي غير صحيحة:

  • قيود طول السطر على الشيفرة
  • استخدام مكتبات اختبار قائمة على التوكيدات

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

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

هذا الموقع مفتوح المصدر. حسّن هذه الصفحة.