go: 编码规范

272 阅读8分钟

0/参考网址

juejin.cn/post/716923…

0/重点总结

库包名(也就是目录名称),小写,如果是组合词,用`中划线`连接,而不是`下划线`。
文件名,小写,组合词用下划线连接。

单驼峰:第一个单词首字母小写,以后每一个单词的首字母都大写。
双驼峰:每一个单词的首字母都大写

1/代码风格

1.1代码格式

- 代码必须用gofmt进行格式化,goland可以配置。如果不会配置,可以自行搜索下如何配置。
- 我们编写的代码每行应该不超过 120 个字符,超出部分用换行解决。
- 单个文件最大行数最大不超过 800 行.
- 单个函数最大行数不超过 80 行。

import规范:
   不要使用相对路径引入包,例如:import "../util/net",要使用绝对路径
   在导入包的时候,当多个包的名字重复的时候,必须使用别名
    // 错误
    "github.com/google/uuid"

    // 正确
    uuid "github.com/google/uuid"
    
    导入的包建议分组,
    其实,vscode现在会对不同的包进行排序,内置的包会放在最前面,第三方库包或者自己项目中的库包会放在后面。
import (
	// Go 标准库
	"fmt"

	//第三方包
	"github.com/jinzhu/gorm"
	"github.com/google/uuid"
	"github.com/go-redis/redis/v8"

	// 匿名包
	/import mysql driver
	_"github.com/jinzhu/gorm/dialects/mysql"

	// 内部包,自己项目中的包
	slice "xxx.local/pkg/v1/goslice"
	meta "xxx.local/pkg/v1/meta"
	gomap "xxx.local/pkg/v2/gomap"
)

1.2声明、初始化和定义

a)一个函数需要使用多个变量时,可以在函数的开头处使用var关键字声明。
在函数外声明的变量不能使用:=,会踩坑。
var {
   prot = 8081
   metricserverport = 2001
}

b)在初始化结构体用 &struct 代替 new(struct),确保与结构体初始化一致,初始化结构体时换行。
// 错误
stu := new(S)
stu.Name = "张三"

// 正确
stu := &S{
    Name:"李四"
}
c)使用make()函数来声明map array等容器对象的时候,应该指定容器的容量,从而达到预先分配内容。
users := make(map[int]string, 10)
tags := make([]int, 0, 10)

d)使用标准 var 关键字时,不要指定类型,除非它与表达式的类型不同。
// 错误
var _f string F()

func F() string {
    return "hello world!"
}

// 正确
var _f F()

func F() string {
    return "hello world!"
}

1.3error处理

1)如果函数返回error,那么必须对error进行处理,必须处理。
 如果业务上允许用_接受忽略,那么可以。
// bad
func InitConfig() error {
	...
}
InitConfig()


// good
func InitConfig() error {
	...
}
err := InitConfig()
if err != nil {
	...
}
// or 
_ := InitConfig() 
b)error 作为返回值时必须作为最后一个参数返回
// bad
func InitConfig() (error,int) {
	...
}

// good 
func InitConfig() (int, error) {
	...
}
c)错误需要单独处理,尽量不要与其他的逻辑耦合在一起。
// bad
res, err := InitConfig()
if err != nil || res != nil {
	return err
}

// good
res, err := InitConfig()
if err != nil {
    return err
}
if res != nil {
    return fmt.Errorf("invalid result")
}

1.4panic的处理

业务代码中,禁止抛出panic错误。
panic只允许在服务启动的时候出现,比如读取配置,链接数据库等,比如redis,mysql等。
业务代码中,建议使用error,而不是panic

1.5单元测试

每个重要的函数都要编写测试用例,合并代码要自动化运行一下所有的 test。
文件命名 xxx_test.go。
函数命名建议使用 Test函数名。

2/命名规范

在任何一门语言中,命名规范在代码规范中都非常重要。
一个统一的,精确的命名不仅仅是可以提高代码的可读性,也可以让人觉得这个同志真的会啊,牛!

2.1库包命名规范

包名必须与目录名一致(这和其他 php、Java 还是有一点不太一样的),
尽量采取有意义、简短的库包的名,不要与 go 的标准库名称一样。

包名小写,没有下划线,可以使用中划线隔开,使用多级目录来划分目录。
包名不要出现复数命名。
包名命名尽量简单一目了然,ge:user、log

2.2文件命名规范

文件名要见名思义,尽量简而短
文件名小写,组合词用下划线分割

2.3函数命名规范

与 php、Java 一样,必须遵循驼峰规范,Go 语言中需要根据访问的控制决定大驼峰还是小驼峰。
单元测试的函数用大驼峰,TestFunc。

2.4结构体命名规范

与 php、Java 一样,必须遵循驼峰规范,Go 语言中需要根据访问的控制决定大驼峰还是小驼峰。
避免使用 info 、data 这种无意义的名称。
命名使用名词而非动词。
结构体在声明和初始化的时候需要换行,eg:
type Student struct{
    Name string
    Age uint8
}

student := Student{
    Name: "张三",
    Age: 18,
}

2.5变量命名规范

和 php、Java 一样,必须遵循驼峰规范,Go 语言中需要根据访问的控制决定大驼峰还是小驼峰。
若变量为私有时,可以使用小写命名。
局部变量可以简写,eg:i 表示 index。
若变量代表 bool 值,则可以使用 Is 、Can、Has 前缀命名,eg:
var isExit bool
var canReturn bool

2.6 常量命名规范

必须遵循驼峰规范,Go 语言中需要根据访问的控制决定大驼峰还是小驼峰。
若代表枚举值,需要先创建。
type Code int

const (
    ErrNotFound Code = iota
    ErrFatal
)

3/类型

3.1字符串

a)字符串判空值
// bad
if s == "" {
    ...
}

// good
if len(s) == 0 {
	...
}
b)字符串去除前后子串。
// bad
var s1 "hello world"
var s2 "hello"
var s3 strings.TrimPrefix(s1, s2)

// good
var s1 "hello world"
var s2 "hello"
var s3 string

if strings.HasPrefix(s1, s2){
    s3 = s1[len(s2):]
}

3.2切片 slice

-   声明 slice。
// bad
s := []string{}
s := make([]string, 10)

// good
var s []string
s := make([]string, 0, 10)
-   非空判断
//bad
if len(slice) >0 {
    ...
}

// good
if slice != nil && len(slice) > 0 {
    ...
}
-   slice copy。
// bad
var b1,b2 []byte
for i, v := range b1 {
    b2[i] = v
}
for i := range b1 {
    b2[i] = b1[i]
}

// good
copy(b2,b1)
-   slice 新增。
// bad
var a,b []int
for _, v := range a {
    b = append(b,v)
}

// good
var a, b []int
b := append(b, a...)

3.4 结构体 struct

-   初始化需要多行。
type Student struct{
    Name string
    Age uint8
}

student := Student{
    Name: "张三",
    Age: 18,
}

4/控制语句

4.1if

if可以用局部变量的方式初始化
 if err := InitConfig; err != nil {
    return err
  }

4.2for

不允许在for中使用defer, defer只在函数结束时才会执行
// bad
for file := range files {
    fd, err := os.Open(file)
    if err != nil {
    	return err
    }
    defer fd.close()
    
}

// good
for file := range files{
    func() {
        fd,err := os.open(file)
    	if err!=nil {
    		return err
        }
    	defer fd.close()
    }()
}

4.3range

如果不需要 index,那么可以直接用 _ 忽略,value 也一样。
for _, v := range students {
    ...
}

for i, _ := range students {
    ...
}

for i, v := range students {
    ...
}

4.4 switch

和其他语言不一样,必须要有 defalt
switch type {
     case 1:
        fmt.Println("type = 1")
     case 2:
        fmt.Println("type = 2")
     default :
        fmt.Println("unKnown type")
}

4.5 goto

-   业务中不允许使用 goto。
-   框架和公共工具也不允许使用 goto

5/函数

传参和返回的变量小写字母。
传入参数时slice、mapinterfacechan 禁止传递指针类型。
采用值传递,不用指针传值。
入参个数不能超出 5 个,超过的可以用 struct 传值。

5.1 函数参数

返回值超出 1 个时,需要用变量名返回。
多个返回值可以用 struct 传。

5.2 defer

当操作资源、或者事物需要提交回滚时,可以在创建开始下方就使用 defer 释放资源。
创建资源后判断 error,非 error 情况后在用 defer 释放。

5.3 代码嵌套

为了代码可读性,为了世界和平,尽量别用太多的嵌套,因为真的很难有人类能看懂。

6/日常使用感悟

能不用全局变量就不用,可以用参数传值的方式,这样可以大大降低耦合,更有利于单元测试。
衣服开发中,在函数间多用 context 传递上下文,在请求开始时可以生成一个 request_id,便于链路、日志追踪。

6.1 提高性能

在业务开发中,尽量使用 strconv 来替代 fmt。
我们使用 string 字符串类型时,当修改的场景较多,尽量在使用时用 []byte 来替代。
因为每次对 string 的修改都需要重新再申请内存。

6.2 避免踩坑

append 要小心自动扩容的情况,最好在申明时分配好容量,避免扩容所带来的性能上的损耗以及分配新的内存地址。若不能确定容量,应选择一个比较大一点的值。
并发场景下,map 非线程安全,需要加锁。还有一种评论区告诉我吧。
interface 在编译期间无法被检查,使用上会出现 panic,需要注意

7/总结

本篇很讲了 Go 语言的编码规范,当时想说的,规范是大家预定的东西,每个公司、团队都会有不一样的规范,
只要大家一起遵循就好啦。你可以根据自己团队的需求,定一套属于自己团队的项目规范。如果想小伙伴一起遵循,可以借助一些工具来保障执行度。

讲了很多,虽然很基础,希望对于刚刚转 Go 语言,或者刚学习 Go 语言的同学有帮助吧。今天就到这里了。希望得到大家的一键三连。感谢!