编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 质量保障

  • 产品思考

  • 软实力

  • 开发流程

  • Git应用

  • 技术模版

  • 技术规范

    • 技术规范
    • C++编程代码规范指南
    • Java编程代码规范指南
    • JavaScript编程规范指南
    • TypeScript编程规范指南
    • Python编程代码规范指南
    • Go编程代码规范指南
      • 目录
      • 01.规范概述
        • 1.1 为何需要 Go 规范
        • 1.2 核心原则
      • 02.命名规范
      • 03.代码格式
      • 04.错误处理
        • 4.1 基础模式
        • 4.2 多错误与自定义错误 【推荐】
      • 05.并发模式
        • 5.1 goroutine 与 channel
        • 5.2 errgroup 与工作池 【推荐】
      • 06.泛型规范(Go 1.18+)【推荐】
      • 07.零值利用
      • 08.defer 使用
      • 10.测试规范
      • 11.包组织与依赖注入
      • 09.接口设计
      • 12.性能优化
      • 13.常见反模式
      • 14.工具链与自动化
      • 15.代码审查清单
      • 16.常见陷阱速查
        • 16.1 goroutine 陷阱
        • 16.2 error 与 defer 陷阱
        • 16.3 性能陷阱
    • Kotlin编程代码规范指南
    • Swift编程代码规范指南
    • Rust编程代码规范指南
    • Shell编程代码规范指南
    • 项目代码提交规范
  • markdown

  • mermaid

  • license

  • 博客部署

  • 技术招聘

  • 测试经验

  • 技术
  • 技术规范
杨充
2020-08-15
目录

Go编程代码规范指南

# Go编程代码规范指南

基于 Effective Go (opens new window) 和 Uber Go Style Guide (opens new window)。

# 目录

  • 01.规范概述
    • 1.1 为何需要 Go 规范
    • 1.2 核心原则
  • 02.命名规范
  • 03.代码格式
  • 04.错误处理
    • 4.1 基础模式
    • 4.2 多错误与自定义错误
  • 05.并发模式
    • 5.1 goroutine-与-channel
    • 5.2 errgroup-与工作池
  • 06.泛型规范
  • 07.零值利用
  • 08.defer-使用
  • 09.接口设计
  • 10.测试规范
  • 11.包组织与依赖注入
  • 12.性能优化
  • 13.常见反模式
  • 14.工具链与自动化
  • 15.代码审查清单
  • 16.常见陷阱速查
    • 16.1 goroutine-陷阱
    • 16.2 error-与-defer-陷阱
    • 16.3 性能陷阱

# 01.规范概述

# 1.1 为何需要 Go 规范

疑惑:Go 有 go fmt 统一格式,还需要编码规范吗?

答疑:go fmt 只解决了格式问题——命名、错误处理、并发模式、包组织这些设计决策,依然需要规范来统一。Go 社区有一句名言:"Don't be clever. Be clear." 规范的核心就是让团队的 Go 代码简单、清晰、一致。

# 1.2 核心原则

原则 说明
简洁 拒绝过度抽象,能直白就不花哨
显式 错误显式处理,零值显式利用
并发安全 goroutine 必须有退出机制,channel 由发送方关闭
接受接口,返回结构体 调用方定义接口,实现方返回具体类型

# 02.命名规范

类型 规范 示例
包名 小写,单数,无下划线 http, json, userservice
导出标识符 大写开头 User, GetName()
私有标识符 小写开头 user, parseData()
接口(单方法) 方法名 + er Reader, Writer, Closer
变量 驼峰法 userCount, maxRetry
常量 驼峰法 MaxSize, DefaultPort
缩写 全大写或全小写 HTTPClient, urlPath
// ✅ 正确
package user

type Service struct {
    repo Repository
}

func (s *Service) GetByID(id int64) (*User, error) {
    return s.repo.FindByID(id)
}

// ✅ 接口命名
type Reader interface {
    Read(p []byte) (n int, err error)
}

// ❌ 错误
type User_Service struct {}   // 不要下划线
func (s *Service) get_user()  // 不要下划线
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 03.代码格式

Go 有官方工具,直接使用即可:

go fmt ./...           # 格式化
goimports -w .         # 自动管理 import
go vet ./...           # 静态分析
golangci-lint run      # 综合检查
1
2
3
4
// ✅ 分组导入(标准库 → 第三方 → 本模块)
import (
    "context"
    "fmt"

    "github.com/pkg/errors"

    "myproject/internal/config"
)

// ✅ 结构体字段对齐
type User struct {
    ID        int64     `json:"id"`
    Name      string    `json:"name"`
    CreatedAt time.Time `json:"created_at"`
}

// ❌ 空标识符 import
import _ "net/http/pprof"  // 仅在有充分理由时使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 04.错误处理

# 4.1 基础模式

// ✅ 始终检查错误
result, err := doSomething()
if err != nil {
    return fmt.Errorf("doSomething: %w", err)
}

// ✅ 包装错误,保留上下文(Go 1.13+ %w)
if err := validate(user); err != nil {
    return fmt.Errorf("validating user %d: %w", user.ID, err)
}

// ✅ 错误只处理一次(记日志或返回,不要同时做)
data, err := fetchData()
if err != nil {
    log.Printf("fetchData failed: %v", err)  // 这里记日志
    return err                                // 上层不应再记
}

// ✅ 哨兵错误
var ErrNotFound = errors.New("not found")

// ✅ errors.Is 和 errors.As 判断错误链
if errors.Is(err, ErrNotFound) {
    // 处理未找到
}
var valErr *ValidationError
if errors.As(err, &valErr) {
    fmt.Println(valErr.Field)  // 提取自定义错误属性
}

// ❌ 忽略错误(除非有充分理由并加注释)
// _ = json.Unmarshal(data, &v)   // 应解释为何可忽略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 4.2 多错误与自定义错误 【推荐】

// ✅ 自定义错误类型(带上下文)
type ValidationError struct {
    Field string
    Msg   string
}
func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}

// ✅ 多错误聚合(Go 1.20+ errors.Join)
var errs []error
for _, item := range items {
    if err := validate(item); err != nil {
        errs = append(errs, err)
    }
}
if len(errs) > 0 {
    return errors.Join(errs...)  // 聚合所有错误
}

// ✅ panic 仅用于不可恢复的情况
// ❌ 不要用 panic 替代 error 返回值
// 初始化阶段的致命错误可以用 panic(如配置文件损坏)
if err := config.Load(); err != nil {
    panic(fmt.Sprintf("加载配置失败: %v", err))  // 仅 main 或 init 中
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 05.并发模式

# 5.1 goroutine 与 channel

// ✅ sync.WaitGroup
var wg sync.WaitGroup
for _, url := range urls {
    wg.Add(1)
    go func(u string) {
        defer wg.Done()
        fetch(u)
    }(url)  // ⚠️ 注意:复制循环变量
}
wg.Wait()

// ✅ context 传递取消信号和超时
func Process(ctx context.Context, input string) error {
    ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
    defer cancel()

    select {
    case <-ctx.Done():
        return ctx.Err()
    case result := <-doWork(ctx, input):
        return handle(result)
    }
}

// ✅ channel 关闭:由发送方负责
ch := make(chan int)
go func() {
    defer close(ch)
    for _, v := range data {
        ch <- v
    }
}()

// ✅ sync.Once 单例初始化
var (
    instance *Service
    once     sync.Once
)
func GetService() *Service {
    once.Do(func() { instance = &Service{} })
    return instance
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 5.2 errgroup 与工作池 【推荐】

import "golang.org/x/sync/errgroup"

// ✅ errgroup:并行任务 + 错误传播
func FetchAll(ctx context.Context, urls []string) ([]*Response, error) {
    g, ctx := errgroup.WithContext(ctx)
    results := make([]*Response, len(urls))

    for i, url := range urls {
        i, url := i, url  // ⚠️ Go 1.22 前闭包需复制
        g.Go(func() error {
            resp, err := fetch(ctx, url)
            if err != nil {
                return err  // 第一个错误取消其他 goroutine
            }
            results[i] = resp
            return nil
        })
    }
    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}

// ✅ 工作池:限制并发数(用 channel 作信号量)
func ProcessAll(items []Item, concurrency int) {
    sem := make(chan struct{}, concurrency)
    var wg sync.WaitGroup

    for _, item := range items {
        wg.Add(1)
        sem <- struct{}{}          // 获取令牌
        go func(it Item) {
            defer wg.Done()
            defer func() { <-sem }() // 释放令牌
            process(it)
        }(item)
    }
    wg.Wait()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 06.泛型规范(Go 1.18+)【推荐】

// ✅ 泛型函数:简洁约束
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

// ✅ 泛型约束用 interface
type Number interface {
    ~int | ~int64 | ~float64  // ~ 表示允许底层类型
}
func Sum[T Number](vals []T) T {
    var total T
    for _, v := range vals {
        total += v
    }
    return total
}

// ✅ 泛型类型:自定义容器
type Set[T comparable] map[T]struct{}
func NewSet[T comparable](items ...T) Set[T] {
    s := make(Set[T])
    for _, item := range items {
        s[item] = struct{}{}
    }
    return s
}

// ❌ 泛型不是必须的——能用接口解决就不用泛型
// Go 的泛型设计哲学:显式优于隐式,简单优于灵活
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 07.零值利用

// ✅ 零值就是有用的初始值
var buf bytes.Buffer       // 可直接 Write
var mu sync.Mutex          // 可直接 Lock/Unlock
var wg sync.WaitGroup      // 可直接 Add/Wait

// ✅ 切片判空用 nil
if users == nil {
    users = make([]User, 0)
}

// ✅ 空 map 可以读,但不能写
var m map[string]int
v := m["key"]    // ✅ OK: v = 0
// m["key"] = 1  // ❌ panic: assignment to nil map
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 08.defer 使用

// ✅ 资源释放
f, err := os.Open("file.txt")
if err != nil {
    return err
}
defer f.Close()

// ✅ defer 配合函数返回值
func trace(name string) func() {
    start := time.Now()
    return func() {
        log.Printf("%s took %v", name, time.Since(start))
    }
}
defer trace("query")()  // 注意 () 调用

// ❌ 循环中 defer 导致资源泄漏
for _, f := range files {
    fd, _ := os.Open(f)
    defer fd.Close()  // 全部延迟到函数结束!
}

// ✅ 循环中用闭包
for _, f := range files {
    func() {
        fd, _ := os.Open(f)
        defer fd.Close()
        process(fd)
    }()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 10.测试规范

// ✅ 表驱动测试
func TestAdd(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"正数", 1, 2, 3},
        {"零", 0, 0, 0},
        {"负数", -1, 1, 0},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Add(tt.a, tt.b); got != tt.want {
                t.Errorf("Add() = %v, want %v", got, tt.want)
            }
        })
    }
}

// ✅ testify 断言库
func TestUser(t *testing.T) {
    u := NewUser("name")
    assert.NotNil(t, u)
    assert.Equal(t, "name", u.Name)
}

// ✅ 并行测试
func TestParallel(t *testing.T) {
    t.Parallel()
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 11.包组织与依赖注入

// ✅ 按功能分层,避免循环依赖
myapp/
├── cmd/              // 入口
│   └── server/
│       └── main.go
├── internal/         // 私有包
│   ├── handler/      // HTTP handler(薄层)
│   ├── service/      // 业务逻辑
│   ├── repository/   // 数据访问
│   └── model/        // 数据模型
├── pkg/              // 可公开的库
├── go.mod
└── go.sum

// ✅ 依赖注入:构造函数注入
type UserService struct {
    repo Repository    // 依赖接口,不依赖具体实现
    cache Cache
}

func NewUserService(repo Repository, cache Cache) *UserService {
    return &UserService{repo: repo, cache: cache}
}

// ❌ 全局变量——阻碍测试,并发不安全
var db *sql.DB
func GetUser(id int64) (*User, error) { ... }  // 隐式依赖全局 db

// ✅ 显式传递依赖
func GetUser(db *sql.DB, id int64) (*User, error) { ... }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 09.接口设计

// ✅ 接口要小(单一职责)
type Reader interface {
    Read(p []byte) (n int, err error)
}

// ✅ 由调用方定义接口(Accept interfaces, return structs)
func Save(w io.Writer, data []byte) error {  // 接受接口
    _, err := w.Write(data)
    return err
}

// ✅ 编译期检查接口实现
var _ io.Reader = (*MyReader)(nil)

// ❌ 不要过早抽象
// 等到确实有第二个实现时再抽取接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 12.性能优化

// ✅ 切片预分配容量(减少扩容)
var s []int                      // nil slice, append 会扩容
s := make([]int, 0, 100)         // ✅ len=0, cap=100, 预分配

// ✅ strings.Builder 替代 + 拼接
var b strings.Builder
for _, s := range strs {
    b.WriteString(s)             // 零内存分配
}
result := b.String()

// ✅ sync.Pool 复用临时对象
var bufPool = sync.Pool{
    New: func() any { return new(bytes.Buffer) },
}
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)

// ✅ 传值 vs 传指针
func Process(u User) { ... }     // 小结构体(< 64 bytes)传值更快
func Process(u *User) { ... }    // 大结构体或需要修改时传指针

// ✅ 避免不必要的 []byte ↔ string 转换
// ❌ 每次都拷贝
// ✅ 只在最终需要时转换一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 13.常见反模式

反模式 问题 改进
忽略 error 隐患难排查 始终检查
panic 用于普通错误 崩溃 用 error 返回值
循环中 defer 资源泄漏 用闭包
裸 go func() goroutine 泄漏 配合 context
全局变量 并发不安全 依赖注入
过长的函数 难以维护 40 行内拆分
interface{} 滥用 丢失类型安全 用泛型或具体接口

# 14.工具链与自动化

# 必须用
go fmt ./...           # 格式化(无参数,Go 的风格是统一的)
go vet ./...           # 静态分析
golangci-lint run      # 综合检查

# 推荐用
go test ./...          # 单元测试
go test -race ./...    # 竞态检测(CI 中必须跑)
go test -cover ./...   # 代码覆盖率
go test -bench=. ./... # 性能基准测试
1
2
3
4
5
6
7
8
9
10
# .golangci.yml
linters:
  enable:
    - errcheck         # 检查未处理的 error
    - goimports        # 自动管理 import
    - govet            # go vet
    - staticcheck      # 深度静态分析
    - unused           # 未使用的变量/函数
    - revive           # 风格检查
    - gocritic         # 代码优化建议
    - gocyclo          # 圈复杂度

linters-settings:
  gocyclo:
    min-complexity: 15  # 超过 15 的圈复杂度报警
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# GitHub Actions
- name: Lint & Test
  run: |
    go fmt ./... && git diff --exit-code   # 格式不对则 CI 红
    go vet ./...
    golangci-lint run
    go test -race -cover ./...
1
2
3
4
5
6
7

# 15.代码审查清单

每次 Code Review 时,按以下清单逐项检查:

## 命名与格式
- [ ] 包名小写无下划线,导出符号大写开头,缩写全大写/全小写
- [ ] 单方法接口带 er 后缀(Reader/Writer)
- [ ] `go fmt` 通过(无 diff)

## 错误处理
- [ ] 所有 error 被检查,无 `_ = ...` 忽略
- [ ] 用 `%w` 包装错误保留链
- [ ] 错误只处理一次(log 或 return,不两者都做)

## 并发
- [ ] goroutine 有退出机制(context/cancel)
- [ ] channel 由发送方关闭
- [ ] 无竞态条件(`go test -race` 通过)
- [ ] 并发任务优先用 errgroup

## 设计
- [ ] 接口小而精,由调用方定义
- [ ] 接受接口,返回结构体
- [ ] 无循环依赖,无全局变量
- [ ] 能用接口解决就不引入泛型

## 性能
- [ ] 切片预分配容量(`make([]T, 0, cap)`)
- [ ] 字符串拼接用 `strings.Builder`
- [ ] 小结构体传值,大结构体传指针

## 测试
- [ ] 表驱动测试
- [ ] 覆盖正常和异常路径
- [ ] 有竞态检测和覆盖率报告
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 16.常见陷阱速查

# 16.1 goroutine 陷阱

# 陷阱 正解
1 闭包捕获循环变量 → 所有 goroutine 用同一个值 go func(v T) {}(item) 传参或 item := item(Go 1.22 前)
2 goroutine 没有退出 → 泄漏 用 context 或 channel 通知退出
3 向已关闭的 channel 发送 → panic defer close(ch) 确保只关闭一次
4 无缓冲 channel 死锁 用 select + default 或改用缓冲 channel

# 16.2 error 与 defer 陷阱

# 陷阱 正解
1 defer 中修改具名返回值 → 覆盖错误 注意 defer 执行时机,return err 后 defer 仍可改 err
2 defer f.Close() 吞掉 Close 的 error 用 defer func() { err = f.Close() }() 或 errors.Join
3 errors.Is(err, io.EOF) 未 wrap 无法匹配 fmt.Errorf("read: %w", err) 用 %w
4 nil interface 不是 nil error return (*MyError)(nil) → 返回值 != nil(interface 有类型信息)

# 16.3 性能陷阱

# 陷阱 正解
1 循环中 += 拼接字符串 strings.Builder 或 strings.Join
2 []byte(s) 和 string(b) 反复转换 一次转换到底,减少不必要的拷贝
3 大片连续内存的 slice → 底层数组无法 GC copy 到新 slice 再只保留新 slice
4 defer 在热循环中 → 函数退出才执行 热循环中不用 defer
#Go#代码规范
上次更新: 2026/06/17, 11:39:29
Python编程代码规范指南
Kotlin编程代码规范指南

← Python编程代码规范指南 Kotlin编程代码规范指南→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式