錯(cuò)誤風(fēng)險(xiǎn)是代碼中可能導(dǎo)致生產(chǎn)錯(cuò)誤和中斷的問(wèn)題。錯(cuò)誤是代碼中的缺陷,它會(huì)產(chǎn)生不希望的或不正確的結(jié)果。由于糟糕的編碼實(shí)踐、缺乏版本控制、需求傳達(dá)錯(cuò)誤、不切實(shí)際的開(kāi)發(fā)時(shí)間表以及有缺陷的第三方工具,代碼通常存在錯(cuò)誤風(fēng)險(xiǎn)。在這篇文章中,讓我們來(lái)看看 Go 中一些常見(jiàn)的錯(cuò)誤風(fēng)險(xiǎn)。
1.無(wú)限遞歸調(diào)用
遞歸調(diào)用自身的函數(shù)需要有一個(gè)退出條件。否則,它將永遠(yuǎn)遞歸,直到系統(tǒng)內(nèi)存耗盡。
此問(wèn)題可能是由常見(jiàn)錯(cuò)誤引起的,例如忘記添加退出條件。它也可能“故意”發(fā)生。某些語(yǔ)言具有尾調(diào)用優(yōu)化,這使得某些無(wú)限遞歸調(diào)用可以安全使用。尾調(diào)用優(yōu)化允許您避免為函數(shù)分配新的堆棧幀,因?yàn)檎{(diào)用函數(shù)將返回它從被調(diào)用函數(shù)獲取的值。最常見(jiàn)的用途是尾遞歸,其中為利用尾調(diào)用優(yōu)化而編寫的遞歸函數(shù)可以使用常量堆??臻g。然而,Go 并沒(méi)有實(shí)現(xiàn)尾調(diào)用優(yōu)化,你最終會(huì)耗盡內(nèi)存。然而,這個(gè)問(wèn)題不適用于產(chǎn)生新的 goroutine。
2. 分配給nil地圖
在添加任何元素之前,需要使用make函數(shù)(或map文字)初始化映射。使用內(nèi)置函數(shù)創(chuàng)建一個(gè)新的空映射值make,該函數(shù)將map類型和可選的容量提示作為參數(shù):
make(map[string]int)
make(map[string]int, 100)
初始容量不限制其大小:地圖增長(zhǎng)以容納存儲(chǔ)在其中的項(xiàng)目數(shù)量,nil
地圖除外。甲nil
地圖相當(dāng)于不同之處在于可以添加沒(méi)有元素的空映射。
不好的模式:
var countedData map[string][]ChartElement
好的模式:
countedData := make(map[string][]ChartElement)
推薦閱讀:Go:賦值到 nil 映射中的條目
3.方法修改接收器
修改非指針接收器值的方法可能會(huì)產(chǎn)生不良后果。這是一個(gè)錯(cuò)誤風(fēng)險(xiǎn),因?yàn)樵摲椒赡軙?huì)更改方法內(nèi)部接收器的值,但不會(huì)反映在原始值中。要傳播更改,接收者必須是一個(gè)指針。
例如:
type data struct {
num int
key *string
items map[string]bool
}
func (d data) vmethod() {
d.num = 8
}
func (d data) run() {
d.vmethod()
fmt.Printf("%+v", d) // Output: {num:1 key:0xc0000961e0 items:map[1:true]}
}
如果num
必須修改:
type data struct {
num int
key *string
items map[string]bool
}
func (d *data) vmethod() {
d.num = 8
}
func (d *data) run() {
d.vmethod()
fmt.Printf("%+v", d) // Output: &{num:8 key:0xc00010a040 items:map[1:true]}
}
4. Goroutine 中可能使用了不需要的值
循環(huán)中的范圍變量在每次迭代中都被重用;因此,在循環(huán)中創(chuàng)建的 goroutine 將指向上作用域的范圍變量。這樣,goroutine 就可以使用帶有不需要的值的變量。
在下面的示例中,goroutine 中使用的 index 和 value 的值來(lái)自外部范圍。因?yàn)?goroutine 是異步運(yùn)行的,所以 index 和 value 的值可能(通常是)與預(yù)期值不同。
mySlice := []string{"A", "B", "C"}
for index, value := range mySlice {
go func() {
fmt.Printf("Index: %d\n", index)
fmt.Printf("Value: %s\n", value)
}()
}
為了克服這個(gè)問(wèn)題,必須創(chuàng)建一個(gè)本地作用域,如下例所示。
mySlice := []string{"A", "B", "C"}
for index, value := range mySlice {
index := index
value := value
go func() {
fmt.Printf("Index: %d\n", index)
fmt.Printf("Value: %s\n", value)
}()
}
處理此問(wèn)題的另一種方法是將值作為 args 傳遞給 goroutine。
mySlice := []string{"A", "B", "C"}
for index, value := range mySlice {
go func(index int, value string) {
fmt.Printf("Index: %d\n", index)
fmt.Printf("Value: %s\n", value)
}(index, value)
}
推薦閱讀:作為 goroutine 運(yùn)行的閉包會(huì)發(fā)生什么?
5.Close在檢查可能的錯(cuò)誤之前推遲
對(duì)于實(shí)現(xiàn)接口的值defer的Close()方法,這是 Go 開(kāi)發(fā)人員的常見(jiàn)模式io.Closer。例如,打開(kāi)文件時(shí):
f, err := os.Open("/tmp/file.md")
if err != nil {
return err
}
defer f.Close()
但是這種模式對(duì)于可寫文件是有害的,因?yàn)橥七t函數(shù)調(diào)用會(huì)忽略其返回值,并且該Close()方法可能會(huì)返回錯(cuò)誤。例如,如果您將數(shù)據(jù)寫入文件,則在您調(diào)用Close. 應(yīng)明確處理此錯(cuò)誤。
雖然您可以在不使用的情況下繼續(xù),但defer您需要記住每次完成工作時(shí)關(guān)閉文件。更好的方法是defer使用包裝函數(shù),如下例所示。
f, err := os.Open("/tmp/file.md")
if err != nil {
return err
}
defer func() {
closeErr := f.Close()
if closeErr != nil {
if err == nil {
err = closeErr
} else {
log.Println("Error occured while closing the file :", closeErr)
}
}
}()
return err
推薦閱讀:不要在可寫文件上延遲 Close()
在團(tuán)隊(duì)中工作時(shí),審查其他人的代碼變得很重要。DeepSource是一種自動(dòng)化代碼審查工具,可管理端到端代碼掃描過(guò)程,并在推送新提交或新拉取請(qǐng)求時(shí)自動(dòng)發(fā)出帶有修復(fù)的拉取請(qǐng)求。
為 Go 設(shè)置 DeepSource非常簡(jiǎn)單。一旦設(shè)置完成,將對(duì)整個(gè)代碼庫(kù)執(zhí)行初始掃描,找到改進(jìn)的范圍,修復(fù)它們,并為這些更改打開(kāi) PR。