做網(wǎng)站經(jīng)常加班還是appdz論壇seo
一提到靜態(tài)代碼檢查工具這個詞應(yīng)該比較好理解,所謂靜態(tài)代碼檢查工具就是檢查靜態(tài)代碼的工具,完美~
言歸正傳,相信很多程序員朋友都聽說過靜態(tài)代碼檢查工具這個概念,它可能是我們IDE里的某一個插件,可能是計算機中的一個程序,還可能是Git提交后的一個流程,如果是對代碼要求比較高的個人或組織,靜態(tài)代碼檢查工具則是一個繞不開的東西。
一個事物的出現(xiàn)必然是有所需求的,那么我們不妨先思考一下,為什么需要靜態(tài)代碼檢查工具?
先拋開這個問題本身,我們從編譯器的錯誤檢查開始。
編譯器:最嚴(yán)重代碼的問題我替你規(guī)避
大家都知道我們寫的代碼是需要通過編譯器編譯成中間代碼或可執(zhí)行文件的,比如Java程序代碼會由Java編譯器編譯成class文件,由JVM執(zhí)行,Go代碼會由Go編譯器編譯成二進制文件直接執(zhí)行,中間都會有一個編譯的過程,在編譯原理中會有兩個比較重要的流程——詞法分析和語法分析。先說下詞法分析和語法分析主要是用來做什么的:
詞法分析是編譯過程的第一步,其主要作用和特點如下:
- 掃描源程序:詞法分析器負(fù)責(zé)讀入源程序的字符流,這是編譯過程的輸入。
- 識別單詞符號:根據(jù)源語言的詞法規(guī)則,詞法分析器將字符流分解并識別出各個單詞符號。單詞是源程序中的最小語義單位,如關(guān)鍵字、標(biāo)識符、常數(shù)、運算符等。
- 輸出記號序列:詞法分析器將識別出的單詞符號轉(zhuǎn)換成相應(yīng)的記號(token)序列,作為語法分析的輸入。每個記號通常包括一個詞法單元名稱(如關(guān)鍵字、標(biāo)識符等)和一個可選的屬性值(如標(biāo)識符的名稱、常數(shù)的值等)。
- 過濾空白和注釋:詞法分析器還會跳過源程序中的空白字符(如空格、制表符等)和注釋,這些對語法分析來說是無意義的。
- 錯誤檢測:詞法分析器能夠識別并報告詞法錯誤,即非法的字符或單詞符號,如非法字符、未識別的關(guān)鍵字等。
語法分析是在詞法分析的基礎(chǔ)上進行的,其主要作用和特點如下:
- 分析語法結(jié)構(gòu):語法分析器根據(jù)語言的語法規(guī)則,對詞法分析器輸出的記號序列進行分析,以識別出各種語法單位,如表達式、語句、函數(shù)等。
- 構(gòu)建語法樹:在語法分析過程中,通常會構(gòu)建一棵語法樹來表示源程序的語法結(jié)構(gòu)。語法樹的葉子節(jié)點通常對應(yīng)于記號,而非葉子節(jié)點則對應(yīng)于語法單位。
- 錯誤檢測和處理:語法分析器能夠識別并報告語法錯誤,即不符合語法規(guī)則的記號序列。這些錯誤通常包括括號不匹配、缺少分號、語法單位使用不當(dāng)?shù)取?/li>
- 為后續(xù)階段做準(zhǔn)備:語法分析的結(jié)果(如語法樹)將作為后續(xù)階段(如語義分析、代碼生成等)的輸入。通過語法分析,編譯器能夠確保源程序在語法上是正確的,從而可以進一步進行語義分析和代碼生成等工作。
詞法分析和語法分析是編譯過程中不可或缺的兩個階段。詞法分析負(fù)責(zé)將源程序的字符流轉(zhuǎn)換成記號序列,而語法分析則根據(jù)語法規(guī)則對記號序列進行分析,以構(gòu)建出源程序的語法結(jié)構(gòu)。這兩個階段共同為后續(xù)的編譯工作打下了堅實的基礎(chǔ),確保了編譯過程的順利進行。
現(xiàn)在我們知道了,詞法分析和語法分析有一個共同的作用:錯誤監(jiān)測。
舉個例子,我們使用Go中一個fmt包中不存在的函數(shù):
package mainimport "fmt"func main() {fmt.Printlnx("Hello World")
}
執(zhí)行后報錯,原因很簡單,因為函數(shù)的單詞拼錯了:
.\main.go:6:6: undefined: fmt.Printlnx
再有一個例子:
package mainimport "fmt"func main() {fmt.Println(str)str := "Hello World"
}
執(zhí)行后同樣報錯,因為在程序中我們規(guī)定語法是先聲明后使用,這段代碼卻是先使用后聲明
.\main.go:6:14: undefined: str
.\main.go:7:2: str declared and not used
這兩段代碼其實主要是想闡述一件事:重大的程序問題編譯器層面直接就不會編譯通過。
靜態(tài)代碼檢查工具:代碼不優(yōu)雅的地方我給你指出
那么有沒有編譯器發(fā)現(xiàn)不了的程序問題? 答案肯定是有的,舉例子:
看這段代碼:
package mainimport "fmt"func main() {str := "Hello World"fmt.Printf("%d \n",str)
}
一個很顯然的問題,字符串應(yīng)該使用%s進行轉(zhuǎn)義,但該程序中使用的是表示整型變量的%d,但這個問題如果直接執(zhí)行的話并不會報錯,而是會打印出如下代碼:
%!d(string=Hello World)
雖然我們寫錯了,但是Go程序還是會把我們想打印的數(shù)據(jù)打印出現(xiàn)并標(biāo)記正確的類型,這個贊我點給Go的開發(fā)者!
還有這個:
package mainfunc hello() (string, error) {return "Hello", nil
}func main() {hello()
}
從優(yōu)雅的角度來講,我們應(yīng)該去認(rèn)真處理每個函數(shù)中的錯誤返回值,但是以上示例中并沒有處理,編譯器也編譯通過了,這樣就導(dǎo)致程序中一旦真的返回了錯誤,那么排查問題可能就不太方便。
除了以上兩個示例,Go程序中還要很多常見的編碼問題需要注意,在大型項目中我們不可能逐行代碼的去看,因此衍生出了靜態(tài)代碼檢查工具這個東西?;卮鹞恼麻_頭的問題,進行靜態(tài)代碼檢查的原因主要有以下幾點:
提高代碼質(zhì)量。 靜態(tài)代碼檢查能夠在不運行代碼的情況下,通過自動化的方式分析代碼,幫助開發(fā)者及時發(fā)現(xiàn)并修復(fù)潛在的缺陷以及不符合編碼規(guī)范的問題。這有助于提升代碼的整體質(zhì)量,減少因代碼問題導(dǎo)致的錯誤和故障。
降低維護成本。產(chǎn)品是資產(chǎn),代碼是負(fù)債,因為代碼越多就一定程度上意味著維護成本越高。 高質(zhì)量的代碼意味著更低的維護成本。通過靜態(tài)代碼檢查,開發(fā)者可以在早期發(fā)現(xiàn)并解決潛在問題,避免在后期測試和維護階段投入更多的時間和資源。除此之外,還可以確保所有成員都遵循相同的編碼規(guī)范,減少因風(fēng)格不一致而導(dǎo)致的代碼合并沖突。
Go語言如何進行靜態(tài)代碼檢查
Go語言作為一門非常簡單的編程語言,語法可以非常靈活,靜態(tài)代碼檢查的方式也非常多,主要有以下幾種方式:
使用內(nèi)置的??go vet
??工具:go vet
是Go語言內(nèi)置的一個靜態(tài)分析工具,它可以幫助開發(fā)者檢查Go代碼中的潛在問題,如未使用的變量、錯誤的使用標(biāo)志位等。
使用??golint
??工具:golint
是一個用于Go代碼的Lint工具,它可以幫助開發(fā)者找出一些可疑的或者不規(guī)范的代碼寫法,如導(dǎo)出的函數(shù)沒有注釋、變量名不符合規(guī)范等,但本項目官方已經(jīng)不再維護。
使用第三方靜態(tài)分析工具:Go語言社區(qū)還提供了許多第三方靜態(tài)分析工具,如staticcheck
、errcheck
等。這些工具通常具有更豐富的功能和更強大的檢測能力,可以幫助開發(fā)者更全面地檢查代碼中的潛在問題。
集成到持續(xù)集成/持續(xù)部署(CI/CD)流程中:為了自動化地進行靜態(tài)代碼檢查,開發(fā)者可以將靜態(tài)分析工具集成到項目的CI/CD流程中。每次代碼提交或合并時,都會自動運行靜態(tài)檢查工具,確保代碼質(zhì)量符合要求。
使用IDE插件:如GoLand、Visual Studio Code在的一些代碼檢查插件可以在編寫代碼的過程中實時提供靜態(tài)檢查反饋,幫助開發(fā)者及時發(fā)現(xiàn)并修復(fù)問題。
現(xiàn)在有哪些工具我們大概知道了,怎么用具體還是要試一試,比如剛剛的這段代碼,我們使用go vet
工具檢查一下:
package mainimport "fmt"func main() {str := "Hello World"fmt.Printf("%d \n",str)
}
使用命令go vet main.go
輸出結(jié)果,可以看出它會把問題表達的很清楚:
.\main.go:7:2: fmt.Printf format %d has arg str of wrong type string
對于第二段代碼,我們也可以有一個三方插件來進行代碼檢查,安裝:go install github.com/kisielk/errcheck@latest
package mainfunc hello() (string, error) {return "Hello", nil
}func main() {hello()
}
然后使用命令errcheck main.go
,就會輸出如下來告訴你哪行代碼哪個方法沒有處理錯誤返回值
main.go:8:7: hello()
最好用的Go靜態(tài)代碼檢查工具:golangci-lint
golangci-lint
可以說是一個大一統(tǒng),它幾乎把所有最有用的Go靜態(tài)代碼檢查工具進行統(tǒng)一,并且還支持在CI/CD中使用。
官網(wǎng):https://golangci-lint.run
Github:https://github.com/golangci/golangci-lint
我們可以使用命令golangci-lint help linters
查看它都支持哪些靜態(tài)檢查工具(打印很多沒截全):
可以看到有些是默認(rèn)支持,有些是默認(rèn)關(guān)閉支持的,我們不妨來試一下它和上面的兩個工具有什么不同,我們將上面的兩個代碼示例合并到一起執(zhí)行,運行命令golangci-lint run main.go
:
main.go:13:7: Error return value is not checked (errcheck)hello()^
main.go:11:2: printf: fmt.Printf format %d has arg str of wrong type string (govet)fmt.Printf("%d \n",str)^
可以看出這個工具打印的提升更加精準(zhǔn)。后面如果想要繼續(xù)深入可以閱讀一下官方文檔。
小總結(jié)
總的來說,靜態(tài)代碼檢查工具是一個能夠提升我們代碼質(zhì)量的工具,可以發(fā)現(xiàn)編譯器發(fā)現(xiàn)不了的問題。這些工具的使用可以顯著提高軟件質(zhì)量,所以大家趕快掌握起來,寫出更優(yōu)雅的代碼!