2

送给学Go或者转Go同学的一套编码规范

 1 year ago
source link: https://studygolang.com/articles/35990
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
code.png

有没有 xdm 是从别的语言转 Go 的,比如 Java 、php 等,有兄弟在刚开始学的时候疑惑怎么能写出来优秀的代码。最近在项目中也 codereview 了不少 Go 语言的代码,有必要总结下代码规范,算是一个笔记记录了。

说在前面,这只是我们团队的一套规范而已。

今天我们聊一下 Go 的编码规范,大概分为几大模块,如注包/变量/常量命名、基本语法、函数、错误处理、心得等。

1. 代码风格

1.1 代码格式

  • 代码必须用 gofmt 进行格式化,goland 可以配置,可以自行搜索一下配置
  • 我们编写的代码每行应该不超过 120 个字符,超出部分用换行解决。
  • 单个文件最大行数最大不超过 800 行.
  • 单个函数最大行数不超过 80 行。
  • import 规范
    • 不要使用相对路径引入包,例如 import ../util/net
    • 在导入包时,多个相同包名冲突时,必须使用导入别名
// bad
"github.com/google/uuid"

// good
uuid "github.com/google/uuid"
  • 导入的包建议分组,引用匿名包建议用一个新的分组,并加上注释方便后面小伙伴阅读
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 声明、初始化和定义

  • 一个函数需要使用多个变量时,可以在函数最开头处使用 var 声明。在函数外部声明的变量不能使用 :=,会踩坑,不知道的可以评论区留言(要评论不易呀)!
var (
    port = 8081
    metricServerPort = 2001
)
  • 在初始化结构体用 &struct 代替 new(struct),确保与结构体初始化一致,初始化结构体时换行。
// bad
stu := new(S)
stu.Name = "张三"

// good
stu := &S{
    Name:"李四"
}
  • 使用 make 在声明 map、array 等应该指定容器的容量,从而达到预先分配内容。
users := make(map[int]string, 10)

tags := make([]int, 0, 10)
  • 使用标准 var 关键字事,不要指定类型,除非它与表达式的类型不同。
// bad
var _f string F()

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

// good 
var _f F()

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

1.3 error 处理

  • 若函数返回 error, 必须对 error 进行处理,如果业务允许可以用 _ 接受忽略。对应 defer 可以不用显式进行处理。
// bad
func InitConfig() error {
    ...
}
InitConfig()


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

// good 
func InitConfig() (int, error) {
    ...
}
  • 错误需要单独处理,尽量不要与其他的逻辑耦合在一起。
// 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.4 panic处理

  • 业务代码中禁止抛出 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.1 字符串

好像学过的语言中,都是从字符串开始说起的。就像写代码第一行都是从 Hello World!一样!同意的点赞哈。

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

// good
if len(s) == 0 {
    ...
}
  • 字符串去除前后子串。
// 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.1 if

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

4.2 for

  • 不允许在 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.3 range

  • 如果不需要 key 直接用 _ 忽略,value 也一样。
for _, v := range students {
    ...
}

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

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

注: 若操作指针时请注意不能直接用 s := v。想知道可以评论区告诉我哦!

4.4 switch

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

4.5 goto

  • 业务中不允许使用 goto。
  • 框架和公共工具也不允许使用 goto。
  • 传参和返回的变量小写字母。
  • 传入参数时slice、map、interface、chan 禁止传递指针类型。
  • 采用值传递,不用指针传值。
  • 入参个数不能超出 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,需要注意

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

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

欢迎关注微信【程序员祝融】,原文链接:https://mp.weixin.qq.com/s/lfjP9DEia2WL4UabxsDq0w

微信.png


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK