Go التحكم في تناسق الرمز


إذا كنت تعتقد أن الاتساق جزء مهم من كود الجودة ، فهذه المقالة مناسبة لك.


في انتظارك:


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

الاتساق هو شيءنا


بادئ ذي بدء ، سنحدد ما نسميه "الاتساق".


كلما بدت أكواد المصدر للبرنامج وكأنها مكتوبة بواسطة شخص واحد ، كلما كانت متسقة.


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


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

هناك مستويات مختلفة من الاتساق ، على سبيل المثال ، يمكننا تمييز الثلاثة الأكثر وضوحًا:


  • تناسق ملف مصدر واحد
  • الاتساق على مستوى الحزمة (أو المكتبة)
  • الاتساق على مستوى المشروع بأكمله (إذا كان يسيطر عليه بائع واحد)

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


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


العمليات المكافئة في Go


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


أي من هاتين الطريقتين لإنشاء شريحة بطول 100 تعتقد أن معظم مبرمجي Go يستخدمونها؟


 // (A) new([100]T)[:] // (B) (&[100]T{})[:] 

الجواب


لا يفضل أي من الخيارات. في الكود الحقيقي ، لم أر أبدا استخدام أي منها.


واستخدم make([]T, 100) في هذه الحالة.




استيراد واحد


هناك طريقتان لاستيراد حزمة واحدة:


 // (A)   import "github.com/go-lintpack/lintpack" // (B)   import ( "github.com/go-lintpack/lintpack" ) 

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


تعليم مؤشر بقيمة خالية


طالما أن Go يحتوي على وظيفة مضمنة وطرق new وبديلة للحصول على مؤشر لكائن جديد ، فستواجه كلاً من new(T) و &T{} .


 // (A)   new new(T) new([]T) // (B)     &T{} &[]T{} 

قم بإنشاء شريحة فارغة


هناك طريقتان شائعتان على الأقل لإنشاء شريحة فارغة (يجب عدم الخلط بينها وبين شريحة لا شيء):


 // (A)   make make([]T, 0) // (A)   []T{} 

إنشاء جدول تجزئة فارغ


قد تجد أن فصل إنشاء شريحة map فارغين ليس منطقيًا جدًا ، ولكن ليس كل الأشخاص الذين يفضلون []T{} سيستخدمون map[K]V{} بدلاً من make(map[K]V) . وبناء على ذلك ، فإن التمييز هنا ليس على الأقل مفرطًا.


 // (A)   make make(map[K]V) // (B)  - map[K]V{} 

حرفية سداسية عشرية


 // (A) af,   0xff // (B) AF,   0xFF 

نوع الكتابة 0xFf ، بحالة مختلطة ، لا يتعلق بالاتساق. يجب أن يتم العثور على هذا من قبل محلل ثابت (اللنت). أيهما؟ حاول ، على سبيل المثال ، gocritic .


التحقق من النطاق


في الرياضيات (وبعض لغات البرمجة ، ولكن ليس Go) ، يمكنك وصف النطاق بأنه low < x < high . لا يمكن كتابة كود المصدر الذي سيعبر عن هذا القيد على هذا النحو. في الوقت نفسه ، هناك طريقتان شائعتان على الأقل للتحقق من الدخول إلى النطاق:


 // (A)     x > low && x < high // (B)    low < x && x < high 

عامل التشغيل وليس


هل تعلم أن Go لديه عامل التشغيل &^ ثنائي؟ يتم استدعاؤه and-not إجراء نفس العملية مثل & ، المطبقة على النتيجة ^ من المعامل (الثاني) الأيمن.


 // (A)   &^ ( ) x &^ y // (B)  &  ^ ( ) x & ^y 

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


حرفيا من الأعداد الحقيقية


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


 // (A)      0.0 1.0 // (B)      ( ) .0 1. 

LABEL أو تسمية؟


لسوء الحظ ، لا توجد اصطلاحات معروفة لتسمية التسميات. كل ما تبقى هو اختيار إحدى الطرق الممكنة والالتزام بها.


 // (A)     LABEL_NAME: goto PLEASE // (B) upper camel case LabelName: goto TryTo // (C) lower camel case labelName: goto beConsistent 

Snake_case ممكن أيضًا ، لكنني لم أر مثل هذه التسميات في أي مكان باستثناء Go المجمّع. على الأرجح ، يجب ألا تلتزم بهذا الخيار.


مواصفات النوع للحروف الرقمية غير المكتوبة


 // (A)     "=" var x int32 = 10 const y float32 = 1.6 // (B)     "=" var x = int32(10) const y = float32(1.6) 

قم بتنفيذ قوس الإغلاق للدالة المطلوبة


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


 // (A)         multiLineCall( a, b, c) // (B)       multiLineCall( a, b, c, ) 

تحقق من الطول غير الصفري


 // (A)  "    0" len(xs) != 0 // (B)  " 0 " len(xs) > 0 // (C)  "  1 " len(xs) >= 1 

بالنسبة للسلاسل النصية ، يتم استخدام s != "" Or s == "" ( المصدر ) عادةً.


موقع التسمية الافتراضية في التبديل


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


 // (A) default   switch { default: return "?" case x > 10: return "more than 10" } // (B) default   switch { case x > 10: return "more than 10" default: return "?" } 

متسق


لقد أدرجنا قائمة بالعمليات المكافئة أعلاه.


كيفية تحديد أي واحد لاستخدامه؟ أبسط إجابة: إجابة لها تواتر أعلى للاستخدام في الجزء المدروس من المشروع (كحالة خاصة ، في المشروع بأكمله).


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


عد مباشر


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


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




إذا كان $(go env GOPATH)/bin في النظام PATH ، $(go env GOPATH)/bin الأمر التالي على تعيين go-consistent :


 go get -v github.com/Quasilyte/go-consistent go-consistent --help #    

بالعودة إلى حدود الاتساق ، إليك كيفية التحقق من كل منها:


  • يمكنك التحقق من التناسق داخل ملف واحد عن طريق تشغيل go-consistent على هذا الملف
  • يتم احتساب الاتساق داخل الحزمة عند بدء التشغيل باستخدام وسيطة حزمة واحدة (أو مع جميع الملفات الموجودة في تلك الحزمة)
  • يتطلب الاتساق العالمي الحوسبة تمرير جميع الحزم كوسيطات

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


ميزة أخرى مهمة هي التكوين صفر. إن التشغيل go-consistent بدون أي علامات وملفات تهيئة هو ما يصلح لـ 99٪ من الحالات.


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

تحقق من الاختيار


يمكن أن تؤدي التسمية غير المتسقة لمعلمات الوظيفة أو المتغيرات المحلية إلى تقليل اتساق التعليمات البرمجية.


بالنسبة لمعظم مبرمجي Go ، من الواضح أن erro اسم أقل نجاحًا للخطأ من err . ماذا عن s vs str ؟


لا يمكن حل مهمة التحقق من تناسق أسماء المتغيرات باستخدام طرق go-consistent . من الصعب الاستغناء عن بيان الاتفاقيات المحلية.


يحدد go-namecheck تنسيق هذا البيان ويسمح بالتحقق منه ، مما يجعل من السهل اتباع معايير تسمية الكيانات المحددة في المشروع.


على سبيل المثال ، يمكنك تحديد أنه بالنسبة لمعلمات دالات سلسلة النوع ، يجدر استخدام المعرفات بدلاً من str .


يتم التعبير عن هذه القاعدة على النحو التالي:


 {"string": {"param": {"str": "s"}}} 

  • string عبارة عن تعبير عادي يلتقط نوع الاهتمام
  • param - نطاق قواعد الاستبدال (النطاق). قد يكون هناك العديد
  • يشير الزوج "str": "s" إلى استبدال من str إلى s . قد يكون هناك العديد

بدلاً من استبدال 1 إلى 1 ، يمكنك استخدام تعبير عادي يلتقط أكثر من معرّف واحد. على سبيل المثال ، إليك قاعدة تتطلب استبدال البادئة re على متغيرات من النوع *regexp.Regexp باللاحقة RE . بمعنى آخر ، بدلاً من reFile تتطلب القاعدة استخدام fileRE .


 { "regexp\\.Regexp": { "local+global": {"^re[AZ]\\w*$": "use RE suffix instead of re prefix"} } } 

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


سيبدو الملف الذي يصف القاعدتين كما يلي:


 { "string": { "param": { "str": "s", "strval": "s" }, }, "regexp\\.Regexp": { "local+global": {"^re[AZ]\\w*$": "use RE suffix instead of re prefix"} } } 

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


go-namecheck تثبيت go-namecheck واستخدامه go-namecheck الطريقة go-consistent مع go-consistent ، باستثناء حقيقة أنه من أجل الحصول على النتيجة الصحيحة ، لا تحتاج إلى إجراء فحص على المجموعة الكاملة من الحزم والملفات.


الخلاصة


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


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


هام : إذا كان لديك أي فكرة أو إضافة ، فيرجى إخبارنا بها!
هناك عدة طرق:


  • اكتب في التعليقات على هذه المقالة
  • إنشاء مشكلة متسقة
  • تنفيذ الأداة الخاصة بك وإعلام العالم بذلك

تحذير : يمكن أن تكون إضافة go-namecheck go-consistent و / أو go-namecheck في CI إجراءً جذريًا للغاية. قد يكون العمل مرة واحدة في الشهر مع التصحيح اللاحق لجميع التناقضات حلاً أفضل.

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


All Articles