Files
cs-note/hzh/GolangStar/Go编码规范/Go编码规范.md
T

265 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [go, golang, code-style, coding-standard]
create time: 2026-06-07 14:30
---
# Go 编码规范
## 概述
本文档整理自企业级 Go 项目开发规范,涵盖代码格式化、命名约定、错误处理、注释规范等核心方面。良好的编码规范是团队协作的基石,也是区分初级和中级开发者的关键标志。
> [!question] ❓ 思考一下
> 在编写 Go 代码之前,你是否考虑过:为什么 Go 要强制 `gofmt` 而不像其他语言那样提供多种风格选择?这种"一刀切"的设计背后体现了怎样的哲学?
## 正文
### 1. 代码格式化
#### 1.1 【必须】使用 gofmt 格式化
所有 Go 代码必须使用 `gofmt` 格式化,这是 Go 社区的硬性要求。
> [!tip] 💡 面试技巧
> 面试中如果被问到"Go 如何保证代码风格统一",可以回答:Go 通过内置的 `gofmt` 工具强制统一格式,不需要像 Java(Google Style / Alibaba Style)或 JavaScript(Prettier / ESLint)那样额外配置,从语言层面杜绝了代码风格的争论。
#### 1.2 【推荐】行宽控制
建议一行代码不超过 **120 列**,超长时合理换行。
> [!warning] ⚠️ 高频陷阱
> 函数签名**不要**为了凑列数而换行!`gofmt` 会使签名与函数内语句对齐,反而降低可读性。
```go
// 正确的长函数签名 —— 不换行
func (i *webImpl) GenerateAgentInstallLink(ctx context.Context, req *pb.GenerateAgentInstallLinkRequest) (*pb.GenerateAgentInstallLinkResponse, error) {
// ...
}
// 错误的长字符串拼接 —— 不要这样做!
pubkey := "ssh-rsa AAAAB3NzaC1yc2E..." +
"zi2SqaZVeeXmsF5GAGFJcUylujr78Wf6od8//SApYx8RCSkRhGo8cTsxADlBCoTttJvk6Ocmy+uqEFXulsI0j+nh2x352eCExlDSqr0Me0J0LIGq/u9eqwhNN5k"
// 正确做法:多行文本用原始字符串字面量
tmpl := `some
long
tedious
template`
```
### 2. Import 规范
#### 2.1 【必须】使用 goimports
- 标准包永远在最上面的第一组
- 内部包与第三方包之间用空行分隔
- 不使用相对路径引入包
```go
import (
// standard package & inner package
"encoding/json"
"myproject/models"
"strings"
// third-party package
"github.com/opentracing/opentracing-go"
// anonymous import package
// import filesystem storage driver
_ "git.code.oa.com/org/repo/pkg/storage/filesystem"
)
```
> [!note] 📝 核心考点
> - 带域名的包名都属于第三方包(如 `github.com/xxx/xxx`),无论是否当前项目内部
> - 匿名导入 `_` 通常用于执行包的 `init()` 函数(如注册驱动)
### 3. 错误处理
#### 3.1 【必须】error 作为最后一个返回值
```go
// 正确
func do() (int, error) { ... }
// 错误
func do() (error, int) { ... }
```
#### 3.2 【必须】独立错误流
```go
// 推荐:尽早 return
if err != nil {
return err
}
// normal code here
// 不推荐:else 嵌套
if err != nil {
// error handling
} else {
// normal code
}
```
#### 3.3 【必须】panic 的使用场景
> [!warning] ⚠️ 高频陷阱
> 不要用 `panic` 处理用户输入错误!`panic` 仅用于:
> 1. 不变量断言(invariant assertion)
> 2. `init()` 函数中初始化失败
> 3. 全局变量初始化中调用 `MustXXX` 系列函数
```go
// 正确:对不变量断言
func readText(n Node) string {
switch n := n.(type) {
case *TextNode:
return n.Text
case *CommentNode:
return n.Comment
default:
panic(fmt.Errorf("unexpected node type: %T", n))
}
}
// 错误:用户输入应该返回 error
v, err := strconv.Atoi(userInputFromKeyboard)
if err != nil {
panic(fmt.Errorf("invalid user input: %v", err)) // DON'T!
}
```
#### 3.4 【必须】recover 的正确姿势
```go
defer func() {
e := recover() // 注意:返回 interface{},不要命名为 err
if e != nil {
err, ok := e.(FatalError)
if !ok {
panic(e) // 继续抛出不认识的异常
}
// 处理已知类型的 panic
}
}()
```
### 4. 命名规范
> [!tip] 💡 面试技巧
> 面试中被问"go 命名规范"时,可以总结为:**驼峰式 + 首字母大小写控制可见性 + 专有名词保持原写法**。举例说明 `apiClient`(私有)vs `APIClient`(导出)。
| 规则 | 要点 |
|------|------|
| 包名 | 小写、简短、不与标准库冲突、避免 util/common/misc |
| 文件名 | 小写 + 下划线分割 |
| 结构体 | 驼峰、名词短语、避免 Info/Data |
| 接口 | 单方法以 `-er` 结尾(Reader/Writer) |
| 变量/常量 | 驼峰、短命名优先(局部变量 c > lineCount) |
| 函数 | 驼峰、首字母大小写控制导出 |
### 5. 控制结构
#### 5.1 【推荐】if 初始化语句
```go
if err := file.Chmod(0664); err != nil {
return err
}
```
#### 5.2 【必须】switch 必须有 default
```go
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.\n", os)
}
```
#### 5.3 【必须】range 丢弃不需要的值
```go
for key := range m { // 只需要 key
delete(m, key)
}
for _, v := range slice { // 只需要 value
_ = v
}
```
### 6. 注释规范
> [!question] ❓ 思考一下
> Go 要求每个导出的名字都必须有注释,但非导出类型的方法可以没有。你觉得这种设计反映了什么考量?
**必填注释清单:**
- 包注释(`// Package xxx`)
- 导出的结构体和接口
- 导出的函数和方法
- 导出的常量和变量
- 导出的类型定义和类型别名
### 7. 函数设计
| 规则 | 约束 |
|------|------|
| 参数数量 | 不超过 5 个 |
| 传递方式 | 优先值传递,非指针传递 |
| map/slice/chan/interface | 不要传指针 |
| 文件长度 | 不超过 800 行 |
| 函数长度 | 不超过 80 行 |
| 嵌套深度 | 不超过 4 层 |
### 8. defer 使用
> [!warning] ⚠️ 高频陷阱
> **禁止在循环中使用 `defer`!** 因为 defer 要到函数结束时才统一调用,会导致资源堆积。
```go
// 错误:循环中 defer
for _, v := range values {
fields, _ := db.Query(v)
defer fields.Close() // 所有 Close 直到函数结束才执行!
}
// 正确:闭包内 defer
for _, v := range values {
func() {
fields, _ := db.Query(v)
defer fields.Close() // 立即关联到当前迭代
}()
}
```
### 9. 依赖管理
- Go 1.11+ 必须使用 `go modules`
- `go.sum` 必须提交,不要加入 `.gitignore`
- 不建议提交 `vendor` 目录
---
## 附录:常用工具速查表
> [!tip] 💡 工具速查
> | 工具 | 作用 |
> |------|------|
> | `gofmt` | 自动格式化代码,保证格式统一 |
> | `goimports` | 在 gofmt 基础上自动增删 import |
> | `go vet` | 静态分析,检测多余代码、提前 return 等 |
> | `golint` | 检测不规范的地方(已迁移至 `staticcheck`) |
## 关联笔记
- [[hzh/GolangStar/Go环境搭建/Go环境搭建]] — 环境搭建配合 lint 工具使用
- [[hzh/GolangStar/Go面试题库/基础面试题]] — 编码规范相关面试题
- [[hzh/GolangStar/Go语言前景/Go语言前景]] — Go 的语言优势之一就是代码统一