ساهم في Go باستخدام المحلل اللغوي الناقد


قد تتذكر الإعلان الأخير عن محلل ثابت جديد لـ Go يسمى Go -Critic .


لقد تحققت من مشروع golang / go وأرسلت بعض التصحيحات التي تعمل على إصلاح بعض المشاكل الموجودة هناك.


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


لأكثر الصبر: قائمة محدثة بالبطولات .



dupSubExpr


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


يحتوي CL122776 على إصلاح خطأ تم العثور عليه بواسطة dupSubExpr :


 func (a partsByVarOffset) Less(i, j int) bool { - return varOffset(a.slots[a.slotIDs[i]]) < varOffset(a.slots[a.slotIDs[i]]) + return varOffset(a.slots[a.slotIDs[i]]) < varOffset(a.slots[a.slotIDs[j]]) // ^__________________________________^ } 

انتبه إلى الفهرس على اليسار واليمين. قبل التصحيح ، كان كل من LHS و RHS للعامل متطابقين ، وعمل dupSubExpr على dupSubExpr .


علق خارج الرمز


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


علق OutCode يمكن أن يجد مثل هذا الجزء المثير للاهتمام ( CL122896 ):


 switch arch.Family { // ...  case clause. case sys.I386: return elf.R_386(nr).String() case sys.MIPS, sys.MIPS64: // return elf.R_MIPS(nr).String() // <- 1 case sys.PPC64: // return elf.R_PPC64(nr).String() // <- 2 case sys.S390X: // return elf.R_390(nr).String() // <- 3 default: panic("unreachable") } 

هناك تعليق أعلى قليلاً:


 // We didn't have some relocation types at Go1.4. // Uncomment code when we include those in bootstrap code. 

إذا قمت بالتبديل إلى فرع go1.4 وأزلت هذه الأسطر الثلاثة من التعليق ، فلن يتم تجميع الرمز ، ولكن إذا قمت go1.4 على go1.4 ، go1.4 كل شيء.


عادةً ما يتطلب الرمز المخفي في التعليق إما الحذف أو التنشيط العكسي.


من المفيد ، من وقت لآخر ، زيارة أصداء الماضي في شفرتك.


حول صعوبات الكشف

هذا هو أحد الشيكات المفضلة لدي ، ولكنه واحد من أكثر الشيكات "صاخبة".


الكثير من الإيجابيات الكاذبة للحزم التي تستخدم math/big وداخل المجمع. في الحالة الأولى ، عادة ما تكون هذه تعليقات توضيحية على العمليات التي تم إجراؤها ، وفي الحالة الثانية ، وصف للرمز الذي يصف جزء AST. إن تمييز مثل هذه التعليقات عن الشفرة "الميتة" الحقيقية دون تقديم سلبيات كاذبة أمر غير بديهي.


هذا يثير الفكرة: ماذا لو وافقنا على تخصيص الرمز بطريقة ما داخل التعليقات ، وهو أمر توضيحي؟ ثم سيتم تبسيط التحليل الثابت. يمكن أن يكون هذا أي تافه سيجعل من السهل تعريف مثل هذا التعليق التوضيحي ، أو جعله رمز Go غير صالح (على سبيل المثال ، إذا أضفت علامة الجنيه ، # إلى بداية السطر).


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


boolExprSimplify


في بعض الأحيان يكتب الناس رمزًا غريبًا. ربما يبدو لي ، لكن التعبيرات المنطقية ( المنطقية ) تبدو غريبة في بعض الأحيان.


يحتوي Go على خلفية تجميع ممتازة لـ x86 (هنا سقط دمعة) ، لكن ARM أخطأ حقًا:


 if !(o1 != 0) { break } 

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


الخيار المصحح (لأولئك الذين لا يمكن إغرائهم بالمراجعة)
 - if !(o1 != 0) { + if o1 == 0 { 

يهدف boolExprSimplify إلى التبسيط الذي يزيد من قابلية القراءة (وكان محسن Go سيتعامل مع مشكلة الأداء بدونها).


ناقص


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


في الكود القديم ، لا يزال بإمكانك رؤية شيء مثل هذا:


 // -    : buf := (*bufp).ptr() // ...     : buf := bufp.ptr() 

يتم تشغيل عدة محللات سفلية ثابتة في CL122895 .


appendCombine


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


في Go ، وجد فحص appendCombine القسم التالي:


 - for i := len(ip) - 1; i >= 0; i-- { - v := ip[i] - buf = append(buf, hexDigit[v&0xF]) - buf = append(buf, '.') - buf = append(buf, hexDigit[v>>4]) - buf = append(buf, '.') - } + for i := len(ip) - 1; i >= 0; i-- { + v := ip[i] + buf = append(buf, hexDigit[v&0xF], + '.', + hexDigit[v>>4], + '.') + } 

 name old time/op new time/op delta ReverseAddress-8 4.10µs ± 3% 3.94µs ± 1% -3.81% (p=0.000 n=10+9) 

التفاصيل في CL117615 .


rangeValCopy


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


Go لديه رابط بطيء إلى حد ما (cmd / link) ، وبدون تغييرات كبيرة في بنيته ، لا يمكن تحقيق مكاسب قوية في الأداء. ولكن بعد ذلك يمكنك تقليل عدم كفاءته بشكل طفيف بمساعدة التحسينات الدقيقة. كل في المئة أو تهمين.


وجد فحص rangeValCopy عدة دورات مع نسخ البيانات غير المرغوب فيها دفعة واحدة. هنا الأكثر إثارة للاهتمام منهم:


 - for _, r := range exports.R { - d.mark(r.Sym, nil) - } + for i := range exports.R { + d.mark(exports.R[i].Sym, nil) + } 

بدلاً من نسخ R[i] في كل تكرار ، ننتقل فقط إلى العضو الوحيد الذي يهمنا ، Sym .


 name old time/op new time/op delta Linker-4 530ms ± 2% 521ms ± 3% -1.80% (p=0.000 n=17+20) 

النسخة الكاملة من التصحيح متوفرة على: CL113636 .


اسمه كونست


في Go ، للأسف ، الثوابت المسماة ، حتى المجمعة في مجموعات ، ليست مترابطة ولا تشكل تعدادًا ( اقتراح: المواصفات: إضافة دعم تعداد مطبوع ).


تتمثل إحدى المشاكل في إرسال ثوابت غير من النوع إلى النوع الذي ترغب في استخدامه كعدد.


افترض أنك const ColDefault Color = 0 نوع Color ، وله قيمة const ColDefault Color = 0 .
أي من مقتطفات الشفرة هذه تحب أكثر؟


 // (A) if color == 0 { return colorBlack } // (B) if color == colorDefault { return colorBlack } 

إذا بدا لك (B) أكثر ملاءمة لك ، سيساعدك التحقق من nameConst على تتبع استخدام القيم الثابتة المسماة ، وتجاوز الثابت المسمى نفسه.


هذه هي الطريقة التي تم بها تحويل طريقة context.mangle من حزمة html/template :


  s := templateName + "$htmltemplate_" + c.state.String() - if c.delim != 0 { + if c.delim != delimNone { s += "_" + c.delim.String() } - if c.urlPart != 0 { + if c.urlPart != urlPartNone { s += "_" + c.urlPart.String() } - if c.jsCtx != 0 { + if c.jsCtx != jsCtxRegexp { s += "_" + c.jsCtx.String() } - if c.attr != 0 { + if c.attr != attrNone { s += "_" + c.attr.String() } - if c.element != 0 { + if c.element != elementNone { s += "_" + c.element.String() } return s 

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


افصل


ميزة تعبير الشريحة هي أن x[:] مطابق دائمًا لـ x إذا كان النوع x شريحة أو سلسلة. في حالة الشرائح ، يعمل هذا مع أي نوع من عناصر []T


كل شيء في القائمة أدناه هو نفسه ( x - شريحة):


  • x
  • x[:]
  • x[:][:]
  • ...

يجد unlice تعابير شريحة زائدة مماثلة. هذه التعبيرات ضارة ، أولاً وقبل كل شيء ، بحمل إدراكي إضافي. x[:] له دلالات مهمة للغاية في حالة أخذ شريحة من المصفوفة. لا تعمل شريحة الشريحة ذات النطاقات الافتراضية إلا الضوضاء.


أطلب التصحيح في CL123375 .


رمز التبديل صحيح


في CL123378 ، يتم استبدال " switch true {...} " بـ " switch {...} ".
كلا النموذجين متكافئان ، لكن الشكل الثاني هو أكثر اصطلاحيًا.
وجدت من خلال التحقق من switchTrue .


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


النوع


اذهب ، مثل العديد من لغات البرمجة الأخرى ، يحب الأقواس. لدرجة أنني على استعداد لقبول أي عدد منهم:


 type ( t0 int t1 (int) t2 ((int)) // ... ,  . ) 

ولكن ماذا يحدث إذا قمت بتشغيل gofmt ؟


 type ( t0 int t1 (int) // <- !   . t2 (int) // <-    ... ) 

لهذا السبب يوجد نوع Unparen . يجد في البرنامج جميع أنواع التعبيرات التي يمكنك من خلالها تقليل عدد الأقواس. حاولت إرسال CL123379 ، دعنا نرى كيف سيقبلها المجتمع.


Lisp لا تحب الأقواس

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


الناقد على الذهاب



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


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


الذهاب المساهمة المساهمة


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


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

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


All Articles