就用Go
摘要
一篇带有强烈观点的开发者文章倡导使用Go编程语言,强调其简洁的语法、强大的标准库、高效的并发模型以及单二进制部署,作为对过于复杂的现代技术栈的实用替代方案。
<p><a href="https://lobste.rs/s/znlkib/just_fucking_use_go">评论</a></p>
查看缓存全文
缓存时间: 2026/05/08 14:29
# 给我他妈用 Go - Blain Smith
来源:https://blainsmith.com/articles/just-fucking-use-go/
2026年5月8日
嘿,蠢货。你知道什么东西编译只要两秒、部署为单个二进制文件、而且不会因为凌晨三点 npm 上某个传递依赖被删就炸掉吗?Go。就像HTML从该死的互联网诞生之初就一直存在(https://justfuckingusehtml.com/)等着你停止把前端搞复杂一样,Go 也已经存在了十多年,等着你停止把后端搞复杂。
但你没有。你正在把十五个 Node 包、三个 TypeScript 构建工具和一个 Kubernetes 集群拼在一起,就为了服务一个他妈的表单。你雇了一个平台团队来保姆你的 Rails 单体应用。你说服你的 CTO 说,对于一个每秒可能只有四十个请求的 CRUD 应用,Rust 是必需的。恭喜你,混蛋。你把自己坑了。
## 这门语言故意很无聊
你知道为什么 Go 感觉无聊吗?因为它就是无聊,而这他妈就是重点。没有装饰器。没有元类。没有宏。没有特型、单子,或者 Haskell 这帮人这周正在吸食的任何被诅咒的抽象。有结构体、函数、接口、goroutine 和 channel。就这些。你可以在午餐休息时读完规范,并在当天下午就能干活。
无聊意味着你上个月招的初级员工能读懂首席两年写的代码。格式化只有一种方式,而 `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/sql`。JSON?`encoding/json`。想和另一个服务通信?`net/http` 也是一个客户端。想同时做五件事?前面加个 `go`。测试?`go test`。基准测试?`go test -bench`。性能分析?`pprof` 已经在那里嘲笑你的 `console.log` 调试了。
## 标准库也他妈很深
`io.Reader` 和 `io.Writer` 是两个各有一个方法的接口。这也是为什么你可以用三行代码把 HTTP 响应体通过 gzip writer 管道传输到磁盘文件上,而不用动脑子。生态系统中每一个正经的包都使用它们。一旦你掌握了这一点,Go 一半的“魔法”就只是这两个接口到处出现而已。
`context.Context` 是用来取消操作的。用户关闭浏览器标签页,请求上下文取消,数据库查询取消,下游 HTTP 调用取消。一路下去。没有泄露的 goroutine。没有消耗连接池的僵尸查询。你把它作为第一个参数传递,并且尊重它。整个 API 就是这样。
`encoding/json`、`encoding/xml`、`encoding/csv`、`encoding/binary`,都在标准库里。相同的结构体标签模式。相同的解码到指针的体验。学会一个,基本上就全会了。
## 不会让你哭的并发
Goroutine 不是线程。它们是栈式的,由运行时多路复用到 OS 线程上,启动成本大约 2KB。你可以在笔记本电脑上生成十万个。试试用你的 Node 事件循环做同样的事,然后看着它拉一裤兜子。
Channel 是 goroutine 之间带类型的管道。你在一端发送,在另一端接收,运行时负责同步。如果你需要共享状态,`sync.Mutex` 就在那里,而竞态检测器会在你搞砸时告诉你。
```go
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 仪式。语言本身就做到了。
## 一个真实的例子,不是 hello-world
下面是一个从 Postgres 读取数据并渲染 HTML 的 CRUD 路由。完整代码。
```go
//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,没有 DI 容器,没有服务层,没有包含十七个抽象基类的 `controllers/` 目录。你可以从上到下阅读,并且确切知道它在做什么。
## 不会毁了你周末的依赖管理
`go mod init`。搞定。你的依赖在 `go.mod` 和 `go.sum` 里。sum 文件是你实际得到内容的加密记录,所以当有人对你搞 left-pad 时你能发现。没有 `node_modules` 目录。没有开发环境和 CI 之间的锁文件漂移。没有同级依赖、可选依赖、devDependencies、peerDependenciesMeta。只有一个文件列出你用的东西,一个文件证明你得到了你期望的。
你想要离线构建?`go mod vendor` 把所有东西丢进 `vendor/` 目录,工具链自动使用它。整个项目,包括依赖,都能放进一个 tarball。你的安全团队会感激涕零。
`gofmt` 格式化你的代码。没有争议。没有 `.prettierrc` 的圣战。格式就是格式,所有人都用它。你的差异保持很小,因为没人会调整空白。
`go vet` 捕获明显的错误。`go test` 运行你的测试。`go test -race` 用竞态检测器运行,找出你以为没有的数据竞争。`go test -bench` 运行基准测试。`go test -cover` 告诉你漏掉了什么。`go tool pprof` 通过你两行代码就能接上的 HTTP 端点,给出来自运行中生产服务的 CPU 和内存使用火焰图。
这些都不是第三方工具,都不是插件,都不需要你维护的配置文件。它们都在盒子里。
## 部署就是一个复制命令
这是让 Rails 和 Node 的人物理上感到愤怒的部分。你构建一个 Go 二进制文件。你把它复制到服务器上。你运行它。
```bash
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 / Django / Express / Next 呢?”
它们怎么了?Rails 需要一个涉及 Capistrano、三个配置文件、一只山羊的部署仪式。Django 要你学习它的 ORM、管理后台、中间件系统,以及关于一切的观点。Express 靠 `npm audit` 警告和祈祷撑着。Next.js 每六个月改变一次路由惯例,并为此对你进行煤气灯效应。
你的 Go 二进制文件不在乎。它编译了,运行了,并且五年后在不存在的硬件上仍然能运行。你的框架?圣诞节前就废弃了,维护者会写一篇关于倦怠的 Medium 文章。
## “但是微服务!”
不。
写他妈的单体应用。一个 Go 二进制文件。一个 Postgres。一个 Redis,如果你绝对需要的话。在同一个端口上提供 HTML 和 JSON API。运行在一个单价低于你每月燕麦奶预算的 VPS 上。毫不费力扩展到每秒一万个请求,因为 Go 就是为此设计的,而且 goroutine 便宜得要命。
当你真的需要拆分它时——而且你不会需要——拆分 Go 单体应用只是把包移到它们自己的仓库里。接口已经在那里了。你没有刻意设计就做到了,因为语言迫使你这么做了。
## “但是泛型!但是错误处理!但是没有异常!”
`if err != nil` 是特性,不是 bug。它迫使你查看每一个可能出错的地方,并且*决定*该怎么做。你那 try/catch 嵌套地狱并没有让错误消失,它只是把它们藏起来,直到凌晨两点的生产环境爆发。
泛型在 1.18 中落地了。它们很好。需要时就用它们。别抱怨了。
## 给我他妈用 Go
别假装你需要一个框架了。你也不需要微服务,或者 Rust 重写,或者上周二发布的任何能拯救你——而前六个没能做到——的 JavaScript 元框架。
打开你的编辑器。运行 `go mod init`,写一个 `main.go`,嵌入你的模板,然后编译。把那该死的东西交付出去。
无聊的选择是正确的选择。一直如此。
相似文章
构建Gin:简单胜于容易
作者回顾了为Go语言创建Gin Web框架的过程,强调了“简单胜于容易”的设计原则,以平衡魔法和样板代码,并介绍了框架的上下文对象如何演变以兼容Go标准库。
就他妈用 React
这篇主观性很强的文章激进地主张在复杂 Web 应用中使用现代 JavaScript 框架(如 React)而非纯 HTML,认为复杂性需要合适的工具。
Solod: Go 可以成为更好的 C
Solod 是 Go 的一个严格子集,可编译为可读的 C11,零运行时,无GC,拥有丰富的标准库,让 Go 开发者获得系统级控制,同时为 C 程序员提供熟悉的工具链。
依赖 Go
Solod 是一种新的系统编程语言,它是 Go 的一个子集,复用 Go 的工具链和标准库,同时编译为 C11,提供手动内存管理。
C语言中的Go风格并发
一篇详细的技术文章,探讨如何在C语言中复制Go的并发模型,使用POSIX线程、互斥锁、条件变量和工作池,作为Solod转译器项目的一部分。