点击上方“中兴开发者社区”,关注我们
每天读一篇一线开发者原创好文
前言
本规范是针对 Golang 语言的编码规范,目的是为了统一项目的编码风格,提高软件源程序的可读性、可靠性和可重用性,提高软件源程序的质量和可维护性,减少软件维护成本。
本规范适用于部门所有产品的软件源程序,同时考虑到不同产品和项目的实际开发特性,本规范分成规则性和建议性两种:对于规则性规范,要求所有软件开发人员严格执行;对于建议性规范,各项目编程人员可以根据实际情况选择执行。本规范的示例都以Golang 语言描述。
本规范的内容包括:开发环境、包设计、布局、注释、命名、基本元素设计、函数设计、错误和异常设计、整洁测试等。
本规范自生效日起,对以后新编写的和修改的代码有约束力。
对本规范中所用的术语解释如下:
原则:编程时应该坚持的指导思想。
规则:编程时必须遵守的约定。
建议:编程时必须加以考虑的约定。
说明:对此规则或建议的必要的解释。
正例:对此规则或建议给出的正确例子。
反例:对此规则或建议给出的反面例子。
开发环境
【规则1-1】为了防止代码出现可移植性问题和兼容性问题,团队使用的操作系统、编译器类型、版本保持一致性。
【规则1-2】团队统一使用相同的IDE,并使用统一的代码模板,保持代码风格的一致性。
说明:系统中所有的代码看起来就好像是由单独一个值得胜任的人编写的。
【规则1-3】团队统一配置 IDE 的 TAB 为4个空格。
包设计
【原则2-1】包设计要满足单一职责原则。
说明: 这是SRP(Single Reponsibility Priciple) 在包(package)设计时的一个具体运用,我们要将包设计的非常内聚,包间的 API 比较少(类似于class中的public方法)。
【原则2-2】包内标识符遵守最小可见性原则。
说明: 如果一个标识符(interface名、类型名、变量名或函数名)在语义上仅在包内可见,则它的命名不要用大写开头。
【规则2-1】测试文件放在实现文件的同级目录下,便于Golang工具的使用。
说明: 虽然测试文件和实现文件的代码在同一个包内,但是测试用例的设计尽量站在包用户的角度去考虑,一般只测试包外可见的函数。
【规则2-2】包间禁止共享全局变量。
**说明:常量除外
【规则2-3】 不允许一个目录下有多个包。
【规则2-4】 import包时不允许使用点(.)操作。
正例:
反例:
**说明:测试文件中的测试框架除外
【规则2-5】 import包时不允许使用别名。
正例:
反例:
【规则2-6】 import包时不允许使用下划线(_)操作。
说明:下划线(_)操作的含义是:导入该包,但不导入整个包,而是执行该包中的init函数,因此无法通过包名来调用包中的其他函数。使用下划线(_)操作往往是为了注册包里的引擎,让外部可以方便地使用。
正例:
反例:
注:反例中的time包仅为冗余的包,并不是为了注册引擎,同时我们的产品代码中,也没有隐式注册引擎的需求,所以我们在import包时统一不允许使用下划线(_)操作。
布局
**【规则3-1】import 导入包时统一使用小括号,包名要另起一行 **
**说明:import "C" 除外
正例:
**【规则3-2】import 包时,路径分隔符一律使用Unix 风格,拒绝使用Windows 风格;即采用/ 而不是使用\ 分割路径。
**
正例:
反例:
**【规则3-3】import 包时以 $GOPATH 为基准使用绝对路径,不要从当前位置开始使用相对路径 **
正例:
反例:
**【规则3-4】包含空格在内,代码的行宽不应超过120列。 **
说明: 长行要在低优先级操作符处拆分成新行,拆分出的新行要进行适当的缩进,使排版整齐。
【规则3-5】程序实体之间有且仅有一行空行区分。
说明: 函数之间的空行,能够帮组我们快速定位函数的始末的准确位置;甚至在函数内部,将逻辑相关的代码放在一起也同样具有意义,它能够帮组我们更好地理解代码块的语义。超过一行的空行完全没有必要,部分粗心的程序员在处理这些细节时总存在着或多或少的问题,团队应该杜绝这样的情况发生。
【规则3-6】每个文件末尾都应该有且仅有一行空行。
【规则3-7】一元操作符如“!”、“~”、“++”、“--”、 “”、 “&”(地址运算符)等前后不加空格; “[]”、“.”、“->”这类操作符前后不加空格。*
【规则3-8】函数名之后不要留空格
说明: 函数名后紧跟左括号‘(’,以与关键字区别。
正例:
反例:【规则3-9】在进行“==”或“!="比较时,将常量或常数放在“==”或“!="号的右边。
正例:
反例:【规则3-10】 数组的初始化按照矩阵结构分行书写。
正例:
【建议3-1】 每写完一段代码,就使用gofmt工具格式化一下。
注释
注释有助于理解代码,有效的注释是指在代码的功能、意图层次上进行注释,提供有用、额外的信息,而不是代码表面意义的简单重复。
注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。每次写注释,你都应该做个鬼脸,感受自己在表达能力上的失败。
写注释时,首先想到的应该是通过重构来提高表达力,不要太早放弃。
【规则4-1】注释与所描述内容进行同样的缩进。
说明: 可使程序排版整齐,并方便注释的阅读与理解。
【规则4-2】避免垃圾注释。
说明: 对于代码本身能够表达的意思,不必增加额外的注释。
【规则4-3】注释符 “//” 或 "/*” (“/”) 与注释内容之间用一个空格分隔。*
【建议4-1】并非所有的函数都要配有函数头,短函数需要一个好名字而非太多描述。
【建议4-2】提倡代码自注释。
说明: 能用函数或变量时就不要用注释,如果可以的话,应该创建一个描述与注释所言同一事物的函数或变量用于消除注释。
【建议4-3】行注释和块注释都可行时,优先使用行注释。
【建议4-4】保证代码和注释的一致性。
说明: 修改代码同时修改相应的注释,不再有用的注释要删除。
【建议4-5】注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不可放在下面,如放于上方则需与其上面的代码用空行隔开,而且注释内容与与被注释的代码相同缩进。
命名
【规则5-1】命名要名副其实——要像给自己的baby 起名字一样谨慎来对待程序命名。
说明: 变量、函数的命名告诉我们,它为什么会存在,它做什么事,应该怎么用。如果名称需要注释来补充,那就不算是名副其实。要像给自己的baby 起名字一样谨慎来对待程序命名。
【规则5-2】目录名一律使用小写和中划线风格的名称。
正例:
knitter-agent
反例:
knitteragent
knitter_agent
KnitterAgent
knitterAgent
【规则5-3】 包名一律使用小写风格,通常为过滤掉中划线的目录名。
正例1:
目录名:context
对应的包名:context
正例2:
目录名:port-obj
对应的包名:portobj
【规则5-4】 开发文件命名一律使用小写和下划线风格的名称。
正例:
knitter_virtual_machine.go
反例:
knitter-virtual-machine.go
KnitterVirtualMachine.go
knittervirtualmachine.go
【规则5-5】标识符要采用英文单词或其组合,便于记忆和阅读,切忌使用汉语拼音来命名。
说明: 标识符应当直观且可以拼读,可望文知义,避免使人产生误解。程序中的英文单词一
般不要太复杂,用词应当准确。
【规则5-6】如果函数返回值的类型或变量的类型为bool,则名字前面加上is, has, may, can, should, need等词修饰会增强语意。
正例:
反例:
【规则5-7】接口名、类型名、变量名和函数名统一使用驼峰命名法,首字母是否大写由包外可见性决定。
说明: 应遵循最小可见性原则
【规则5-8】避免在名称中携带类型信息。
正例:
反例:
【规则5-9】避免在名称中携带作用域的信息。
正例:
反例:
【规则5-10】 变量名的主体应当使用“名词”或者“形容词+名词”。
【规则5-11】 函数名应当使用“动词”或者“动词+名词”(动宾词组)。
【规则5-12】 系统中每个实体概念对应一个词。
说明: 给每个抽象概念选一个词,并且在同一个系统中统一,以便符合SRP 原则。如在同一系统的代码中既有controller,还有manager 和driver,会令使用者困惑,应统一。
【规则5-13】 不使用双关语命名变量。
说明: 变量命名时应避免将同一单词用于不同目的,同一术语用于不同概念,应遵从“一词一义”规则。比如add在表达计算两个值的和的语义时,就不能再表达往一个数组切片插入一个元素的语义。
【规则5-14】 常量名使用驼峰命名法,首字母是否大写由包外可见性决定。
说明: 应遵循最小可见性原则
【规则5-15】 团队使用统一的缩略语,并和业界常用的缩略语保持一致。
说明: 较短的单词可通过去掉“元音”形成缩写,较长的单词可取单词的头几个字母形成缩写,一些单词有大家公认的缩写,常用单词的缩写必须统一。协议中的单词的缩写与协议保持一致。对于某个系统使用的专用缩写应该在某处注释中做统一说明。
正例: 如下单词的缩写能够被大家认可
temp 可缩写为:tmp
flag 可缩写为:flg
statistic 可缩写为:stat
increment 可缩写为:inc
message可缩写为:msg
规范的常用缩写如下:
常用词 | 缩写 | 常用词 | 缩写
----|----
Argument | Arg | Buffer | Buf
Clear | Clr | Clock | Clk
Compare | Cmp | Configuration | Cfg
Context | Ctx | Delay | Dly
Device | Dev | Disable | Dis
Display | Disp | Enable | En
Error | Err | Function | Fnct
Hexadecimal | Hex | High Priority Task | HPT
I/O System | IOS | Initialize | Init
Mailbox | Mbox | Manager | Mgr
Maximum | Max | Message | Msg
Minimum | Min | Multiplex | Mux
Operating System | OS | Overflow | Ovf
Parameter | Param | Pointer | Ptr
Previous | Prev | Priority | Prio
Read | Rd | Ready | Rdy
Register | Reg | Request | Req
Response | Rsp | Schedule | Sched
Semaphore | Sem | Stack | Stk
Synchronize | Sync | Timer | Tmr
Trigger | Trig | Write | Wr
【规则5-16】 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。
正例:
基本元素设计
变量与常量
【规则6-1-1】 一个变量有且只有一个功能,并与其名称相一致,不能把一个变量用作多种用途。
说明: 一个变量只用来表示一个特定功能,不能把一个变量用作多种用途,即同一变量取值不同时,其代表的意义也不同。除循环变量和收集计算结果的变量,在一个函数中,一个变量被赋值不应该超过一次。
【规则6-1-2】 代码中不允许出现魔法数。
说明: 魔法数,即拥有特殊意义,却又不能明确表现出这种意义的数字。用const来定义常数,并根据其意义为它命名,既提高了代码的可读性,又便于使用IDE 等工具进行查找修改。
【规则6-1-3】 如果 struct 中的数据变量需要进行 json 序列化,则需要以大写字母开头,同时需要 json 重命名。
说明: 结构体中的变量以大写字母开头,可以保证 json.Marshal 的时候数据持久化正确。如果结构体中的变量以小写字母开头,则使得 json.Marshal 的时候忽略该字段,使得该字段的值丢失,从而 json.Unmarshal 的时候将该变量的值置为默认值。由于结构体中的变量以大写字母开头, json 串中的字段 key 的字符串形式变成了以大写字母开始,这对于追求以 json 串全小写为美的我们来说,需要进行 json 重命名。
正例:
反例:
【建议6-1-1】 变量应尽可能的满足短跨度和短存活时间。
说明: 那些介于同一个变量多个引用点之间的代码可称为攻击窗口,我们用跨度来衡量一个变量的不同引用点之间的靠近程度,而变量的存活时间是一个变量存在期间所跨越的语句总数。跨度越短,则表明一个变量的不同引用点越靠近;存活时间越短,则表明一个变量经历的语句数越少。
我们追求的目标是短跨度和短存活时间,因为
(1)可以提高程序的可读性;
(2)可以减小变量的攻击窗口;
(3)可以减少变量的初始化错误;
(4)可以减少全局变量的使用;
(5)可以方便修改Bug;
(6)可以方便重构代码。
扩展阅读