使用文件名作为输入
另一个常见错误是将文件名传递给函数。
假设我们必须实现一个函数来计算文件中的空行数。最自然的实现是这样的:
filename
作为输入给出,所以我们打开它然后我们实现我们的逻辑,对吧?
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
}
现在,假设我们希望在此函数之上实现单元测试,以使用普通文件,空文件,具有不同编码类型的文件等进行测试。很容易变得非常难以管理。
此外,如果我们想要实现相同的逻辑但是对于HTTP主体,例如,我们将不得不为此创建另一个函数。
Go有两个很棒的抽象:io.Reader
和io.Writer
。相反,通过一个文件名,我们可以简单地传递一个io.Reader
作为抽象的数据源。
它是文件吗?一个HTTP正文?字节缓冲区?这并不重要,因为我们仍然会使用相同的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++
}
}
}
现在,打开文件本身的责任委托给count
客户:
func ReadFile() {
filename := os.Getenv("fileExample")
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))
}
使用第二种实现,无论实际数据源如何,都可以调用该函数。同时,它将促进我们的单元测试,因为我们可以简单地创建一个bufio.Reader
来自string
:
count, err := count(bufio.NewReader(strings.NewReader("input")))
翻译自:https://medium.com/swlh/the-most-common-mistakes-with-read-file-in-golang-be87239fd03b