首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Go 编码建议——风格篇

Go 编码建议——风格篇

作者头像
恋喵大鲤鱼
发布2022-01-10 10:38:45
发布2022-01-10 10:38:45
1.4K0
举报
文章被收录于专栏:C/C++基础C/C++基础

文章目录

为形成统一的 Go 编码风格,提高代码的可读性、安全性和易维护性,在 Google Golang 代码规范 的基础上,给出编码风格建议。使用时,可根据实际情况进行了调整和补充。

1.格式化

代码必须用 gofmt 格式化。

gofmt 使用制表符进行缩进,使用空白进行对齐。

IDE 在保存代码时可设置自动执行 gofmt,如 GoLand 的 Settings > Tools > File Watchers 中可勾选 go fmt 并指定作用范围。

2.代码行

行长度

一行代码不要超过120列,超过的情况,使用合理的换行方法换行。

例外场景:

  • import 模块语句
  • struct tag
  • 工具生成代码

换行方式

采用惰性换行,换行前应尽可能地占满不留空位。

代码语言:javascript
复制
// Bad
fmt.Printf("%v %v %v %v %v %v %v %v %v %v %v %v %v %v\n",
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,89, 144, 233)

// Good
fmt.Printf("%v %v %v %v %v %v %v %v %v %v %v %v %v %v\n", 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,
89, 144, 233)

不必要的空行

  • 函数体第一行不要换行。
代码语言:javascript
复制
// Bad
func foo() {

	// func body
}

// Good
func foo() {
	// func body
}
  • 函数调用和对调用结果的处理,是紧密相连的,不能加空行
代码语言:javascript
复制
// Bad
res, err := foo()

if err != nil || res.Ret != 0 {
	return
}

// Good
res, err := foo()
if err != nil || res.Ret != 0 {
	return
}
  • return 语句前不要换行
代码语言:javascript
复制
// Bad
func foo() {
	// func body
	
	return
}

// Good
func foo() {
	// func body
	return
}

3.括号和空格

  • 遵循 gofmt 的逻辑。
  • 运算符和操作数之间要留空格。
  • 作为输入参数或者数组下标时,运算符和运算数之间不需要空格,紧凑展示。
代码语言:javascript
复制
var i int= 1 + 2 					// 运算符和操作数之间要留空格
v := []float64{1.0, 2.0, 3.0}[i-i]  // i-i 作为下标不留空格
fmt.Printf("%f\n", v+1)				// v+1 作为入参不要留空格
  • 不必要的括号
代码语言:javascript
复制
// Bad
if foo && (int(bar) > 0) {
	// ...
}

// Good
if foo && int(bar) > 0 {
	// ...
}

4.字符串

使用反引号表示原始字符串字面值,避免转义。

代码语言:javascript
复制
// Bad
wantError := "unknown name:\"test\""

// Good
wantError := `unknown error:"test"`

5.import 包

分组

导入应该分为三组:

  • 标准包
  • 外部包
  • 内部包

带域名的包名都属于外部包,如 github.com/xxx/xxx。内部包是指不能被外部 import 的包

代码语言:javascript
复制
// Bad
import (
	"fmt"
	"os"
	"go.uber.org/atomic"
	"golang.org/x/sync/errgroup"
	"myproject/models"
    "myproject/controller"
)

// Good
import (
	"encoding/json"
    "strings"
	
	"go.uber.org/atomic"
	"golang.org/x/sync/errgroup"
	
	"myproject/models"
    "myproject/controller"
)

路径

在导入内部包时,不要使用相对路径引入包,应该使用完整的路径引入包。

代码语言:javascript
复制
// Bad
import (
    "../net"
)

// Good
import (
    "xxxx.com/proj/net"
)

别名

包名和 git 路径名不一致时,或者多个相同包名冲突时,使用别名代替。

代码语言:javascript
复制
// Bad
import (
	elastic "github.com/olivere/elastic/v7"
)

// Good
import (
	elastic "github.com/olivere/elastic/v7"
)

6.初始化

6.1 初始化 struct

使用字段名初始化结构体

代码语言:javascript
复制
// Bad
k := User{"John", "Doe", true}

// Good
k := User{
    FirstName: "John",
    LastName: "Doe",
    Admin: true,
}

例外:如果有 3 个或更少的字段,则可以在测试表中省略字段名称。

代码语言:javascript
复制
tests := []struct{
  op Operation
  want string
}{
  {Add, "add"},
  {Subtract, "subtract"},
}

省略结构中的零值字段

代码语言:javascript
复制
// Bad
user := User{
  FirstName: "John",
  LastName: "Doe",
  MiddleName: "",
  Admin: false,
}

// Good
user := User{
  FirstName: "John",
  LastName: "Doe",
}

例外:在字段名提供有意义上下文的地方可以显示指定零值。例如,表驱动测试中的测试用例可以受益于字段的名称,即使它们是零值。

代码语言:javascript
复制
tests := []struct{
  give string
  want int
}{
  {give: "0", want: 0},
  // ...
}

声明零值结构使用 var

代码语言:javascript
复制
// Bad
var user := User{}

// Good
var user User

初始化结构引用

在初始化结构引用时,请使用&T{}代替new(T)可以与结构体初始化在代码风格上保持一致。

代码语言:javascript
复制
// Bad
sval := T{Name: "foo"}

// inconsistent
sptr := new(T)
sptr.Name = "bar"

// Good
sval := T{Name: "foo"}

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

6.2 初始化 map

初始化 map 优先使用 make() 函数而不是字面量,因为这样看起来容易和申明区分开来。

代码语言:javascript
复制
// Bad
var (
  // m1 读写安全
  // m2 在写入时会 panic
  m1 = map[T1]T2{}
  m2 map[T1]T2
)
// 声明和初始化在视觉上很相似

// Good
var (
  // m1 读写安全
  // m2 在写入时会 panic
  m1 = make(map[T1]T2)
  m2 map[T1]T2
)
// 声明和初始化在视觉上是不同的

尽可能的情况下,请在初始化时提供 map 容量大小。

在另一方面,如果 map 包含固定的元素列表,则使用字面量初始化 map,因为这样可以在初始化的时候指定元素。

代码语言:javascript
复制
// Bad
m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3

// Good
m := map[T1]T2{
  k1: v1,
  k2: v2,
  k3: v3,
}

6.2 初始化 slice

非零值 slice 使用make()初始化,并指定容量。

代码语言:javascript
复制
// Bad
nums := []int{}

// Good
nums := make([]int, 0, SIZE)

零值切片(用 var 声明的切片)可立即使用,无需调用make()创建。

代码语言:javascript
复制
// Bad
// 非 nil 切片
nums := []int{}

// Good
// nil 切片
var nums []int

6.3 变量申明

短变量声明

本地变量声明应使用短变量声明形式(:=

代码语言:javascript
复制
// Bad
var s = "foo"

// Good
s := "foo"

最小化作用域

尽量缩小变量作用范围。

代码语言:javascript
复制
// Bad
err := ioutil.WriteFile(name, data, 0644)
if err != nil {
 return err
}

// Good
if err := ioutil.WriteFile(name, data, 0644); err != nil {
 return err
}

就近申明

变量申明的位置尽量靠近使用的地方。

代码语言:javascript
复制

6.4 避免使用 init()

尽可能避免使用 init()。当init()是不可避免或可取的,代码应先尝试:

  • 无论程序环境或调用如何,都要完全确定。
  • 避免依赖于其他 init() 函数的顺序或副作用。虽然 init() 顺序是明确的,但代码可以更改, 因此 init() 函数之间的关系可能会使代码变得脆弱和容易出错。
  • 避免访问或操作全局或环境状态,如机器信息、环境变量、工作目录、程序参数/输入等。
  • 避免I/O,包括文件系统、网络和系统调用。

7.错误处理

error 处理

  • error 作为函数的值返回,必须对 error 进行处理,或将返回值赋值给明确忽略。对于defer xx.Close()可以不用显式处理
  • error 作为函数的值返回且有多个返回值的时候,error 必须是最后一个参数
代码语言:javascript
复制
// 不要采用这种方式
func do() (error, int) {
}

// 要采用下面的方式
func do() (int, error) {
}
  • 采用独立的错误流进行处理。
代码语言:javascript
复制
// 不要采用这种方式
if err != nil {
    // error handling
} else {
    // normal code
}

// 而要采用下面的方式
if err != nil {
    // error handling
    return // or continue, etc.
}
// normal code
  • 如果函数返回值需用于初始化其他变量,则采用下面的方式:
代码语言:javascript
复制
x, err := f()
if err != nil {
    // error handling
    return // or continue, etc.
}
// use x
  • 错误返回的判断独立处理,不与其他变量组合逻辑判断。
代码语言:javascript
复制
// 不要采用这种方式:
x, y, err := f()
if err != nil || y == nil {
    return err   // 当y与err都为空时,函数的调用者会出现错误的调用逻辑
}

// 应当使用如下方式:
x, y, err := f()
if err != nil {
    return err
}
if y == nil {
    return fmt.Errorf("some error")
}
  • 带参数的 error 生成方式为:fmt.Errorf("module xxx: %v", err),而不是 errors.New(fmt.Sprintf("module xxx: %v",err))

panic 处理

  • 在业务逻辑处理中禁止使用 panic
  • 在 main 包中只有当完全不可运行的情况可使用 panic,例如:文件无法打开,数据库无法连接导致程序无法正常运行
  • 对于其它的包,可导出的接口一定不能有 panic
  • 在包内传递错误时,不推荐使用 panic 来传递 error
代码语言:javascript
复制
// 不推荐为传递 error 而在包内使用 panic。以下为反面示例

// TError 包内定义的错误类型
type TError string

// Error error接口方法
func (e TError) Error() string {
    return string(e)
}

func do(str string) {
    // ...
    // 此处的 panic 用于传递 error
    panic(TError("错误信息"))
    // ...
}

// Do 包级访问入口
func Do(str string) (err error) {
    defer func() {
        if e := recover(); e != nil {
            err = e.(TError)
        }
    }()
    do(str)
    return nil
}
  • 建议在 main 包中使用 log.Fatal 来记录错误,这样就可以由 log 来结束程序,或者将 panic 抛出的异常记录到日志文件中,方便排查问题
  • panic 捕获只能到 goroutine 最顶层,每个自行启动的 goroutine,必须在入口处捕获 panic,并打印详细堆栈信息或进行其它处理

recover 处理

  • recover 用于捕获 runtime 的异常,禁止滥用 recover
  • 必须在 defer 中使用,一般用来捕获程序运行期间发生异常抛出的 panic 或程序主动抛出的 panic
代码语言:javascript
复制
package main

import (
    "log"
)

func main() {
    defer func() {
        if err := recover(); err != nil {
            // do something or record log
            log.Println("exec panic error: ", err)
            // log.Println(debug.Stack())
        }
    }()
    
    getOne()
    
    panic(44) //手动抛出panic
}

// getOne 模拟 slice 越界 runtime 运行时抛出的 panic
func getOne() {
    defer func() {
        if err := recover(); err != nil {
            // do something or record log
            log.Println("exec panic error: ", err)
            // log.Println(debug.Stack())
        }
    }()
    
    var arr = []string{"a", "b", "c"}
    log.Println("hello,", arr[4])
}

// 执行结果:
2021/10/04 11:07:13 exec panic error:  runtime error: index out of range [4] with length 3
2021/10/04 11:07:13 exec panic error:  44

类型断言失败处理

  • type assertion 的单个返回值形式针对不正确类型将产生 panic。因此,请始终使用 “comma ok” 惯用法。
代码语言:javascript
复制
// 不要采用这种方式
t := i.(string)

// 而要采用下面的方式
t, ok := i.(string)
if !ok {
    // 优雅地处理错误
}

参考文献

Go Code Review Comments github.com/uber-go/guide

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/11/14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 1.格式化
  • 2.代码行
    • 行长度
    • 换行方式
    • 不必要的空行
  • 3.括号和空格
  • 4.字符串
  • 5.import 包
    • 分组
    • 路径
    • 别名
  • 6.初始化
    • 6.1 初始化 struct
      • 使用字段名初始化结构体
      • 省略结构中的零值字段
      • 声明零值结构使用 var
      • 初始化结构引用
    • 6.2 初始化 map
    • 6.2 初始化 slice
    • 6.3 变量申明
      • 短变量声明
      • 最小化作用域
      • 就近申明
    • 6.4 避免使用 init()
  • 7.错误处理
    • error 处理
    • panic 处理
    • recover 处理
    • 类型断言失败处理
  • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档