App下載

Go開(kāi)發(fā)中的5個(gè)常見(jiàn)錯(cuò)誤分享!

著名奶茶鑒定家 2021-08-26 15:59:21 瀏覽數(shù) (2168)
反饋

錯(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)化而編寫(xiě)的遞歸函數(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類(lèi)型和可選的容量提示作為參數(shù):

make(map[string]int)
make(map[string]int, 100)

初始容量不限制其大?。旱貓D增長(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ì)于可寫(xiě)文件是有害的,因?yàn)橥七t函數(shù)調(diào)用會(huì)忽略其返回值,并且該Close()方法可能會(huì)返回錯(cuò)誤。例如,如果您將數(shù)據(jù)寫(xiě)入文件,則在您調(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

推薦閱讀:不要在可寫(xiě)文件上延遲 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。


0 人點(diǎn)贊