三个原则:
- Clarity(清晰)
Hal Abelson and Gerald Sussman
说过:
Programs must be written for people to read, and only incidentally for machines to execute.
程序是什么,程序必须是为了开发人员阅读而编写的,只是偶尔给机器去执行,99% 的时间程序代码面向的是开发人员, 而只有 1% 的时间可能是机器在执行,这里比例不是重点,从中我们可以看出,清晰的代码是多么的重要, 因为所有程序,不仅是 Go 语言,都是由开发人员编写,供其他人阅读和维护。
- Simplicity(简单)
可靠的前提条件就是简单,我们在实际开发中都遇到过,这段代码在写什么,想要完成什么事情,往往因为:
- 没有注释,代码密密麻麻,根本不知道从何入手
- 调用名称千奇百怪,不能见名知义
- 内部逻辑极其复杂等
令开发人员不理解这段代码,因此也不知道如何去维护,这就带来了复杂性, 程序越是复杂就越难维护,越难维护就会是程序变得越来越复杂, 因此,遇到程序变复杂时首先应该想到的是——重构,重构会重新设计程序,让程序变得简单。
- Productivity(生产力)
开发中往往有许多不必要的重复劳动和简单但麻烦的操作步骤,需要利用各种有效的途径来利用有限的时间完成开发效率最大化,
go-zero
有个准则是 工具大于约定和文档
,我个人是非常认同的,尽量使用工具来补足重复劳动和简单但麻烦的步骤,提升开发效率。
好的命名规范主要可以对应到上面的 清晰
和 简单
原则,因为它可以:
- 降低代码阅读成本
- 降低维护难度
- 降低代码复杂度
- 当变量名称在定义和最后依次使用之间的距离很短时,简短的名称看起来会更好
- 变量命名应尽量描述其内容,而不是类型
- 常量命名应尽量描述其值,而不是如何使用这个值
- 在遇到 for,if 等循环或分支时,推荐简短字母命名来标识参数和返回值
- package 名称也是命名的一部分,请尽量将其利用起来
- 使用一致的命名风格
- 全部小写
- 除单元测试
*_test.go
外尽量避免文件中出现下划线_
- 文件名称不宜过长,一些单词可以选择缩写
- 首字母小写
- 驼峰命名
- 见名知义,避免拼音替代英文
- 不建议包含下划线
_
- 不建议包含数字
适用范围
- 局部变量
- 函数出参、入参
- 驼峰式命名
- 可导出的必须首字母大写
- 不可导出的必须首字母小写
- 避免全部大写与下划线
_
组合
- 简单增删改查接口尽量组合 HTTP Method 的语意,如 GET(获取),POST(新建),PUT(更新),DELETE(删除)
- 带上版本号
- 脊柱式命名
- 小写单数单词、横杠
-
组合 - 见名知义
- 复杂接口统一为 POST 请求
GET /v1/personal-auth/1 获取用户 id 为 1 的个人认证信息
GET /v1/order?page=1&page_size=10&name=VIP1 分页获取订单信息列表
PUT /v1/user/1/password 更新用户 id 为 1 的用户的密码
GET /v1/proof/1 获取存证 id 为 1 的存证信息
POST /v1/proof 添加存证
DELETE /v1/proof/1 删除存证
- 请求体注意标对正确的参数来源,如
form, path 和 json
- 请求体显示设置必填参数和可选参数,必填参数附上
validate
和label
标签,可选参数在其数据来源后加上,optional
- 响应体统一以
json
形式返回 - 不推荐没有响应,如果不知道返回什么就返回该资源的
id
- 一般整数类型只选择
int32
或int64
,偏向枚举含义的整数类型选择int32
,其他选择int64
- 请求体和响应体各个参数均须附上注释,枚举类型须附上各个数值对应的含义
例子:
// Department 部门
type Department struct {
Id int64 `json:"id"` // 部门ID
Name string `json:"name"` // 部门名称
Description string `json:"description"` // 部门描述
}
// DepartmentPage 部门分页
type DepartmentPage struct {
Department
JobCount int64 `json:"job_count"` // 岗位数量
PersonnelCount int64 `json:"personnel_count"` // 人员数量
UnableDelete bool `json:"unable_delete"` // 不能删除
}
// GetDepartmentRequest 查询部门请求
type GetDepartmentRequest struct {
Id int64 `path:"id" validate:"required" label:"部门ID"` // 部门ID
}
// GetDepartmentResponse 查询部门响应
type GetDepartmentResponse struct {
Department
}
// GetDepartmentPagesRequest 查询部门分页请求
type GetDepartmentPagesRequest struct {
Type *int32 `form:"type,optional" validate:"omitempty,oneof=0 1 2" label:"部门类型"` // 部门类型(0-内部 1-外部 2-特殊)
Name string `form:"name,optional"` // 部门名称
Search string `form:"search,optional"` // 搜索
Page int64 `form:"page" validate:"required" label:"页数"` // 页数
PageSize int64 `form:"page_size" validate:"required" label:"每条页数"` // 每条页数
}
// GetDepartmentPagesResponse 查询部门分页响应
type GetDepartmentPagesResponse struct {
Count int64 `json:"count"` // 总数
PageCount int64 `json:"page_count"` // 页数
Results []*DepartmentPage `json:"results"` // 结果
}
// CreateDepartmentRequest 创建部门请求
type CreateDepartmentRequest struct {
Name string `json:"name" validate:"required" label:"部门名称"` // 部门名称
Description string `json:"description,optional"` // 部门描述
}
// CreateDepartmentResponse 创建部门响应
type CreateDepartmentResponse struct {
Department
}
// UpdateDepartmentRequest 更新部门请求
type UpdateDepartmentRequest struct {
Id int64 `path:"id" validate:"required" label:"部门ID" swaggerignore:"true"` // 部门ID
Name string `json:"name" validate:"required" label:"部门名称"` // 部门名称
Description *string `json:"description,optional"` // 部门描述
}
// UpdateDepartmentResponse 更新部门响应
type UpdateDepartmentResponse struct {
Department
}
PS:
- validator 结构体字段参数校验库:
- swag swagger 文档快速生成工具:
- swag2md 基于 swagger 文档快速生成 markdown 文档工具:
- 单个
import
不建议用圆括号包裹 - 按照
标准库
、第三方包
、其他项目包
和项目包
顺序分组引入(可以使用 goimports-reviser 工具):
import (
"context"
"github.com/zero-micro/go-zero/core/logx"
"gorm.io/gorm"
"gitlab.xxx.cn/go-tool/timex"
"gitlab.xxx.cn/my-project/model"
"gitlab.xxx.cn/my-project/pkg/db"
)
- 一般在 pkg 下的包,最好能业务无关化,达到复制粘贴到别的项目下也能开箱即用
- 不建议包调用设计成单例模式,最好设计成依赖注入的模式,减少包与包之间调用的耦合性
- 必须格式化(可以使用 gofumpt 工具)
- 必须有注释
- 必须有单元测试文件,复杂的包要附上 README.md
- 保持不信任原则,要有参数合法性检查或者默认参数机制
可以参考 go-tool 的包代码
- 另起协程要有 recover 机制,不能直接
go func
,建议使用 github.com/zeromicro/go-zero/core/threading 起协程
- 对象避免非指针返回
- 遵循有正常值返回则一定无 error,有 error 则一定无正常值返回的原则
- 有 error 必须处理,如果不能处理就必须抛出
- 避免下划线
_
接收 error
- 建议一个代码块结束空一行,如 if、for 等
func demo () {
if xxx {
// do something
}
for _, x := range xxx {
// do something
}
fmt.Println("xxx")
}
- return 前空一行
func demo(id string) (string, error) {
......
return "xxx", nil
}
- return 结构体时,直接声明并赋值,然后返回
// 推荐
func demo() (*user.GetUserInfoResp, error) {
......
return &user.GetUserInfoResp{
Id: in.Id,
Name: in.Name,
}, nil
}
PS:
- gofumpt 加强版 gofmt:
- goimports-reviser 加强版 goimports:
- goctl 定制化 goctl:
仓库一般有两个基础分支:
- dev(默认分支)
- main(用于发布)
通过 pull request 来合并新的代码:
- 协作者的代码通过 pr 合并到 dev
- dev 通过 pr 合并到 master
PS:
- merge 到 dev,使用 squash merge
- merge 到 main,使用普通的 merge
- 永远不向 main 直接 commit 代码
需要规范化 commit message,格式建议为:
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
其中 type 的类型有:
feat: A new feature
fix: A bug fix
docs: Documentation only changes
style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
refactor: A code change that neither fixes a bug nor adds a feature
perf: A code change that improves performance
test: Adding missing or correcting existing tests
chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
参考文献: