# Go编码规范(uber)
##指向接口的指针
```go
// 接口使用值传递
// 接口包含两部分:
// 1.指向类型信息的指针
// 2.数据指针,如果存储的数据为指针,则直接存储,否则存储指向数据的指针
```
## 接收者与接口
```go
type S struct {
data string
}
// 可以被实例或指针调用
func (s S) Read() string {
return s.data
}
// 只能被指针调用
func (s *S) Write(str string) {
s.data = str
}
```
## Mutex默认值
```go
// sync.Mutex和sync.RWMutex的默认值可用,无需使用指针
// 如果使用指针指向struct,mutex可能为空字段
// bad
m := new(sync.Mutex)
// good
var m sync.Mutex
// 结构体不导出时可直接嵌套
type smap struct {
sync.Mutex
}
// 结构体导出时,使用名称决定是否导出
type SMap struct {
m sync.Mutex
}
```
## 复制切片和Map
```go
// 切片和map自带指针指向底层数据结构,传递时需注意
func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}
```
## 使用defer收尾
```go
p.Lock()
defer p.Unlock()
...
return
```
## Channel大小
```go
// Channel要么为1要么为0,其他大小需要考虑用什么防止Channel填满并阻止写入,以及发生这种情况时会发生什么
// 1
c := make(chan int, 1)
// 无缓存
c := make(chan int)
```
## 从1开始枚举
```go
// 使用const和iota定义枚举,一般不以0开始
const (
Add = iota + 1
Sub
)
// 在一些情况下,如0表示默认值等可以使用0开始
```
## Error类型
```go
// 简单的字符串error,无需多余信息
errors.New
// 格式化的字符串
fmt.Errorf
// 自定义类型实现Error()方法即可,可交给客户端检测和处理
// 使用errors.Wrap包裹
// 传递由内层函数return的error适用
// Wrap改变cause和other字段,Wrap调用的位置会存储在error栈里
func Wrap(other, newDescriptive error) error {
err := &Err{
previous: other, // error栈里上个error
cause: newDescriptive, // error的原因
}
err.SetLocation(1)
return err
}
// 使用var声明和初始化error方便调用方检测
var ErrCouldNotOpen = errors.New("could not open")
if err := f(); err != nil {
if err == ErrCouldNotOpen {
// handle
} else {
panic("unknown error")
}
}
// 使用自定义类型传递更多信息
type errNotFound struct {
file string
}
func (e errNotFound) Error() string {
return fmt.Sprintf("file %q not found", e.file)
}
func open(file string) error {
return errNotFound{file: file}
}
func use() {
if err := open(); err != nil {
if _, ok := err.(errNotFound); ok {
// handle
} else {
panic("unknown error")
}
}
}
// 作为公共API的一部分可以提供检测函数方便别人调用
func IsNotFoundError(err error) bool {
_, ok := err.(errNotFound)
return ok
}
```
## 处理类型断言
```go
t, ok := i.(string)
if !ok {
// handle
}
```
## 勿panic
```go
// 生产环境下的代码需要避免panic,容易导致联级错误
// 如果发生错误,则函数返回error让调用者处理
// panic/recover不是处理错误的手段,只有在无法解决时panic
// 如空引用
// 有一个例外,程序初始化时,有错误直接panic掉退出程序
// 在测试中,使用Fatal或FailNow来明显标记失败
f, err := ioutil.TempFile("", "test")
if err != nil {
t.Fatal("failed to set up test")
}
```
##使用strconv
```go
// strconv比fmt更快
// 避免频繁string与byte转化,一次性转化使用
```
# 代码风格
## 导入
```go
// 类似的分组声明
// 不相关的另起一组
import (
"a"
"b"
)
const (
a = 1
b = 2
)
const ENV = "env"
// func内也可用这种写法
var (
a = 1
b = 2
)
type (
Area float64
Volume float64
)
// 导入顺序
// 1.标准库
// 2.其他
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
```
## 包名
```go
// 包名全小写,无下划线,非复数,够简洁,大多情况下被导入无需重命名
// 避免common、util、shared、lib等指代不明的命名
// 导入的包名最后不符合时需重命名
```
## 函数名
```go
// 驼峰命名
// 测试函数可包含下划线
```
## 函数组织
```go
// 同文件内根据接收者分组
// 导出的函数尽可能靠前,在struct、const、var之后
// newXX()函数可在struct后,其他函数前
// 单个功能性函数靠后
```
## 全局变量声明
```go
// 表达式足够清晰时,不指定类型
// 为不导出的全局变量或常量加下划线前缀
// 为不导出的error,用err前缀
var _s = F()
func F() string { return "A" }
// 不直观时写明类型
var _e error = F()
func F() myError { return myError{} }
```
## 结构体嵌入
```go
// 嵌入的结构体应该靠前,并使用空行与其他类型字段区分
type Client struct {
http.Client
version int
}
// 初始化时使用字段名字
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
```
## 本地变量声明
```go
// 明确赋值使用:=
s := "str"
// 默认值明确使用var
// 使用var声明的切片可以直接使用,无需make
var s []int
```
## nil是有效的切片
```go
// nil是有效的切片,长度为0
// 无需返回长度为0的切片
if x == "" {
return nil // []int{}
}
// 检查切片是否为空时,使用len(),而不是nil
func isEmpty(s []string) bool {
return len(s) == 0 // s == nil
}
```
## 缩小变量作用域
```go
if err := ioutil.WriteFile(name, data, 0644); err != nil {
return err
}
```
## 避免使用不明参数
```go
// 使用/* */让参数可读性更高
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true /* isLocal */, true /* done */)
// 使用自定义类型,让参数可读性更高并保证类型安全
type Region int
func printInfo(name string, region Region)
```
## 避免转义
```go
// 使用原生字符串
wantErr := `unkown "text"`
```
## 初始化结构体引用
```go
// 使用&保持一致,而不是new
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}
```
## 格式化字符串
```go
// 在函数外声明格式化用字符串加const
const msg = "%v %v\n"
fmt.Printf(msg, 1, 2)
```
# 模式
## 测试表
```go
// 测试数据使用表的形式,测试逻辑保持简洁
tests := []struct{
give string
wantHost string
wantPort string
}{
{
give: "192.0.2.0:8000",
wantHost: "192.0.2.0",
wantPort: "8000",
},
{
give: "192.0.2.0:http",
wantHost: "192.0.2.0",
wantPort: "http",
},
{
give: ":8000",
wantHost: "",
wantPort: "8000",
},
{
give: "1:8",
wantHost: "1",
wantPort: "8",
},
}
for _, tt := range tests {
t.Run(tt.give, func(t *testing.T) {
host, port, err := net.SplitHostPort(tt.give)
require.NoError(t, err)
assert.Equal(t, tt.wantHost, host)
assert.Equal(t, tt.wantPort, port)
})
}
```
## 功能性选项
```go
// 在可遇见的会多传入的可选参数
// bad
func Connect(
addr string,
timeout time.Duration,
caching bool,
) (*Connection, error) {
// ...
}
// Timeout与caching必须提供
db.Connect(addr, db.DefaultTimeout, db.DefaultCaching)
db.Connect(addr, newTimeout, db.DefaultCaching)
db.Connect(addr, db.DefaultTimeout, false /* caching */)
db.Connect(addr, newTimeout, false /* caching */)
// good
type options struct {
timeout time.Duration
caching bool
}
// Option重新定义connect内行为
type Option interface {
apply(*options)
}
type optionFunc func(*options)
func (f optionFunc) apply(o *options) {
f(o)
}
func WithTimeout(t time.Duration) Option {
return optionFunc(func(o *options) {
o.timeout = t
})
}
func WithCaching(cache bool) Option {
return optionFunc(func(o *options) {
o.caching = cache
})
}
func Connect(
addr string,
opts ...Option,
) (*Connection, error) {
options := options{
timeout: defaultTimeout,
caching: defaultCaching,
}
for _, o := range opts {
o.apply(&options)
}
// ...
}
// Options使用时才用提供
db.Connect(addr)
db.Connect(addr, db.WithTimeout(newTimeout))
db.Connect(addr, db.WithCaching(false))
db.Connect(
addr,
db.WithCaching(false),
db.WithTimeout(newTimeout),
)
```
- Hello World
- UDP
- UDP服务端
- UDP客户端
- UDP广播
- 错误处理
- 编写好的异常处理
- panic和recover
- 并发编程
- Hello Goruntine
- 共享内存并发机制
- RWMutex
- CSP并发机制
- 多路复用和超时控制
- 通道关闭与广播
- Context与任务的取消
- 只运行一次
- 按需任意任务完成
- 所有任务完成
- 补充:range channel注意实现
- 对象池
- sync.Pool临时对象池
- 单元测试
- 表格测试法
- Banchmark
- BDD
- 反射
- 利用反射编写灵活的代码
- Struct Tag
- 万能程序
- 常用架构模式
- Pipe-filter pattern
- Micro Kernel
- 性能分析
- 高性能代码
- sync.MAP分析
- Concurrent Map
- GC友好的代码
- Uber开发风格规范