14

我使用 Golang 两年后的总结:优点和令人讨厌的怪癖

 4 years ago
source link: https://www.infoq.cn/article/yDMrvVr1IJAAih3eh5fW
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

过去两年来,我就职于 Assembled ,一直使用 Go 工作。自公司成立以来,Go 就一直是我们的主要后端语言。在我之前的工作中,我混合着使用 Ruby 和 Scala,所以肯定有过一段适应期。总体而言,我发现 Go 的表现基本上与广告宣传的一样:尽管有些怪异,但它非常适合专业工作。

背景介绍

Assembled 是客户支持团队用来管理人员配备和分析的 Web 应用程序。从架构的角度来看,它是一个标准的 Web 应用程序:React 前端通过内部 HTTP API 访问 Go 后端,后者反过来访问 PostgreSQL 数据库。

也就是说,有几个特定的应用程序,我们也必须对其进行优化:

  • 我们维护一个外部 API
  • 我们从基于 Python 的机器学习服务中生成时间序列预测
  • 我们执行相当繁重的批处理,因为我们通过 SFTP 同步请求中心系统的调度。
  • 我们运行相当复杂的优化算法;我的兄弟 John 在 HN 评论 中对此进行了详细的分享。
  • 我们处理令人痛苦的时区和重复事件相关的逻辑

生产中的 Go

我们构建 Assembled,不使用 Python、Java、Haskell(或……),特意决定使用 Go。决定因素可归结为:静态类型、简单性、标准库和速度。在实践中,我所见过的一切都与 Go 在 文档 中所呈现的方式相吻合:

Go 富有 表现力、简洁、干净且高效 ……Go 可以 快速编译 成机器码,同时还具有垃圾收集的便利性及运行时反射的能力。这是一种 快速的、静态类型的编译 语言,但它感觉起来就像是一种动态类型的解释语言。

对新手工程师来说,它很乏味,但很简单

Go 的简单性使得新手工程师可以快速使用我们的代码库,他们中的许多人以前从未使用过该语言(包括我自己)。我怀疑 Go 的设计迫使我们编写比以前更简单、更明确的代码。也就是说,简单的确会导致重复,这一点已经得到了充分的证明。截至撰写本文时,代码片段 if err != nil 在我们的代码库中出现了 2919 次。

标准库可以满足需要

Go 的标准库是一座闪亮的“灯塔”。像 bytes、encoding/json、database/sql、net/http 和 time 这样的包是全面且经过精心设计的。例如,在 Python 中,它会告诉我们第三方库(显然,三方库是好的!)采用的是 HTTP 默认缺省值。

Go 的设计显然考虑了生产代码。最引人注目的是,Assembled 有一段时间存在严重的性能问题(敲打敲打),我们可以使用 net/pprof 和 runtime/pprof 进行调试。这些功能非常强大,并且易于通过 HTTP 处理程序启用,如下所示。我唯一的遗憾是,我找到的解释该输出的最佳指南被埋没在一篇 博客文章 里了。

复制代码

superAdminMux.HandleFunc("/debug/pprof/heap",pprof.Handler("heap").ServeHTTP)
superAdminMux.HandleFunc("/debug/pprof/profile",pprof.Profile)

快速、简单的构建影响一切

Go 最好的地方在于,我们可以轻松运行 go build,并且几乎无需等待,就可以可靠地期望一个可以运行的可执行文件。在没有 IntelliJ 或 Eclipse 的情况下, Java 仍然会使编译过程痛苦不堪,更不要说从 Ruby 或 Python 开始了。

快速、简单的构建体验使许多下游任务变得更容易:

  • 我们的部署命令本质上是 git pull,然后是 go install
  • 持续集成(CI),好吧……我们可以说 Go 也没有问题

主要的困难在于,采用文件监视和循环重建的方式进行本地开发。我们有多个构建目标(例如,应用程序后端和 API),并使用 https://github.com/cespare/reflex ,但这需要一些配合 工作才能很好地 在 Mac OS X 运行。

标准格式和文档

我是否提到过,Go 显然是为专业人士打造的?

  • gofmt 处理缩进和对齐;我使用 vim-go 插件,该插件会在我们保存.go 文件时自动应用
  • 这里有一个关于 Go 文档是如何不同的雄辩解释;我最喜欢的是标准外观,位于 https://godoc.org/ 公共存储库中,它能在本地运行并能快速提取项目特定代码的事实。

Gopher :heart:

如此可爱的化身。这里有篇关于 Go gopher 起源的有趣读物: https://blog.golang.org/gopher

remIvu6.png!web

痛点

Go 也有令人讨厌的怪癖。其中的很多已经在其他地方被很好地记录下来了,但出于完整性的考虑,我将它们包括进来。

没有官方的包管理者故事(直到最近都没有)

从 Go 1.14(2020 年 2 月)开始,Go 模块已经准备好投入生产使用了。在此之前,这里是“荒芜的西部”:我们登陆了 dep,但没有机会迁移到模块中。 dep 是(或曾经是)一项令人钦佩的工作,但它也非常缓慢。通常的建议是将依赖项签入到存储库中(例如,在 /vendor 文件夹中),这在生产环境设置中可能并不令人欣狂。

GOPATH 让人困惑

GOPATH 目录应该魔法地包含所有代码。我认为(基于 Wiki 的推测)这与简化从远程存储库中的获取有关,例如,go get github.com/my/repo。从理论上讲这很优雅,但在实践中却是令人困惑的,因为如果我们没有将代码放在正确的位置上,就会什么都行不通。因此,Go 给我留下了非常负面的第一印象。

现在,我工作计算机上的.profile 文件只有如下内容:

复制代码

exportGOPATH=$HOME/go
exportPATH="$GOPATH/bin:$PATH"
cd$GOPATH/src/github.com/assembledhq/assembled

错误很难自省

大多数人都抱怨 Go 的错误处理太过冗长,也很难使用。在 Go 1.13(2019 年 10 月)中,添加了用于包装、拆包和比较的 优秀方法 ,但不幸的是,采用率方面我们处于落后状态。

使用 1.13 版本之前编写的代码,不会对错误进行包装,这也特别痛苦。例如,在 Google 自己的 API bindings 中,请求的底层的 HTTP 错误不会被包装,因此无法作为 googleapi.RetrieveError、公共错误接口、甚至是低级的 url.Error 进行检查。唯一的选项是字符串匹配,我们这样做,通常是为了捕获类似 invalid_grant 这样的 OAuth 错误。

例如,比较一下 Scala 中 模式匹配 是如何进行 错误处理 的。

Nil versus zero values Nil 与零值

对空值或缺省后故意将其值设置为零的值进行建模,是很乏味的。例如,在 Stripe 的 Go bindings 中可以看到 这种迁移 。在我们的代码中,我们通常会返回一个指针和错误,例如(*string,error)。通过引入非关联 nil 指针的可能性来打破类型安全。我们可以检查 res == nil 以及 err != nil,但编译器不能把我们从健忘或懒惰中拯救出来。

缺乏面向对象的表现力

Go 建议使用接口和类型嵌入来复制有用的面向对象行为,这种行为在其他语言中是自然存在的。事实证明,这些工具具有很大的限制性,在不同的情况下,我们可以偶然绕过类型系统。这导致会诱惑。

Go 社区对此进行了详细介绍。这里有一个非常不错的总结: https://blog.golang.org/why-generics

结论

最初使用 Go,我花了一些时间来热身。正如我在 GOPATH 中所描述的那样,入门有些困惑。从诸如 Ruby 和 Scala 之类的语言转换过来,也意味着需要转变思维方式(或两种)。然而,在使用该语言的两年里,我开始真正享受它的简单和明确性了。

在 Assembled 公司,该语言确实非常适合我们的用例,这是一个主要的标准 Web 应用程序。我对这个生态系统寄予厚望,感觉就像我们正在使用经过精心设计和良好维护的工具一样。因此,在快速变更代码库的同时,提供稳定服务所需的基线工作量就更少了。

原文链接:

https://wgyn.github.io/2020/04/12/reflections-on-2-years-of-golang.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK