Go 语言入门系列(六):工程实践——测试、泛型与项目组织

本篇目标:从"会写代码"走向"能维护项目"。前五篇让你能写出功能正确的程序,这一篇补上让它可靠、可扩展、可交付的部分。

一、测试

Go 把测试做进了工具链,写测试几乎零成本。测试文件以 _test.go 结尾,与被测代码同包:

// calc.go
package calc

func Add(a, b int) int { return a + b }

// calc_test.go
package calc

import "testing"

func TestAdd(t *testing.T) {
    if got := Add(1, 2); got != 3 {
        t.Errorf("Add(1,2) = %d, 期望 3", got)
    }
}

运行 go test ./...。表驱动测试是 Go 的惯用写法,一个函数覆盖多组用例:

func TestAdd(t *testing.T) {
    cases := []struct {
        name string
        a, b, want int
    }{
        {"正数", 1, 2, 3},
        {"负数", -1, 1, 0},
        {"零", 0, 0, 0},
    }
    for _, c := range cases {
        t.Run(c.name, func(t *testing.T) {
            if got := Add(c.a, c.b); got != c.want {
                t.Errorf("got %d, want %d", got, c.want)
            }
        })
    }
}

t.Run 起子测试,失败时能精确定位是哪组。覆盖率用 go test -cover,基准测试用 Benchmark 前缀的函数配合 go test -bench .。

二、泛型入门

Go 1.18 引入泛型。典型场景:写一个对多种类型都适用的函数,而不牺牲类型安全:

func Sum[T ~int | ~float64](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

Sum([]int{1, 2, 3})        // 6
Sum([]float64{1.5, 2.5})   // 4.0
  • [T ~int | ~float64] 是类型参数和约束,~ 表示"底层类型为 int/float64 的自定义类型也算"
  • 常用约束:comparable(可比较,能作 map 键)、any(任意类型)
  • 何时用:容器、算法这类真正与类型无关的逻辑。业务代码优先用接口,泛型别滥用——它会增加阅读成本。

三、包组织

  • 按职责分目录,而非按层(不搞 models/、services/ 这种空壳分层,而是 user/、order/ 各自封装)
  • internal/ 目录:放在里面的包只能被本模块引用,是封装边界的利器
project/
├── go.mod
├── main.go
├── internal/
│   └── store/        // 外部模块无法导入
└── api/
  • 避免循环依赖:包 A 导入 B,B 就不能导入 A。出现循环说明职责划分有问题,通常靠抽出公共包或定义接口解决。
  • 包名简短小写,不用下划线,不叫 util、common 这种含义模糊的名字。

四、依赖管理

$ go get github.com/gin-gonic/gin@v1.9.0   # 添加依赖
$ go mod tidy                               # 清理无用依赖、补全缺失
$ go list -m all                            # 查看全部依赖

版本遵循语义化:v1.2.3(主.次.补丁)。主版本升级到 v2 时导入路径要带 /v2,这是 Go 的模块版本规则。

五、标准库速览

日常高频的几个包,先混个脸熟:

  • fmt:格式化输出
  • strings:Contains、Split、ReplaceAll、TrimSpace
  • strconv:字符串与数字互转,Atoi、Itoa、ParseFloat
  • time:Now、Duration、Format(用 2006-01-02 15:04:05 这个魔法参考时间)
  • encoding/json:Marshal/Unmarshal,结构体字段用 tag 控制:json:"name"
  • os:文件与进程操作,os.ReadFile、os.Args

六、代码风格

  • 提交前跑 go fmt ./... 和 go vet ./...
  • 更严格的静态检查用 staticcheck
  • 命名:导出用大写开头,短变量名(i、n、err),包名不用驼峰
  • 错误字符串不大写开头、不以标点结尾(errors.New("file not found"))
  • 读一遍 Effective Go,能写之后纠正习惯最有效

七、小结与下一步

  • _test.go + go test,表驱动 + t.Run 是标配
  • 泛型用于类型无关的容器/算法,约束用 ~、comparable
  • 按职责分包,internal/ 做封装边界,避免循环依赖
  • go get / go mod tidy 管依赖,主版本 v2+ 路径带 /v2
  • 风格靠 fmt、vet、staticcheck 保证

练习:把前几篇的通讯录项目整理成规范模块——internal/ 放核心逻辑,补一套表驱动测试,跑通 go test -cover ./...,再用 go vet 和 staticcheck 检查一遍。

下一步方向:做 Web 服务(net/http 或 Gin)、写 CLI 工具、读标准库源码(sync、context 的实现很值得看)。


六篇到此结束。回顾这条路径:工具链 → 语法 → 数据 → 抽象与错误 → 并发 → 工程化。每一步都建立在前一步之上,走完这套,你已经具备了用 Go 写真实项目的基础。接下来靠项目和源码继续打磨。

posted @ 2026-11-01 21:03  sunlon  阅读(6)  评论(0)    收藏  举报