目录
实践出来的2千字Go编程规范
今天这篇文章是站在巨人的肩膀上,汇总了目前主流的开发规范,同时结合Go语言的特点,以及自己的项目经验总结出来的:爆肝分享两千字Go编程规范。
命名风格
(1)【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
反例:_name / __name / $name / name_ / name$ / name__
(2)【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。
注意,纯拼音命名方式更要避免采用。
正例:renminbi / alibaba / taobao / youku / hangzhou 等国际通用的名称,可视同英文。
反例:DaZhePromotion [ 打 折 ] / getPingfenByName() [评分] / int某变量 = 3。
(3)【强制】公用的变量、类型、接口、结构、函数,以及结构体的成员变量等命名使用UpperCamelCase风格。
正例:GolangStruct / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:Golangstruct / UserDo / XMLService / TCPUDPDeal / TAPromotion
(4)【强制】私有的变量、类型、接口、结构、函数,以及参数名、局部变量都统一使用lowerCamelCase风格,必须遵从驼峰形式。
正例:localValue / getHttpMessage() / inputUserId。
(5)【强制】常量命名命名使用 UpperCamelCase 风格,并使用 const 声明,力求语义表达完整清楚,不要嫌名长。
正例:const StatusOK = 200。
(6)抽象结构命名使用 Abstract 或 Base 开头;异常类命名使用 Err 结尾;测试类命名以 Test 开头,以它要测试的函数的名称结尾。
正例:ParamsErr := errors.New(“params err”)。
(7)接口命名规范一般使用 er 结尾:单个函数的接口名以“er”作为后缀,接口的实现则去掉 er;两个函数的接口名综合两个函数名,以 er 作为后缀,接口的实现则去掉 er;三个以上函数的接口,抽象这个接口的功能,类似于结构体命名。
(8)【强制】数据和切片类型命名以 Arr 结尾,map 类型以 Map 结尾。相同功用的结构体可以根据功能采用相同的结尾,Api 请求以 Req 结尾,相应以 Res 结尾,数据结构体以 xxxModel 结尾,xxx 即为数据表名。
正例:var userArr [3]string / type LoginReq struct{} / type UserDO struct{}。
(9)【强制】返回结果主要为布尔类型的函数,函数名可以 is、has 等开头。
(10)【强制】工程名统一使用小写,单词之间使 用 - 分割。包目录名一律使用小写,尽量采用一 个单词命名,单词间不用符号分割,统一使用单 数形式,但是结构体名如果有复数含义,结构体名可以使用复数形式。包目录下的包名( package namepackage ),如非 main 函数,和包目录名保持一致且单词间用 _分隔,测试文件以 _test 结尾。
正例:db-utils / package db_utils / package db_utils_test
反例:services
(11)为了达到代码自解释的目标,任何自定义编程元 素在命名时,使用尽量完整的单词组合来表达其意。反例:var a int 的随意命名方式。
(12)在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度,如规范【8】所示。
正例:startTime / startDate
反例:startedAt / startDt
(13)如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
代码格式
(1)【强制】如果是大括号内为空,则简洁地写成{}即可,大括号中间无需换行和空格;如果是非空代码块则:
左大括号前不换行
左大括号后换行
右大括号前换行
右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。
(2)【强制】左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格;而左大括号前需要空格。反例:if (空格 a == b 空格)。
(3)【强制】if / for / switch 等保留字与括号之间都必须加空格。
(4)【强制】任何二目、三目运算符的左右两边都需要加一个空格。说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号等。
(5)【强制】采用 tab 字符缩进,宽度设置为 4 个空格。
(6)【强制】单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
第二行相对第一行缩进一个 tab,从第三行开始,不再继续缩进;
运算符与上文一起作为结尾;
方法调用的点符号与上文一起作为结尾;
方法调用中的多个参数需要换行时,在逗号后进行;
在括号前不要换行;
【强制】函数参数在定义和传入时,多个参数逗号后边必须加空格。
控制语句
(1)【强制】在一个 switch 块内,每个 case 无需声明 break 来终止,如果想顺序执行使用 fallthrough;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代 码也没有。
(2)【强制】在高并发场景中,避免使用“等于”判断作为中断或退出的条件。说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。反例:判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。
(3)【推荐】表达异常的分支时,少用 if-else 方式,这种方式可以改写成:if condition { … return obj; } // 接着写 else 的业务逻辑代码;。
(4)说明:如果非使用 if()…else if()…else…方式表达逻辑,避免后续代码维护困难,【强制】请勿超过 3 层。
(5)正例:超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现,其中卫语句即代码逻辑先考虑失败、异常、中断、退出等直接返回的情况,以方法多个出口的方式,解决代码中判断分支嵌套的问题,这是逆向思维的体现。
(6)【参考】下列情形,需要进行参数校验:
调用频次低的方法;
执行时间开销很大的方法,此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失;
需要极高稳定性和可用性的方法;
对外提供的开放接口,不管是 RPC/API/HTTP 接口;
敏感权限入口。
(7)【参考】下列情形,不需要进行参数校验:
极有可能被循环调用的方法,但在方法说明里必须注明外部参数检查要求;
底层调用频度比较高的方法,毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题,一般 DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所以 DAO 的参数校验,可以省略。
杂项
(1)
【强制】在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
正例:
// 预编译当前正则表达式
re, _ := regexp.Compile("^hel+o")
// 是否匹配指定字符串
isMatch := re.MatchString("hello world")反例:
// 未进行预编译
re := "^hel+o"
isMatch := regexp.MatchString(re, "hello world")(2)【推荐】及时清理不再使用的代码段或配置信息。说明:对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余。正例:对于暂时被注释掉,后续可能恢复使用的代码片段,在注释代码上方,统一规定使用三个斜杠(///)来说明注释掉代码的理由。
异常日志
(1)【强制】使用控制流机制应对错误,通过从函数返回错误作为附加返回值来指示错误,如果函数有多个返回值,习惯上将错误值作为最后一个结果返回。如果错误只有一种情况,结果通常设置为布尔类型。nil 值表示没有错误。正例:res, err := somepkgAction()。
(2)【强制】对于总是成功返回的函数,不必返回错误。
(3)【强制】错误信息的首字母小写且避免换行。
