use-modern-go by jetbrains/go-modern-guidelines
npx skills add https://github.com/jetbrains/go-modern-guidelines --skill use-modern-go包含 Shell 命令
此技能包含可能执行系统命令的 shell 命令指令(!command``)。安装前请仔细审查。
!grep -rh "^go " --include="go.mod" . 2>/dev/null | cut -d' ' -f2 | sort | uniq -c | sort -nr | head -1 | xargs | cut -d' ' -f2 | grep . || echo unknown
请勿自行搜索 go.mod 文件或尝试检测版本。仅使用上面显示的版本。
如果检测到版本(非 "unknown"):
如果版本是 "unknown":
在编写 Go 代码时,请使用本文档中截至目标版本的所有特性:
slices、maps、),而非旧模式广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
cmptime.Since:使用 time.Since(start) 而非 time.Now().Sub(start)time.Until:使用 time.Until(deadline) 而非 deadline.Sub(time.Now())errors.Is:使用 errors.Is(err, target) 而非 err == target(适用于包装错误)any:使用 any 而非 interface{}bytes.Cut:使用 before, after, found := bytes.Cut(b, sep) 而非 Index+slicestrings.Cut:使用 before, after, found := strings.Cut(s, sep)fmt.Appendf:使用 buf = fmt.Appendf(buf, "x=%d", x) 而非 []byte(fmt.Sprintf(...))
atomic.Bool/atomic.Int64/atomic.Pointer[T]:使用类型安全的原子操作替代 atomic.StoreInt32
var flag atomic.Bool flag.Store(true) if flag.Load() { ... }
var ptr atomic.Pointer[Config] ptr.Store(cfg)
strings.Clone:使用 strings.Clone(s) 复制字符串而不共享内存bytes.Clone:使用 bytes.Clone(b) 复制字节切片strings.CutPrefix/CutSuffix:使用 if rest, ok := strings.CutPrefix(s, "pre:"); ok { ... }errors.Join:使用 errors.Join(err1, err2) 组合多个错误context.WithCancelCause:使用 ctx, cancel := context.WithCancelCause(parent) 然后 cancel(err)context.Cause:使用 context.Cause(ctx) 获取导致取消的错误内置函数:
min/max:使用 max(a, b) 而非 if/else 比较clear:使用 clear(m) 删除所有映射条目,clear(s) 将切片元素清零slices 包:
slices.Contains:使用 slices.Contains(items, x) 而非手动循环slices.Index:slices.Index(items, x) 返回索引(未找到返回 -1)slices.IndexFunc:slices.IndexFunc(items, func(item T) bool { return item.ID == id })slices.SortFunc:slices.SortFunc(items, func(a, b T) int { return cmp.Compare(a.X, b.X) })slices.Sort:对有序类型使用 slices.Sort(items)slices.Max/slices.Min:使用 slices.Max(items) 而非手动循环slices.Reverse:使用 slices.Reverse(items) 而非手动交换循环slices.Compact:slices.Compact(items) 原地移除连续重复项slices.Clip:slices.Clip(s) 移除未使用的容量slices.Clone:slices.Clone(s) 创建副本maps 包:
maps.Clone:使用 maps.Clone(m) 而非手动映射迭代maps.Copy:maps.Copy(dst, src) 将条目从 src 复制到 dstmaps.DeleteFunc:maps.DeleteFunc(m, func(k K, v V) bool { return condition })sync 包:
sync.OnceFunc:使用 f := sync.OnceFunc(func() { ... }) 而非 sync.Once + 包装器sync.OnceValue:getter := sync.OnceValue(func() T { return computeValue() })context 包:
context.AfterFunc:stop := context.AfterFunc(ctx, cleanup) 在取消时运行清理context.WithTimeoutCause:ctx, cancel := context.WithTimeoutCause(parent, d, err)context.WithDeadlineCause:类似,但使用截止时间而非持续时间循环:
for i := range n:使用 for i := range len(items) 而非 for i := 0; i < len(items); i++cmp 包:
cmp.Or:cmp.Or(flag, env, config, "default") 返回第一个非零值
// 替代: name := os.Getenv("NAME") if name == "" { name = "default" } // 使用: name := cmp.Or(os.Getenv("NAME"), "default")
reflect 包:
reflect.TypeFor:使用 reflect.TypeFor[T]() 而非 reflect.TypeOf((*T)(nil)).Elem()net/http:
http.ServeMux 模式:mux.HandleFunc("GET /api/{id}", handler) 支持方法和路径参数r.PathValue("id") 获取路径参数maps.Keys(m) / maps.Values(m) 返回迭代器
slices.Collect(iter) 无需手动循环即可从迭代器构建切片
slices.Sorted(iter) 一步完成收集和排序
keys := slices.Collect(maps.Keys(m)) // 替代:for k := range m { keys = append(keys, k) } sortedKeys := slices.Sorted(maps.Keys(m)) // 收集 + 排序 for k := range maps.Keys(m) { process(k) } // 直接迭代
time 包
time.Tick:可以自由使用 time.Tick —— 从 Go 1.23 开始,垃圾回收器可以回收未引用的定时器,即使它们尚未停止。Stop 方法不再需要帮助垃圾回收器。当 Tick 可用时,不再有任何理由优先使用 NewTicker。t.Context(),而非 context.WithCancel(context.Background())。当测试函数需要上下文时,始终使用 t.Context()。之前:
func TestFoo(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
result := doSomething(ctx)
}
之后:
func TestFoo(t *testing.T) {
ctx := t.Context()
result := doSomething(ctx)
}
omitzero,而非 omitempty。对于 time.Duration、time.Time、结构体、切片、映射,始终使用 omitzero。之前:
type Config struct {
Timeout time.Duration `json:"timeout,omitempty"` // 对 Duration 无效!
}
之后:
type Config struct {
Timeout time.Duration `json:"timeout,omitzero"`
}
b.Loop(),而非 for i := 0; i < b.N; i++。在基准测试函数的主循环中,始终使用 b.Loop()。之前:
func BenchmarkFoo(b *testing.B) {
for i := 0; i < b.N; i++ {
doWork()
}
}
之后:
func BenchmarkFoo(b *testing.B) {
for b.Loop() {
doWork()
}
}
strings.SplitSeq,而非 strings.Split。在 for-range 循环中迭代分割结果时,始终使用 SplitSeq/FieldsSeq。之前:
for _, part := range strings.Split(s, ",") {
process(part)
}
之后:
for part := range strings.SplitSeq(s, ",") {
process(part)
}
同样适用于:strings.FieldsSeq、bytes.SplitSeq、bytes.FieldsSeq。
wg.Go(fn),而非 wg.Add(1) + go func() { defer wg.Done(); ... }()。在使用 sync.WaitGroup 生成 goroutine 时,始终使用 wg.Go()。之前:
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func() {
defer wg.Done()
process(item)
}()
}
wg.Wait()
之后:
var wg sync.WaitGroup
for _, item := range items {
wg.Go(func() {
process(item)
})
}
wg.Wait()
new(val),而非 x := val; &x —— 返回指向任何值的指针。Go 1.26 扩展了 new() 以接受表达式,而不仅仅是类型。类型会被推断:new(0) → *int, new("s") → *string, new(T{}) → *T。不要使用 x := val; &x 模式 —— 始终直接使用 new(val)。不要使用冗余的类型转换,如 new(int(0)) —— 直接写 new(0)。常见用例:具有指针类型的结构体字段。之前:
timeout := 30
debug := true
cfg := Config{
Timeout: &timeout,
Debug: &debug,
}
之后:
cfg := Config{
Timeout: new(30), // *int
Debug: new(true), // *bool
}
errors.AsType[T](err),而非 errors.As(err, &target)。检查错误是否匹配特定类型时,始终使用 errors.AsType。之前:
var pathErr *os.PathError
if errors.As(err, &pathErr) {
handle(pathErr)
}
之后:
if pathErr, ok := errors.AsType[*os.PathError](err); ok {
handle(pathErr)
}
每周安装数
139
仓库
GitHub 星标数
548
首次出现
2026年2月21日
安全审计
安装于
gemini-cli130
codex130
cursor130
opencode130
github-copilot130
kimi-cli128
Contains Shell Commands
This skill contains shell command directives (!command``) that may execute system commands. Review carefully before installing.
!grep -rh "^go " --include="go.mod" . 2>/dev/null | cut -d' ' -f2 | sort | uniq -c | sort -nr | head -1 | xargs | cut -d' ' -f2 | grep . || echo unknown
DO NOT search for go.mod files or try to detect the version yourself. Use ONLY the version shown above.
If version detected (not "unknown"):
If version is "unknown":
When writing Go code , use ALL features from this document up to the target version:
slices, maps, cmp) over legacy patternstime.Since: time.Since(start) instead of time.Now().Sub(start)time.Until: time.Until(deadline) instead of deadline.Sub(time.Now())errors.Is: errors.Is(err, target) instead of err == target (works with wrapped errors)any: Use any instead of interface{}bytes.Cut: before, after, found := bytes.Cut(b, sep) instead of Index+slicestrings.Cut: before, after, found := strings.Cut(s, sep)fmt.Appendf: buf = fmt.Appendf(buf, "x=%d", x) instead of []byte(fmt.Sprintf(...))
atomic.Bool/atomic.Int64/atomic.Pointer[T]: Type-safe atomics instead of atomic.StoreInt32
var flag atomic.Bool flag.Store(true) if flag.Load() { ... }
var ptr atomic.Pointer[Config] ptr.Store(cfg)
strings.Clone: strings.Clone(s) to copy string without sharing memorybytes.Clone: bytes.Clone(b) to copy byte slicestrings.CutPrefix/CutSuffix: if rest, ok := strings.CutPrefix(s, "pre:"); ok { ... }errors.Join: errors.Join(err1, err2) to combine multiple errorscontext.WithCancelCause: ctx, cancel := context.WithCancelCause(parent) then Built-ins:
min/max: max(a, b) instead of if/else comparisonsclear: clear(m) to delete all map entries, clear(s) to zero slice elementsslices package:
slices.Contains: slices.Contains(items, x) instead of manual loopsslices.Index: slices.Index(items, x) returns index (-1 if not found)slices.IndexFunc: slices.IndexFunc(items, func(item T) bool { return item.ID == id })slices.SortFunc: slices.SortFunc(items, func(a, b T) int { return cmp.Compare(a.X, b.X) })slices.Sort: slices.Sort(items) for ordered typesmaps package:
maps.Clone: maps.Clone(m) instead of manual map iterationmaps.Copy: maps.Copy(dst, src) copies entries from src to dstmaps.DeleteFunc: maps.DeleteFunc(m, func(k K, v V) bool { return condition })sync package:
sync.OnceFunc: f := sync.OnceFunc(func() { ... }) instead of sync.Once + wrappersync.OnceValue: getter := sync.OnceValue(func() T { return computeValue() })context package:
context.AfterFunc: stop := context.AfterFunc(ctx, cleanup) runs cleanup on cancellationcontext.WithTimeoutCause: ctx, cancel := context.WithTimeoutCause(parent, d, err)context.WithDeadlineCause: Similar with deadline instead of durationLoops:
for i := range n: for i := range len(items) instead of for i := 0; i < len(items); i++cmp package:
cmp.Or: cmp.Or(flag, env, config, "default") returns first non-zero value
// Instead of: name := os.Getenv("NAME") if name == "" { name = "default" } // Use: name := cmp.Or(os.Getenv("NAME"), "default")
reflect package:
reflect.TypeFor: reflect.TypeFor[T]() instead of reflect.TypeOf((*T)(nil)).Elem()net/http:
http.ServeMux patterns: mux.HandleFunc("GET /api/{id}", handler) with method and path paramsr.PathValue("id") to get path parametersmaps.Keys(m) / maps.Values(m) return iterators
slices.Collect(iter) not manual loop to build slice from iterator
slices.Sorted(iter) to collect and sort in one step
keys := slices.Collect(maps.Keys(m)) // not: for k := range m { keys = append(keys, k) } sortedKeys := slices.Sorted(maps.Keys(m)) // collect + sort for k := range maps.Keys(m) { process(k) } // iterate directly
time package
time.Tick: Use time.Tick freely — as of Go 1.23, the garbage collector can recover unreferenced tickers, even if they haven't been stopped. The Stop method is no longer necessary to help the garbage collector. There is no longer any reason to prefer NewTicker when Tick will do.t.Context() not context.WithCancel(context.Background()) in tests. ALWAYS use t.Context() when a test function needs a context.Before:
func TestFoo(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
result := doSomething(ctx)
}
After:
func TestFoo(t *testing.T) {
ctx := t.Context()
result := doSomething(ctx)
}
omitzero not omitempty in JSON struct tags. ALWAYS use omitzero for time.Duration, time.Time, structs, slices, maps.Before:
type Config struct {
Timeout time.Duration `json:"timeout,omitempty"` // doesn't work for Duration!
}
After:
type Config struct {
Timeout time.Duration `json:"timeout,omitzero"`
}
b.Loop() not for i := 0; i < b.N; i++ in benchmarks. ALWAYS use b.Loop() for the main loop in benchmark functions.Before:
func BenchmarkFoo(b *testing.B) {
for i := 0; i < b.N; i++ {
doWork()
}
}
After:
func BenchmarkFoo(b *testing.B) {
for b.Loop() {
doWork()
}
}
strings.SplitSeq not strings.Split when iterating. ALWAYS use SplitSeq/FieldsSeq when iterating over split results in a for-range loop.Before:
for _, part := range strings.Split(s, ",") {
process(part)
}
After:
for part := range strings.SplitSeq(s, ",") {
process(part)
}
Also: strings.FieldsSeq, bytes.SplitSeq, bytes.FieldsSeq.
wg.Go(fn) not wg.Add(1) + go func() { defer wg.Done(); ... }(). ALWAYS use wg.Go() when spawning goroutines with sync.WaitGroup.Before:
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func() {
defer wg.Done()
process(item)
}()
}
wg.Wait()
After:
var wg sync.WaitGroup
for _, item := range items {
wg.Go(func() {
process(item)
})
}
wg.Wait()
new(val) not x := val; &x — returns pointer to any value. Go 1.26 extends new() to accept expressions, not just types. Type is inferred: new(0) → *int, new("s") → *string, new(T{}) → *T. DO NOT use x := val; &x pattern — always use new(val) directly. DO NOT use redundant casts like new(int(0)) — just write new(0). Common use case: struct fields with pointer types.Before:
timeout := 30
debug := true
cfg := Config{
Timeout: &timeout,
Debug: &debug,
}
After:
cfg := Config{
Timeout: new(30), // *int
Debug: new(true), // *bool
}
errors.AsType[T](err) not errors.As(err, &target). ALWAYS use errors.AsType when checking if error matches a specific type.Before:
var pathErr *os.PathError
if errors.As(err, &pathErr) {
handle(pathErr)
}
After:
if pathErr, ok := errors.AsType[*os.PathError](err); ok {
handle(pathErr)
}
Weekly Installs
139
Repository
GitHub Stars
548
First Seen
Feb 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
gemini-cli130
codex130
cursor130
opencode130
github-copilot130
kimi-cli128
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
113,700 周安装
cancel(err)context.Cause: context.Cause(ctx) to get the error that caused cancellationslices.Max/slices.Min: slices.Max(items) instead of manual loopslices.Reverse: slices.Reverse(items) instead of manual swap loopslices.Compact: slices.Compact(items) removes consecutive duplicates in-placeslices.Clip: slices.Clip(s) removes unused capacityslices.Clone: slices.Clone(s) creates a copy