Go代码规范(1)

本文介绍了Uber在编写Go代码时遵循的规范和最佳实践,包括接口使用、接收者与接口、零值Mutexes、复制Slice和Map、Defer的使用、channel大小、枚举值、错误处理、代码样式等方面,旨在提高代码可读性和效率,同时减少错误。文章强调避免Panic,提倡使用defer管理和错误处理,并提供了关于包导入、函数命名、变量作用域等方面的指导。
摘要由CSDN通过智能技术生成

介绍

代码风格是代码的一种约定。用风格这个词可能有点不恰当,因为这些约定涉及到的远比源码文件格式工具 gofmt 所能处理的更多。

本指南的目标是通过详细描述 Uber 在编写 Go 代码时的取舍来管理代码的这种复杂性。这些规则的存在是为了保持代码库的可管理性,同时也允许工程师更高效地使用 go 语言特性。

本指南最初由 Prashant Varanasi 和 Simon Newton 为了让同事们更便捷地使用 go 语言而编写。多年来根据其他人的反馈进行了一些修改。

本文记录了 uber 在使用 go 代码中的一些习惯用法。许多都是 go 语言常见的指南,而其他的则延伸到了一些外部资料:

Effective Go
The Go common mistakes guide
所用的代码在运行 golint 和 go vet 之后不会有报错。建议将编辑器设置为:

保存时运行 goimports
运行 golint 和 go vet 来检查错误
你可以在下面的链接找到 Go tools 对一些编辑器的支持:[https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins]

指南

接口的指针

你几乎不需要指向接口的指针,应该把接口当作值传递,它的底层数据仍然可以当成一个指针。

一个接口是两个字段:

  1. 指向特定类型信息的指针。你可以认为这是 “type.”。
  2. 如果存储的数据是指针,则直接存储。如果数据存储的是值,则存储指向此值的指针。
    如果你希望接口方法修改底层数据,则必须使用指针。

接收者和接口

具有值接收者的方法可以被指针和值调用。

例如,

package main

import "fmt"

func main() {

	s1Val := S1{} //值
	s1Ptr := S1{} //指针
	s2Val := S2{} //值
	s2Ptr := &S2{}//指针

	var i F
	i = s1Val
	i = s1Ptr
	 
	// 以下不能被编译,因为 s2Val 是一个值,并且 f 没有值接收者
	//i = s2Val
	i = s2Ptr

	fmt.Print(i)
}
//(s S1)可以包容Value和Pointer传递
//(s *S2)只能接收Pointer传递
type F interface {
	f()
}
type S1 struct{}

func (s S1) f() {}

type S2 struct{}

func (s *S2) f() {}

零值 Mutexes 是有效的

零值的 sync.Mutexsync.RWMutex 是有效的,所以你几乎不需要指向 mutex 的指针
在这里插入图片描述

var mu sync.Mutex

mu.Lock
defer mu.Unlock()

如果你使用一个指针指向的结构体,mutex 可以作为一个非指针字段,或者,最好是直接嵌入这个结构体。
在这里插入图片描述

复制 Slice 和 Map

slice 和 map 包含指向底层数据的指针,因此复制的时候需要当心
接收 Slice 和 Map 作为入参
需要留意的是,如果你保存了作为参数接收的 map 或 slice 的引用,可以通过引用修改它。
在这里插入图片描述
返回 Slice 和 Map
类似的,当心 map 或者 slice 暴露的内部状态是可以被修改的。
在这里插入图片描述

Defer 的使用

使用 defer 去关闭文件句柄和释放锁等类似的这些资源。

在这里插入图片描述
defer 的开销非常小,只有在你觉得你的函数执行需要在纳秒级别的情况下才需要考虑避免使用。使用 defer 换取的可读性是值得的。这尤其适用于具有比简单内存访问更复杂的大型方法,这时其他的计算比 defer 更重要。

channel 的大小是 1 或者 None

channel 的大小通常应该是 1 或者是无缓冲的。默认情况下,channel 是无缓冲的且大小为 0。任何其他的大小都必须经过仔细检查。应该考虑如何确定缓冲的大小,哪些因素可以防止 channel 在负载时填满和阻塞写入,以及当这种情况发生时会造成什么样的影响。
在这里插入图片描述

枚举值从 1 开始

在 Go 中引入枚举的标准方法是声明一个自定义类型和一个带 iota 的 const 组。由于变量的默认值为 0,因此通常应该以非零值开始枚举。
在这里插入图片描述
在某些情况下,使用零值是有意义的,例如零值是想要的默认值。

type LogOutPut int

consg (
	LogToStdout LogOutput = iota
	LogToFile
	LogToRemote
)
//LogToStdout=0 LogToFile=1,LogToRemote=2

Error 类型

声明 error 有多种选项:

  • [ errors.New] 声明简单静态的字符串
  • [ fmt.Errorf] 声明格式化的字符串
  • 实现了 Error() 方法的自定义类型
  • 使用 [ “pkg/errors”.Wrap] 包装 error
    返回 error 时,可以考虑以下因素以确定最佳选择:
  • 不需要额外信息的一个简单的 error? 那么 [ errors.New] 就够了
  • 客户端需要检查并处理这个 error?那么应该使用实现了 Error() 方法的自定义类型
  • 是否需要传递下游函数返回的 error?那么请看 section on error wrapping
  • 否则, 可以使用 [ fmt.Errorf]
    如果客户端需要检查这个 error,你需要使用 [ errors.New] 和 var 来创建一个简单的 error。
    在这里插入图片描述
    如果你有一个 error 可能需要客户端去检查,并且你想增加更多的信息(例如,它不是一个简单的静态字符串),这时候你需要使用自定义类型。
    在这里插入图片描述
    在直接导出自定义 error 类型的时候需要小心,因为它已经是包的公共 API。最好暴露一个 matcher 函数(译者注:以下示例的 IsNotFoundError 函数)去检查 error。
// package foo
type errNotFound struct{
  file string
}

func (e errNotFound)Error() string{
  return fmt.Sprintf("file %q not found",e.file)
}
func IsNotFoundError(err error)bool{
 _,ok :=err.(errNotFound)
  return ok
}
func Open(file string) error {  
  return errNotFound{file: file}
}
// package bar
if err :=foo.Open("foo");err !=nil{
 if foo.IsNotFoundError(err){
    // handle
  }else{
    panic("unknown error")
  }
}

Error 包装

如果调用失败,有三个主要选项用于 error 传递:

  • 如果没有额外增加的上下文并且你想维持原始 error 类型,那么返回原始 error
  • 使用 [ “pkg/errors”.Wrap] 增加上下文,以至于 error 信息提供更多的上下文,并且 [ “pkg/errors”.Cause] 可以用来提取原始 error
  • 如果调用者不需要检查或者处理具体的 error 例子,那么使用 [ fmt.Errorf]
    推荐去增加上下文信息取代描述模糊的 error,例如 “connection refused”,应该返回例如 “failed to
    call service foo: connection refused” 这样更有用的 error。

请参考 Don’t just check errors, handle them gracefully.

处理类型断言失败

简单的返回值形式的类型断言在断言不正确的类型时将会 panic。因此,需要使用 “, ok” 的常用方式。
在这里插入图片描述

避免 Panic

生产环境跑的代码必须避免 panic。它是导致 级联故障 的主要原因。如果一个 error 产生了,函数必须返回 error 并且允许调用者决定是否处理它。
在这里插入图片描述
panic/recover 不是 error 处理策略。程序在发生不可恢复的时候会产生 panic,例如对 nil 进行解引用。一个例外是在程序初始化的时候:在程序启动时那些可能终止程序的问题会造成 panic。

var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML"))

甚至在测试用例中,更偏向于使用 t.Fatal 或者 t.FailNow 解决 panic 确保这个测试被标记为失败。
在这里插入图片描述

使用 go.uber.org/atomic

性能

strconv 优于 fmt

避免 string 到 byte 的转换

代码样式

聚合相似的声明

包的分组导入的顺序

包命名

函数命名

别名导入

函数分组和顺序

减少嵌套

不必要的 else

顶层变量的声明

在不可导出的全局变量前面加上 _

结构体的嵌入

使用字段名去初始化结构体

局部变量声明

nil 是一个有效的 slice

减少变量的作用域

避免裸参数

使用原生字符串格式来避免转义

初始化结构体

在 Printf 之外格式化字符串

Printf-style 函数的命名

设计模式

表格驱动测试

函数参数可选化

《未完待续,此文章为转载》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值