如何優化Golang中重複的錯誤處理

2021-09-18 01:32:54 字數 3173 閱讀 6636

golang 錯誤處理最讓人頭疼的問題就是**裡充斥著「if err != nil」,它們破壞了**的可讀性,本文收集了幾個例子,讓大家明白如何優化此類問題。

讓我們看看 errors are values 中提到的乙個 io.writer 例子:

_, err = fd.write(p0[a:b])

if err != nil

_, err = fd.write(p1[c:d])

if err != nil

_, err = fd.write(p2[e:f])

if err != nil

如上**乍一看無法直觀的看出其本來的意圖是什麼,改進版:

type errwriter struct 

func (ew *errwriter) write(buf byte)

_, ew.err = ew.w.write(buf)

}ew := &errwriter

ew.write(p0[a:b])

ew.write(p1[c:d])

ew.write(p2[e:f])

if ew.err != nil

通過自定義型別 errwriter 來封裝 io.writer,並且封裝了 error,新型別有乙個 write 方法,不過其方法簽名並沒有返回 error,而是在方法內部判斷一旦有問題就立刻返回,有了這些準備工作,我們就可以把原本穿插在業務邏輯中間的錯誤判斷提出來放到最後來統一呼叫,從而在視覺上保證讓人可以直觀的看出**本來的意圖是什麼。

讓我們再看看 eliminate error handling by eliminating errors 中提到的另乙個 io.writer 例子:

type header struct 

type status struct

func writeresponse(w io.writer, st status, headers header, body io.reader) error

for _, h := range headers

} if _, err := fmt.fprint(w, "\r\n"); err != nil

_, err = io.copy(w, body)

return err

}

第一感覺既然錯誤是 fmt.fprint 和 io.copy 返回的,是不是我們要重新封裝一下它們?實際上真正的源頭是它們的引數 io.writer,因為直接呼叫 io.writer 的 writer 方法的話,方法簽名中有返回值 error,所以每一步 fmt.fprint 和 io.copy 操作都不得不進行重複的錯誤處理,看上去是壞味道,改進版:

}通過自定義型別 errwriter 來封裝 io.writer,並且封裝了 error,同時重寫了 writer 方法,雖然方法簽名中仍然有返回值 error,但是我們單獨儲存了乙份 error,並且在方法內部判斷一旦有問題就立刻返回,有了這些準備工作,新版的 writeresponse 不再有重複的錯誤判斷,只需要在最後檢查一下 error 即可。

類似的做法在 golang 標準庫中屢見不鮮,讓我們繼續看看 eliminate error handling by eliminating errors 中提到的乙個關於 bufio.reader 和 bufio.scanner 的例子:

func countlines(r io.reader) (int, error) 

} if err != io.eof

return lines, nil

}

我們構造乙個 bufio.reader,然後在乙個迴圈中呼叫 readstring 方法,如果讀到檔案結尾,那麼 readstring 會返回乙個錯誤(io.eof),為了判斷此類情況,我們不得不在每次迴圈時判斷「if err != nil」,看上去這是壞味道,改進版:

func countlines(r io.reader) (int, error) 

return lines, sc.err()

}

實際上,和 bufio.reader 相比,bufio.scanner 是乙個更高階的型別,換句話簡單點來說的話,相當於是 bufio.scanner 抽象了 bufio.reader,通過把低階的 bufio.reader 換成高階的 bufio.scanner,迴圈中不再需要判斷「if err != nil」,因為 scan 方法簽名不再返回 error,而是返回 bool,當在迴圈裡讀到了檔案結尾的時候,迴圈直接結束,如此一來,我們就可以統一在最後呼叫 err 方法來判斷成功還是失敗,看看 scanner 的定義:

type scanner struct
可見 scanner 封裝了 io.reader,並且封裝了 error,和我們之前討論的做法一致。有一點說明一下,實際上檢視 scan 源**的話,你會發現它不是通過 err 來判斷是否結束的,而是通過 done 來判斷是否結束,這是因為 scan 只有遇到檔案結束的錯誤才退出,其它錯誤會繼續執行,當然,這只是具體的細節問題,不影響我們的結論。

通過對以上幾個例子的分析,我們可以得出優化重複錯誤處理的大概套路:通過建立新的型別來封裝原本幹髒活累活的舊型別,同時在新型別中封裝 error,新舊型別的方法簽名可以保持相容,也可以不相容,這個不是關鍵的,視客觀情況而定,至於具體的邏輯實現,先判斷有沒有 error,如果有就直接退出,如果沒有就繼續執行,並且在執行過程中儲存可能出現的 error 以便後面操作使用,最後通過統一呼叫新型別的 error 來完成錯誤處理。提醒一下,此方案的缺點是要到最後才能知道有沒有錯誤,好在如此的控制粒度在多數時候並無大礙。

Golang中重複錯誤處理的優化方法

golang 錯誤處理最讓人頭疼的問題就是 裡充斥著 if err nil 它們破壞了 的可讀性,本文收集了幾個例子,讓大家明白如何優化此類問題。讓我們看看 errors are values 中提到的乙個 io.writer 例子 err fd.write p0 a b if err nil er...

如何修復 WordPress 中的 HTTP 錯誤

剛剛在上傳影象時出現了乙個http錯誤,網上找了教程,似乎是php許可權的問題,修改了一下,問題解除。如果是在基於 wordpress 的網頁中上傳影象時出現錯誤,這也許是因為伺服器上 php 的配置,例如儲存空間不足或者其他配置問題造成的。用如下命令查詢 php 配置檔案 php i grep p...

E排序陣列中刪除重複項 golang

給定乙個排序陣列,你需要在原地刪除重複出現的元素,使得每個元素只出現一次,返回移除後陣列的新長度。不要使用額外的陣列空間,你必須在原地修改輸入陣列並在使用 o 1 額外空間的條件下完成。示例 給定 nums 0,0,1,1,1,2,2,3,3,4 函式應該返回新的長度 5,並且原陣列 nums 的前...