前言
這是Go十大常見錯誤系列的第9篇:使用檔名稱作為函式輸入。素材來源於Go佈道者,現Docker公司資深工程師Teiva Harsanyi。
本文涉及的原始碼全部開源在:Go十大常見錯誤原始碼,歡迎大家關注公眾號,及時獲取本系列最新更新。
問題場景
一個常見錯誤是把檔名作為函式引數,在函式里讀取檔案內容。
比如,我們要實現一個函式,用來統計指定檔案裡有多少空行,很多人最容易聯想到的實現方式如下:
func count(filename string) (int, error) {
file, err := os.Open(filename)
if err != nil {
return 0, errors.Wrapf(err, "unable to open %s", filename)
}
defer file.Close()
scanner := bufio.NewScanner(file)
count := 0
for scanner.Scan() {
if scanner.Text() == "" {
count++
}
}
return count, nil
}
這段程式碼邏輯很簡單:
- 檔名作為函式入參
- 函式里讀取檔案每一行資料,判斷是否為空行,如果是空行就計數+1
這種方式其實也沒大問題,比較好理解。只是可擴充套件性不強,沒有充分利用到Go語言裡關於資料讀寫的介面(interface)型別的優勢。
試想下,如果你想對一個HTTP body裡的內容實現相同的邏輯,那上面的程式碼無法支援,要另外實現一個新的函式。
解決方案
Go語言裡有2個很好的抽象介面(interface),分別是io.Reader
和io.Writer
。
和上面函式傳參使用檔名不一樣,我們可以使用io.Reader
作為函式的引數型別。
因為檔案、HTTP body、bytes.Buffer都實現了io.Reader
,所以
- 使用
io.Reader
作為函式引數可以相容不同型別的資料來源。 - 不同的資料來源,可以統一使用
io.Reader
型別裡的Read
方法來讀取資料。
具體到這個例子裡,我們可以使用bufio.Reader
和其ReadLine
方法,程式碼如下所示:
func count(reader *bufio.Reader) (int, error) {
count := 0
for {
line, _, err := reader.ReadLine()
if err != nil {
switch err {
default:
return 0, errors.Wrapf(err, "unable to read")
case io.EOF:
return count, nil
}
}
if len(line) == 0 {
count++
}
}
}
err為io.EOF
,就表示讀到了空行。
EOF is the error returned by Read when no more input is available. (Read must return EOF itself, not an error wrapping EOF, because callers will test for EOF using ==.) Functions should return EOF only to signal a graceful end of input. If the EOF occurs unexpectedly in a structured data stream, the appropriate error is either ErrUnexpectedEOF or some other error giving more detail.
有了上面的count
函式,我們就可以使用如下的方式開啟檔案,計算檔案裡空行的數量。
file, err := os.Open(filename)
if err != nil {
return errors.Wrapf(err, "unable to open %s", filename)
}
defer file.Close()
count, err := count(bufio.NewReader(file))
這種實現方式可以讓我們在計算邏輯裡不需要關心真正的資料來源。同時,也可以方便我們做單元測試。
比如下面的例子,我們直接把字串作為輸入,來測試上面實現的count
函式。
count, err := count(bufio.NewReader(strings.NewReader("input")))
推薦閱讀
- Go十大常見錯誤第1篇:未知列舉值
- Go十大常見錯誤第2篇:benchmark效能測試的坑
- Go十大常見錯誤第3篇:go指標的效能問題和記憶體逃逸
- Go十大常見錯誤第4篇:break操作的注意事項
- Go十大常見錯誤第5篇:Go語言Error管理
- Go十大常見錯誤第6篇:slice初始化常犯的錯誤
- Go十大常見錯誤第7篇:不使用-race選項做併發競爭檢測
- Go十大常見錯誤第8篇:併發程式設計中Context使用常見錯誤
- Go面試題系列,看看你會幾題?
開源地址
文章和示例程式碼開源在GitHub: Go語言初級、中級和高階教程。
公眾號:coding進階。關注公眾號可以獲取最新Go面試題和技術棧。
個人網站:Jincheng's Blog。
知乎:無忌。
福利
我為大家整理了一份後端開發學習資料禮包,包含程式語言入門到進階知識(Go、C++、Python)、後端開發技術棧、面試題等。
關注公眾號「coding進階」,傳送訊息 backend 領取資料禮包,這份資料會不定期更新,加入我覺得有價值的資料。還可以傳送訊息「進群」,和同行一起交流學習,答疑解惑。