讲解

走完语法、并发与工程化的 25 章,最后把散落的经验收敛成一份清单。可读性方面:gofmt(或 gofmt 的超集 gofumpt)格式化一切;包名、变量名短而达意,Go 社区偏爱短命名(i、r、buf)而不是长篇大论的标识符;接口保持小巧;函数保持「主流程左对齐」——错误处理缩进一层就返回,不让正常逻辑陷进深层嵌套。

正确性方面:每个 error 要么处理要么向上传,绝不 _ 丢弃;判断错误用 errors.Is/As;共享数据必同步,写完并发代码跑一次 -race;资源申请处立刻 defer 释放;map、切片、指针使用前想清楚零值与 nil 的行为。工具链方面:go vet ./... 和 staticcheck 纳入日常,go test ./... -race -cover 进 CI,go mod tidy 保持依赖干净。

学习路径方面:先把本教程的示例全部亲手跑通、改坏再修好;然后用 Go 重写一个你熟悉的小工具(批量重命名、Markdown 转 HTML、简易 KV 缓存),在真实需求里踩一遍文件、JSON、HTTP、并发的坑;最后读标准库源码——net/http 和 strings 的实现是公认的优秀教材。Go 的「少即是多」要在实践里体会:很多时候不抽象、写两遍,比设计一个精巧的通用方案更接近正确答案。

示例

一份贯彻「错误即值 + 早返回 + %w 包装」的完整小工具:解析逗号分隔的成绩并统计:

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func parseScores(text string) ([]int, error) {
	var scores []int
	for _, part := range strings.Split(text, ",") {
		n, err := strconv.Atoi(strings.TrimSpace(part))
		if err != nil {
			return nil, fmt.Errorf("解析 %q 失败: %w", part, err)
		}
		scores = append(scores, n)
	}
	return scores, nil
}

func main() {
	scores, err := parseScores("90, 85, 77")
	if err != nil {
		fmt.Println("错误:", err)
		return // 早返回,主流程保持左对齐
	}
	total := 0
	for _, s := range scores {
		total += s
	}
	fmt.Printf("总分 %d,平均 %.1f\n", total, float64(total)/float64(len(scores)))
}

综合运用:词频统计小工具(切片、map、排序、闭包一次到位):

package main

import (
	"fmt"
	"slices"
	"strings"
)

func main() {
	text := "go is simple go is fast go is fun"
	counts := make(map[string]int)
	for _, w := range strings.Fields(text) {
		counts[w]++
	}

	type pair struct {
		word  string
		count int
	}
	pairs := make([]pair, 0, len(counts))
	for w, c := range counts {
		pairs = append(pairs, pair{w, c})
	}
	slices.SortFunc(pairs, func(a, b pair) int { return b.count - a.count })

	fmt.Println("词频排行:")
	for _, p := range pairs {
		fmt.Printf("  %s: %d\n", p.word, p.count)
	}
}

常见坑

  • 过度设计:一来就抽象接口层、工厂模式、依赖注入框架。Go 的答案是先用具体代码跑通业务,重复出现三次再抽象。YAGNI(你不会需要它)在 Go 社区不是口号是日常。
  • 忽视工具链:不跑 gofmt、不看 go vet、不写测试,等于放弃了 Go 一半的生产力。把这些命令挂进编辑器保存钩子和 CI,成本接近零。
  • 到处用 _ 吞错误换取「能跑」:演示代码里勉强接受,生产代码里每个被吞掉的 error 都是定时炸弹。宁可多写三行处理,也不要省这一行。
  • 并发先用锁还是先用 channel 的纠结:经验法则是「数据流向清晰用 channel,只是保护共享状态用 Mutex」,两者都会但都不要滥用——能单 goroutine 解决的就不要并发。
  • 学了语法不读标准库:标准库是 Go 风格的最高权威,自己的代码和标准库长得越像,越容易被其他 Go 程序员(包括未来的你)秒懂。

小结

gofmt 管格式、错误必处理、并发必 race、抽象要克制、标准库是最好的老师。26 章到此结束——Go 的世界已经向你打开,去写真正的东西吧。