Go में स्टेटिक विश्लेषण: कोड चेक करते समय हम समय कैसे बचाते हैं


हाय, हैब्र। मेरा नाम सर्गेई रुडाचेंको है, मैं रोस्तैट में तकनीकी विशेषज्ञ हूं। पिछले दो वर्षों में, हमारी टीम परियोजना के विभिन्न हिस्सों को गो सेवा के माइक्रोसोर्सेज में अनुवादित कर रही है। वे कई टीमों द्वारा विकसित किए गए हैं, इसलिए हमें एक हार्ड कोड गुणवत्ता बार सेट करने की आवश्यकता है। ऐसा करने के लिए, हम कई उपकरणों का उपयोग करते हैं, इस लेख में हम उनमें से एक पर ध्यान केंद्रित करेंगे - स्थैतिक विश्लेषण पर।


स्थैतिक विश्लेषण विशेष उपयोगिताओं का उपयोग करके स्वचालित रूप से स्रोत कोड की जांच करने की प्रक्रिया है। यह लेख इसके लाभों के बारे में बात करेगा, लोकप्रिय उपकरणों का संक्षेप में वर्णन करेगा और कार्यान्वयन के लिए निर्देश देगा। यह पढ़ने के लायक है यदि आप एक जैसे उपकरण में बिल्कुल नहीं आए हैं या उनका उपयोग अनंतिम रूप से करते हैं।


इस विषय पर लेखों में, "लिंटर" शब्द अक्सर पाया जाता है। हमारे लिए, यह स्थैतिक विश्लेषण के लिए सरल उपकरणों के लिए एक सुविधाजनक नाम है। लिंटर का कार्य सरल त्रुटियों और गलत डिज़ाइन की खोज करना है।


लिंटर की आवश्यकता क्यों है?


एक टीम में काम करते समय, आप सबसे अधिक संभावना कोड समीक्षा कर रहे हैं। समीक्षा में छोड़ी गई त्रुटियाँ संभावित बग हैं। एक अनहेल्ड error - एक सूचनात्मक संदेश प्राप्त न करें और आप समस्या को आँख बंद करके देखेंगे। टाइप कास्टिंग में गलती या शून्य मानचित्र में बदल गया - इससे भी बदतर, बाइनरी आतंक से गिर जाएगी।


ऊपर वर्णित त्रुटियों को कोड कन्वेंशन में जोड़ा जा सकता है, लेकिन पुल अनुरोध पढ़ने पर उन्हें खोजने के लिए इतना सरल नहीं है, क्योंकि समीक्षक को कोड पढ़ना होगा। यदि आपके सिर में कोई कंपाइलर नहीं है, तो कुछ समस्याएं वैसे भी लड़ाई में चली जाएंगी। इसके अलावा, छोटी त्रुटियों की खोज तर्क और वास्तुकला की जाँच से विचलित करती है। दूरी पर, इस तरह के कोड का समर्थन करना अधिक महंगा हो जाएगा। हम एक सांख्यिकीय रूप से टाइप की गई भाषा में लिखते हैं, इसका उपयोग नहीं करना अजीब है।


लोकप्रिय उपकरण


स्थैतिक विश्लेषण के लिए अधिकांश उपकरण go/ast और go/parser पैकेज का उपयोग करते go/ast । वे .go फ़ाइलों के सिंटैक्स को पार्स करने के लिए कार्य प्रदान करते हैं। मानक निष्पादन धागा (उदाहरण के लिए, गोल की उपयोगिता के लिए) निम्नानुसार है:


  • आवश्यक पैकेज से फाइलों की सूची लोड की गई है
  • 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) 

एएसटी के अलावा, एकल स्टेटिक असाइनमेंट (एसएसए) है। यह पार्सिंग कोड का एक अधिक जटिल तरीका है जो वाक्य रचना के बजाय निष्पादन के धागे के साथ काम करता है। इस लेख में, हम इस पर विस्तार से विचार नहीं करेंगे, आप दस्तावेज़ीकरण पढ़ सकते हैं और स्टैकचेक उपयोगिता उदाहरण पर एक नज़र डाल सकते हैं


अगला, केवल लोकप्रिय उपयोगिताओं जो हमारे लिए उपयोगी जांच करते हैं, पर विचार किया जाएगा।


gofmt


यह गो पैकेज से मानक उपयोगिता है जो शैली मिलान के लिए जांच करता है और स्वचालित रूप से इसे ठीक कर सकता है। शैली का अनुपालन हमारे लिए एक अनिवार्य आवश्यकता है, इसलिए हमारे सभी प्रोजेक्टों में gofmt सत्यापन शामिल है।


typecheck


कोड में टाइप मिलान के लिए टाइपेक चेक और वेंडर का समर्थन करता है (गेटाइप के विपरीत)। संकलन की जांच के लिए इसके लॉन्च की आवश्यकता है, लेकिन पूर्ण गारंटी नहीं देता है।


पशु चिकित्सक


गो वीटी उपयोगिता मानक पैकेज का हिस्सा है और गो टीम द्वारा उपयोग के लिए अनुशंसित है। उदाहरण के लिए, कई सामान्य त्रुटियों की जाँच करता है:


  • प्रिंटफ और इसी तरह के कार्यों का दुरुपयोग
  • गलत बिल्ड टैग
  • फंक्शन और नील की तुलना करना

golint


गोल्ट को गो टीम द्वारा विकसित किया गया है और प्रभावी गो और कोडरव्यूकाममेंट दस्तावेजों के आधार पर कोड को मान्य किया गया है। दुर्भाग्य से, कोई विस्तृत दस्तावेज नहीं है, लेकिन कोड से पता चलता है कि निम्नलिखित जाँच की गई है:


 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() 

staticcheck


डेवलपर्स खुद को स्थिर गो वेट के रूप में प्रस्तुत करते हैं। बहुत सारे चेक हैं, उन्हें समूहों में विभाजित किया गया है:


  • मानक पुस्तकालयों का दुरुपयोग
  • बहुस्तरीय समस्याएं
  • परीक्षण के साथ समस्या
  • बेकार कोड
  • प्रदर्शन के मुद्दे
  • संदिग्ध डिजाइन

gosimple


यह उन संरचनाओं को खोजने में माहिर है जो उदाहरण के लिए सरल बनाने लायक हैं:


पहले ( गोलमाल सोर्स कोड )


 func (f *file) isMain() bool { if ffName.Name == "main" { return true } return false } 

के बाद


 func (f *file) isMain() bool { return ffName.Name == "main" } 

प्रलेखन स्थिरक के समान है और इसमें विस्तृत उदाहरण शामिल हैं।


errcheck


फ़ंक्शंस द्वारा लौटाए गए त्रुटियों को अनदेखा नहीं किया जा सकता है। बाध्यकारी दस्तावेज़ प्रभावी गो में कारणों का विस्तार से वर्णन किया गया है। Errcheck निम्नलिखित कोड को नहीं छोड़ेगी:


 json.Unmarshal(text, &val) f, _ := os.OpenFile(/* ... */) 

गैस


कोड में कमजोरियों को ढूंढता है: हार्डकोडेड एक्सेस, एसक्यूएल इंजेक्शन और असुरक्षित हैश फ़ंक्शन का उपयोग।


त्रुटियों के उदाहरण:


 //    IP  l, err := net.Listen("tcp", ":2000") //  sql  q := fmt.Sprintf("SELECT * FROM foo where name = '%s'", name) q := "SELECT * FROM foo where name = " + name //     import "crypto/md5" 

बदनाम


गो में, संरचनाओं में फ़ील्ड का क्रम मेमोरी की खपत को प्रभावित करता है। निरुपित गैर-इष्टतम छँटाई पाता है। खेतों के इस आदेश के साथ:


 struct { a bool b string c bool } 

फ़ील्ड a और c के बाद खाली बिट्स को जोड़ने के कारण स्मृति में 32 बिट्स पर कब्जा होगा।


छवि


यदि हम छंटाई को बदलते हैं और दो बूल फ़ील्ड को एक साथ रखते हैं, तो संरचना केवल 24 बिट्स ले जाएगी:


छवि


Stackoverflow पर मूल छवि


goconst


कोड में जादुई चर अर्थ और जटिल पठन को प्रतिबिंबित नहीं करते हैं। Goconst में शाब्दिक और संख्याएँ पाई जाती हैं जो कोड 2 या अधिक बार दिखाई देती हैं। कृपया ध्यान दें, अक्सर एक भी उपयोग गलती हो सकती है।


gocyclo


हम कोड की चक्रीय जटिलता को एक महत्वपूर्ण मीट्रिक मानते हैं। गॉसायकल प्रत्येक फ़ंक्शन के लिए जटिलता दिखाता है। केवल वे कार्य जो निर्दिष्ट मान से अधिक हैं, प्रदर्शित किए जा सकते हैं।


 gocyclo -over 7 package/name 

हमने अपने लिए 7 का थ्रेशोल्ड मान चुना, क्योंकि हमें एक उच्च जटिलता वाला कोड नहीं मिला जिसे रिफैक्टिंग की आवश्यकता नहीं थी।


मृत कोड


अप्रयुक्त कोड खोजने के लिए कई उपयोगिताओं हैं, उनकी कार्यक्षमता आंशिक रूप से ओवरलैप हो सकती है।


  • अप्रभावी: बेकार कार्यों की जाँच करता है

 func foo() error { var res interface{} log.Println(res) res, err := loadData() //  res    return err } 

  • समय सीमा: अप्रयुक्त कार्यों पाता है
  • अप्रयुक्त: अप्रयुक्त कार्यों को ढूँढता है, लेकिन क्या यह समय सीमा से बेहतर है

 func unusedFunc() { formallyUsedFunc() } func formallyUsedFunc() { } 

नतीजतन, अप्रयुक्त एक ही बार में दोनों कार्यों को इंगित करेगा, और केवल अप्रयुक्त के लिए समय सीमा। इसके लिए धन्यवाद, अतिरिक्त कोड एक पास में हटा दिया गया है। अप्रयुक्त भी अप्रयुक्त चर और संरचना क्षेत्र पाता है।


  • varcheck: अप्रयुक्त चर पाता है
  • अपरंपरागत: बेकार प्रकार के रूपांतरण पाता है

 var res int return int(res) // unconvert error 

यदि चेक लॉन्च करने में लगने वाले समय को बचाने का कोई काम नहीं है, तो उन सभी को एक साथ चलाना बेहतर है। यदि अनुकूलन की आवश्यकता होती है, तो मैं अप्रयुक्त और अनजाने के उपयोग की सलाह देता हूं।


कॉन्फ़िगर करना कितना सुविधाजनक है


अनुक्रम में उपरोक्त उपकरणों को चलाना असुविधाजनक है: त्रुटियों को एक अलग प्रारूप में जारी किया जाता है, निष्पादन में बहुत समय लगता है। कोड की ~ 8000 लाइनों के आकार के साथ हमारी एक सेवा की जाँच में दो मिनट से अधिक का समय लगा। आपको उपयोगिताओं को अलग से भी स्थापित करना होगा।


इस समस्या को हल करने के लिए एकत्रीकरण उपयोगिताओं हैं, उदाहरण के लिए गोरक्षक और गोमेटालीनट । Goreporter html में रिपोर्ट प्रस्तुत करता है, और gometalinter कंसोल को लिखता है।


अभी भी कुछ बड़ी परियोजनाओं (जैसे कर्ता) में Gometalinter का उपयोग किया जाता है। वह जानता है कि एक ही आदेश के साथ सभी उपयोगिताओं को कैसे स्थापित किया जाए, उन्हें समानांतर में चलाएं, और टेम्पलेट के अनुसार त्रुटियों को प्रारूपित करें। उपरोक्त सेवा में निष्पादन का समय डेढ़ मिनट हो गया था।


एकत्रीकरण केवल त्रुटि पाठ के सटीक संयोग से काम करता है, इसलिए, आउटपुट पर बार-बार त्रुटियां अपरिहार्य हैं।


मई २०१ the में, गॉलंग्सी-लिंट प्रोजेक्ट गीथूब पर दिखाई दिया, जो सुविधा में गोमेटेलेंडर्न से बहुत आगे है:


  • एक ही परियोजना पर निष्पादन का समय घटाकर 16 सेकंड (8 गुना) कर दिया गया
  • लगभग कोई डुप्लिकेट त्रुटियाँ नहीं
  • स्पष्ट yaml config
  • कोड की एक पंक्ति और एक समस्या के लिए एक सूचक के साथ अच्छा त्रुटि आउटपुट
  • अतिरिक्त उपयोगिताओं को स्थापित करने की आवश्यकता नहीं है

अब एसएसए और लोडर का पुन: उपयोग करके गति में वृद्धि प्रदान की जाती है। भविष्य में, यह एएसटी पेड़ के पुन: उपयोग के लिए भी योजना बनाई गई है, जिसे मैंने टूल सेक्शन की शुरुआत में लिखा था।


Hub.docker.com पर इस लेख को लिखने के समय प्रलेखन के साथ कोई छवि नहीं थी, इसलिए हमने सुविधा के अनुसार अपने विचारों के अनुसार, अपना खुद का बनाया। भविष्य में, कॉन्फ़िगरेशन बदल जाएगा, इसलिए उत्पादन के लिए हम इसे अपने साथ बदलने की सलाह देते हैं। ऐसा करने के लिए, बस .golangci.yaml फ़ाइल को प्रोजेक्ट की मूल निर्देशिका में जोड़ें ( एक उदाहरण गोलंग्सी-लिंट रिपॉजिटरी में है)।


 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 बदलें। सत्यापन सभी आंतरिक पैकेजों पर पुनरावर्ती रूप से काम करता है।


कृपया ध्यान दें कि यह आदेश केवल विक्रेता का उपयोग करते समय सही ढंग से काम करता है।


की शुरूआत


हमारी सभी विकास सेवाएं docker का उपयोग करती हैं। कोई भी परियोजना स्थापित किए गए पर्यावरण के बिना चलती है। कमांड चलाने के लिए, मेकफाइल का उपयोग करें और इसमें लिंट कमांड जोड़ें:


 lint: @docker run --rm -t -v $(GOPATH)/src/$(PACKAGE):/go/src/$(PACKAGE) -w /go/src/$(PACKAGE) roistat/golangci-lint 

अब इस कमांड के साथ चेक शुरू किया गया है:


 make lint 

मास्टर को दर्ज करने से त्रुटियों के साथ कोड को ब्लॉक करने का एक आसान तरीका है - पूर्व-प्राप्त-हुक बनाएं। यह उपयुक्त है अगर:


  1. आपके पास एक छोटी परियोजना और कुछ निर्भरताएँ हैं (या वे भंडार में हैं)
  2. यह कुछ मिनट के लिए git push कमांड के पूरा होने की प्रतीक्षा करने के लिए आपके लिए कोई समस्या नहीं है

हुक विन्यास निर्देश: Gitlab , Bitbucket Server , Github Enterprise


अन्य मामलों में, CI का उपयोग करना और मर्ज कोड को प्रतिबंधित करना बेहतर है, जिसमें कम से कम एक त्रुटि हो। हम बस यही करते हैं, परीक्षणों से पहले लिंटर के प्रक्षेपण को जोड़ते हैं।


निष्कर्ष


व्यवस्थित समीक्षाओं की शुरूआत ने समीक्षा अवधि को काफी कम कर दिया है। हालांकि, एक और बात अधिक महत्वपूर्ण है: अब हम ज्यादातर समय बड़ी तस्वीर और वास्तुकला पर चर्चा कर सकते हैं। यह आपको प्लग छेद करने के बजाय परियोजना के विकास के बारे में सोचने की अनुमति देता है।

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


All Articles