Golang编码规范
作者:互联网
文章目录
1. 前言
1.1. 一般信息【重要必读】
- 文档内容可能会与您的喜好冲突, 请尽量用包容的心态来接受; 不合理之处, 请评论指出。
1.2. 如何使用本编程规范
本规范的层次结构
- 本规范可分为三大部分,分别对 Go 语法、风格、编程实践作出规定与建议。
- 每一部分有若干专题,每一专题下有若干条目。
- 条目是规范的基本组成部分,每一条目由规定、定义、解释、示例、参考等项组成。
条目的级别和编号
- 本规范的条目分两个级别
- RULE:要求所有程序必须遵守,不得违反
- ADVICE:建议遵守,除非确有特殊情况
- 本规范所有条目都有编号,用来标识单条规范并用于自动化检查工具错误提示。
- 标号由"RULE"或"ADVICE"和三位数字标识,例如 RULE001, ADVICE001
1.3. 说明
参考下列规范并结合自己的业务场景
- 参考 “百度 Golang 编程规范V1.1”
- 参考 https://golang.org/doc/effective_go.html
- 参考 https://github.com/golang/go/wiki/CodeReviewComments
2. 语言规范
2.1 true/false 求值
-
{RULE001} 当明确 expr 为 bool 类型时,禁止使用 == 或 != 与 true/false 比较,应该使用 expr 或 !expr
-
{RULE002} 判断某个整数表达式 expr 是否为零时,禁止使用 !expr,应该使用 expr == 0
示例:
// GOOD:
var isWhiteCat bool
var num int
if isWhiteCat {
//
}
if num == 0 {
// ...
}
//BAD:
var isWhiteCat bool
var num int
if isWhite == true {
// ...
}
if !num {
// ...
}
2.2 Receiver
2.2.1 Receiver Type
- {RULE003} 如果 receiver 是 map、函数或者 chan 类型,类型不可以是指针
- {RULE004} 如果 receiver 是 slice,并且方法不会进行 reslice 或者重新分配 slice,类型不可以是指针
- {RULE005} 如果 receiver 是 struct,且包含 sync.Mutex 类型字段,则必须使用指针避免拷贝。
- {RULE006} 如果 receiver 是比较大的 struct/array,建议使用指针,这样会更有效率
- {ADVICE001} 如果 receiver 是 struct、array 或 slice,其中指针元素所指的内容可能在方法内被修改,建议使用指针类型
- {ADVICE002} 如果 receiver 是比较小的 struct/array,建议使用 value 类型
解释
-
关于 receiver 的定义详见 Receiver 定义:The receiver is specified via an extra parameter section preceeding the method name. That parameter section must declare a single parameter, the receiver. Its type must be of the form T or *T (possibly using parentheses) where T is a type name. The type denoted by T is called the receiver base type; it must not be a pointer or interface type and it must be declared in the same package as the method. The method is said to be bound to the base type and the method name is visible only within selectors for that type.
-
struct 或者 array 中的元素个数超过 3 个,则认为比较大,反之,则认为比较小
2.2.2 receiver 命名
- {ADVICE003} 尽量简短并有意义。
- {RULE007} 禁止使用“this"、”self“等面向对象语言中特定的叫法。
- {ADVICE004} receiver 的命名要保持一致性
示例
//GOOD:
// call()和 done()都使用了在上下文中有意义的"c"进行 receiver 命名
func (c Client) call() error {
// ...
}
func (c Client) done() error {
// ...
}
//BAD:
// 1. "c"和"client"命名不一致:done()用了 c,call()用了 client
// 2. client 命名过于冗余
func (c Client) done() error {
// ...
}
func (client Client) call() error {
// ...
}
// 不允许使用 self
func (self Server) rcv() error {
// ...
}
// 不允许使用 this
func (this Server) call() error {
// ...
}
2.3 类型申明
- {RULE008} 申明 slice 时,建议使用 var 方式申明,不建议使用大括号的方式,如果使用make方式,最好能够预估slice的大小和容量
解释:var 方式申明在 slice 不被 append 的情况下避免了内存分配示例,make指定容量会避免append扩容 - {RULE009} struct声明和初始化格式采用多行
- {RULE010} map,chan 均使用make方式初始化,可以理解返回的是个指针,slice有点区别,在扩容时,地址会变化
//GOOD:
var t []string
// 指定容量
res := make([]string, 0 ,10)
//定义如下:
type User struct{
Username string
Email string
}
u := User{
Username: "astaxie",
Email: "webben@gmail.com",
}
//BAD:
t := []string{}
res := make([]string)
2.4 Error Handler
-
{RULE011} 对于返回值中的 error,一定要进行判断和处理,不可以使用 ”_“ 变量忽略 error
-
{RULE012} 逻辑处理中禁用panic,对于其他的package对外的接口不能有panic,只能在包内采用。
-
{RULE012} 错误描述如果是英文必须为小写,不需要标点结尾
-
{RULE013} error的处理采用独立的错误流进行处理
//GOOD:
if inputIo, err := ioutil.ReadAll(ctx.Request.Body); err != nil {
// handling
}
if err != nil {
// error handling
return // or continue, etc.
}
// normal code
// 如果返回值需要初始化,则采用下面的方式
x, err := f()
if err != nil {
// error handling
return
}
// use x
//BAD:
// 忽略err不可采取
inputIo, _ := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
// error handling
} else {
// normal code
}
2.5 自定义类型的String循环问题
- {RULE014} 自定义的类型定义了String方法,那么在打印的时候会产生隐藏的一些bug
type MyInt int
// GOOD:
func(m MyInt) String() string {
return fmt.Sprint(int(m)) //这是安全的,因为我们内部进行了类型转换
}
//BAD:
func (m MyInt) String() string {
return fmt.Sprint(m) //BUG:死循环
}
2.6 Typical Data Races-数据竞争
参考:http://golang.org/doc/articles/race_detector.html#Race_on_loop_counter
-
{RULE015} Race on loop counter:注意闭包的调用,在循环中调用函数或者goroutine方法,一定要采用显示的变量调用,不要再闭包函数里面调用循环的参数,
-
{RULE016} Accidentally shared variable:协程中外层共享变量的使用,如外层中使用的err,协程中依然使用
-
{RULE017} Unprotected global variable:全局变量如果有修改的操作,需要进行加锁,如Map
//GOOD:
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
fmt.Println(j) // Good. Read local copy of the loop counter.
wg.Done()
}(i)
}
wg.Wait()
}
func main() {
f1, err := os.Create("file1")
if err != nil {
res <- err
} else {
go func() {
// This err is shared with the main goroutine,
// so the write races with the write below.
_, err := f1.Write(data)
res <- err
f1.Close()
}()
}
}
//BAD:
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i)
// Not the 'i' you are looking for.
wg.Done()
}()
}
wg.Wait()
}
func main() {
f1, err := os.Create("file1")
if err != nil {
res <- err
} else {
go func() {
// This err is shared with the main goroutine,
// so the write races with the write below.
_, err = f1.Write(data)
res <- err
f1.Close()
}()
}
}
2.7 引用第三包需要验证
-
{RULE018}业务中引入任何第三包之前,需要调研对比各个包之间的性能差异,使用范围(如star数量)
如json包:官方包encoding/json的性能较差,选用: jsoniter “github.com/json-iterator/go” 效率更高
2.8 字符串使用注意事项
- {RULE019} 避免循环内进行字符串拼接时,如果避免不了,采用bytes.Buffer方式,预估内部的Grow次数,避免内存的频繁申请
- {RULE020} 字符串的传值是传引用,任何修改都会导致重新分配空间
- {RULE021} 少量字符串可使用+或string包操作,尽量少用fmt.Sprintf
- {RULE022} 大量字符串的传参要传指针
2.9 embedding 的使用
- {ADVICE005} embedding 只用于"is a"的语义下,而不用于"has a"的语义下
- {ADVICE006} 一个定义内,多于一个的 embedding 尽量少用
解释
- 语义上 embedding 是一种“继承关系“,而不是”成员关系“
- 一个定义内有多个 embedding,则很难判断某个成员变量或函数是从哪里继承得到的
- 一个定义内有多个 embedding,危害和在 python 中使用”from xxx import *"是类似的
示例
//GOOD:
type Automobile struct {
// ...
}
type Engine struct {
// ....
}
// 正确的定义
type Car struct {
Automobile // Car is a Automobile engine engine
Engine // Car has a Engine
}
//BAD:
type Car struct {
Automobile // Car is a Automobile
Engine // Car has a Engine, but Car is NOT a Engine
}
2.10 完善单元测试和性能测试
- 测试应该会失败,并提供有用的消息,说明错误,输入内容,实际内容以及预期结果。gotest
- 公共功能函数应该做下性能测试,了解关键节点的IO代价,benchmark
if got != tt.want {
t.Errorf("Foo(%q) = %d; want %d", tt.in, got, tt.want)
// or Fatalf, if test can't test anything more past this point
}
2.11 业务需要梳理接口IO消耗
- 在访问stored,redis,da等资源时,需要梳理业务接口的IO代价,及时调整设计实现,实现IO最小化
3. 风格规范
3.1 Go 文件 Layout
-
{RULE023} 建议文件按以下顺序进行布局
-
- General Documentation: 对整个模块和功能的完整描述注释,写在文件头部。
- package:当前 package 定义
- imports:包含的头文件
- Constants:常量
- Typedefs: 类型定义
- Globals:全局变量定义
- functions:函数实现
- public functions :首字符大写的函数
- private functions:首字母小写的函数
-
{RULE024} 对于以上的各个部分,采用单个空行分割,同时:
- 多个类型定义采用单个空行分割
- 多个函数采用单个空行分割
-
{ADVICE007} 函数内不同的业务逻辑处理建议采用单个空行分割
-
{ADVICE008} 常量或者变量如果较多,建议按照业务进行分组,组间用单个空行分割
示例
//GOOD:
/* Copyright 2015 Webben Inc. All Rights Reserved. */
/* bfe_server.go - the main structure of bfe-server */
/* modification history -------------------- 2014/6/5, by Zhang San, create*/
/* DESCRIPTION This file contains the most important struct 'BfeServer' of go- bfe and new/init method of the struct.*/
package bfe_server
// imports
import (
"fmt"
"time"
"code.google.com/p/log4go"
"bfe_config/bfe_conf"
"bfe_module"
)
const (
version = "1.0.0.0"
)
// typedefs
type BfeModule interface {
Init(cfg bfe_conf.BfeConfig, cbs *BfeCallbacks) error
}
type BfeModules struct {
modules map[string]BfeModule
}
// vars
var errTooLarge = errors.New("http: request too large")
//functions
func foo() {
//...
}
3.2 General Documentation Layout
- {ADVICE009} 建议每个文件开头部分包括文件 copyright 说明(copyright)
- {ADVICE010} 建议每个文件开头部分包括文件标题(Title)
- {ADVICE011} 建议每个文件开头部分包括修改记录(Modification History)
- {ADVICE012} 建议每个文件开头部分包括文件描述(Description)
解释
-
Title 中包括文件的名称和文件的简单说明
- Title 应该在一行内完成
-
Modification History 记录文件的修改过程,并且只记录最主要的修改
- 当书写新的函数模块时,只需要使用形如"Add func1()"这样的说明
- 如果后面有对函数中的算法进行了修改,需要指出修改的具体位置和修改方法
- Modification History 的具体格式为:<修改时间>, <修改人>, <修改动作 >
-
Description 详细描述文件的功能和作用
示例
//GOOD:
/* Copyright 2015 Webben Inc. All Rights Reserved. */
/* bfe_server.go - the main structure of bfe-server */
/* modification history -------------------- 2014/6/5, by Zhang San, create*/
/* DESCRIPTION This file contains the most important struct 'BfeServer' of go- bfe and new/init method of the struct.*/
package bfe_server func func1() {
// ...
}
//BAD:
package bfe_server func func1() {
// ...
}
3.3 import 规范
- {RULE025} 需要按照如下顺序进行头文件 import,并且每个 import 部分内的 package 需按照字母升序排列
- 系统 package
- 第三方的 package
- 程序自己的 package
- {RULE026} 每部分 import 间用单个空行进行分隔
示例:
//GOOD:
import (
"fmt"
"time"
)
import (
"code.google.com/p/log4go"
)
import (
"bfe_config/bfe_conf"
"bfe_module"
)
//GOOD:
import (
"fmt"
"time"
"code.google.com/p/log4go"
"bfe_config/bfe_conf"
"bfe_module"
)
//BAD:
// 同一类 package 内 import 顺序需按字母升序排序
import (
"time"
"fmt"
)
// 不同类的 package import 顺序出错(先第三方 package,再程序自己的 package)
import (
"bfe_config/bfe_conf"
"bfe_module"
)
import (
"code.google.com/p/log4go"
)
//BAD:
import (
// 同一类 package 内 import 顺序出错
"time"
"fmt"
// 不同类的 package import 顺序出错(先第三方 package,再程序自己的 package)
"bfe_config/bfe_conf"
"bfe_module"
"code.google.com/p/log4go"
)
3.4 Go 函数 Layout
3.4.1 函数注释
- {ADVICE013} 函数的注释,建议包括以下内容
- Description:对函数的完整描述,主要包括函数功能和使用方法
- Params:对参数的说明
- Returns:对返回值的说明
示例
/**
Init - initialize log lib
** PARAMS:
* - levelStr: "DEBUG", "TRACE", "INFO", "WARNING", "ERROR", "CRITICAL"
* - when: "M", minute* "H", hour* "D", day
* "MIDNIGHT", roll over at midnight
* - backupCount: If backupCount is > 0, when rollover is done, no more than
* backupCount files are kept - the oldest ones are deleted.
** RETURNS:
* nil, if succeed
* error, if fail
**/
func Init(levelStr string, when string, backupCount int) error {
// ...
}
3.4.2 函数参数和返回值
- {ADVICE014} 对于“逻辑判断型”的函数,返回值的意义代表“真”或“假”,返回值类型定义为 bool
- {ADVICE015} 对于“操作型”的函数,返回值的意义代表“成功”或“失败”,返回值类型定义为 error
- 如果成功,则返回 nil
- 如果失败,则返回对应的 error 值
- {ADVICE016} 对于“获取数据型”的函数,返回值的意义代表“有数据”或“无数据/获取数据失败”,返回值类型定义为(data, error)
- 正常情况下,返回为:(data, nil)
- 异常情况下,返回为:(data, error)
- {RULE027} 函数返回值小于等于 3 个,大于 3 个时必须通过 struct 进行包装
- {ADVICE017} 函数参数不建议超过 3 个,大于 3 个时建议通过 struct 进行包装
示例:
//GOOD:
type student struct {
name string
email string
id int
class string
}
// bool 作为逻辑判断型函数的返回值
func isWhiteCat() bool {
// ...
}
// error 作为操作型函数的返回值
func deleteData() error {
// ...
}
// 利用多返回值的语言特性
func getData() (student, error) {
// ...
}
// BAD:
type student struct {
name string
email string
id int
class string
}
// 使用 int 而非 bool 作为逻辑判断函数的返回值
func isWhiteCat() int {
// ...
}
// 操作型函数没有返回值
func deleteData() {
// ...
}
// 没有充分利用 go 多返回值的特点
func getData() student {
// ...
}
// 返回值>3
func getData() ( string, string, int, string, error) {
// ...
}
3.5 程序规模
- {RULE028} 每行代码不超过 100 个字符。
- {RULE029} 每行注释不超过 100 个字符。
- {ADVICE018} 函数不超过 100 行。
- {ADVICE019} 文件不超过 2000 行。
解释
- 现在宽屏比较流行,所以从传统的 80 个字符限制扩展到 100 个字符
- 函数/文件太长一般说明函数定义不明确/程序结构划分不合理,不利于维护
3.6 命名规范
3.6.1 文件名
- {RULE030} 文件名都使用小写字母,如果需要,可以使用下划线分割
- {RULE031} 文件名的后缀使用小写字母
示例:
//GOOD:
// 可以使用下划线分割文件名web_server.go
// 文件名全部小写http.go
//BAD:
// 文件名不允许出现大写字符webServer.go
// 文件名后缀不允许出现大写字符http.GO
3.6.2 函数名/变量名
- {RULE032} 采用驼峰方式命名,禁止使用下划线命名。首字母是否大写,根据是否需要外部访问来决定
示例:
//GOOD:
// 本 package 可以访问的函数
func innerFunc() bool {
// ...
}
// 其余 package 可以访问的函数
func OuterFunc() error {
// ...
}
//BAD: // 禁止用下划线分割
func inner_Func() bool {
var srv_name string
// ...
}
// 禁止用下划线分割
// 其余 package 可以访问的函数
func Outer_Func() error {
// ...
}
3.6.3 常量
- {ADVICE020} 建议都使用大写字母,如果需要,可以使用下划线分割
- {ADVICE021} 尽量不要在程序中直接写数字,特殊字符串,全部用常量替代
解释
- go 标准库中常量也有驼峰的命名方式,故这里不做强制限制。
示例
//GOOD:
// 大写字母
const METHOD = "Get"
// 下划线分割
const HEADER_USER_AGENT = "User-Agent"
// go 标准库中的命名方式
const defaultUserAgent = "Go 1.1 package http"
//BAD:
// 全部为下划线分割的小写字母
const header_user_agent = "User-Agent"
3.6.4 缩写词
- {RULE033} 缩写词要保持命名的一致性。
- 同一变量字母大小写的一致性
- 不同变量间的一致性
示例:
//GOOD:
var URL string
var ID int
var appID int
type ServeURL struct {
// ...
}
//BAD:
var Url string
// not consistent
var ID int
var appid int
type ServUrl struct {
// ...
}
3.7 缩进
- {RULE034} 使用 tab 进行缩进。
- {RULE035} 跨行的缩进使用 gofmt 的缩进方式。
- {RULE036} 设置 tabstop=4
解释
- 要求设置 tabstop=4 是考虑到不同编辑器跨行字符串对齐显示的一致性
3.8 空格
- {RULE037} 圆括号、方括号、花括号内侧都不加空格
- {RULE038} 逗号、冒号(slice 中冒号除外)前不加空格,后边加一个空格
- {RULE039} 所有二元运算符前后各加一个空格(作为函数参数时除外)
//GOOD:
var (
s = make([]int, 10)
)
func foo() {
m := map[string]string{"language": "golang"}
r := 1 + 2
func1(1+2)
fmt.Println(m["language"])
}
//BAD:
var ( s = make( []int , 10 ))
func foo() {
m := map[string]string{ "language" : "golang" }
r := 1+2
func1(1 + 2)
fmt.Println(m[ "language" ])
}
3.9 括号
- {ADVICE022} 除非用于明确算术表达式优先级,否则尽量避免冗余的括号
//GOOD:
if x {}
func func1() int {
var num int
return num
}
//BAD:
if (x) {
}
func func1 int {
var num int
return (num)
}
3.10 注释
- {ADVICE023} 单行注释,采取"//“或者”/…/"的注释方式。
- {ADVICE024} 多行注释,采取每行开头“//“或者用”/* … /“包括起来的注释(”/“和”*/"作为独立的行)
- {ADVICE025} 紧跟在代码之后的注释,使用"//"
解释
- 大多数情况下,使用”//"更方便
示例
//GOOD:
/* This is the correct format for a single-line comment */
// This is the correct format for a single-line comment
/** This is the correct format for a multiline comment* in a section of code.*/
// This is the correct format for a multiline comment
// in a section of code. var a int
// this is the correct format for a
// multiline comment in a declaration
//BAD:
/* This is an incorrect format for a multiline comment* in a section of code.*/
var a int /* this is an incorrect comment format */
4. 编程实践
4.1 error string
- {ADVICE026} error string 尽量使用小写字母,并且结尾不带标点符号解释
- 因为可能 error string 会用于其它上下文中示例
//GOOD:
fmt.Errorf("something bad")
//BAD:
fmt.Errorf("Something bad")
4.2 Don’t panic, Do Recover
- {RULE040} 除非出现不可恢复的程序错误,不要使用 panic,用多返回值和 error。
- {RULE041} 避免大量使用Recover,在比较重要的代码中增加 recover 代码,将错误截获到并自己处理,如gin框架中Render数据的时候会抛出panic,这时候需要捕获panic,避免服务宕机
4.3 关于 lock 的保护
-
{ADVICE026} 如果临界区内的逻辑较复杂、无法完全避免 panic 的发生,则要求适用 defer 来调用Unlock,即使在临界区过程中发生了 panic,也会在函数退出时调用 Unlock 释放锁
解释:go 提供了 recover,可以对 panic 进行捕获,但如果 panic 发生在临界区内,则可能导致对锁的使用没有释放
这种情况下,即使 panic 不会导致整个程序的奔溃,也会由于”锁不释放“的问题而使临界区无法被后续的调用访问
示例
//GOOD:
func doDemo() {
lock.Lock()
defer lock.Unlock()
// 访问临界区
}
//BAD:
func doDemo() {
lock.Lock()
// 访问临近区
lock.Unlock()
}
-
{ADVICE027} 上述操作如果造成临界区扩大后,需要建立单独的一个函数访问临界区。
对于如下的代码:
func doDemo() {
lock.Lock()
// step1: 临界区内的操作
lock.Unlock()
// step2: 临界区外的操作
}
//如果改造为 defer 的方式,变为如下代码,实际上扩大了临界区的范围(step2 的操作也被放置在临界区了)
func doDemo() {
lock.Lock()
defer lock.Unlock()
// step1: 临界区内的操作
// step2: 临界区外的操作
}
//优化:需要使用单独的匿名函数,专门用于访问临界区:
func doDemo() {
func() {
lock.Lock()
defer lock.Unlock()
// step1: 临界区内的操作操作
}()
// step2: 临界区外的操作
}
4.4 日志的处理
- {ADVICE028} 日志打印会损耗性能,在关键节点上打印信息,区分Warn和Error,强制需要立即报错的使用Error,Warn用于一段时间内累计的错误报警
4.5 unsafe package
- {ADVICE029} 除非特殊原因,不建议使用 unsafe package
- 比如进行指针和数值 uintptr 之间转换就是一个特殊原因
- 临时变量使用,并且内存占用很大,代码逻辑上不会修改和传入未知的三方包进行任何的修改和访问,如string和[]byte的转换,go-common基础组件中封装utils/extend方法,用于操作这类情况的处理,具体使用看业务场景
示例
// ToString 把 []byte 转换为 string 没有多余的内存开销。
// 使用该方法需要了解到 []byte 将和 string 公用一块内存, 修改 []byte 的数据将导致 string 发生变化,
// 这打破了字符串不可以修改的特性,如果你恰恰需要这么做,可能非常的有用。
// 要保证字符串不可变的特性,就必须保证 []byte 不会发生变化,或者立即消费 string,
func ToString(s []byte) string {
return *(*string)(unsafe.Pointer(&s))
}
//使用该函数要明确两个事情:
// - 确定字符串是否是字面量,或者内存被分配在只读空间上。
// - 确保访问该函数结果的函数是否会按照你的意愿访问或修改数据。
func UnsafeToBytes(s string) []byte {
strHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: strHeader.Data,
Len: strHeader.Len,
Cap: strHeader.Len,
}))
}
4.6 避免Golang函数传参引发的值拷贝
- {RULE042} 遵守 2.2 receiver 规则,对于大字符串,使用指针传参,对于大的struct切片,使用[]*struct声明使用方式,避免拷贝
4.7 业务代码中禁止使用直接开协程,管道
-
{RULE043} 业务代码中禁止直接使用go关键字,避免启动的协程不可控,使用 go-common 包提供的协程池进行相关的操作,限制收敛协程的数量,协程池使用说明:[gouroutine使用说明]
-
{RULE044} 业务代码中禁止使用chan管道,避免造成死锁和性能损失,有需要使用管道的地方,向基础组件同学提相关的需求
4.8 Slice使用的注意事项
-
{RULE045} 使用常量值做为slice的索引取值时,要注意越界问题,需要判断len(arr) > index
-
{RULE046} 了解slice的append底层原理,容量不够时会另开空间,在函数传递时使用slice,注意函数内部使用append后,另开空间,函数外层无感知
4.9 类型断言
- {RULE047} 对interface进行类型的断言时,必须先进行判断
4.10 代码复用及抽象化设计
- {ADVICE030}进行代码复用,提炼抽象公共模块设计实现复杂功能
BAD:
4.11 定义静态变量替换代码硬编码
- {RULE}多定义静态变量,取代代码中的硬编码,增强代码的可以阅读性,
//GOOD:
type cacheLevel int
const (
//多级缓存开启策略配置
USE_ONLY_STORED cacheLevel = 1 //仅仅开启第二层stored分布式缓存,关闭第三层redis缓存
USE_ONLY_REDIS cacheLevel = 2 //仅仅开启第三层redis缓存,关闭第二层stored分布式缓存
USE_STORED_REDIS cacheLevel = 3 //开启第二层stored,第三层redis缓存
)
if level == USE_ONLY_STORED {
//...
}
//BAD:
if level == 1 {
//...
}
4.12 这份文档存在的意义是让大家写出统一风格的代码,让模块可维护性和可读性更好;
标签:编码,string,bfe,...,使用,规范,Golang,func,error 来源: https://blog.csdn.net/Webben/article/details/111587610