浪白的博客の小站
首页项目归档照片墙音乐灵境说说杂谈友链关于
封面

从 PHP 转到 Go 半年,说几个真正难受的地方

写作时间:2026-04-08 21:40:00
# Go
# PHP
# 随笔

半年前开始接手一个 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 让我在写的时候更累,在读的时候更轻松。

avatar

浪白

邪恶的浪白

RECOMMENDED

Composer 用了这么多年,几个我认为值得坚持的习惯

2026-04-27 19:15:00

context 传参这件事,我踩过的三个坑

2026-05-06 20:05:00

Go 的 error 处理,用久了反而觉得还行

2026-04-19 22:15:00

Table of Contents