命名规范
包
- 包名必须和目录名一致,尽量采取有意义、简短的包名,不要和标准库冲突。
- 包名全部小写,没有大写或下划线,使用多级目录来划分层级。
- 项目名可以通过中划线来连接多个单词。
- 包名以及包所在的目录名,不要使用复数,比如 net/utl 而不是 net/urls。
- 不要用 common、util、shared 或者 lib 这类宽泛的、无意义的包名。
- 包名要简单明了,例如 net、time、log。
函数
函数名采用驼峰式,首字母根据访问控制决定使用大写或小写。
文件
文件名要简短有意义,应小写并使用下划线分割单词。
结构体
- 采用驼峰命名方式,首字母根据访问控制决定使用大写或小写,例如 MixedCaps 或者 mixedCaps。
- 结构体名不应该是动词,应该是名词,比如 Node、NodeSpec。
- 避免使用 Data、Info 这类无意义的结构体名。
- 结构体的声明和初始化应采用多行:
// User 多行声明 type User struct { Name string Email string } // 多行初始化 u := User{ UserName: "colin", Email: "colin404@foxmail.com", }
接口
基本和结构体命名规则保持一致:
- 单个函数的接口名以 “er”作为后缀(例如 Reader,Writer),有时候可能导致蹩脚的英文,但是没关系。
- 两个函数的接口名以两个函数名命名,例如 ReadWriter。
- 三个以上函数的接口名,类似于结构体名。
变量
- 变量名必须遵循驼峰式,首字母根据访问控制决定使用大写或小写。
- 在相对简单(对象数量少、针对性强)的环境中,可以将一些名称由完整单词简写为单个字母,比如:user 可简写为 u;userID 可简写 uid。
- 对于私有特有名词为首个单词则使用小写(如 apiClient)。其他特有名词都应当使用该名词原有的写法,如 APIClient、repoID、UserID。
- 若变量类型为 bool 类型,则名称应以 Has,Is,Can 或 Allow 开头。
- 局部变量应当尽可能短小,比如使用 buf 指代 buffer,使用 i 指代 index。
常量
常量名必须遵循驼峰式,首字母根据访问控制决定使用大写或小写
Error
Error 类型应该写成 FooError 的形式,比如 type ExitError struct {}
Error 变量写成 ErrFoo
的形式,比如 var ErrFormat = errors.New("unknown format")
注释规范
日志规范
-
打印日志
- 在分支语句处打印。可判断出代码走了哪个分支,有助于判断请求的下一跳,继而继续排查问题。
- 写操作必须打印。写操作最可能引起比较严重的业务故障,写操作打印日志可在出问题时找到关键信息。
- 在循环中打印要慎重。如果循环次数过多会导致打印大量日志,严重拖累代码的性能,建议在循环中记录要点,在循环外面总结打印。
- 在错误产生的最原始位置打印。对于嵌套 Error,可在 Error 产生的最初位置打印 Error 日志,上层如果不需要添加必要的信息,可直接返回下层 Error(减少重复的日志打印)。
-
日志级别
- Debug:在开发测试阶段打印最详尽的信息(比如整个 HTTP 请求/响应的 Body)有助于 Debug,由于可能会严重拖慢性能,在上线时必须禁用。
- Info:记录有助于运营分析的有用信息,目标是满足需求但不至于日志量太大、频度过高。
- Warn:提示程序异常,一般是业务层面上不符合预期但不影响继续运行,或暂时影响但后续会恢复的情况。
- Error:提示程序出错(大部分),比如请求失败、创建资源失败等。需要记录以避免日后排障过程中被忽略。
- Panic:实际开发中很少用,通常只在需要错误堆栈,或不希望发生严重错误导致程序退出而采用 defer 处理错误时使用。
- Fatal:实际开发中很少用,此时问题相当严重,导致整个程序无法运行。
-
日志内容
- 避免输出敏感信息,例如密码、密钥等。
- 为方便调试,通常在 Debug 级别记录临时日志,可以特殊字符开头,比如
log.Debugf("XXXXXXXXXXXX-1:Input key was: %s", setKeyName)
。完成调试后可查找 XXXXXXXXXXXX 字符串找到临时日志,commit 前删除。 - 以小写字母开头,以 . 结尾,比如
log.Info("update user function called.")
。 - 为了提高性能,尽可能明确类型,例如使用
log.Warnf("init datastore: %s", err.Error())
而非log.Warnf("init datastore: %v", err)
。 - 最好包含两个信息。请求 ID(每次请求的唯一 ID可放在请求的通用日志字段中,便于过滤出某次请求)以及用户和行为(标识谁做了什么)。
- 避免将普通日志记录在错误级别上。
-
日志内容
- 避免输出敏感信息,例如密码、密钥等。
- 为方便调试,通常在 Debug 级别记录临时日志,可以特殊字符开头,比如
log.Debugf("XXXXXXXXXXXX-1:Input key was: %s", setKeyName)
。完成调试后可查找 XXXXXXXXXXXX 字符串找到临时日志,commit 前删除。 - 以小写字母开头,以 . 结尾,比如
log.Info("update user function called.")
。 - 为了提高性能,尽可能明确类型,例如使用
log.Warnf("init datastore: %s", err.Error()) 而非 log.Warnf("init datastore: %v", err)
。 - 最好包含两个信息。请求 ID(每次请求的唯一 ID可放在请求的通用日志字段中,便于过滤出某次请求)以及用户和行为(标识谁做了什么)。
- 避免将普通日志记录在错误级别上。
-
实践
- 开发调试、现网故障排障时,根据排障的过程优化日志打印。
- 避免打印无用日志,也不要遗漏关键信息,目标是仅凭借关键日志就能定位到问题。
- 总是将记录在本地文件,与日志平台解耦(确保中间出错时仍能记录日志)。
- 应用可能包含多个服务,一个服务包含多个实例,应集中化日志存储处理。
- 结构化日志记录,即添加默认通用字段到每行日志,方便日志查询和分析。包括:时间(精确到微秒),文件名-函数-行号,日志级别。如果是错误,还需要把调用栈打出来。
- 日志可分布在不同的组件、机器上,使用 RequestID 串联请求日志可提高排障效率。
- 支持动态开关 Debug 日志,可避免为排查问题调整日志级别时重启服务。
版本规范
一般使用 语义化版本规范(SemVer,Semantic Versioning),即 主版本号.次版本号.修订号(X.Y.Z,其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零)。
- 主版本号(MAJOR):不兼容的 API 修改。
- 必须在有任何不兼容的修改被加入公共 API 时递增。其中可以包括次版本号及修订级别的改变。每当主版本号递增时,次版本号和修订号必须归零。
- 主版本号为零(0.y.z)的软件处于开发初始阶段,由于一切都可能随时被改变,不应该被视为稳定版。1.0.0 被界定为第一个稳定版本,之后的所有版本号更新都基于该版本修改。
- 次版本号(MINOR):向下兼容的功能性新增及修改。一般偶数为稳定版本,奇数为开发版本。在任何公共 API 功能被标记为弃用时也必须递增,当有改进时也可以递增。其中可以包括修订级别的改变。每当次版本号递增时,修订号必须归零。
- 修订号(PATCH):向下兼容的问题修正,即 bug 修复。
项目目录结构规范
git commit 规范
TODO
Git Flow
TODO
其他可参考的文章:
1、https://mp.weixin.qq.com/s/ZiWTxLAhw-aJxHQBUG_9YA
2、Go 语言实践:编写可维护的程序的建议
网友评论