Go 是我一直很想学习但从未掌握的语言之一。我第一次对这门语言感兴趣是在 2016-2018 年我从事第一份工作的时候。当时这门语言完全是一头野兽:没有模块,没有泛型,还没有简单的错误包装方法等等,关键是一个不留神还会 painc。
在 2023 年,我用 Go 编写了我的第一个项目,这个项目没有使用任何框架,也没有引入任何第三方 lib 库。
我喜欢 Go 的几点对一些程序员来说来说可能很明显,但无论如何还是值得一谈。自从我第一次听说该语言以来,我就很欣赏该语言默认生成静态二进制文件并且编译时间很快,现在我经常使用该语言,我更加欣赏这一点。在更改一行代码并运行后 go run 几乎可以立即获得反馈,这对开发人员的体验非常有益。以前我遇到一些自动化相关的问题时,我首先想到是使用 Python 来达到自动化,现在基本都是使用 Go。
然后 Go 有快速的启动时间。我对延迟有点敏感,尤其是命令行实用程序,当我期望它们快速响应时,这是我在 Python 中可能遇到问题的一部分,尤其是对于更复杂的程序,但在 Go 中,这很少成为问题。
模块也很棒。它并非没有怪异之处(就像 Go 生态系统中的所有东西一样),但事实上,仅使用 CLI 就可以如此轻松地在项目中添加和管理依赖项,这一点 Go 很棒。
自从我开始谈论 go CLI 以来,这真是一个很棒的工具!事实上,你可以管理依赖项、生成文档、格式化代码、lint、运行测试/基准测试/模糊测试、检查代码是否存在竞态条件等,所有这些都只需使用该语言的“编译器”即可,这真是太棒了。这仍然可能是我所知道的任何编程语言中最好的开发人员体验之一(也许只有 Zig 可以与之媲美)。
我甚至不会谈论每个人都在谈论的有关 Go 的东西,比如 Goroutines,因为我认为我无法为这个话题添加任何有趣的内容。
现在说说我不太喜欢的部分,测试部分仍然让我感到奇怪,因为它不是基于断言,但值得庆幸的是,现在用泛型编写断言很容易:
func Equal[T comparable](t *testing.T, got, want T) { t.Helper() if got != want { t.Errorf("got: %#v, want: %#v", got, want) }}func GreaterOrEqual[T cmp.Ordered](t *testing.T, actual, expected T) { t.Helper() if actual < expected { t.Errorf("got: %v; want: >=%v", actual, expected) }}// etc...
这只是我在每个项目中都会重写的东西之一。是的,我知道 testify 和其他断言库,但引用 Rob Pike 的话,“一点复制比一点依赖要好”。只要你写的代码很简单,复制代码比尝试导入依赖项要好。
关于泛型允许我编写的另一段代码,并且我在每个项目中最终都会重写它是 must*函数系列:
func must(err error) { if err != nil { panic(err) }}func must1[T any](v T, err error) T { must(err) return v}func must2[T1 any, T2 any](v1 T1, v2 T2, err error) T { must(err) return v1, v2}// must3, must4, etc...
这些函数非常有用,特别是对于我通常不想处理每个错误的脚本:如果我遇到错误,我希望程序停止并打印堆栈跟踪(就像我使用带有异常的语言一样)。它基本上允许我将代码从:
contents, err := os.ReadFile("file")if err != nil { panic(err)}
到:
contents := must1(os.ReadFile("file"))
在我看来,这使 Go 更接近 Python,并且我认为对于脚本来说,这是一件很棒的事情。
最后,说到我讨厌的事情,目前最大的问题是缺乏可空性。在使用具有此功能的语言(如 Kotlin)或甚至类似 mypy 的东西之后,这是完全改变开发人员体验的事情之一。我仍然不喜欢错误处理(但 must*在可以使用时,它可以大大改善情况),特别是因为它很容易丢失上下文
// badfunc readFileContents(file) ([]byte, error) { contents, err := os.ReadFile(file) if err != nil { return nil, err } return contents, nil}// goodfunc readFileContents(file) ([]byte, error) { contents, err := os.ReadFile(file) if err != nil { return nil, fmt.Errorf("readFileContents: error while reading a file: %w", err) } return contents, nil}
对于一种本该简单明了的语言来说,它所依赖的魔法却如此之多,这真是奇怪,比如 internal 和 main 包、名称大写以表示可见性(privatevs Public)有时使用它的 JSON 序列化打印时,无论怎样都无法输出,最后发现是字段名称没有大写导致的、通过文件名进行条件编译(例如:foo_amd64.go)bar_linux.go、魔法注释(例如://go:build)等。
我希望以后能写更多 Go 代码。不是因为它是一门完美的语言,而是因为它具有一些简单的特点,即使我遇到一些问题,它仍然很有吸引力。这让它成为一门相当不错的语言,至少对我来说,这已经足够好了。
随手关注和在看,诚挚感谢!!