gopilot
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Engineering
Go 工程实践
Design Guidelines
设计准则
- Keep things simple.
- Prefer stateless, pure functions over stateful structs with methods if no state is needed to solve the problem.
- Prefer synchronous code to concurrent code, an obvious concurrency pattern applies.
- Prefer simple APIs and small interfaces.
- Make the zero value useful: and
bytes.Bufferwork without init. When zero values compose, there's less API.sync.Mutex - Avoid external dependencies. A little copying is better than a little dependency.
- Clear is better than clever. Maintainability and readibility are important.
- Don't just check errors, handle them gracefully.
- Design the architecture, name the components, document the details: Good names carry the weight of expressing your design. If names are good, the design is clear on the page.
- Reduce nesting by using guard clauses.
- Invert conditions for early return
- In loops, use early for invalid items instead of nesting
continue - Max 2-3 nesting levels
- Extract helpers for long methods
- 保持简洁。
- 若无需状态即可解决问题,优先使用无状态纯函数,而非带方法的有状态结构体。
- 优先使用同步代码,仅在明确需要时使用并发模式。
- 优先设计简洁的API和小型接口。
- 让零值具备可用性:和
bytes.Buffer无需初始化即可工作。当零值可组合时,API会更简洁。sync.Mutex - 避免外部依赖。少量的复制操作比引入依赖更优。
- 清晰胜于巧妙。可维护性和可读性至关重要。
- 不要仅检查错误,还要优雅地处理它们。
- 先设计架构,再命名组件,最后记录细节:好的命名能清晰传达设计意图。如果命名得当,设计在代码中自然一目了然。
- 使用卫语句减少嵌套层级。
- 反转条件以提前返回
- 在循环中,对无效项使用提前而非嵌套
continue - 最多保留2-3层嵌套
- 为长方法提取辅助函数
Code Style
代码风格
- Avoid stuttering: not
http.Clienthttp.HTTPClient - Getters: not
Foo()GetFoo() - Receiver: short (1-2 chars), consistent across type methods
- prefix for panicking functions
Must - Enums: use to start at one, distinguishing intentional values from zero default
iota + 1
- 避免重复命名:使用而非
http.Clienthttp.HTTPClient - Getter方法:使用而非
Foo()GetFoo() - 接收器名称:简短(1-2个字符),且在类型的所有方法中保持一致
- 会触发panic的函数使用前缀
Must - 枚举类型:使用从1开始,区分有意设置的值与零默认值
iota + 1
Error Handling
错误处理
- Errors are values. Design APIs around that.
- Wrap with context:
fmt.Errorf("get config %s: %w", name, err) - Sentinel errors:
var ErrNotFound = errors.New("not found") - Check with or
errors.Is(err, ErrNotFound), or use genericerrors.As(err, &target)(Go 1.26+)errors.AsType[T] - Static errors: prefer over
errors.Newwithout formattingfmt.Errorf - Join multiple errors: (Go 1.20+)
err := errors.Join(err1, err2, err3) - Error strings: lowercase, no punctuation
- Avoid "failed to" prefixes - they accumulate through the stack (not
"connect: %w")"failed to connect: %w" - CRITICAL: Always check errors immediately before using returned values (Go 1.25 fixed compiler bug that could delay nil checks)
- 错误是值。围绕这一点设计API。
- 携带上下文包装错误:
fmt.Errorf("get config %s: %w", name, err) - 哨兵错误:
var ErrNotFound = errors.New("not found") - 使用或
errors.Is(err, ErrNotFound)检查错误,或使用泛型errors.As(err, &target)(Go 1.26+)errors.AsType[T] - 静态错误:优先使用而非无格式化的
errors.Newfmt.Errorf - 合并多个错误:(Go 1.20+)
err := errors.Join(err1, err2, err3) - 错误字符串:小写,无标点符号
- 避免使用"failed to"前缀——该前缀会在调用栈中重复出现(使用而非
"connect: %w")"failed to connect: %w" - 关键注意事项:在使用返回值前务必立即检查错误(Go 1.25修复了可能延迟nil检查的编译器bug)
Error Strategy (Opaque Errors First)
错误策略(优先使用不透明错误)
Prefer opaque error handling: treat errors as opaque values, don't inspect internals. This minimizes coupling.
Three strategies in order of preference:
- Opaque errors (preferred): return and wrap errors without exposing type or value. Callers only check .
err != nil - Sentinel errors (): use sparingly for expected conditions callers must distinguish. They become public API.
var ErrNotFound = errors.New(...) - Error types (): use when callers need structured context. Also public API — avoid when opaque or sentinel suffices.
type NotFoundError struct{...}
优先采用不透明错误处理:将错误视为不透明值,不检查其内部细节。这能最小化耦合。
按优先级排序的三种策略:
- 不透明错误(首选):返回并包装错误,不暴露其类型或值。调用者仅需检查。
err != nil - 哨兵错误():仅在调用者必须区分预期条件时谨慎使用。它们属于公共API的一部分。
var ErrNotFound = errors.New(...) - 错误类型():仅在调用者需要结构化上下文时使用。同样属于公共API——当不透明错误或哨兵错误足够时应避免使用。
type NotFoundError struct{...}
Assert Behavior, Not Type
断言行为而非类型
When you must inspect errors beyond /, assert on behavior interfaces instead of concrete types:
errors.Iserrors.Asgo
type temporary interface {
Temporary() bool
}
func IsTemporary(err error) bool {
te, ok := err.(temporary)
return ok && te.Temporary()
}当必须通过/之外的方式检查错误时,应断言行为接口而非具体类型:
errors.Iserrors.Asgo
type temporary interface {
Temporary() bool
}
func IsTemporary(err error) bool {
te, ok := err.(temporary)
return ok && te.Temporary()
}Handle Errors Once
错误仅处理一次
An error should be handled exactly once. Handling = logging, returning, or degrading gracefully. Never log and return — duplicates without useful context.
go
// Bad: logs AND returns
if err != nil {
log.Printf("connect failed: %v", err)
return fmt.Errorf("connect: %w", err)
}
// Good: wrap and return; let the top-level caller log
if err != nil {
return fmt.Errorf("connect to %s: %w", addr, err)
}Wrap with context at each layer; log/handle only at the application boundary.
一个错误应仅被处理一次。处理包括日志记录、返回或优雅降级。绝不要既记录又返回——这会产生无有用上下文的重复信息。
go
// 错误示例:既记录又返回
if err != nil {
log.Printf("connect failed: %v", err)
return fmt.Errorf("connect: %w", err)
}
// 正确示例:包装后返回;由顶层调用者负责日志记录
if err != nil {
return fmt.Errorf("connect to %s: %w", addr, err)
}在每个层级都携带上下文包装错误;仅在应用边界处记录/处理错误。
Context with Cause (Go 1.21+)
带原因的上下文(Go 1.21+)
Propagate cancellation reasons through context:
go
ctx, cancel := context.WithCancelCause(parent)
cancel(fmt.Errorf("shutdown: %w", reason))
// Later retrieve the cause
if cause := context.Cause(ctx); cause != nil {
log.Printf("context cancelled: %v", cause)
}通过上下文传播取消原因:
go
ctx, cancel := context.WithCancelCause(parent)
cancel(fmt.Errorf("shutdown: %w", reason))
// 后续获取原因
if cause := context.Cause(ctx); cause != nil {
log.Printf("context cancelled: %v", cause)
}Generics
泛型
- Type parameters:
func Min[T cmp.Ordered] (a, b T) T - Use for map keys,
comparablefor sortable typescmp.Ordered - Custom constraints:
type Number interface { ~int | ~int64 | ~float64 } - Generic type alias (Go 1.24+):
type Set[T comparable] = map[T]struct{} - Self-referential constraints (Go 1.26+):
type Adder[A Adder[A]] interface { Add(A) A } - Prefer concrete types when generics add no value
- Use sparingly; prefer specific constraints
any
- 类型参数:
func Min[T cmp.Ordered] (a, b T) T - 使用作为映射键的约束,
comparable作为可排序类型的约束cmp.Ordered - 自定义约束:
type Number interface { ~int | ~int64 | ~float64 } - 泛型类型别名(Go 1.24+):
type Set[T comparable] = map[T]struct{} - 自引用约束(Go 1.26+):
type Adder[A Adder[A]] interface { Add(A) A } - 当泛型无法带来价值时,优先使用具体类型
- 谨慎使用;优先使用特定约束
any
Built-in Functions
内置函数
- and
min(a, b, c)- compute smallest/largest values (Go 1.21+)max(a, b, c) - - delete all map entries;
clear(m)- zero all slice elements (Go 1.21+)clear(s) - - allocate and initialize with value (Go 1.26+):
new(expr)ptr := new(computeValue())
- 和
min(a, b, c)- 计算最小值/最大值(Go 1.21+)max(a, b, c) - - 删除映射的所有条目;
clear(m)- 将切片的所有元素置零(Go 1.21+)clear(s) - - 分配内存并使用指定值初始化(Go 1.26+):
new(expr)ptr := new(computeValue())
Testing
测试
Table-Driven Tests
表驱动测试
go
func TestFoo(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr error
}{
{"EmptyInput", "", "", ErrEmpty},
{"ValidInput", "hello", "HELLO", nil},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got, err := Foo(tc.input)
if tc.wantErr != nil {
require.ErrorIs(t, err, tc.wantErr)
return
}
require.NoError(t, err)
require.Equal(t, tc.want, got)
})
}
}go
func TestFoo(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr error
}{
{"EmptyInput", "", "", ErrEmpty},
{"ValidInput", "hello", "HELLO", nil},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got, err := Foo(tc.input)
if tc.wantErr != nil {
require.ErrorIs(t, err, tc.wantErr)
return
}
require.NoError(t, err)
require.Equal(t, tc.want, got)
})
}
}Benchmarks (Go 1.24+)
基准测试(Go 1.24+)
go
func BenchmarkFoo(b *testing.B) {
for b.Loop() { // Cleaner than for i := 0; i < b.N; i++
Foo()
}
}Benefits: single execution per , prevents compiler optimizations away.
-countgo
func BenchmarkFoo(b *testing.B) {
for b.Loop() { // 比for i := 0; i < b.N; i++更简洁
Foo()
}
}优势:每次执行一次;避免被编译器优化掉。
-countAssertions
断言
- Use the testify library for conciseness. Use for fatal assertions,
requirefor non-fatalassert - for sentinel errors (not string matching)
require.ErrorIs - /
require.JSONEqfor semantic comparisonrequire.YAMLEq - Use folders for expected values
testdata/ - Use for test data files
embed.FS
- 使用testify库以简化代码。使用进行致命断言,
require进行非致命断言assert - 对哨兵错误使用(而非字符串匹配)
require.ErrorIs - 使用/
require.JSONEq进行语义比较require.YAMLEq - 使用文件夹存储预期值
testdata/ - 使用加载测试数据文件
embed.FS
Practices
实践
- in helper functions
t.Helper() - for resource cleanup
t.Cleanup() - for test-scoped context (Go 1.24+)
t.Context() - for temp directory changes (Go 1.24+)
t.Chdir() - for test output files (Go 1.26+)
t.ArtifactDir() - for independent tests
t.Parallel() - flag always
-race - Don't test stdlib; test YOUR code
- Bug fix → add regression test first
- Concurrent code needs concurrent tests
- 在辅助函数中使用
t.Helper() - 使用进行资源清理
t.Cleanup() - 使用获取测试作用域的上下文(Go 1.24+)
t.Context() - 使用切换到临时目录(Go 1.24+)
t.Chdir() - 使用存储测试输出文件(Go 1.26+)
t.ArtifactDir() - 对独立测试使用
t.Parallel() - 始终启用标志
-race - 不要测试标准库;只测试你自己的代码
- 修复bug前先添加回归测试
- 并发代码需要并发测试
Testing Concurrent Code with synctest (Go 1.25+)
使用synctest测试并发代码(Go 1.25+)
testing/synctestgo
import "testing/synctest"
func TestPeriodicWorker(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
var count atomic.Int32
go func() {
for {
time.Sleep(time.Second)
count.Add(1)
}
}()
// Fake clock advances 5s instantly (no real waiting)
time.Sleep(5 * time.Second)
synctest.Wait() // wait for all goroutines to settle
require.Equal(t, int32(5), count.Load())
})
}Key rules:
- blocks until all bubble goroutines are idle
synctest.Wait() - ,
time.Sleep,time.After,time.NewTimerall use the fake clock inside the bubbletime.NewTicker - Goroutines spawned inside the bubble belong to it; goroutines outside are unaffected
- Blocking on I/O or syscalls does NOT advance the clock — only channel ops, sleeps, and sync primitives do
- Prefer over manual
synctest.Testmocking for new codetimeNow
testing/synctestgo
import "testing/synctest"
func TestPeriodicWorker(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
var count atomic.Int32
go func() {
for {
time.Sleep(time.Second)
count.Add(1)
}
}()
// 虚拟时钟立即推进5秒(无需实际等待)
time.Sleep(5 * time.Second)
synctest.Wait() // 等待所有goroutines进入空闲状态
require.Equal(t, int32(5), count.Load())
})
}核心规则:
- 会阻塞,直到沙箱中的所有goroutine都处于空闲状态
synctest.Wait() - 在沙箱内,、
time.Sleep、time.After、time.NewTimer均使用虚拟时钟time.NewTicker - 在沙箱内启动的goroutine属于该沙箱;沙箱外的goroutine不受影响
- 阻塞在I/O或系统调用不会推进时钟——只有通道操作、睡眠和同步原语会推进时钟
- 对于新代码,优先使用而非手动模拟
synctest.TesttimeNow
Concurrency
并发
Design
设计
- Don't communicate by sharing memory, share memory by communicating
- Channels orchestrate; mutexes serialize
- Use to launch goroutines that return errors;
errgroup.WithContextreturns first errorg.Wait() - (Go 1.25+): cleaner goroutine launching
sync.WaitGroup.Go()govar wg sync.WaitGroup wg.Go(func() { work() }) // Combines Add(1) + go wg.Wait() - Make goroutine lifetime explicit; document when/why they exit
- Avoid goroutine leaks (blocked on unreachable channels); use goroutine leak profile to detect (, Go 1.26+)
GOEXPERIMENT=goroutineleakprofile - Use for cancellation
context.Context - Subscribe to for graceful shutdown
context.Done() - Prefer synchronous functions; let callers add concurrency if needed
- /
sync.Mutexfor protection; zero value is ready to useRWMutex - when reads >> writes
RWMutex - Pointer receivers with mutexes (prevent struct copy)
- Keep critical sections small; avoid locks across I/O
- for one-time initialization; helpers:
sync.Once,sync.OnceFunc(),sync.OnceValue()(Go 1.21+)sync.OnceValues() - for primitive counters
atomic - Don't embed mutex (exposes Lock/Unlock); use named field
- Channels:
- Sender closes, receiver checks
- Don't close from receiver side
- Never close with multiple concurrent senders
- Document buffering rationale
- Prefer with
selectfor cancellationcontext.Done()
- 不要通过共享内存来通信,而要通过通信来共享内存
- 通道用于协调;互斥锁用于序列化
- 使用启动返回错误的goroutine;
errgroup.WithContext返回第一个错误g.Wait() - (Go 1.25+):更简洁的goroutine启动方式
sync.WaitGroup.Go()govar wg sync.WaitGroup wg.Go(func() { work() }) // 合并了Add(1) + go wg.Wait() - 明确goroutine的生命周期;注明其退出的时间和原因
- 避免goroutine泄漏(阻塞在不可达的通道上);使用goroutine泄漏分析工具检测(,Go 1.26+)
GOEXPERIMENT=goroutineleakprofile - 使用进行取消操作
context.Context - 订阅以实现优雅关闭
context.Done() - 优先使用同步函数;让调用者根据需要添加并发
- 使用/
sync.Mutex进行保护;零值即可直接使用RWMutex - 当读取操作远多于写入操作时使用
RWMutex - 互斥锁使用指针接收器(防止结构体复制)
- 保持临界区尽可能小;避免跨I/O操作持有锁
- 使用进行一次性初始化;辅助函数:
sync.Once、sync.OnceFunc()、sync.OnceValue()(Go 1.21+)sync.OnceValues() - 使用操作处理原始计数器
atomic - 不要嵌入互斥锁(会暴露Lock/Unlock方法);使用命名字段
- 通道:
- 发送者负责关闭通道,接收者检查通道状态
- 不要从接收端关闭通道
- 永远不要在多个并发发送者的情况下关闭通道
- 注明缓冲的理由
- 优先使用带的
context.Done()进行取消select
Axioms
公理
| Operation | nil channel | closed channel |
|---|---|---|
| Send | blocks forever | panics |
| Receive | blocks forever | returns zero value |
| Close | panics | panics |
- Nil channels are useful in to disable a case
select - Use to receive until closed
for range ch - Check closure with
v, ok := <-ch
| 操作 | nil通道 | 已关闭通道 |
|---|---|---|
| 发送 | 永久阻塞 | 触发panic |
| 接收 | 永久阻塞 | 返回零值 |
| 关闭 | 触发panic | 触发panic |
- Nil通道在中可用于禁用某个分支
select - 使用接收数据直到通道关闭
for range ch - 使用检查通道是否关闭
v, ok := <-ch
Iterators (Go 1.22+)
迭代器(Go 1.22+)
Range Over Integers (Go 1.22+)
遍历整数范围(Go 1.22+)
go
for i := range 10 {
fmt.Println(i) // 0..9
}go
for i := range 10 {
fmt.Println(i) // 0..9
}Range Over Functions (Go 1.23+)
遍历函数返回值(Go 1.23+)
go
// String iterators
for line := range strings.Lines(s) { }
for part := range strings.SplitSeq(s, sep) { }
for field := range strings.FieldsSeq(s) { }
// Slice iterators
for i, v := range slices.All(items) { }
for v := range slices.Values(items) { }
for v := range slices.Backward(items) { }
for chunk := range slices.Chunk(items, 3) { }
// Map iterators
for k, v := range maps.All(m) { }
for k := range maps.Keys(m) { }
for v := range maps.Values(m) { }
// Collect iterator to slice
keys := slices.Collect(maps.Keys(m))
sorted := slices.Sorted(maps.Keys(m))
// Custom iterator
func (s *Set[T]) All() iter.Seq[T] {
return func(yield func(T) bool) {
for v := range s.items {
if !yield(v) {
return
}
}
}
}go
// 字符串迭代器
for line := range strings.Lines(s) { }
for part := range strings.SplitSeq(s, sep) { }
for field := range strings.FieldsSeq(s) { }
// 切片迭代器
for i, v := range slices.All(items) { }
for v := range slices.Values(items) { }
for v := range slices.Backward(items) { }
for chunk := range slices.Chunk(items, 3) { }
// 映射迭代器
for k, v := range maps.All(m) { }
for k := range maps.Keys(m) { }
for v := range maps.Values(m) { }
// 将迭代器结果收集到切片
keys := slices.Collect(maps.Keys(m))
sorted := slices.Sorted(maps.Keys(m))
// 自定义迭代器
func (s *Set[T]) All() iter.Seq[T] {
return func(yield func(T) bool) {
for v := range s.items {
if !yield(v) {
return
}
}
}
}Interface Design
接口设计
- Accept interfaces, return concrete types
- Define interfaces at the consumer, not the provider; keep them small (1-2 methods)
- Compile-time interface check:
var _ http.Handler = (*MyHandler)(nil) - For single-method dependencies, use function types instead of interfaces
- Don't embed types in exported structs—exposes methods and breaks API compatibility
- 接受接口类型,返回具体类型
- 在消费者端定义接口,而非提供者端;保持接口小型化(1-2个方法)
- 编译时接口检查:
var _ http.Handler = (*MyHandler)(nil) - 对于单方法依赖,优先使用函数类型而非接口
- 不要在导出的结构体中嵌入类型——这会暴露方法并破坏API兼容性
Slice & Map Patterns
切片与映射模式
- Pre-allocate when size known:
make([]User, 0, len(ids)) - Nil vs empty: (nil, JSON null) vs
var t []string(non-nil, JSONt := []string{})[] - Copy at boundaries with to prevent external mutations
slices.Clone(items) - Prefer over
strings.Cut(s, "/")for prefix/suffix extractionstrings.Split - Append handles nil:
var items []Item; items = append(items, newItem)
- 已知大小时预先分配:
make([]User, 0, len(ids)) - Nil切片与空切片的区别:(Nil,JSON为null) vs
var t []string(非Nil,JSON为t := []string{})[] - 在边界处使用复制,防止外部修改
slices.Clone(items) - 提取前缀/后缀时优先使用而非
strings.Cut(s, "/")strings.Split - Append操作可处理Nil切片:
var items []Item; items = append(items, newItem)
Common Patterns
常见模式
Options Pattern
选项模式
Define . Create functions returning that set fields. Constructor takes , applies each to default config.
type Option func(*Config)WithXOption...Option定义。创建返回的函数来设置字段。构造函数接受参数,将每个选项应用到默认配置。
type Option func(*Config)OptionWithX...OptionDefault Values (Go 1.22+)
默认值(Go 1.22+)
Use to return first non-zero value—e.g., .
cmp.Or(a, b, c)cmp.Or(cfg.Port, envPort, 8080)使用返回第一个非零值——例如:。
cmp.Or(a, b, c)cmp.Or(cfg.Port, envPort, 8080)Context Usage
Context使用
- First parameter:
func Foo(ctx context.Context, ...) - Don't store in structs
- Use for cancellation, deadlines, request-scoped values only
- 第一个参数:
func Foo(ctx context.Context, ...) - 不要将Context存储在结构体中
- 仅用于取消操作、截止时间和请求作用域的值
HTTP Best Practices
HTTP最佳实践
- Use with explicit
http.Server{}/ReadTimeout; avoidWriteTimeouthttp.ListenAndServe - Always after checking error
defer resp.Body.Close() - Accept as dependency for testability
*http.Client
- 使用并显式设置
http.Server{}/ReadTimeout;避免使用WriteTimeouthttp.ListenAndServe - 检查错误后务必
defer resp.Body.Close() - 接受作为依赖以提高可测试性
*http.Client
HTTP Routing (Go 1.22+)
HTTP路由(Go 1.22+)
go
// Method-based routing with path patterns
mux.HandleFunc("POST /items/create", createHandler)
mux.HandleFunc("GET /items/{id}", getHandler)
mux.HandleFunc("GET /files/{path...}", serveFiles) // Greedy wildcard
// Extract path values
func getHandler(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
}go
// 基于方法的路由和路径模式
mux.HandleFunc("POST /items/create", createHandler)
mux.HandleFunc("GET /items/{id}", getHandler)
mux.HandleFunc("GET /files/{path...}", serveFiles) // 贪婪通配符
// 提取路径参数
func getHandler(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
}CSRF Protection (Go 1.25+)
CSRF防护(Go 1.25+)
go
import "net/http"
handler := http.CrossOriginProtection(myHandler)
// Rejects non-safe cross-origin requests using Fetch metadatago
import "net/http"
handler := http.CrossOriginProtection(myHandler)
// 使用Fetch元数据拒绝非安全的跨域请求Directory-Scoped File Access (Go 1.24+)
目录范围的文件访问(Go 1.24+)
go
root, err := os.OpenRoot("/var/data")
if err != nil {
return err
}
f, err := root.Open("file.txt") // Can't escape /var/dataPrevents path traversal attacks; works like a chroot.
go
root, err := os.OpenRoot("/var/data")
if err != nil {
return err
}
f, err := root.Open("file.txt") // 无法突破/var/data目录防止路径遍历攻击;功能类似chroot。
Cleanup Functions (Go 1.24+)
清理函数(Go 1.24+)
go
runtime.AddCleanup(obj, func() { cleanup() })Advantages over : multiple cleanups per object, works with interior pointers, no cycle leaks, faster.
SetFinalizergo
runtime.AddCleanup(obj, func() { cleanup() })相较于的优势:每个对象可添加多个清理函数,支持内部指针,无循环泄漏,速度更快。
SetFinalizerStructured Logging (log/slog)
结构化日志(log/slog)
- Use with key-value pairs
slog.Info("msg", "key", value, "key2", value2) - Add persistent attributes:
logger := slog.With("service", "api") - JSON output:
slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
- 使用格式的键值对日志
slog.Info("msg", "key", value, "key2", value2) - 添加持久属性:
logger := slog.With("service", "api") - JSON输出:
slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
Common Gotchas
常见陷阱
- Check errors immediately (Go 1.25 fixed compiler bug): always check before using any returned values
err != nilgo// WRONG: could execute f.Name() before err check in Go 1.21-1.24 f, err := os.Open("file") name := f.Name() if err != nil { return } // CORRECT: check immediately f, err := os.Open("file") if err != nil { return } name := f.Name() - : always call
time.Tickerto prevent leaksStop() - Slices hold refs to backing arrays (can retain large memory)
- interface vs
nilconcrete:nil→var err error = (*MyError)(nil)is trueerr != nil - Loop variables: each iteration has own copy (Go 1.22+); older versions share
- Timer/Ticker channels: capacity 0 (Go 1.23+); previously capacity 1
- is an anti-pattern; prefer explicit initialization
init()
- 立即检查错误(Go 1.25修复了编译器bug):在使用任何返回值前务必检查
err != nilgo// 错误示例:在Go 1.21-1.24中可能在检查错误前执行f.Name() f, err := os.Open("file") name := f.Name() if err != nil { return } // 正确示例:立即检查错误 f, err := os.Open("file") if err != nil { return } name := f.Name() - :务必调用
time.Ticker以防止泄漏Stop() - 切片持有对底层数组的引用(可能占用大量内存)
- Nil接口与Nil具体值的区别:→
var err error = (*MyError)(nil)为trueerr != nil - 循环变量:每个迭代有独立副本(Go 1.22+);旧版本共享同一变量
- Timer/Ticker通道:容量为0(Go 1.23+);之前版本容量为1
- 是反模式;优先使用显式初始化
init()
Linting (golangci-lint)
代码检查(golangci-lint)
yaml
undefinedyaml
undefined.golangci.yml
.golangci.yml
linters:
enable:
- errcheck # Unchecked errors
- govet # Suspicious constructs
- staticcheck # Static analysis
- unused # Unused code
- gosimple # Simplifications
- ineffassign # Ineffectual assignments
- typecheck # Type checking
- gocritic # Opinionated checks
- gofumpt # Stricter gofmt
- misspell # Spelling
- nolintlint # Malformed //nolint directives
- wrapcheck # Errors from external packages wrapped
- errorlint # errors.Is/As usage
linters-settings:
govet:
enable-all: true
gocritic:
enabled-tags: [diagnostic, style, performance]
```bash
golangci-lint run # Lint current module
golangci-lint run --fix # Auto-fix where possible
golangci-lint run --timeout 5m # Increase timeout for large codebaseslinters:
enable:
- errcheck # 未检查的错误
- govet # 可疑构造
- staticcheck # 静态分析
- unused # 未使用的代码
- gosimple # 代码简化建议
- ineffassign # 无效赋值
- typecheck # 类型检查
- gocritic # 主观性检查
- gofumpt # 更严格的gofmt
- misspell # 拼写检查
- nolintlint # 格式错误的//nolint指令
- wrapcheck # 外部包错误的包装检查
- errorlint # errors.Is/As使用检查
linters-settings:
govet:
enable-all: true
gocritic:
enabled-tags: [diagnostic, style, performance]
```bash
golangci-lint run # 检查当前模块
golangci-lint run --fix # 自动修复可修复的问题
golangci-lint run --timeout 5m # 为大型代码库增加超时时间Pre-Commit
提交前检查
Check for Makefile targets first (, or read Makefile). Common targets:
make help- or
make lintmake check make testmake build
Fallback if no Makefile:
go build ./...go test -v -race ./...golangci-lint run- (Go 1.26+: modernizes code to latest idioms)
go fix ./... - or
gofmt -w .goimports -w . go mod tidy
先查看Makefile中的目标(或直接阅读Makefile)。常见目标:
make help- 或
make lintmake check make testmake build
若无Makefile,可按以下步骤执行:
go build ./...go test -v -race ./...golangci-lint run- (Go 1.26+:将代码更新为最新的惯用写法)
go fix ./... - 或
gofmt -w .goimports -w . go mod tidy
Performance
性能
Profile-Guided Optimization (PGO)
基于分析的优化(PGO)
bash
undefinedbash
undefinedCollect profile
收集分析数据
go test -cpuprofile=default.pgo
go test -cpuprofile=default.pgo
PGO automatically enabled if default.pgo exists in main package
若主包中存在default.pgo,会自动启用PGO
go build # Uses default.pgo
go build # 使用default.pgo
Typical 2-7% performance improvement
通常可提升2-7%的性能
undefinedundefinedSecurity
安全
Based on OWASP Go Secure Coding Practices. Read the linked reference for each topic.
基于OWASP Go安全编码实践。每个主题请参考链接的详细指南。
Quick Checklist
快速检查清单
Input/Output:
- All user input validated server-side
- SQL queries use prepared statements only
- XSS protection via
html/template - CSRF tokens on state-changing requests
- File paths validated against traversal (Go 1.24+)
os.OpenRoot
Auth/Sessions:
- Passwords hashed with bcrypt/Argon2/PBKDF2
- for all tokens/session IDs (
crypto/randGo 1.24+)crypto/rand.Text() - Secure cookie flags (HttpOnly, Secure, SameSite)
- Session expiration enforced
Communication:
- HTTPS/TLS everywhere, TLS 1.2+ only (post-quantum ML-KEM default Go 1.24+)
- HSTS header set
-
InsecureSkipVerify = false
Data Protection:
- Secrets in environment variables, never in logs/errors
- Generic error messages to users
输入/输出:
- 所有用户输入均在服务端验证
- SQL查询仅使用预编译语句
- 通过防护XSS
html/template - 状态变更请求使用CSRF令牌
- 文件路径验证以防止遍历(Go 1.24+使用)
os.OpenRoot
认证/会话:
- 密码使用bcrypt/Argon2/PBKDF2哈希
- 所有令牌/会话ID使用生成(Go 1.24+使用
crypto/rand)crypto/rand.Text() - Cookie设置安全标志(HttpOnly、Secure、SameSite)
- 强制会话过期
通信:
- 全程使用HTTPS/TLS,仅支持TLS 1.2+(Go 1.24+默认使用后量子ML-KEM)
- 设置HSTS头
-
InsecureSkipVerify = false
数据保护:
- 密钥存储在环境变量中,绝不要记录在日志/错误信息中
- 向用户返回通用错误信息
Detailed Guides
详细指南
- Input Validation — whitelisting, boundary checks, escaping
- Database Security — prepared statements, parameterized queries
- Authentication — bcrypt, password storage
- Cryptography — , never
crypto/randfor securitymath/rand - Session Management — secure cookies, session lifecycle
- TLS/HTTPS — TLS config, HSTS, post-quantum key exchanges
- CSRF Protection — token generation, (Go 1.25+)
http.CrossOriginProtection - Secure Error Handling — generic user messages, detailed server logs
- File Security — path traversal prevention,
os.OpenRoot - Security Logging — what to log, what never to log
- Access Control
- XSS Prevention
- 输入验证 — 白名单、边界检查、转义
- 数据库安全 — 预编译语句、参数化查询
- 认证 — bcrypt、密码存储
- 加密 — ,绝不要在安全场景使用
crypto/randmath/rand - 会话管理 — 安全Cookie、会话生命周期
- TLS/HTTPS — TLS配置、HSTS、后量子密钥交换
- CSRF防护 — 令牌生成、(Go 1.25+)
http.CrossOriginProtection - 安全错误处理 — 通用用户消息、详细服务端日志
- 文件安全 — 路径遍历防护、
os.OpenRoot - 安全日志 — 日志内容规范、禁止记录的信息
- 访问控制
- XSS防护
Security Tools
安全工具
| Tool | Purpose | Command |
|---|---|---|
| gosec | Security scanner | |
| govulncheck | Vulnerability scanner | |
| trivy | Container/dep scanner | |
| 工具 | 用途 | 命令 |
|---|---|---|
| gosec | 安全扫描器 | |
| govulncheck | 漏洞扫描器 | |
| trivy | 容器/依赖扫描器 | |