ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 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), ) ```