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