Go十大常见错误第9篇:使用文件名称作为函数输入

2022年9月5日 392点热度 0人点赞 0条评论

前言

这是Go十大常见错误系列的第9篇:使用文件名称作为函数输入。素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi[1]

本文涉及的源代码全部开源在:Go十大常见错误源代码[2],欢迎大家关注公众号,及时获取本系列最新更新。

问题场景

一个常见错误是把文件名作为函数参数,在函数里读取文件内容。

比如,我们要实现一个函数,用来统计指定文件里有多少空行,很多人最容易联想到的实现方式如下:

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.Readerio.Writer

和上面函数传参使用文件名不一样,我们可以使用io.Reader作为函数的参数类型。

因为文件、HTTP body、bytes.Buffer[3]都实现了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")))

推荐阅读

开源地址

文章和示例代码开源在GitHub: Go语言初级、中级和高级教程[4]

公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。

个人网站:Jincheng's Blog[5]

知乎:无忌[6]

福利

我为大家整理了一份后端开发学习资料礼包,包含编程语言入门到进阶知识(Go、C++、Python)、后端开发技术栈、面试题等。

关注公众号「coding进阶」,发送消息 backend 领取资料礼包,这份资料会不定期更新,加入我觉得有价值的资料。还可以发送消息「进群」,和同行一起交流学习,答疑解惑。

References

  • https://itnext.io/the-top-10-most-common-mistakes-ive-seen-in-go-projects-4b79d4f6cd65

参考资料

[1]

Teiva Harsanyi: https://teivah.medium.com/

[2]

Go十大常见错误源代码: https://github.com/jincheng9/go-tutorial/tree/main/workspace/senior/p28

[3]

bytes.Buffer: https://pkg.go.dev/bytes#Buffer

[4]

Go语言初级、中级和高级教程: https://github.com/jincheng9/go-tutorial

[5]

Jincheng's Blog: https://jincheng9.github.io/

[6]

无忌: https://www.zhihu.com/people/thucuhkwuji

84220Go十大常见错误第9篇:使用文件名称作为函数输入

这个人很懒,什么都没留下

文章评论