我是一只可爱的土拨鼠,专注于分享 Go 职场、招聘和求职,解 Gopher 之忧!欢迎关注我。
欢迎大家加入Go招聘交流群,来这里找志同道合的小伙伴!跟土拨鼠们一起交流学习。
本文由小土翻译自 What I'd like to see in Go 2.0[1],翻译不当之处,烦请指出。
阅读完文章,如果你也有想在Go2中看到的特性,欢迎留言讨论。
目录
- 关于作者
- 现代模板引擎
- 改进`range`,以免Copy值
- 确定性的`select`
- 结构化日志记录接口
- 多错误处理
- 针对 JSON Marshal的error
- 标准库中不再有公共变量
- 对缓冲渲染器的本地支持
- 结束语
Go是我最喜欢的编程语言之一,但它仍然远非完美。在过去的10年里,我用Go来构建小型的辅助项目和大型的应用程序。虽然这门语言与2009年最初发布时相比已经有了很大的发展,但这篇文章强调了我认为Go仍有改进空间的一些领域。
在我们开始之前,我想明确一点:我不是在批评个人或他们的贡献。我唯一的意图是努力使Go成为最好的编程语言。
关于作者
Seth Vargo是谷歌的工程师。此前他曾在HashiCorp、Chef Software、CustomInk和一些匹兹堡的创业公司工作。他是《Learning Chef[2]》一书的作者,热衷于减少技术上的不平等。当他没有写作、从事开源工作、教学或在会议上发言时,Seth 喜欢与他的朋友共度时光并为非营利组织提供建议。
现代模板引擎
Go 标准库有两个模板包:`text/template`[3]和`html/template`[4].它们使用大致相同的语法,但html/template
处理实体转义和其他一些特定于 Web 的结构。不幸的是,这两个软件包都不适合或不够强大,在没有大量开发人员投资的情况下,可以满足足够高级的使用情况。
- 编译时错误。与 Go 本身不同,Go 模板包会很乐意让您将整数作为字符串传递,但会在运行时呈现错误。这意味着开发人员需要严格测试其模板中所有可能的输入,而不是能够依赖类型系统。Go 的模板包应该支持编译时类型检查。
- 与 Go 匹配
range
子句。 我仍然把Go模板中的范围子句的顺序弄得一团糟,因为它有时与Go本身的顺序是相反的。对于两个参数,模板引擎与标准库相匹配。
但是,只有一个参数,模板引擎产生的是值,而 Go 渲染产生的是索引:
Go 的模板包应该符合标准库的工作方式。
- 开箱即用(Batteries included),反射是可选的。作为一般规则,我认为大多数开发人员不应该需要与反射进行交互。但是,如果你想做任何基本的加法和减法之外的事情,Go的模板包将迫使你使用反射。内置函数非常小,而且只能满足一小部分用例。
在我写完Consul Template[5]之后,很明显,标准的Go模板功能不足以满足用户的需求。超过一半的问题是关于尝试使用Go的模板语言。今天,Consul Template有超过[超过50个"辅助"函数](https://github.com/hashicorp/consul-template/blob/master/docs/templating-language.md "超过50个"辅助"函数"),其中绝大部分确实应该在标准模板语言中使用。
Consul Template在这里并不孤单。Hugo[6]还有一个相当广泛的辅助函数列表[7],同样,其中绝大多数应该真正使用标准模板语言。即使在我最近的项目中,Exposure Notification[8]我们也无法逃脱反射[9].
Go的模板语言确实需要具有更广泛的函数表面积。 - 短路评估。
编辑: 正如许多人所指出的那样,这个特性将出现在Go 1.18[10].
Go的模板语言总是在子句中评估整个条件,这会产生一些非常有趣的bug(直到运行时才会再次出现)。请考虑以下情况,其中$foo
可能为零:
看起来这似乎很好,但是将评估这两个条件 - 表达式中没有短路逻辑。这意味着$foo
如果为 nil,这将引发运行时异常。
要解决此问题,你必须分离条件子句:
Go的模板语言应该像标准库一样工作,在第一个真值时停止执行条件。
- 对特定web工具的投资。我当
Ruby on Rails
开发人员已经很多年了,我真的很喜欢建立漂亮的网络应用程序是如此简单。使用Go的模板语言,即使是最简单的任务 - 例如将一个项目列表打印成一个句子 - 对于初学者来说也是难以企及的,特别是与Rails的Enumerable#to_sentence
的相比。
改进range
,以免Copy值
虽然它有很好的文档记录,但总是意外地复制范围子句中的值。例如,请思考以下代码:
cp
的价值是什么?如果你说[A B C]
,可悲的是你是错误的。而实际上是:
这是因为 Go 在子句中使用了值的副本,而不是值本身。在 Go 2.0 中,Range
子句应通过引用传递值。在这个领域已经有一些关于Go 2.0的建议,包括改善 for-loop 人体工程学设计[11]和在每次迭代中重新定义范围循环变量[12],所以我对此抱有谨慎的希望。
确定性的select
在select语句的多个条件为真的情况下,会通过统一的伪随机来选择case的[13].这是一个非常微妙的错误来源,并且它被看起来外观相似的switch
语句而加剧,该语句确实按其编写的顺序进行评估。
考虑一下下面的代码,我们希望它的行为是 "如果系统停止了,什么都不做。否则等待新的工作,最多5秒,然后超时"。:
如果在输入语句时满足多个条件(例如 doneCh
已关闭且已超过 5 秒),那么哪个路径将被执行是不确定的行为。这使得编写正确的取消代码变得恼人的冗长:
如果select被更新为确定性的,原来的代码(在我看来,它更简单,更容易达到)将按原定计划工作。然而,由于select的非确定性,我们必须不断检查主导条件。
与此相关的是,我很想看到一种 "如果这个通道包含任何消息,就从这个通道读取,否则继续 "的速记语法。目前的语法是冗长的。
我很想看到这个检查的更简洁的版本,也许是这样的语法:
结构化日志记录接口
Go 的标准库包括`log`[14]包,这对于基本用途来说是不错的。但是,大多数生产系统都希望进行结构化日志记录,Go 中不乏结构化日志库[15]:
- apex/log[16]
- go-kit/log[17]
- golang/glog[18]
- hashicorp/go-hclog[19]
- inconshreveable/log15[20]
- rs/zerolog[21]
- sirupsen/logrus[22]
- uber/zap[23]
Go在这一领域缺乏主见,导致了这些包的泛滥,其中大部分都有不兼容的函数和签名。因此,一个库的作者不可能发出结构化的日志。例如,我希望能够在 go-retry[24]、 go-envconfig[25]、或 go-githubactions[26]中发射结构化日志,但这样做需要与这些库中的一个紧密耦合。理想情况下,我希望我的库的用户可以选择他们的结构化日志解决方案,但由于缺乏一个通用的结构化日志接口,这一点非常困难。
Go 标准库需要定义一个结构化的日志接口, 所有这些现有的上游包都可以选择实现该接口。然后,作为库作者,我可以选择接受一个接口log.StructuredLogger
,实现者可以做出自己的选择:
我把这样一个接口的草图快速勾勒了出来。
围绕着实际的接口会是什么样子,如何最小化分配,以及如何最大化兼容性,有很多讨论,但目标是定义一个其他日志库可以轻松实现的接口。
在我的Ruby时代,有大量的Ruby版本管理器,每个都有自己的dotfile名称和语法。Fletcher Nichol设法说服了这些Ruby版本管理器的所有维护者,使其标准化为.ruby-version,只需写一个gist[27]。我希望我们能在Go 社区中使用结构化日志记录做类似的事情。
多错误处理
有很多情况,特别是对于后台工作或周期性任务,系统可能会并行处理一些事情或在出现错误时继续处理。在这些情况下,返回一个多重错误是有帮助的。在标准库中没有对处理错误集合的内置支持。
围绕多错误处理有清晰简洁的标准库定义,可以统一社区,减少错误处理不当的风险,正如我们看到的错误包装(wrap)和解包(unwrap)。
针对 JSON Marshal的error
说到error,你知道吗,将error类型嵌入到一个结构字段中,然后将该结构作为JSON进行marshal
,将 "error"字段marshal
为{}?
至少对于内置的errorString
类型,Go应该为.Error()的结果进行marshal
。另外,对于Go 2.0来说,当试图marshal
一个没有实现自定义marshal
逻辑的error类型时,JSON marshal
会返回一个错误。
标准库中不再有公共变量
仅举一个例子,http.DefaultClient
和http.DefaultTransport
都是具有共享状态的全局变量。http.DefaultClient
没有配置超时,这使得DOS你自己的服务和创造瓶颈变得很容易。许多软件包都会突变,http.DefaultClient
和http.DefaultTransport
,这可能会浪费开发者数天的资源来追踪错误。
Go 2.0应该把这些东西变成私有的,并通过一个函数调用来公开它们,返回有关变量的唯一分配。另外,Go 2.0还可以实现 "冻结 "的全局变量,这样它们就不能被其他包所改变。
从软件供应链的角度来看,我也担心这类问题。如果我可以开发一个有用的包,秘密地修改http.DefaultTransport
,使用一个自定义的RoundTripper
,将你的所有流量通过我的服务器输送出去,那将是一个非常糟糕的瞬间。
对缓冲渲染器的本地支持
这更像是一个"不为人所知或有据可查的事情"。大多数示例(包括 Go 文档中的示例)都鼓励执行以下操作,以便通过 Web 请求对 JSON 进行marshal或渲染 HTML:
然而,对于这两种情况,如果i
足够大,有可能在发送第一个字节(和200状态码)后,编码/执行失败。在这一点上,请求是无法恢复的,因为你无法改变响应代码。
大体上被接受的缓解方案是先渲染,然后复制到w。这仍然会有很小的出错空间(由于连接问题导致向w写入失败),但它确保在发送第一个字节之前,编码/执行是成功的。然而,在每个请求中分配一个字节片是很昂贵的,所以你通常会使用缓冲池[28]。
这种方法非常冗长,并将许多不必要的复杂性推给实现者。相反,如果 Go能自动处理此缓冲池管理,可能会使用EncodePooled
等函数,那就更好了。
结束语
Go仍然是我最喜欢的编程语言之一,这就是为什么我觉得可以强调这些批评的原因。与任何编程语言一样,Go也在不断发展。你认为这些是好主意吗?还是说它们是糟糕的建议?请在Twitter[29]上告诉我。
参考资料
[1]
What I'd like to see in Go 2.0: https://www.sethvargo.com/what-id-like-to-see-in-go-2
[2]
Learning Chef: https://www.amazon.com/Learning-Chef-Configuration-Management-Automation/dp/1491944935
[3]
text/template
: https://pkg.go.dev/text/template
[4]
html/template
: https://pkg.go.dev/html/template
[5]
Consul Template: https://github.com/hashicorp/consul-template
[6]
Hugo: https://gohugo.io/
[7]
相当广泛的辅助函数列表: https://gohugo.io/functions/
[8]
Exposure Notification: https://g.co/ens
[9]
无法逃脱反射: https://github.com/google/exposure-notifications-verification-server/blob/0ec489ba95137d5be10e1617d1dcdc2d1ee6e5e9/pkg/render/renderer.go#L232-L280
[10]
Go 1.18: https://tip.golang.org/doc/go1.18#text/template
[11]
改善 for-loop 人体工程学设计: https://github.com/golang/go/issues/24282
[12]
在每次迭代中重新定义范围循环变量: https://github.com/golang/go/issues/20733
[13]
会通过统一的伪随机来选择case的: https://golang.org/ref/spec#Select_statements
[14]
log
: https://pkg.go.dev/log
[15]
Go 中不乏结构化日志库: https://www.client9.com/logging-packages-in-golang/
[16]
apex/log: https://github.com/apex/log
[17]
go-kit/log: https://github.com/go-kit/kit/tree/master/log
[18]
golang/glog: https://github.com/golang/glog
[19]
hashicorp/go-hclog: https://github.com/hashicorp/go-hclog
[20]
inconshreveable/log15: https://github.com/inconshreveable/log15
[21]
rs/zerolog: https://github.com/rs/zerolog
[22]
sirupsen/logrus: https://github.com/sirupsen/logrus
[23]
uber/zap: https://github.com/uber-go/zap
[24]
go-retry: https://github.com/sethvargo/go-retry
[25]
go-envconfig: https://github.com/sethvargo/go-envconfig
[26]
go-githubactions: https://github.com/sethvargo/go-githubactions
[27]
gist: https://gist.github.com/fnichol/1912050
[28]
使用缓冲池: https://github.com/google/exposure-notifications-verification-server/blob/08797939a56463fe85f0d1b7325374821ee31448/pkg/render/html.go#L65-L91
[29]
Twitter: https://twitter.com/sethvargo
欢迎关注Go招聘公众号,获取Go专题、大厂内推、面经、简历、股文等相关资料可回复和点击导航查阅。