
مرحباً هابر. اسمي سيرجي روداشينكو ، أنا خبير فني في Roistat. على مدى العامين الماضيين ، قام فريقنا بترجمة أجزاء مختلفة من المشروع إلى خدمات صغيرة على Go. تم تطويرها من قبل العديد من الفرق ، لذلك كنا بحاجة إلى وضع شريط جودة الكود الصلب. للقيام بذلك ، نستخدم عدة أدوات ، في هذه المقالة سنركز على واحدة منها - على التحليل الثابت.
التحليل الثابت هو عملية التحقق من شفرة المصدر تلقائيًا باستخدام أدوات مساعدة خاصة. ستتحدث هذه المقالة عن فوائدها ، وتصف بإيجاز الأدوات الشائعة وتعطي تعليمات للتنفيذ. يجدر القراءة إذا لم تصادف أدوات مماثلة على الإطلاق أو تستخدمها بشكل غير منهجي.
غالبًا ما يتم العثور على مصطلح "linter" في المقالات حول هذا الموضوع. بالنسبة لنا ، هذا اسم مناسب للأدوات البسيطة للتحليل الثابت. مهمة اللنتر هي البحث عن أخطاء بسيطة وتصميم غير صحيح.
لماذا تحتاج الوبر؟
عند العمل في فريق ، فأنت على الأرجح تقوم بمراجعة الكود. الأخطاء التي تم تخطيها في المراجعة هي أخطاء محتملة. غاب عن error
لم تتم معالجته - لا تتلقى رسالة إعلامية وستبحث عن المشكلة بشكل أعمى. خطأ في نوع الصب أو تحول إلى خريطة معدومة - والأسوأ من ذلك ، سوف يسقط الثنائي من الذعر.
يمكن إضافة الأخطاء الموضحة أعلاه إلى اصطلاحات التعليمات البرمجية ، ولكن العثور عليها عند قراءة طلب السحب ليس بهذه البساطة ، لأنه سيتعين على المراجع قراءة الشفرة. إذا لم يكن هناك مترجم في رأسك ، فستذهب بعض المشاكل إلى المعركة على أي حال. بالإضافة إلى ذلك ، يصرف البحث عن الأخطاء الطفيفة عن التحقق من المنطق والهندسة المعمارية. على مسافة ، سيصبح دعم مثل هذا الرمز أكثر تكلفة. نكتب بلغة مكتوبة بشكل ثابت ، ومن الغريب عدم استخدامها.
أدوات شعبية
تستخدم معظم أدوات التحليل الثابت حزم go/ast
و go/parser
. أنها توفر وظائف لتحليل بناء جملة ملفات .go. خيط التنفيذ القياسي (على سبيل المثال ، لأداة golint) هو كما يلي:
- يتم تحميل قائمة الملفات من الحزم المطلوبة
parser.ParseFile(...) (*ast.File, error)
لكل ملف- يتحقق من القواعد المدعومة لكل ملف أو حزمة
- يمر التحقق من خلال كل تعليمات ، على سبيل المثال ، مثل:
f, err := parser.ParseFile() ast.Walk(func (n *ast.Node) { switch v := node.(type) { case *ast.FuncDecl: if strings.Contains(v.Name, "_") { panic("wrong function naming") } } }, f)
بالإضافة إلى AST ، هناك تعيين ثابت واحد (SSA). هذه طريقة أكثر تعقيدًا لتحليل التعليمات البرمجية التي تعمل مع سلسلة تنفيذ بدلاً من تراكيب بناء الجملة. في هذه المقالة ، لن نعتبرها بالتفصيل ، يمكنك قراءة الوثائق وإلقاء نظرة على مثال الأداة المساعدة stackcheck .
بعد ذلك ، سيتم النظر فقط في المرافق العامة التي تقوم بإجراء فحوصات مفيدة لنا.
حكومي
هذه هي الأداة المساعدة القياسية من حزمة go التي تتحقق من مطابقة النمط ويمكنها إصلاحها تلقائيًا. يعد الامتثال للأسلوب مطلبًا إلزاميًا بالنسبة لنا ، وبالتالي يتم تضمين التحقق من gofmt في جميع مشاريعنا.
فحص الكتابة
يتحقق Typecheck من نوع المطابقة في الكود ويدعم البائع (بخلاف gotype). مطلوب إطلاقه للتحقق من التجميع ، لكنه لا يعطي الضمانات الكاملة.
اذهب للطبيب البيطري
أداة go vet هي جزء من الحزمة القياسية ويوصى باستخدامها من قبل فريق Go. للتحقق من عدد من الأخطاء الشائعة ، على سبيل المثال:
- إساءة استخدام printf والوظائف المماثلة
- علامات بناء غير صحيحة
- مقارنة الوظيفة والصفر
غولنت
تم تطوير Golint بواسطة فريق Go ويتحقق من صحة التعليمات البرمجية بناءً على مستندات Go Effective و CodeReviewComments . لسوء الحظ ، لا يوجد وثائق تفصيلية ، لكن الكود يظهر أنه تم فحص ما يلي:
f.lintPackageComment() f.lintImports() f.lintBlankImports() f.lintExported() f.lintNames() f.lintVarDecls() f.lintElses() f.lintRanges() f.lintErrorf() f.lintErrors() f.lintErrorStrings() f.lintReceiverNames() f.lintIncDec() f.lintErrorReturn() f.lintUnexportedReturn() f.lintTimeNames() f.lintContextKeyTypes() f.lintContextArgs()
فحص ثابت
يقدم المطورون أنفسهم فحصًا ثابتًا كطبيب بيطري محسّن. هناك الكثير من الشيكات ، وهي مقسمة إلى مجموعات:
- إساءة استخدام المكتبات القياسية
- مشاكل تعدد المواضيع
- مشاكل في الاختبارات
- كود عديم الفائدة
- قضايا الأداء
- تصاميم مشكوك فيها
انحشار الدم
وهي متخصصة في العثور على الهياكل التي تستحق التبسيط ، على سبيل المثال:
قبل ( كود مصدر golint )
func (f *file) isMain() bool { if ffName.Name == "main" { return true } return false }
بعد ذلك
func (f *file) isMain() bool { return ffName.Name == "main" }
الوثائق مشابهة للفحص الثابت وتتضمن أمثلة تفصيلية.
أخطأ
لا يمكن تجاهل الأخطاء التي ترجعها الوظائف. الأسباب موصوفة بالتفصيل في وثيقة ملزمة Effective Go . لن يتخطى Errcheck الرمز التالي:
json.Unmarshal(text, &val) f, _ := os.OpenFile()
غاز
يجد الثغرات في التعليمات البرمجية: الوصول المشفر ، وحقن SQL واستخدام وظائف التجزئة غير الآمنة.
أمثلة على الأخطاء:
خبيث
في Go ، يؤثر ترتيب الحقول في الهياكل على استهلاك الذاكرة. يجد الخبيث الفرز غير الأمثل. مع ترتيب الحقول هذا:
struct { a bool b string c bool }
سيشغل الهيكل 32 بت في الذاكرة بسبب إضافة البتات الفارغة بعد الحقلين a و c.

إذا قمنا بتغيير الترتيب ووضعنا مجالين منطقيين معًا ، فستأخذ البنية 24 بت فقط:

الصورة الأصلية على stackoverflow
جوكونست
لا تعكس المتغيرات السحرية في الكود المعنى وتعقيد القراءة. يعثر Goconst على الأحرف والأرقام التي تظهر في الرمز مرتين أو أكثر. يرجى ملاحظة أنه غالبًا ما يكون الاستخدام مرة واحدة خطأ.
جوسيكلو
نعتبر التعقيد السيكلومي للكود مقياسًا مهمًا. يظهر Gocycle التعقيد لكل وظيفة. يمكن عرض الوظائف التي تتجاوز القيمة المحددة فقط.
gocyclo -over 7 package/name
لقد اخترنا قيمة عتبة 7 لأنفسنا ، لأننا لم نجد رمزًا بدرجة تعقيد أعلى لا يتطلب إعادة هيكلة.
كود ميت
هناك العديد من الأدوات المساعدة للعثور على التعليمات البرمجية غير المستخدمة ؛ قد تتداخل وظائفها جزئيًا.
- غير فعال: يتحقق من المهام غير المفيدة
func foo() error { var res interface{} log.Println(res) res, err := loadData()
- deadcode: يجد وظائف غير مستخدمة
- unused: يجد وظائف غير مستخدمة ، ولكنه يفعل ذلك أفضل من deadcode
func unusedFunc() { formallyUsedFunc() } func formallyUsedFunc() { }
ونتيجة لذلك ، ستشير كلمة "غير المستخدمة" إلى كلتا الوظيفتين في وقت واحد ، والرمز الميت إلى "unusedFunc" فقط. بفضل هذا ، يتم حذف الرمز الإضافي في مسار واحد. كما يجد غير المستخدمة المتغيرات غير المستخدمة وحقول الهيكل.
- varcheck: يجد المتغيرات غير المستخدمة
- unconvert: يجد تحويلات نوع غير مجدية
var res int return int(res)
إذا لم تكن هناك مهمة للتوفير في الوقت المستغرق لبدء عمليات التحقق ، فمن الأفضل تشغيلها معًا. إذا لزم الأمر التحسين ، أوصي باستخدام غير المستخدمة وغير محولة.
كم هو ملائم للتكوين
يعد تشغيل الأدوات المذكورة أعلاه بالتسلسل غير مريح: يتم إصدار الأخطاء بتنسيق مختلف ، ويستغرق التنفيذ الكثير من الوقت. استغرق التحقق من إحدى خدماتنا بحجم 8000 سطر من التعليمات البرمجية أكثر من دقيقتين. سيكون عليك أيضًا تثبيت الأدوات المساعدة بشكل منفصل.
هناك أدوات تجميع لحل هذه المشكلة ، على سبيل المثال goreporter و gometalinter . يقدم Goreporter التقرير بتنسيق html ، ويكتب gometalinter إلى وحدة التحكم.
لا يزال Gometalinter مستخدمًا في بعض المشاريع الكبيرة (مثل عامل الميناء ). إنه يعرف كيفية تثبيت جميع الأدوات المساعدة بأمر واحد ، وتشغيلها بالتوازي ، وأخطاء التنسيق وفقًا للقالب. تم تخفيض مدة التنفيذ في الخدمة المذكورة أعلاه إلى دقيقة ونصف.
يعمل التجميع فقط من خلال المصادفة الدقيقة لنص الخطأ ، وبالتالي ، لا مفر من الأخطاء المتكررة في الإخراج.
في مايو 2018 ، ظهر مشروع golangci-lint على جيثب ، والذي يفوق بشكل كبير gometalinter في الراحة:
- تم تقليل وقت التنفيذ على نفس المشروع إلى 16 ثانية (8 مرات)
- تكاد لا توجد أخطاء مكررة
- مسح تكوين yaml
- ناتج خطأ لطيف مع سطر من التعليمات البرمجية ومؤشر لمشكلة

- لا حاجة لتثبيت أدوات إضافية
الآن يتم توفير الزيادة في السرعة عن طريق إعادة استخدام SSA والمحمل. البرنامج ، في المستقبل ، يُخطط أيضًا لإعادة استخدام شجرة AST ، التي كتبت عنها في بداية قسم الأدوات.
في وقت كتابة هذا المقال على hub.docker.com لم يكن هناك صورة مع وثائق ، لذلك قمنا بصنع صورنا الخاصة ، حسب الطلب وفقًا لأفكارنا حول الراحة. في المستقبل ، سيتغير التكوين ، لذا نوصي باستبداله بالإصدار الخاص بالإنتاج. للقيام بذلك ، ما عليك سوى إضافة ملف .golangci.yaml إلى الدليل الجذر للمشروع ( يوجد مثال في مستودع golangci-lint).
PACKAGE=package/name docker run --rm -t \ -v $(GOPATH)/src/$(PACKAGE):/go/src/$(PACKAGE) \ -w /go/src/$(PACKAGE) \ roistat/golangci-lint
يمكن لهذا الأمر اختبار المشروع بأكمله. على سبيل المثال ، إذا كان في ~/go/src/project
، فقم بتغيير قيمة المتغير إلى PACKAGE=project
. يعمل التحقق بشكل متكرر على جميع الحزم الداخلية.
يرجى ملاحظة أن هذا الأمر يعمل بشكل صحيح فقط عند استخدام البائع.
التنفيذ
تستخدم جميع خدمات التطوير لدينا عامل الميناء. يتم تشغيل أي مشروع بدون تثبيت بيئة التشغيل. لتشغيل الأوامر ، استخدم Makefile وأضف أمر اللين إليها:
lint: @docker run --rm -t -v $(GOPATH)/src/$(PACKAGE):/go/src/$(PACKAGE) -w /go/src/$(PACKAGE) roistat/golangci-lint
الآن بدأ الفحص باستخدام هذا الأمر:
make lint
هناك طريقة سهلة لمنع التعليمات البرمجية التي تحتوي على أخطاء من الدخول إلى الرئيسي - قم بإنشاء خطاف ما قبل الاستلام. وهي مناسبة إذا:
- لديك مشروع صغير وقليل من التبعيات (أو أنها موجودة في المستودع)
- لا توجد مشكلة بالنسبة لك في انتظار اكتمال أمر
git push
لبضع دقائق
تعليمات تكوين الخطاف : Gitlab و Bitbucket Server و Github Enterprise .
في حالات أخرى ، من الأفضل استخدام CI وحظر رمز الدمج الذي يوجد به خطأ واحد على الأقل. نقوم بذلك فقط ، مضيفا إطلاق اللنت قبل الاختبارات.
الخلاصة
أدى إدخال المراجعات المنهجية إلى تقليل فترة المراجعة بشكل ملحوظ. ومع ذلك ، هناك شيء آخر أكثر أهمية: الآن يمكننا مناقشة الصورة الكبيرة والهندسة المعمارية معظم الوقت. هذا يسمح لك بالتفكير في تطوير المشروع بدلاً من سد الثقوب.