技术学习总结:编码规范| 青训营

131 阅读10分钟

 编码规范

1. 大小约定

【建议】单个文件长度尽量不要超过500行

【建议】每个函数长度尽量不要超过50行

【建议】单个函数圈复杂度尽量不要超过10,禁止超过15

【建议】单个行数中嵌套不要超过3层

【建议】单行注释尽量不超过80个字符

【建议】单行语句尽量不超过80个字符

2. 缩进、括号和空格约定

【规则】缩进、括号和空格都使用gofmt工具处理:

强制使用tab缩进

强制左括号不换行

强制所有的运算符和操作数之间要留空格

 

3 命名规范

3.1 包、目录命名规范

【规则】包名和目录名保持一致。一个目录尽量维护一个包下的所有文件

【规则】包名为全小写单词,不使用复数,不使用下划线

【建议】包名因该尽可能简短

3.2 文件名命名规范

【规则】文件名为全小写单词,使用"_"分词

3.3 标识符命名规范

【建议】短名优先,作用域越大命名越长且越有意义

变量、常量名

【规则】变量命名遵循驼峰法

【规则】常量使用全大写单词,使用"_"分词

【说明】首字母根据访问控制原则使用大写或者小写

【建议】对于常规缩略语,一旦选择了大写或者小写的风格,就应当在整份代码中保持这种风格,不要首字符大写和缩写两种风格混用。

以URL为例,如果选择了URL这种风格,则应在整份代码中保持。

Bad:UrlArray

Good:urlArray或URLArray

再以ID为例,如果选择了缩写ID这种风格:

Bad:appleId

Good:appleID

【规则】对于只在本文中有效的顶级变量、常量,应该使用"_"前缀,避免在同一个包中的其他文件中意外使用错误的值。例如:

var (

_defaultPort = 8080

   _defaultUser = "user"

)

【规则】若变量、常量为bool类型,则名称应该以Has、Is、Can或Allow开头

var isExist bool

var hasConflict bool

var canManage bool

var allowGitHook bool

【建议】如果模块的功能较为复杂、常量名称容易混淆的情况下,为了更好地区分枚举类型,可以使用完整的前缀

type PullRequestStatus int

const (

PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota

   PULL_REQUEST_STATUS_CHECKING

   PULL_REQUEST_STATUS_MERGEABLE

)

函数名、方法名

【规则】函数、方法(结构体或者接口下属的函数称为方法)命名规则:动词+名词

【规则】若函数、方法判断类型(返回值主要为bool类型),则名称应以Has、Is、Can或Allow等判断动词开头

func HasPrefix(name string, prefixes []string) bool {...}

func IsEntry(name string, entries []string) bool {...}

func CanManage(name string) bool {...}

func AllowGitHook() bool {...}

结构体、接口名

【规则】结构体命名规则:名词或名词短语

【规则】接口命名规则:以"er"作为后缀,例如Reader、Writer。接口实现的方法去掉"er",例如Read、Write

type Reader interface {

  Read(p []byte)(n int, err error)

}

// 多个函数接口

type WriteFlusher interface {

  Write([]byte)(int, error)

  Flush() error

}

3.4 空行、注释、文档规范

空行

【规则】空行需要体现代码逻辑的关联,所以空行不能随意,非常严重地影响可读性

【规则】保持函数内部实现的组织粒度是相似的,用空行分隔

注释与文档

Golang的go doc工具可以根据注释生成代码文档,所以注释的质量决定了代码文档的质量。

注释风格

【建议】统一使用中文注释,中西文之间严格使用空格分隔,严禁使用中文标点符号

【建议】注释应该是一个完整的句子,以句号结尾

【建议】句子类型的注释首字母需大写,短语类型的注释首字母需小写

【规则】注释的单行长度不能超过80个字符

包注释

【建议】每个包都应该有一个包注释。包注释会首先出现在go doc网页上。包注释应该包含:

包名、简介

创建者

创建时间

// Gogs (Go Git Service) is a painless self-hosted Git Service

package main

【说明】对于简单的非main包,也可用一行注释概况

【说明】对于一个复杂项目的子包,一般情况下不需要包级别注释,除非是代表某个特定功能的模块

【建议】对于相对复杂的非main包,一般都会增加一些使用示例或者基本说明,且以Package开头

【建议】对于特别复杂的包说明,一般使用doc.go文件用于编写包的描述,并提供与整个包相关的信息

函数、方法注释

【规则】每个函数、方法(结构体或者接口下属的函数称为方法)都应该有注释说明,包括三个方面(顺序严格):

函数、方法名,简要说明

参数列表,每行一个参数

返回值,每行一个返回值

结构体、接口注释

【建议】每个自定义的结构体、接口都应该有注释说明,放在实体定义的前一行,格式为:名称、说明。同时,结构体内的每个成员都要有说明,该说明放在成员变量的后面(注意对齐),例如:

// User,用户实例,定义了用户的基础信息。

type User struct{

  Username  string // 用户名

  Email string // 邮箱

}

【规则】当某个部分等待完成时,用TODO(Your name):开头的注释来提醒维护人员

【规则】当某个部分存在已知问题需要进行修复或者改进时,用FIXME(Your name):开头的注释来提醒维护人员

【规则】当需要特别说明某个问题时,可用NOTE(Your name):开头的注释

4. 导入规范

使用goimports工具,在保存文件时自动检查import规范。

【规则】如果使用的包没有导入,则自动导入;如果导入的包没有被使用,则自动删除

【规则】强制使用分行导入,即便仅导入一个包

【规则】导入多个包时注意按照顺序并使用空行区分:标准库包、程序内部包、第三方包

【规则】禁止使用相对路径导入

【规则】禁止使用import dot(“.”)简化导入

【规则】在所有其他情况下,除非导入之间有直接冲突,否则应避免导入别名

import (

"fmt"

   "os"

   "runtime/trace"

   nettrace "golang.net/x/trace"

)

【规则】如果包名导入路径的最后一个元素不匹配,则必须使用导入别名

import (

client "example.com/client-go"

   trace "example.com/trace/v2"

)

5.代码逻辑实现规范

5.1 变量、常量定义规范

【规则】函数内使用短变量声明(海象运算符)

【规则】函数外使用长变量声明(var关键字),var关键字一般用于包级别变量声明,或者函数内的零值情况

【规则】变量、常量的分组说明一般需要按照功能来区分,而不是将所有类型都分在一组

const (

  // Default section name.

  DEFAULT_SECTION = "DEFAULT"

  // Maximum allowed depth when recursively substituing variable names.

  _DEPTH_VALUES = 200

)

type ParseError int

const (

  ERR_SECTION_NOT_FOUND ParseError = iota + 1

  ERR_KEY_NOT_FOUND

  ERR_BLANK_SECTION_NAME ERR_COULD_NOT_PARSE

)

【规则】如果有可能,尽量缩小变量的作用范围

5.2 String类型定义规范

【规则】声明Print-style String时,将其设置为const常量,这有助于go vet对String类型实例执行静态分析

// Bad

msg := "unexpected values %v, %v\n"

fmt.Printf(msg, 1, 2)

// Good

const msg = "unexpected values %v, %v\n"

fmt.Printf(msg, 1, 2)

【建议】优先使用strconv而不是fmt,将原语转换为字符串或从字符串转换时,strconv速度比fmt快

5.3 Slice、Map类型定义规范

【建议】尽可能指定容器的容量,以便为容器预先分配内存,向make()传入容量参数会在初始化时尝试调整Slice、Map类型实例的大小,这将减少在元素添加到Slice、Map类型实例时的重新分配内存造成的损耗

【说明】使用make()初始化Map类型变量,使得开发者可以很好地区分开Map类型实例的声明,或初始化。使用make()还可以方便地添加大小提示

var (

// m1 读写安全

   // m2 在写入时会 panic

   m1 = make(map[T1]T2)

   m2 map[T1]T2

)

【建议】如果Map类型实例包含固定的元素列表,则使用 map literals(map 初始化列表)

5.4 结构体定义规范

【规则】嵌入结构体中作为成员的结构体,应该位于结构体内成员列表的顶部,并且必须有一个空行将嵌入式成员与常规成员分隔开

【规则】在初始化Struct类型的指针实例时,使用&T{}代替new{T},使其与初始化Struct类型实例一致

sval := T{Name: "foo"}

sptr := &T{Name: "bar"}

5.5 接口定义规范

【说明】特别的,如果希望通过接口的方法修改接口实例的实际数据,则必须传递接口实例的指针(将实例指针赋值给接口变量),因为指针指向真正的内存数据

5.6 函数、方法定义规范

【规则】函数、方法的参数排列顺序遵循以下几点原则(从左到右):

参数的重要程度与逻辑顺序

简单类型优先于复杂类型

尽可能将同种类型的参数放在相邻位置,则只需写一次类型

【规则】函数、方法的顺序一般需要按照依赖关系由浅入深由上至下排序,即最底层的函数出现在最前面。例如,函数ExecCmdDirBytes属于最底层的函数,它被ExecCmdDir函数调用,而xecCmdDir又被ExecCmd调用

【建议】避免实参传递时语义不明确,当参数名称的含义不明显时,使用块注释语法

func printInfo(name string, isLocal, done bool)

// Bad

printInfo("foo", true, true)

// Good

printInfo("foo", true /* isLocal /, true / done */)

5.7 函数、方法定义规范

【规则】err总是作为函数返回值列表的最后一个

【规则】如果一个函数return error,一定要检查它是否为空,判断函数调用是否成功。如果不为空,说明发生了错误,一定要处理它

【规则】不能使用_丢弃任何return的err。若不进行错误处理,要么再次向上游return err,或者使用log记录下来

【规则】尽早return err,函数中优先进行return检测,遇见错误马上return err

【建议】错误提示(Error Strings)不需要大写字母开头的单词,即使句子的首字母也不需要。除非那是专有名词或者缩写。同时,错误提示也不需要以句号结尾,因为通常在打印错误提示后还需要跟随别的提示信息

【建议】采用独立的错误流进行处理。尽可能减少正常逻辑代码的缩进,这有利于提高代码的可读性,便于快速分辨出那些还是正常逻辑代码

5.8 单元测试规范

【建议】单元测试都必须使用GoConvey编写,且覆盖率必须在80%以上

【规则】业务代码文件和单元测试文件放在同一目录下

【规则】单元测试文件名以*_test.go为后缀,例如:example_test.go

【规则】测试用例的函数名必须以Test开头,例如:Test_Logger

【规则】如果为结构体的方法编写测试用例,则需要以Test__的形式命名

【建议】每个重要的函数都要同步编写测试用例

【规则】测试用例和业务代码同步提交,方便进行回归测试

【规则】在测试中,我们很可能使用import dot (".") 这个特性,我们可以避免循环引用问题,除此之外都不要使用.进行简易导入

package foo_test

import (

  "bar/testutil" // also imports "foo"

  . "foo"

)

以上例子,该测试文件不能定义在于 foo 包里面,因为它 import bar/testutil,而 bar/testutil 有 import 了 foo,这将构成循环引用。所以我们需要将该测试文件定义在 foo_test 包中。并且使用 import . “foo” 后,该测试文件内代码能直接调用 foo 里面的函数而不需要显式地写上包名。