npx skills add https://github.com/cxuu/golang-skills --skill go-defensive来源:Uber Go 风格指南
使用零值断言在编译时验证接口合规性。
反面示例
type Handler struct{}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
}
正面示例
type Handler struct{}
var _ http.Handler = (*Handler)(nil)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
}
对于指针类型、切片、映射使用 nil;对于值接收器使用空结构体 {}。
来源:Uber Go 风格指南
切片和映射包含指针。在 API 边界处进行复制,以防止意外修改。
反面示例
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
func (d *Driver) SetTrips(trips []Trip) {
d.trips = trips // 调用者仍然可以修改 d.trips
}
正面示例
func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}
反面示例
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
return s.counters // 暴露了内部状态!
}
正面示例
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
result := make(map[string]int, len(s.counters))
for k, v := range s.counters {
result[k] = v
}
return result
}
来源:Uber Go 风格指南,Effective Go
使用 defer 来清理资源(文件、锁)。避免在多个返回路径上遗漏清理。
反面示例
p.Lock()
if p.count < 10 {
p.Unlock()
return p.count
}
p.count++
newCount := p.count
p.Unlock()
return newCount // 容易遗漏解锁
正面示例
p.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.count
defer 的开销可以忽略不计。仅在纳秒级关键路径中避免使用。
为了清晰起见,在打开文件后立即放置 defer f.Close():
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // Close 紧挨着 Open - 清晰得多
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...)
if err != nil {
if err == io.EOF {
break
}
return "", err // f 将被关闭
}
}
return string(result), nil // f 将被关闭
}
延迟函数的参数在 defer 执行时求值,而不是在延迟函数运行时求值:
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
// 输出:4 3 2 1 0 (LIFO 顺序,值在 defer 时捕获)
多个 defer 按后进先出顺序执行:
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a")) // trace() 现在运行,un() 在返回时运行
fmt.Println("in a")
}
// 输出:entering: a, in a, leaving: a
来源:Uber Go 风格指南
枚举从非零值开始,以区分未初始化值和有效值。
反面示例
const (
Add Operation = iota // Add=0,零值看起来有效
Subtract
Multiply
)
正面示例
const (
Add Operation = iota + 1 // Add=1,零值 = 未初始化
Subtract
Multiply
)
例外:当零值是合理的默认值时(例如,LogToStdout = iota)。
来源:Uber Go 风格指南
始终使用 time 包。避免使用原始 int 表示时间值。
反面示例
func isActive(now, start, stop int) bool {
return start <= now && now < stop
}
正面示例
func isActive(now, start, stop time.Time) bool {
return (start.Before(now) || start.Equal(now)) && now.Before(stop)
}
反面示例
func poll(delay int) {
time.Sleep(time.Duration(delay) * time.Millisecond)
}
poll(10) // 秒?毫秒?
正面示例
func poll(delay time.Duration) {
time.Sleep(delay)
}
poll(10 * time.Second)
当无法使用 time.Duration 时,在字段名中包含单位:
反面示例
type Config struct {
Interval int `json:"interval"`
}
正面示例
type Config struct {
IntervalMillis int `json:"intervalMillis"`
}
来源:Uber Go 风格指南
使用依赖注入代替可变全局变量。
反面示例
var _timeNow = time.Now
func sign(msg string) string {
now := _timeNow()
return signWithTime(msg, now)
}
// 测试需要保存/恢复全局变量
func TestSign(t *testing.T) {
oldTimeNow := _timeNow
_timeNow = func() time.Time { return someFixedTime }
defer func() { _timeNow = oldTimeNow }()
assert.Equal(t, want, sign(give))
}
正面示例
type signer struct {
now func() time.Time
}
func newSigner() *signer {
return &signer{now: time.Now}
}
func (s *signer) Sign(msg string) string {
now := s.now()
return signWithTime(msg, now)
}
// 测试清晰地注入依赖
func TestSigner(t *testing.T) {
s := newSigner()
s.now = func() time.Time { return someFixedTime }
assert.Equal(t, want, s.Sign(give))
}
来源:Uber Go 风格指南
嵌入类型会泄露实现细节并阻碍类型演化。
反面示例
type ConcreteList struct {
*AbstractList
}
正面示例
type ConcreteList struct {
list *AbstractList
}
func (l *ConcreteList) Add(e Entity) {
l.list.Add(e)
}
func (l *ConcreteList) Remove(e Entity) {
l.list.Remove(e)
}
嵌入的问题:
来源:Uber Go 风格指南
对于 JSON、YAML 等,始终使用显式的字段标签。
反面示例
type Stock struct {
Price int
Name string
}
正面示例
type Stock struct {
Price int `json:"price"`
Name string `json:"name"`
// 可以安全地将 Name 重命名为 Symbol
}
标签使序列化契约明确,并且重构是安全的。
来源:Go Wiki CodeReviewComments (规范性)
不要使用 math/rand 或 math/rand/v2 来生成密钥,即使是临时密钥。这是一个安全问题。
未设置种子或基于时间设置种子的随机生成器具有可预测的输出:
Time.Nanoseconds() 只提供很少的熵使用 crypto/rand 代替:
import (
"crypto/rand"
)
func Key() string {
return rand.Text()
}
对于文本输出:
crypto/rand.Text(首选)encoding/hex 或 encoding/base64 编码随机字节来源:Effective Go
仅在真正无法恢复的情况下使用 panic。库函数应避免 panic——如果问题可以解决,就让程序继续运行,而不是让整个程序崩溃。
使用 recover 来重新获得对发生 panic 的 goroutine 的控制(仅在延迟函数内部有效):
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
关键规则:
init() 中 panic 是可以接受的关于包括服务器保护和包内部 panic/recover 的详细模式,请参阅 references/PANIC-RECOVER.md。
| 模式 | 规则 |
|---|---|
| 接口合规性 | var _ Interface = (*Type)(nil) |
| 接收切片/映射 | 存储前复制 |
| 返回切片/映射 | 返回副本 |
| 资源清理 | 使用 defer |
| defer 参数求值时机 | 在 defer 时求值,而非调用时 |
| 枚举 | 从 iota + 1 开始 |
| 时间点 | 使用 time.Time |
| 持续时间 | 使用 time.Duration |
| 可变全局变量 | 使用依赖注入 |
| 类型嵌入 | 使用显式委托 |
| 序列化 | 始终使用字段标签 |
| 密钥生成 | 使用 crypto/rand,永不使用 math/rand |
| Panic 使用 | 仅用于真正无法恢复的情况 |
| Recover 模式 | 在 defer 中使用;在 API 边界转换为错误 |
go-style-core - Go 核心风格原则go-concurrency - Goroutine 和通道模式go-error-handling - 错误处理最佳实践每周安装次数
133
代码仓库
GitHub 星标数
34
首次出现
2026 年 1 月 27 日
安全审计
安装于
github-copilot124
gemini-cli122
codex122
opencode121
kimi-cli120
amp120
Source : Uber Go Style Guide
Verify interface compliance at compile time using zero-value assertions.
Bad
type Handler struct{}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
}
Good
type Handler struct{}
var _ http.Handler = (*Handler)(nil)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
}
Use nil for pointer types, slices, maps; empty struct {} for value receivers.
Source : Uber Go Style Guide
Slices and maps contain pointers. Copy at API boundaries to prevent unintended modifications.
Bad
func (d *Driver) SetTrips(trips []Trip) {
d.trips = trips // caller can still modify d.trips
}
Good
func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}
Bad
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
return s.counters // exposes internal state!
}
Good
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
result := make(map[string]int, len(s.counters))
for k, v := range s.counters {
result[k] = v
}
return result
}
Source : Uber Go Style Guide, Effective Go
Use defer to clean up resources (files, locks). Avoids missed cleanup on multiple returns.
Bad
p.Lock()
if p.count < 10 {
p.Unlock()
return p.count
}
p.count++
newCount := p.count
p.Unlock()
return newCount // easy to miss unlocks
Good
p.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.count
Defer overhead is negligible. Only avoid in nanosecond-critical paths.
Place defer f.Close() immediately after opening a file for clarity:
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // Close sits near Open - much clearer
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...)
if err != nil {
if err == io.EOF {
break
}
return "", err // f will be closed
}
}
return string(result), nil // f will be closed
}
Arguments to deferred functions are evaluated when defer executes, not when the deferred function runs:
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
// Prints: 4 3 2 1 0 (LIFO order, values captured at defer time)
Multiple defers execute in Last-In-First-Out order:
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a")) // trace() runs now, un() runs at return
fmt.Println("in a")
}
// Output: entering: a, in a, leaving: a
Source : Uber Go Style Guide
Start enums at non-zero to distinguish uninitialized from valid values.
Bad
const (
Add Operation = iota // Add=0, zero value looks valid
Subtract
Multiply
)
Good
const (
Add Operation = iota + 1 // Add=1, zero value = uninitialized
Subtract
Multiply
)
Exception : When zero is the sensible default (e.g., LogToStdout = iota).
Source : Uber Go Style Guide
Always use the time package. Avoid raw int for time values.
Bad
func isActive(now, start, stop int) bool {
return start <= now && now < stop
}
Good
func isActive(now, start, stop time.Time) bool {
return (start.Before(now) || start.Equal(now)) && now.Before(stop)
}
Bad
func poll(delay int) {
time.Sleep(time.Duration(delay) * time.Millisecond)
}
poll(10) // seconds? milliseconds?
Good
func poll(delay time.Duration) {
time.Sleep(delay)
}
poll(10 * time.Second)
When time.Duration isn't possible, include unit in field name:
Bad
type Config struct {
Interval int `json:"interval"`
}
Good
type Config struct {
IntervalMillis int `json:"intervalMillis"`
}
Source : Uber Go Style Guide
Use dependency injection instead of mutable globals.
Bad
var _timeNow = time.Now
func sign(msg string) string {
now := _timeNow()
return signWithTime(msg, now)
}
// Test requires save/restore of global
func TestSign(t *testing.T) {
oldTimeNow := _timeNow
_timeNow = func() time.Time { return someFixedTime }
defer func() { _timeNow = oldTimeNow }()
assert.Equal(t, want, sign(give))
}
Good
type signer struct {
now func() time.Time
}
func newSigner() *signer {
return &signer{now: time.Now}
}
func (s *signer) Sign(msg string) string {
now := s.now()
return signWithTime(msg, now)
}
// Test injects dependency cleanly
func TestSigner(t *testing.T) {
s := newSigner()
s.now = func() time.Time { return someFixedTime }
assert.Equal(t, want, s.Sign(give))
}
Source : Uber Go Style Guide
Embedded types leak implementation details and inhibit type evolution.
Bad
type ConcreteList struct {
*AbstractList
}
Good
type ConcreteList struct {
list *AbstractList
}
func (l *ConcreteList) Add(e Entity) {
l.list.Add(e)
}
func (l *ConcreteList) Remove(e Entity) {
l.list.Remove(e)
}
Embedding problems:
Source : Uber Go Style Guide
Always use explicit field tags for JSON, YAML, etc.
Bad
type Stock struct {
Price int
Name string
}
Good
type Stock struct {
Price int `json:"price"`
Name string `json:"name"`
// Safe to rename Name to Symbol
}
Tags make the serialization contract explicit and safe to refactor.
Source : Go Wiki CodeReviewComments (Normative)
Do not use math/rand or math/rand/v2 to generate keys, even throwaway ones. This is a security concern.
Unseeded or time-seeded random generators have predictable output:
Time.Nanoseconds() provides only a few bits of entropyUsecrypto/rand instead:
import (
"crypto/rand"
)
func Key() string {
return rand.Text()
}
For text output:
crypto/rand.Text directly (preferred)encoding/hex or encoding/base64Source : Effective Go
Use panic only for truly unrecoverable situations. Library functions should avoid panic—if the problem can be worked around, let things continue rather than taking down the whole program.
Use recover to regain control of a panicking goroutine (only works inside deferred functions):
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
Key rules:
init() if a library truly cannot set itself upFor detailed patterns including server protection and package-internal panic/recover, see references/PANIC-RECOVER.md.
| Pattern | Rule |
|---|---|
| Interface compliance | var _ Interface = (*Type)(nil) |
| Receiving slices/maps | Copy before storing |
| Returning slices/maps | Return a copy |
| Resource cleanup | Use defer |
| Defer argument timing | Evaluated at defer, not call time |
| Enums | Start at iota + 1 |
| Time instants | Use time.Time |
| Time durations | Use time.Duration |
go-style-core - Core Go style principlesgo-concurrency - Goroutine and channel patternsgo-error-handling - Error handling best practicesWeekly Installs
133
Repository
GitHub Stars
34
First Seen
Jan 27, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot124
gemini-cli122
codex122
opencode121
kimi-cli120
amp120
GSAP时间轴动画教程:创建多步骤序列动画与关键帧控制
1,700 周安装
MCP服务器构建器:快速创建生产就绪的MCP服务器与ChatGPT小组件
314 周安装
Mapbox样式质量检查与优化工具 - 验证、可访问性、性能优化指南
384 周安装
PDF 生成器 - Deno 自动化 PDF 创建、填充、合并与处理工具
355 周安装
Azure镜像构建器教程:使用Packer创建Azure托管镜像和计算库镜像
346 周安装
Qdrant向量数据库Java集成指南:Spring Boot与LangChain4j语义搜索实战
339 周安装
Agent Tool Builder:大语言模型工具设计专家,优化AI代理工具模式与错误处理
332 周安装
| Mutable globals | Use dependency injection |
| Type embedding | Use explicit delegation |
| Serialization | Always use field tags |
| Key generation | Use crypto/rand, never math/rand |
| Panic usage | Only for truly unrecoverable situations |
| Recover pattern | Use in defer; convert to error at API boundary |