前言

这是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.Readerio.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-...