为了账号安全,请及时绑定邮箱和手机立即绑定

是否应该在包名称和变量中避免阴影?

是否应该在包名称和变量中避免阴影?

Go
慕无忌1623718 2022-10-10 15:48:02
测试代码中有很多示例隐藏了测试库(如 testify)中的包名称。例如:import (  "testing"  "github.com/stretchr/testify/assert"  "github.com/stretchr/testify/require")func TestSomething(t *testing.T) {  assert := assert.New(t)  require := require.New(t)  var a string = "Hello"  var b string = "Hello"  assert.Equal(a, b, "The two words should be the same.")}用局部变量覆盖包命名空间的值不应该是一个明确的决定并且很少发生吗?import (  "testing"  "github.com/stretchr/testify/assert"  "github.com/stretchr/testify/require")func TestSomething(t *testing.T) {  assertions := assert.New(t)  req := require.New(t)  var a string = "Hello"  var b string = "Hello"  assert.Equal(a, b, "The two words should be the same.")}我希望正确的行为是给包起别名pkgassert,我觉得这不是惯用的,或者确保变量没有设置为与包相同的名称:assertions := assert.New(t).其他示例:好的:buf := bytes.Buffer{}坏的:bytes := bytes.Buffer{}注意:我不关注变量命名模式,因为 Go 倾向于推荐单字母或在您可能使用缓冲区的狭窄范围内的短变量。假设由于函数长度,较长的名称是可以接受和需要的。如果这是测试包中所需的行为,我想知道为什么?它似乎违反了“无魔法”和可读性这一惯用 Go 的关键原则。如果有由此产生的不良副作用,我也想知道这一点。我的假设是这意味着在导入后,由于变量阴影,对同一范围内的包的任何调用都将不起作用编辑Go 中的 Scope Shadowing是一本不错的读物,并在使用方面给出了一些重要的观点。我从这个和 Stack 评论者那里得到的主要收获:它可以增强可读性。(我认为这可能是由于 Go 更喜欢对包进行小而简洁的命名,使 Go 中的包名称可变长度。)Parallel Test Issues是我记得不久前读过的东西。我不理解通过输入“隐藏”循环变量的建议: for _, tc := range tests {     tc := tc     t.Run(tc.name, func(t *testing.T) {         t.Parallel()         // Here you test tc.value against a test function.         // Let's use t.Log as our test function :-)         t.Log(tc.value)     }) }}随着对阴影的更好理解,我看到这个“隐藏”是指通过在循环内分配来隐藏 tc 的调用者范围变量,从而进行阴影。这似乎是一个很好的用例,尽管我仍然觉得它很“聪明”,而不是没有评论,并且违反了惯用的 Go 目标,即清晰易读,没有“魔法”。
查看完整描述

2 回答

?
呼如林

TA贡献1798条经验 获得超3个赞

在查看了我所看到的所有反馈和写作之后,我将在这里总结调查结果。

如果您在看到来自 Goland 之类的工具的 Go linting 警报后正在调查此行为,希望此答案能同时为您节省时间并明确表示它是可以的、惯用的,并且只有在初始化后重用实际包调用的某些情况下才会出现任何问题有经验。

使用局部变量隐藏包名称

  • 这是一个见仁见智的问题,没有官方声明表明这被认为是非惯用代码。

  • 意见似乎总体上倾向于避免阴影,除非为了清晰和可读性而故意这样做,但这又是意见,而不是要求。

  • 一旦被覆盖,该包将在该范围内不可用,因为包名称已被局部变量隐藏。

  • 我观察到这种行为在 Go 中似乎比某些语言更有可能,因为 Go 提示的包名称很短,不是多字,而且易于输入。assert := assert.New(t)当这种情况发生时,这似乎是不可避免的。

  • 在此类问题上,始终如一的一致性是关键。如果团队决定永远不隐藏包名称,则assertions := assert.New(t)可以使用,或者如前所述,包可以使用别名,例如import tassert "github.com/stretchr/testify/assert". 这是一个意见标准,无论哪种方式都是合法的并且被认为是惯用的。

  • 在测试的情况下,这通常是在不进一步使用初始变量集之后进一步使用包来完成的。下面的示例使用is is 包。包作者对此发表评论(同样这是固执己见,但与is := is.New(t).

package package_test


import (

    "testing"

    "package"

    

    iz "github.com/matryer/is"

)


func TestFuncName(t *testing.T) {

    is := iz.New(t)

    

    got := FuncName()

    want := ""

    

    is.Equal(got,want) // AssertMessage

}

影子行为可以解决 Goroutine 变量问题

Shadowing 是一种已知行为,并在Effective Go中有明确记录。

错误在于,在 Go for 循环中,循环变量在每次迭代中都被重用,因此 req 变量在所有 goroutine 之间共享。这不是我们想要的。

写起来可能看起来很奇怪,req := req但在 Go 中这样做是合法且惯用的。你会得到一个新版本的同名变量,故意在本地隐藏循环变量,但每个 goroutine 都是唯一的。

这也可以通过将变量作为参数传递到嵌入式 func 中来解决,从而简化代码。两者都是合法的,并且根据指南被认为是惯用的。

阴影测试

与这种 goroutine 行为相关,但在测试的上下文中,并行测试在 goroutine 中运行,因此也可以从这种模式中受益。

tc := tc 语句在并行测试示例中是必需的,因为闭包在 goroutine 中运行。更一般地说,在闭包的上下文中使用的 tc := tc 习语可以在下一次循环迭代开始后执行闭包 - 注释中的 @gopher

Be Careful With Table Driven Tests的要点在How To Solve This中也提到了这种行为。

检测

该检查go vet可以检查阴影,但被视为稳定包的一部分,并在此处GitHub 问题 29260GitHub 问题 34053相关的内容被删除。unsafeptr由于标志的要求,似乎出于兼容性目的提示了更改。其他评论似乎指出在go vet未经实验的情况下允许进入之前还需要更好的启发式方法。

go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
go vet -vettool=$(which shadow)

对于那些想要对此进行检查的人,golangci-lint确实可以选择在配置中配置阴影检查搜索check-shadowing该文本块的无书签`。


查看完整回答
反对 回复 2022-10-10
?
慕容森

TA贡献1853条经验 获得超18个赞

哇,那是糟糕的代码。虽然它可能不是编译器强制执行的规则,但如果我看到:

import "github.com/stretchr/testify/assert"

然后在那个文件中,就我而言,任何拼写的标识符assert都是从那个包的顶级导入,句号。任何以“聪明”或其他名义打破这种模式的人都是在自找麻烦。人们的肌肉记忆被训练来识别顶级进口,所以请不要做这样的事情。如果您想避免考虑新名称,只需删除一些字母:

asrt := assert.New(t)

或添加一个字母:

nAssert := assert.New(t)

或别名导入:

import tAssert "github.com/stretchr/testify/assert"

请不要遵循该代码中给出的糟糕示例。


查看完整回答
反对 回复 2022-10-10
  • 2 回答
  • 0 关注
  • 73 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信