半年前开始接手一个 Go 服务,之前写了六年多 PHP。网上讲两者对比的文章很多,但大多是列特性表。我这里记的是几个真正卡了我一段时间的地方,纯粹是个人体验。
一、没有 try-catch 之后,错误处理会渗透到每一层
这个大概所有人都知道,但知道和习惯是两回事。
PHP 里写一个三层调用,中间层可以完全不关心底层会不会抛异常,出了事统一在最外面接住就行。Go 不行,错误得一层层往上带:
func loadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取配置 %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("解析配置 %s: %w", path, err)
}
return &cfg, nil
}
最开始我觉得这是废话,写了两个月之后改观了。它逼着你想清楚每个错误到底意味着什么——是调用方该重试,还是该直接失败,还是可以忽略。PHP 里 catch (\Exception $e) 一把抓的时候,其实我从来没认真想过这些。
真正难受的是日志。PHP 里异常栈会自动打印完整的调用链,Go 的 error 默认只有消息。不加 %w 包装、不打 debug.Stack(),线上出问题只有一行 file not found,根本不知道哪来的。
二、nil 是个无底洞
PHP 里 null 顶多让你 Trying to access array offset on value of type null,是个警告,页面还能继续跑。
Go 里 nil 会直接 panic,而且是在运行到那一行的时候才炸。
最坑的一次是往 map 里写数据。我以为 var m map[string]int 声明完就能用:
var counts map[string]int
counts["a"] = 1 // panic: assignment to entry in nil map
得 make 一下。类似的还有往 nil channel 发数据会永久阻塞——这个更阴险,不报错,就是卡住,CPU 也不高,监控看着一切正常,请求全堆在那里。
还有 typed nil 那个经典问题:
func getErr() error {
var p *MyErr = nil
return p // 返回的 error 不是 nil!
}
接口值是 (类型, 值) 两元组,类型不为空就不等于 nil。我在这上面浪费了大概两个小时,最后是靠 fmt.Printf("%#v") 才看出来。
三、代码变长了,但改动反而更让我放心
写 Go 的代码量确实大。同样是解析参数校验入库,PHP 可能三十行搞定,Go 要七八十行。
但有个变化我当时没预料到:改 Go 代码的时候我心里更有底。
因为每一步都能看到类型和错误往哪儿走,不用去猜那个数组里到底有没有这个 key,也不用担心某个方法内部悄悄吞了异常。改完编译过了,八成的低级错误就没了。PHP 里改完得跑一遍测试,不然心里发虚。
代价是前期写得慢。我头一个月效率大概只有平时的一半。
四、工具链这块是真的舒服
go fmt 一句话说完,没什么可争的。PHP 那边为了用 PSR-12 还是别的风格、缩进四格还是两格、要不要对齐数组箭头,团队能讨论一下午。
go test 自带,不用装 PHPUnit。基准测试也是标准库里的:
func BenchmarkParse(b *testing.B) {
for i := 0; i < b.N; i++ {
parse(line)
}
}
go test -bench=. -benchmem 直接出内存分配次数。这个在 PHP 里得装 xdebug 或者别的扩展才能做,而且影响性能。
交叉编译也顺手,GOOS=linux GOARCH=amd64 go build 就能出 Linux 二进制,扔到服务器上直接跑,不用管那边装了什么版本的运行环境。
五、还没适应的地方
泛型。 出来之后我看了一些用法,但自己写业务代码基本没用上。老代码全是 interface{} 加类型断言,改起来工程量大,不值得。
依赖管理的历史包袱。 有些库把整个 Kubernetes 拖进来,go.sum 几千行,编译一次要几分钟。PHP 的 composer 至少还能看到依赖树。
处理动态结构还是别扭。 上游 API 返回的 JSON 字段飘忽不定的时候,PHP 里 $data['a']['b'] ?? null 就完了,Go 得定义结构体或者一路 map[string]interface{} 断言下去,很难看。
最后
现在回过头看,Go 更适合写那些要长期跑、不能随便重启、逻辑分支多的服务。PHP 更适合写那些改得快、上线快、请求进来出去就完事的页面。
我现在是两个都用。新服务用 Go,快速验证和内部工具还是 PHP,因为写着快。
真要一句话总结:Go 让我在写的时候更累,在读的时候更轻松。