导读:如果什么编程语言最直接,大概就是Go语言。
嘿,哥们儿!你知道什么东西编译只需要两秒,部署后就是一个单独的二进制文件,而且就算凌晨三点 npm 上的某个传递依赖被移除也不会崩溃吗?这就是 Go语言。
就像HTML 从互联网诞生之初就一直在那里等着,你别再把前端搞得那么复杂一样,Go 也已经在那里呆了十多年,等着你别再把后端搞得那么复杂了。
不,你居然为了运行一个破表单,把十五个 Node 包、三个 TypeScript 构建工具和一个 Kubernetes 集群拼凑在一起。你雇了个平台团队来照看你的 Rails 单体应用。你还说服了你的 CTO,说一个每秒可能只处理四十个请求的 CRUD 应用必须用 Rust。
恭喜你,混蛋 ——“你搬起石头砸了自己的脚”。
你知道为什么 Go 语言感觉很无聊吗?因为它本来就很无聊,而这正是它的精髓所在。没有装饰器,没有元类,没有宏,没有 trait、monad,也没有 Haskell 圈子这周又在吹捧的那些乱七八糟的抽象概念。只有结构体、函数、接口、goroutine 和通道。仅此而已。
你可以在午休时间读完Go规范,下午就能开始高效率地工作。
枯燥乏味意味着你上个月招的初级员工都能读明白两年前校长写的代码。代码格式只有一种,而且校长gofmt已经这么做了。你那位“聪明”的同事也无法偷偷地在代码库里塞进十七层的抽象层,因为语言本身就不允许。这就是当没人为自己的聪明才智而沾沾自喜时,项目交付的真实面貌。
别再瞎找框架了,你这个十足的笨蛋。在Go中,标准库就是框架。
package main
import (
"embed"
"html/template"
"net/http"
)
//go:embed templates/*.html
var files embed.FS
var tmpl = template.Must(template.ParseFS(files, "templates/*.html"))
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "index.html", map[string]string{
"Name": "asshole",
})
})
http.ListenAndServe(":8080", nil)
}上面就是一个可以正常运行的 Web 应用。
HTML 模板编译成了二进制文件。无需 webpack,无需 Vite,无需“开发服务器”。node_modules体积能大到一辆大众汽车。你go build只需发布一个文件,把它放到服务器上,就搞定了。
想要数据库?database/sqlJSON?encoding/json?想要连接其他服务?net/http它也是客户端。想要同时做五件事?把它放在go前面。测试?go test基准测试?性能分析?go test -bench它pprof已经在那里嘲笑你的console.log 调试过程了。
io.Reader和io.Writer两个接口各有一个方法。也正是因为它们,你才能毫不费力地用三行代码将 HTTP 响应体传递给 gzip 写入器,然后写入磁盘上的文件。生态系统中所有重要的软件包都支持这两个接口。一旦你理解了这一点,就会发现 Go 一半的“魔法”其实都源于这两个接口的普遍存在。
context.Context这就是取消操作的方式。用户关闭浏览器标签页,请求上下文取消,数据库查询取消,下游 HTTP 调用也取消。所有操作都会被取消。不会出现 goroutine 泄漏,也不会有僵尸查询占用连接池。你只需将其作为第一个参数传递,并严格遵守即可。这就是整个 API 的工作原理。
encoding/json,,,,都在标准库中。相同的结构体标签模式。相同的解码为指针的操作方式。学会一个,基本上就掌握了所有encoding/xml,还有encoding/csvencoding/binary
Goroutine 不是线程。它们是栈式的,由运行时复用到操作系统线程中,启动时大约只需要 2KB 的空间。你可以在一台笔记本电脑上启动十万个 Goroutine。试试用你的 Node 事件循环来做同样的事情,看看它会不会崩溃。
通道是 goroutine 之间的类型化管道。你在一端发送数据,在另一端接收数据,运行时会处理同步。如果你需要共享状态,sync.Mutex它就在那里,竞争检测器会在你出错时发出警报。
results := make(chan string, len(urls))
for _, url := range urls {
go func(u string) {
resp, _ := http.Get(u)
results <- resp.Status
}(url)
}
for range urls {
fmt.Println(<-results)
}这是一个并行 HTTP 爬虫。无需任何库、框架或 async/await 机制。Go语言本身就能实现。
这是一个从Postgres读取数据并渲染HTML的CRUD路由,全部代码内容。
//go:embed templates/*.html
var tmplFS embed.FS
var tmpl = template.Must(template.ParseFS(tmplFS, "templates/*.html"))
type Post struct {
ID int
Title string
Body string
}
func postsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rows, err := db.QueryContext(r.Context(),
"SELECT id, title, body FROM posts ORDER BY id DESC LIMIT 50")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
var posts []Post
for rows.Next() {
var p Post
if err := rows.Scan(&p.ID, &p.Title, &p.Body); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
posts = append(posts, p)
}
tmpl.ExecuteTemplate(w, "posts.html", posts)
}
}数据库、模板和 HTTP 处理程序都集成在一个界面中。请求上下文直接与查询关联,因此连接关闭时 SQL 查询也会被取消。没有 ORM,没有依赖注入容器,没有服务层,也没有controllers/包含十七个抽象基类的目录。你可以从上到下阅读代码,完全了解它的功能。
go mod init完成。你的依赖项位于 `go.mod和`go.sum。`node_modules
想要离线构建?go mod vendor只需把所有文件放到一个vendor/ 目录里,工具链就会自动使用它。整个项目,包括所有依赖项,都能打包成一个 tar 包。你的安全团队肯定会“感激涕零”的。
gofmt格式化你的代码。这没什么.prettierrc 好争论的,也没什么争论的。格式就是格式,大家都用它。你的代码差异很小,因为没人会去重新排列空格。
go vet能够发现显而易见的错误。go test运行你的测试。go test -race使用竞争检测器运行测试,并找出你以为不存在的数据竞争。go test -bench运行基准测试。go test -cover 告诉你遗漏了什么。go tool pprof通过你只需两行代码即可配置的 HTTP 端点,为你提供正在运行的生产服务的 CPU 和内存使用情况火焰图。
这些都不是第三方软件,都不是插件,也不需要你维护任何配置文件。它们都内置在产品里了。
这部分内容会让 Rails 和 Node 的用户感到非常恼火。你编译一个 Go 二进制文件,把它复制到服务器上,然后运行它。
GOOS=linux GOARCH=amd64 go build -o myapp ./cmd/myapp
scp myapp user@server:/usr/local/bin/
ssh user@server 'systemctl restart myapp'三条命令,搞定。无需 Dockerfile,无需多阶段构建,无需每周二接收基础镜像 CVE 警报,无需 Kubernetes 清单,无需 Helm Chart,无需 ArgoCD,无需服务网格,无需 Sidecar。
一个 12MB 的静态链接二进制文件和一个 20 行的 systemd 单元文件就能完成生产部署。它的寿命甚至比你的职业生涯还要长。使用 Docker 的唯一理由是你的运维团队在合同中被强制要求使用它,即便如此,你也可以直接把二进制文件打包成FROM scratch镜像,然后就万事大吉了。
那它们呢?Rails 需要一套部署流程,包括 Capistrano、三个配置文件和一个山羊(Goat)。Django 希望你学习它的 ORM、它的管理后台、它的中间件系统,以及它对所有事情的看法。Express 靠npm audit警告和祈祷勉强维持运转。Next.js 每六个月就改一次路由规则,还故意刁难你。
你的 Go 二进制文件不在乎这些。它编译通过,运行正常,即使五年后在尚未出现的硬件上也能继续运行。你的框架呢?圣诞节前就被弃用了,维护者会在 Medium 上发帖抱怨自己精疲力竭。
来,我们来写个单体应用。一个 Go 二进制文件。一个 Postgres 数据库。如果实在不行,再加一个 Redis。HTML 和 JSON API 都用同一个端口。运行在一台 VPS 上,价格比你一个月的燕麦奶预算还低。轻松扩展到每秒一万个请求,因为 Go 就是为此而生的,而且 goroutine 成本低得可怜。
当你真正需要拆分代码时(但你不会这么做),拆分 Go 单体应用实际上只是将包移动到各自的仓库中。接口已经存在。你无需刻意设计就能做到这一点,因为语言本身就赋予了你这种特性。
if err != nil这才是它的优点,而不是缺点。它迫使你检查每一个可能出错的地方,并决定如何解决。你那层层嵌套的 try/catch 语句并不能让错误消失,它只是把错误隐藏起来,直到凌晨两点生产环境才会暴露出来。
通用物品已在 1.18 版本上线。它们很好用。需要的时候就用吧。别抱怨了。
别再假装你需要框架了。你也不需要微服务,不需要 Rust 重写,也不需要上周二发布的那个什么 JavaScript 元框架,别指望它能拯救你,而之前的六个框架都没能做到。
打开编辑器。运行go mod init,编写代码main.go,嵌入模板,然后编译,发布出去。
“平庸的选择”才是正确的选择,一直都是如此。
作者:手扶拖拉斯基
本篇文章为 @ 场长 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 微信公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。
请扫描二维码,使用微信支付哦。