乐趣区

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

前言

这是 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-…
退出移动版