前言
这是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 领取资料礼包,这份资料会不定期更新,加入我觉得有价值的资料。还可以发送消息「进群」,和同行一起交流学习,答疑解惑。
References
- https://itnext.io/the-top-10-...