前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言中常见100问题-#46 Using a filename as a function input

Go语言中常见100问题-#46 Using a filename as a function input

作者头像
数据小冰
发布2022-08-15 15:17:54
2180
发布2022-08-15 15:17:54
举报
文章被收录于专栏:数据小冰数据小冰
使用文件名作为函数的入参

在我们需要实现一个函数功能是读取一个文件的时候,将文件名传递给函数不是一种最佳的实践,可能产生一些反作用,比如在单元测试起来困难。下面将深入讨论这个问题并掌握怎么处理它。

我们想实现一个函数用来统计文件中空行数。一种可能的实现如下,接收一个文件名作为入参,使用bufio.NewScanner扫描并检查文件中的每一行。

代码语言:javascript
复制
func countEmptyLinesInFile(filename string) (int, error) {
        file, err := os.Open(filename)
        if err != nil {
                return 0, err
        }
        // Handle file closure

        scanner := bufio.NewScanner(file)
        for scanner.Scan() {
                // ...
        }
}

我们打开了一个文件,然后使用bufio.NewScanner开始扫描每一行,默认情况下,bufio.NewScanner会将按行切分输入的内容。

这个函数正如我们期望的那样工作,只要提供的文件名是有效的,我们就能够从文件中读取到内容并返回文件中空行数,那有什么问题吗?

假设我们要实现单元测试覆盖这三种情况: 1. 正常的文件 2. 空文件 3. 文件只包含空行, 每种情况都需要创建一个文件进行测试。函数越复杂,需要越多的测试案例来覆盖,就会需要创建更多的文件。在某些情况下,我们甚至不得不创建几十个文件,这很快变得难以管理。

更进一步来说,上述代码是不可重用的。例如,如果我们需要实现相同的逻辑但是从HTTP request中统计空行数,我们将不得不重复主逻辑。

代码语言:javascript
复制
func countEmptyLinesInHTTPRequest(request http.Request) (int, error) {
        scanner := bufio.NewScanner(request.Body)
        // Copy the same logic
}

一种解决办法是将传递给函数的入参调整为*bufio.Scanner, 这样函数内部的逻辑就可以使用传入的scanner参数,两段代码逻辑是一致的。但是更地道的做法是传入一个reader接口。

下面对countEmptyLines进行重构,传入的参数为io.Reader,具体代码如下:

代码语言:javascript
复制
func countEmptyLines(reader io.Reader) (int, error) {
        scanner := bufio.NewScanner(reader)
        for scanner.Scan() {
                // ...
        }
}

由于bufio.NewScanner可以接收io.Reader,所以可以函数入参直接传给它。通过这里的调整,我们能够得到哪些收益呢?

其一 代码能够得到复用,因为入参是一个接口,所以它可以是一个文件,也可以是HTTP, socket等等,函数内部不需要关心传入的是啥,因为无论是*os.File还是http.Request中的Body都实现了io.Reader.

其二 上述代码在测试起来非常方便。现在countEmptyLines接收的是io.Reader,我们可以通过从字符串创建io.Reader来实现单元测试。

代码语言:javascript
复制
func TestCountEmptyLines(t *testing.T) {
        emptyLines, err := countEmptyLines(strings.NewReader(
                `foo
                        bar

                        baz
                        `))
        // Test logic
}

在上面的测试中,我们直接从字符串中使用strings.NewReader创建了一个io.Reader,不必为每个测试用例写一个文件。每个测试用例是独立的,提供了测试的可读性和可维护性。

在大多数情况下,接收文件名作为函数参数,从文件中读取的函数应被视为代码异味。正如上面所见,它使得单元测试更加复杂,因为我们可能需要创建多个文件。此外,它降低了函数的可复用性(尽管并非所有函数都是可以被重用的),使用io.Reader接口抽象数据源,无论输入的是文件、字符串、HTTP还是gRPC请求,都可以重用并轻松对代码进行测试。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据小冰 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用文件名作为函数的入参
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档