| name | go-stdlib-style |
| description | Go 标准库编码风格 Skill。蒸馏自 golang/go 标准库源码、Effective Go、
Rob Pike 的 Go Proverbs、Russ Cox 的设计文档、Go Code Review Comments 官方指南。
触发词:「Go 标准库风格」「idiomatic Go」「地道 Go」「go stdlib style」「Rob Pike 风格」。
适用:Go 库开发、CLI 工具、网络服务、系统工具。
|
Go 标准库 · 编码 DNA
"Clear is better than clever." — Rob Pike, Go Proverbs
"A little copying is better than a little dependency." — Rob Pike
角色定义
此 Skill 激活后,你写出的 Go 代码应该让标准库 contributor 在 code review 时感觉
「这代码像是从 net/http 里拿出来的」,而不是「这是 Java 开发者写的 Go」。
这意味着:简单、显式、接口小、无魔法。
命名 DNA
6 条直觉规则:
-
包名:单词、小写、不下划线、不 camelCase
package http
package json
package sync
package httpUtils
package http_client
package HTTPServer
包名是命名空间,使用时 http.Get,不是 httpClient.Get。
-
导出名不重复包名
package http
type Request struct { ... }
func Get(url string) (*Response, error) { ... }
type HTTPRequest struct { ... }
func HTTPGet(url string) (*HTTPResponse, error) { ... }
-
接口名 = 方法名 + er(单方法接口)
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type Stringer interface { String() string }
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
type Readable interface { ... }
type DataProcessor interface { ... }
-
变量名短而精,作用域小则短
for i, v := range items { ... }
r, err := http.Get(url)
buf := make([]byte, 1024)
var defaultClient = &Client{}
var ErrNotFound = errors.New("not found")
for index, value := range itemList { ... }
httpResponseReader, requestError := httpClient.MakeGetRequest(requestUrl)
-
错误变量叫 err,不叫 error 或 e
n, err := r.Read(buf)
if err != nil {
return err
}
-
常量和包级变量不用全大写(除了真正的常量)
const maxRetries = 3
var defaultTimeout = 30 * time.Second
const MAX_RETRIES = 3
var DEFAULT_TIMEOUT = 30 * time.Second
结构偏好
接口设计:接口越小越好
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type ReadWriter interface {
Reader
Writer
}
type Storage interface {
Read() ([]byte, error)
Write([]byte) error
Delete(key string) error
List() ([]string, error)
Stats() Stats
Flush() error
}
函数设计:
func NewScanner(r io.Reader) *Scanner { ... }
func Open(name string) (*File, error) { ... }
type DialOption struct {
Timeout time.Duration
KeepAlive time.Duration
Resolver Resolver
}
func DialContext(ctx context.Context, network, address string, opt ...DialOption) (Conn, error)
错误处理:显式、不忽略、包上下文
f, err := os.Open(filename)
if err != nil {
return fmt.Errorf("open config file: %w", err)
}
defer f.Close()
return fmt.Errorf("parse response: %w", err)
return fmt.Errorf("Parse Response: %w", err)
return fmt.Errorf("parse response: %w.", err)
f, _ := os.Open(filename)
并发:
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
type Cache struct {
mu sync.Mutex
items map[string]Item
}
func (c *Client) Do(ctx context.Context, req *Request) (*Response, error) { ... }
注释哲学
Go 的注释规则极其简单,但要求严格:
type Reader interface { ... }
func Open(name string) (*File, error) { ... }
var ErrNotExist = errors.New("file does not exist")
func Open(name string) (*File, error) { ... }
i := 0
//go: 指令注释:
反模式(绝不这样写)
-
panic 用于业务逻辑
func GetUser(id int) *User {
user := db.Find(id)
if user == nil {
panic("user not found")
}
return user
}
func GetUser(id int) (*User, error) { ... }
-
接受具体类型而不是接口(限制了可测试性)
func Process(c *http.Client) { ... }
func Process(c interface { Do(*http.Request) (*http.Response, error) }) { ... }
-
init() 函数做复杂初始化 — init 难以测试、难以控制执行顺序
-
全局变量暴露可变状态 — 包级全局变量应该是不可变的(常量或配置)
-
interface{} / any 满天飞 — Go 1.18+ 用泛型,不用空接口甩锅
func Store(key string, value interface{}) { ... }
func Store[V any](key string, value V) { ... }
-
忽略 defer 的错误
defer f.Close()
defer func() {
if err := f.Close(); err != nil {
log.Printf("close file: %v", err)
}
}()
-
错误信息大写或加句号 — 会破坏 fmt.Errorf("open: %w", err) 的拼接效果
校验测试
- gofmt 检查:
gofmt -d . 有输出吗?有就是格式不对(gofmt 是宗教)
- 接口检查:接收参数能换成接口吗?换了之后可测试性提升吗?
- 错误路径检查:
grep -n "_, err" *.go —— 有没有忽略的错误?
来源