Go 里什么时候该用 strings.Builder,什么时候该用 bytes.Buffer
刚开始写 Go 的时候,遇到文本拼接最容易想到的还是 +。代码少时当然没问题,但一旦进入循环、日志拼接、模板前处理或者接口响应组装,这种写法很快就会让代码既不清晰,也不够稳妥。真正开始做一些小工具之后,我发现自己最常用的其实不是花哨技巧,而是把 strings.Builder 和 bytes.Buffer 的边界分清楚。
刚开始写 Go 的时候,遇到文本拼接最容易想到的还是 +。代码少时当然没问题,但一旦进入循环、日志拼接、模板前处理或者接口响应组装,这种写法很快就会让代码既不清晰,也不够稳妥。真正开始做一些小工具之后,我发现自己最常用的其实不是花哨技巧,而是把 strings.Builder 和 bytes.Buffer 的边界分清楚。
Go 里的方法接收者,看起来是一个很小的语法点,但一旦项目写久了,你会发现它会直接影响代码语义。很多人会背口诀:能不用指针就不用指针,或者一旦要改值就用指针。可真正到工程里,光背口诀还是不够。
刚从 GOPATH 思维切到 Go Modules 时,很多人最直观的感受是目录终于自由了,不一定要把项目放在固定路径下。可真正让我觉得它实用的,是本地联调依赖仓库时,不用再靠复制粘贴代码或者临时改 import 路径硬顶过去。
Go 初学阶段很容易把 slice 和 map 当成“比数组和字典好用一点的容器”。真正开始写业务后,很多让人困惑的 bug 其实都和这两个基础类型有关:为什么函数里改了 slice,外面数据也变了;为什么 map 还没赋值就 panic;为什么看起来只是截了个子切片,内存却迟迟下不来。
Go 学久一点以后,很多人都会记住一句话:interface 要小。但真到项目里,接口问题往往不是“大或小”这么简单,而是抽象出现得太早。实现只有一种、调用方式还没定稳,就先定义一堆 UserService、OrderRepository 接口,最后只会让代码多一层跳转。
刚开始写 Go 的时候,很多人会觉得错误处理很重复:每层都要判断 err != nil,看起来很机械。可真到了线上排查问题时,最怕的不是判断多,而是最后拿到的错误只有一句 “open failed” 或 “query error”,根本不知道是哪一步出的事。
很多人刚写 Go 项目时,最有安全感的动作就是先把目录搭完整:controller、service、dao、model、utils 先分好,觉得以后扩展会方便。可项目在功能还不稳定的时候,包分得太细,往往比全写在一个地方更容易把自己绕进去。
刚开始学 Go,并发部分最容易让人紧张的词就是“死锁”。这当然没错,因为死锁一出现,程序常常会非常明显地挂在那里。可等你真正把 Go 服务跑起来一段时间后,会发现另一类问题更难受:goroutine 没有马上死掉,而是在后台一点点堆起来。