# 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開發風格規范