Just Fucking Use Go
Just Fucking Use Go
赶紧去用 Go 吧
Hey, dipshit. You know what compiles in two seconds, deploys as a single binary, and doesn’t shit itself when a transitive dependency gets yanked from npm at 3am? Go. 嘿,笨蛋。你知道有什么东西能在两秒内编译完成、部署时只是一个单一二进制文件,而且不会在凌晨三点因为某个传递依赖被从 npm 撤下而崩溃吗?是 Go。
The same way HTML has been sitting there since the dawn of the goddamn internet waiting for you to stop overcomplicating the frontend, Go has been sitting there for over a decade waiting for you to stop overcomplicating the backend. But no. You’re out there gluing together fifteen Node packages, three TypeScript build tools, and a Kubernetes cluster to serve a fucking form. You hired a Platform Team to babysit your Rails monolith. You convinced your CTO that Rust was necessary for a CRUD app that does maybe forty requests a second. Congratulations, asshole. You played yourself. 就像 HTML 自互联网诞生之初就一直存在,等着你停止把前端搞得过于复杂一样,Go 也已经存在了十多年,等着你停止把后端搞得过于复杂。但你偏不。你非要拼凑十五个 Node 包、三个 TypeScript 构建工具和一个 Kubernetes 集群,就为了展示一个破表单。你雇了一个平台团队来照看你的 Rails 单体应用。你还说服你的 CTO,认为一个每秒只有四十次请求的 CRUD 应用非得用 Rust 不可。恭喜你,混蛋。你把自己坑惨了。
The language is boring on purpose
这门语言故意设计得很“无聊”
You know why Go feels boring? Because it is, and that’s the goddamn point. There are no decorators. No metaclasses. No macros. No traits, monads, or whatever cursed abstraction the Haskell crowd is huffing this week. There are structs, functions, interfaces, goroutines, and channels. That’s it. You can read the spec on a lunch break and be productive that afternoon. Boring means the junior you hired last month can read the code the principal wrote two years ago. There’s exactly one way to format it and gofmt already did it. Your “clever” coworker can’t sneak a seventeen-layer abstraction into the codebase because the language won’t let him. That’s what shipping looks like when nobody is drooling over their own cleverness.
你知道为什么 Go 感觉很无聊吗?因为它确实很无聊,而这正是它的精髓所在。没有装饰器,没有元类,没有宏。没有 Trait、Monad,或者 Haskell 社区这周又在鼓吹的什么乱七八糟的抽象。只有结构体、函数、接口、Goroutine 和 Channel。就这些。你可以在午休时读完规范,下午就能上手干活。无聊意味着你上个月雇的初级工程师也能读懂两年前首席工程师写的代码。代码格式只有一种,而且 gofmt 已经帮你搞定了。你那个“聪明”的同事没法往代码库里塞进十七层抽象,因为这门语言根本不允许。当没人沉迷于卖弄小聪明时,这才是真正的高效交付。
The standard library is the framework
标准库就是框架
Stop looking for a framework, you absolute walnut. The standard library is the framework. 别再找什么框架了,你这个榆木脑袋。标准库本身就是框架。
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)
}
That’s a working web app. HTML templates compiled into the binary. No webpack. No Vite. No “dev server”. No node_modules the size of a fucking Volkswagen. You go build and you ship one file. Drop it on a server. Done. You want a database? database/sql. JSON? encoding/json. Want to talk to another service? net/http is also a client. Want to do five things at once? Slap go in front of it. Tests? go test. Benchmarks? go test -bench. A profile? pprof is already there laughing at your console.log debugging.
这就是一个可运行的 Web 应用。HTML 模板被编译进了二进制文件。没有 Webpack,没有 Vite,没有“开发服务器”,也没有大得像辆大众汽车的 node_modules。你执行 go build,然后交付一个文件。扔到服务器上。搞定。想要数据库?用 database/sql。JSON?用 encoding/json。想调用其他服务?net/http 本身就是个客户端。想同时做五件事?在函数前加个 go 关键字。测试?用 go test。基准测试?用 go test -bench。性能分析?pprof 早就准备好了,正嘲笑着你那还在用 console.log 调试的代码。
The stdlib is also fucking deep. io.Reader and io.Writer are two interfaces with one method each. They are also the reason you can pipe an HTTP response body into a gzip writer into a file on disk in three lines without thinking about it. Every serious package in the ecosystem speaks them. Once you grok that, half of Go’s “magic” turns out to be the same two interfaces showing up everywhere.
标准库的深度也深得可怕。io.Reader 和 io.Writer 是两个各只有一个方法的接口。它们也是你能用三行代码就把 HTTP 响应体通过 gzip 压缩写入磁盘文件,且完全不用动脑子的原因。生态系统中每一个严肃的包都支持它们。一旦你理解了这一点,就会发现 Go 的一半“魔法”其实就是这两个接口在到处出现。
context.Context is how you cancel things. A user closes the browser tab, the request context cancels, the database query cancels, the downstream HTTP call cancels. All the way down. No leaked goroutines. No zombie queries chewing through your connection pool. You pass it as the first argument and you respect it. That’s the whole API. encoding/json, encoding/xml, encoding/csv, encoding/binary, all in the stdlib. Same struct tag pattern. Same decode-into-a-pointer ergonomics. Learn one and you basically know them all.
context.Context 是你取消任务的方式。用户关闭浏览器标签页,请求上下文取消,数据库查询取消,下游的 HTTP 调用也随之取消。一路向下。没有 Goroutine 泄漏,没有僵尸查询在消耗你的连接池。你把它作为第一个参数传递并遵守它,这就是整个 API 的全部。encoding/json、encoding/xml、encoding/csv、encoding/binary,全都在标准库里。同样的结构体标签模式,同样的解码到指针的工程学。学会一个,你就基本全都会了。
Concurrency that doesn’t make you cry
不会让你哭出来的并发
Goroutines are not threads. They are stackful, multiplexed onto OS threads by the runtime, and they cost about 2KB to start. You can spawn a hundred thousand of them on a laptop. Try that with your Node event loop and watch it shit the bed. Channels are typed pipes between goroutines. You send on one end, you receive on the other, and the runtime handles the synchronization. If you need shared state instead, sync.Mutex is right there, and the race detector will tell you when you screwed up.
Goroutine 不是线程。它们是有栈的,由运行时多路复用到操作系统线程上,启动成本仅约 2KB。你可以在笔记本电脑上启动十万个。用你的 Node 事件循环试试看,保证它当场崩溃。Channel 是 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)
}
That’s a parallel HTTP fetcher. No library, no framework, no async/await ceremony. The language does it.
这就是一个并行 HTTP 获取器。没有库,没有框架,没有 async/await 的繁文缛节。语言本身就支持。
A real example, not a hello-world
一个真实的例子,而不是 Hello World
Here’s a CRUD route reading from Postgres and rendering HTML. The whole thing. 这是一个从 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)
}
}
Database, templates, and an HTTP handler in one screen. Request context wired through to the query so a closed connection cancels the SQL. No ORM, no DI container, no service layer, no controllers/ directory with seventeen abstract base classes. You can read it top to bottom and know exactly what it does.
数据库、模板和一个 HTTP 处理程序,一屏搞定。请求上下文贯穿到查询中,因此连接关闭时会自动取消 SQL 执行。没有 ORM,没有依赖注入容器,没有服务层,没有包含十七个抽象基类的 controllers/ 目录。你从头读到尾,就能确切地知道它在做什么。
Dependencies that don’t ruin your weekend
不会毁掉你周末的依赖管理
go mod init. Done. Your dependencies live in go.mod and go.sum. The sum file is a cryptographic record of what you actually got, so you can tell when somebody pulls a left-pad on you. There is no node_modules directory. There is no lockfile drift between dev and CI. There are no peer dependencies, no optional dependencies, no devDependencies, no peerDependenciesMeta. There is one file that lists what you use, and one file that proves you got what you expected. You want offline builds? go mod vendor drops everything into a vendor/
go mod init。搞定。你的依赖项存在 go.mod 和 go.sum 中。sum 文件是你实际获取内容的加密记录,所以当有人对你搞“left-pad”攻击时,你能立刻发现。没有 node_modules 目录。开发环境和 CI 之间不会出现锁文件漂移。没有对等依赖、可选依赖、开发依赖或对等依赖元数据。只有一个文件列出你使用的东西,另一个文件证明你得到了你预期的东西。想要离线构建?go mod vendor 会把所有东西扔进 vendor/ 目录。