[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# Tooling Modernization
|
||||
|
||||
Beyond the Go language itself, suggest updating all non-functional developer tools that improve code quality or security for free. These are low-risk, high-value improvements: CI actions/plugins, linters (e.g. golangci-lint), SAST tools (e.g. gosec, Snyk, Semgrep), vulnerability scanners (e.g. govulncheck, Trivy), Docker base images, test coverage reporters, dependency management bots (e.g. Renovate, Dependabot), etc.
|
||||
|
||||
## Update to the latest Go version
|
||||
|
||||
Compare the project's `go` directive in `go.mod` against the latest stable release and suggest updating it and the `toolchain` directive if present. Each Go release brings performance improvements, security fixes, and new features.
|
||||
|
||||
```bash
|
||||
# Check current version
|
||||
go version
|
||||
|
||||
# Update go.mod to target a newer version
|
||||
go mod edit -go=1.26
|
||||
|
||||
# Update toolchain
|
||||
go get toolchain@latest
|
||||
```
|
||||
|
||||
## golangci-lint v2 _(golangci-lint v2.0.0+, March 2025)_
|
||||
|
||||
Upgrade to golangci-lint v2. **Migration**: Run `golangci-lint migrate` to convert v1 config to v2. See the `samber/cc-skills-golang@golang-lint` skill for the recommended configuration.
|
||||
|
||||
## govulncheck _(works best with Go 1.22+)_
|
||||
|
||||
`govulncheck` scans Go code for known vulnerabilities using the Go vulnerability database at `vuln.go.dev`. It analyzes call graphs to report only **reachable** vulnerabilities.
|
||||
|
||||
**Note**: The vulnerability database tracks Go standard library issues starting from Go 1.18. Third-party module vulnerabilities are tracked regardless of Go version. For best results (better call graph analysis), use Go 1.22+.
|
||||
|
||||
```bash
|
||||
# Pin in the module (Go 1.24+)
|
||||
go get -tool golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
# Scan source code
|
||||
go tool govulncheck ./...
|
||||
|
||||
# Scan a compiled binary
|
||||
go tool govulncheck -mode=binary ./myapp
|
||||
```
|
||||
|
||||
## Profile-Guided Optimization (PGO) _(Go 1.21+)_
|
||||
|
||||
PGO is generally available since Go 1.21, providing 2-14% performance improvements:
|
||||
|
||||
```bash
|
||||
# 1. Build and run with CPU profiling
|
||||
go test -cpuprofile=default.pgo -bench=. ./...
|
||||
|
||||
# 2. Place default.pgo in the main package directory
|
||||
# 3. Rebuild — PGO is applied automatically
|
||||
go build ./...
|
||||
```
|
||||
|
||||
Go 1.22+ expanded PGO to devirtualize more interface calls. Go 1.23+ reduced PGO build time overhead to single digits.
|
||||
|
||||
## AI-Driven Code Review in CI
|
||||
|
||||
Add an AI agent as a PR reviewer alongside traditional static analysis. When configured with this skill plugin, the agent loads the relevant Go skills — `golang-security` for security review, `golang-concurrency` for concurrency issues, `golang-error-handling` for error handling, and so on — giving it the same expertise as a senior Go reviewer. This catches architectural drift, logic bugs, missing context in errors, and subtle concurrency hazards that linters cannot detect.
|
||||
|
||||
See the `samber/cc-skills-golang@golang-continuous-integration` skill for ready-to-use GitHub Actions assets for both Claude Code and GitHub Copilot.
|
||||
@@ -0,0 +1,735 @@
|
||||
# Go Version Modernizations
|
||||
|
||||
## Go 1.21 Modernizations (August 2023)
|
||||
|
||||
Changelog: <https://go.dev/doc/go1.21>
|
||||
|
||||
### Use built-in `min`, `max`, `clear` _(Go 1.21+)_
|
||||
|
||||
Remove custom implementations. `min`/`max` work with any ordered type and accept variadic arguments:
|
||||
|
||||
```go
|
||||
// Before
|
||||
func minInt(a, b int) int {
|
||||
if a < b { return a }
|
||||
return b
|
||||
}
|
||||
x := minInt(a, b)
|
||||
|
||||
// After (Go 1.21+)
|
||||
x := min(a, b)
|
||||
smallest := min(a, b, c, d)
|
||||
```
|
||||
|
||||
`clear` zeroes maps and slices:
|
||||
|
||||
```go
|
||||
// Before
|
||||
for k := range m { delete(m, k) }
|
||||
|
||||
// After (Go 1.21+)
|
||||
clear(m)
|
||||
```
|
||||
|
||||
### Use `log/slog` instead of third-party loggers _(Go 1.21+)_
|
||||
|
||||
`log/slog` is the standard structured logging package. New code SHOULD migrate to `slog` over `zap`, `logrus`, or `zerolog`.
|
||||
|
||||
```go
|
||||
// Before: zap
|
||||
logger, _ := zap.NewProduction()
|
||||
logger.Info("request handled", zap.String("method", r.Method), zap.Int("status", status))
|
||||
|
||||
// Before: logrus
|
||||
logrus.WithFields(logrus.Fields{"method": r.Method, "status": status}).Info("request handled")
|
||||
|
||||
// After (Go 1.21+): slog
|
||||
slog.Info("request handled", "method", r.Method, "status", status)
|
||||
// Or with type-safe attributes:
|
||||
slog.Info("request handled", slog.String("method", r.Method), slog.Int("status", status))
|
||||
```
|
||||
|
||||
**Migration guidance**: For existing projects heavily invested in third-party loggers, migration is optional. For new projects, prefer `slog`. The `samber/slog-*` ecosystem provides handlers for routing slog output to various backends. Go 1.24 added `slog.DiscardHandler` for silent loggers.
|
||||
|
||||
### Use `slices` package instead of `sort` and manual loops _(Go 1.21+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
sort.Strings(names)
|
||||
sort.Slice(users, func(i, j int) bool { return users[i].Name < users[j].Name })
|
||||
|
||||
// After (Go 1.21+)
|
||||
slices.Sort(names)
|
||||
slices.SortFunc(users, func(a, b User) int { return cmp.Compare(a.Name, b.Name) })
|
||||
```
|
||||
|
||||
```go
|
||||
// Before: manual search
|
||||
found := false
|
||||
for _, v := range items { if v == target { found = true; break } }
|
||||
|
||||
// After (Go 1.21+)
|
||||
found := slices.Contains(items, target)
|
||||
```
|
||||
|
||||
```go
|
||||
// Before: manual clone
|
||||
clone := append([]string(nil), original...)
|
||||
|
||||
// After (Go 1.21+)
|
||||
clone := slices.Clone(original)
|
||||
```
|
||||
|
||||
### Use `maps` package _(Go 1.21+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
clone := make(map[string]int, len(original))
|
||||
for k, v := range original { clone[k] = v }
|
||||
|
||||
// After (Go 1.21+)
|
||||
clone := maps.Clone(original)
|
||||
```
|
||||
|
||||
### Use `cmp.Or` for default values _(Go 1.22+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
addr := os.Getenv("ADDR")
|
||||
if addr == "" { addr = ":8080" }
|
||||
|
||||
// After (Go 1.22+)
|
||||
addr := cmp.Or(os.Getenv("ADDR"), ":8080")
|
||||
```
|
||||
|
||||
### Use `sync.OnceFunc`, `sync.OnceValue`, `sync.OnceValues` _(Go 1.21+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
var (
|
||||
once sync.Once
|
||||
client *http.Client
|
||||
)
|
||||
func getClient() *http.Client {
|
||||
once.Do(func() { client = &http.Client{Timeout: 10 * time.Second} })
|
||||
return client
|
||||
}
|
||||
|
||||
// After (Go 1.21+)
|
||||
var getClient = sync.OnceValue(func() *http.Client {
|
||||
return &http.Client{Timeout: 10 * time.Second}
|
||||
})
|
||||
```
|
||||
|
||||
### Use enhanced `context` functions _(Go 1.21+)_
|
||||
|
||||
```go
|
||||
ctx := context.WithoutCancel(parent) // detach from parent cancellation
|
||||
ctx, cancel := context.WithTimeoutCause(parent, 5*time.Second, errTimeout)
|
||||
ctx, cancel := context.WithDeadlineCause(parent, deadline, errDeadline)
|
||||
stop := context.AfterFunc(ctx, func() { cleanup() })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Go 1.22 Modernizations (February 2024)
|
||||
|
||||
Changelog: <https://go.dev/doc/go1.22>
|
||||
|
||||
### SHOULD use `range` over integers _(Go 1.22+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
for i := 0; i < n; i++ { process(i) }
|
||||
|
||||
// After (Go 1.22+)
|
||||
for i := range n { process(i) }
|
||||
|
||||
// When index isn't needed
|
||||
for range 10 { fmt.Println("hello") }
|
||||
```
|
||||
|
||||
### Remove loop variable shadow copies _(Go 1.22+)_
|
||||
|
||||
Go 1.22 changed loop variable semantics: each iteration creates a new variable. Loop variable captures (`v := v`) SHOULD be removed in Go 1.22+ codebases.
|
||||
|
||||
**Requirement**: The `go` directive in `go.mod` must be `go 1.22` or later for this behavior.
|
||||
|
||||
```go
|
||||
// Before (Go < 1.22)
|
||||
for _, v := range items {
|
||||
v := v // shadow copy to avoid closure bug
|
||||
go func() { process(v) }()
|
||||
}
|
||||
|
||||
// After (Go 1.22+): safe by default
|
||||
for _, v := range items {
|
||||
go func() { process(v) }()
|
||||
}
|
||||
```
|
||||
|
||||
### `math/rand` MUST be replaced with `math/rand/v2` _(Go 1.22+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
import "math/rand"
|
||||
rand.Seed(time.Now().UnixNano()) // no longer needed
|
||||
n := rand.Intn(100)
|
||||
|
||||
// After (Go 1.22+)
|
||||
import "math/rand/v2"
|
||||
n := rand.IntN(100) // IntN, not Intn
|
||||
```
|
||||
|
||||
Key `math/rand/v2` changes:
|
||||
|
||||
- No global seed needed — automatically seeded
|
||||
- `Intn` -> `IntN`, `Int63n` -> `Int64N` (renamed)
|
||||
- `rand.N[T]()` generic function for any integer type
|
||||
- Better algorithms (ChaCha8, PCG)
|
||||
- `Read` removed — use `crypto/rand` for random bytes
|
||||
|
||||
### Use enhanced `net/http` routing _(Go 1.22+)_
|
||||
|
||||
```go
|
||||
// Before: gorilla/mux or chi
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/users/{id}", getUser).Methods("GET")
|
||||
|
||||
// After (Go 1.22+): stdlib
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /users/{id}", getUser)
|
||||
|
||||
func getUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
}
|
||||
```
|
||||
|
||||
### Use `strings.CutPrefix` and `strings.CutSuffix` _(Go 1.20+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
if strings.HasPrefix(s, "Bearer ") {
|
||||
token := strings.TrimPrefix(s, "Bearer ")
|
||||
}
|
||||
|
||||
// After (Go 1.20+)
|
||||
if token, ok := strings.CutPrefix(s, "Bearer "); ok {
|
||||
// use token
|
||||
}
|
||||
```
|
||||
|
||||
### Use `reflect.TypeFor[T]()` _(Go 1.22+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
t := reflect.TypeOf((*MyInterface)(nil)).Elem()
|
||||
|
||||
// After (Go 1.22+)
|
||||
t := reflect.TypeFor[MyInterface]()
|
||||
```
|
||||
|
||||
### Use `database/sql.Null[T]` _(Go 1.22+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
var name sql.NullString
|
||||
var age sql.NullInt64
|
||||
|
||||
// After (Go 1.22+)
|
||||
var name sql.Null[string]
|
||||
var age sql.Null[int64]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Go 1.23 Modernizations (August 2024)
|
||||
|
||||
Changelog: <https://go.dev/doc/go1.23>
|
||||
|
||||
### Use iterators (`range` over functions) _(Go 1.23+)_
|
||||
|
||||
Go 1.23 introduced range-over-func with the `iter` package:
|
||||
|
||||
```go
|
||||
// Before: collect all results into a slice
|
||||
func AllUsers(db *sql.DB) ([]User, error) {
|
||||
rows, err := db.Query("SELECT ...")
|
||||
if err != nil { return nil, err }
|
||||
defer rows.Close()
|
||||
var users []User
|
||||
for rows.Next() {
|
||||
var u User
|
||||
rows.Scan(&u.ID, &u.Name)
|
||||
users = append(users, u)
|
||||
}
|
||||
return users, rows.Err()
|
||||
}
|
||||
|
||||
// After (Go 1.23+): lazy iteration
|
||||
func AllUsers(db *sql.DB) iter.Seq2[User, error] {
|
||||
return func(yield func(User, error) bool) {
|
||||
rows, err := db.Query("SELECT ...")
|
||||
if err != nil { yield(User{}, err); return }
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var u User
|
||||
if err := rows.Scan(&u.ID, &u.Name); err != nil {
|
||||
yield(User{}, err); return
|
||||
}
|
||||
if !yield(u, nil) { return }
|
||||
}
|
||||
if err := rows.Err(); err != nil { yield(User{}, err) }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Use iterator-based `slices` and `maps` functions _(Go 1.23+)_
|
||||
|
||||
```go
|
||||
// Sorted keys via iterator
|
||||
for k := range slices.Sorted(maps.Keys(m)) {
|
||||
fmt.Println(k, m[k])
|
||||
}
|
||||
|
||||
// Collect iterator into slice
|
||||
users := slices.Collect(maps.Values(userMap))
|
||||
|
||||
// Chunk a slice into batches
|
||||
for chunk := range slices.Chunk(items, 100) {
|
||||
processBatch(chunk)
|
||||
}
|
||||
```
|
||||
|
||||
### Use `unique` package for value interning _(Go 1.23+)_
|
||||
|
||||
```go
|
||||
// Before: manual string interning
|
||||
var mu sync.Mutex
|
||||
var interned = make(map[string]string)
|
||||
|
||||
// After (Go 1.23+)
|
||||
handle := unique.Make(s) // Handle[string], comparable, memory-efficient
|
||||
s = handle.Value()
|
||||
```
|
||||
|
||||
### Timer/Ticker behavior change _(Go 1.23+)_
|
||||
|
||||
With `go 1.23` or later in `go.mod`:
|
||||
|
||||
- `time.Timer` and `time.Ticker` are garbage collected without calling `Stop()`
|
||||
- Timer channels are now unbuffered (capacity 0, was 1)
|
||||
|
||||
Remove unnecessary `Stop()` calls in defer patterns where the timer goes out of scope.
|
||||
|
||||
---
|
||||
|
||||
## Go 1.24 Modernizations (February 2025)
|
||||
|
||||
Changelog: <https://go.dev/doc/go1.24>
|
||||
|
||||
### Use generic type aliases _(Go 1.24+)_
|
||||
|
||||
```go
|
||||
// Now valid (Go 1.24+)
|
||||
type Set[T comparable] = map[T]struct{}
|
||||
type Result[T any] = struct { Value T; Err error }
|
||||
```
|
||||
|
||||
### Use `os.Root` for directory-scoped file access _(Go 1.24+)_
|
||||
|
||||
**Security-critical**: `os.Root` prevents path traversal attacks (CWE-22) at the OS level. Replace all manual `filepath.Clean` + `strings.HasPrefix` validation with `os.Root` when handling user-supplied paths. Symlinks resolving outside the root are rejected. Supports `Open`, `Create`, `Stat`, `OpenFile`, `Mkdir`, `Remove`, and more.
|
||||
|
||||
```go
|
||||
// Before: manual path validation (risk of path traversal)
|
||||
path := filepath.Join(baseDir, userInput)
|
||||
data, err := os.ReadFile(path)
|
||||
|
||||
// After (Go 1.24+): safe directory-scoped access
|
||||
root, err := os.OpenRoot("/opt/data")
|
||||
if err != nil { return err }
|
||||
defer root.Close()
|
||||
f, err := root.Open(userInput) // cannot escape root directory
|
||||
```
|
||||
|
||||
### Use `omitzero` JSON tag _(Go 1.24+)_
|
||||
|
||||
`omitzero` is more correct than `omitempty` for `time.Time`, `bool`, and custom types:
|
||||
|
||||
```go
|
||||
// Before: omitempty doesn't work well for time.Time
|
||||
type Event struct {
|
||||
At time.Time `json:"at,omitempty"` // zero time.Time is NOT omitted
|
||||
}
|
||||
|
||||
// After (Go 1.24+)
|
||||
type Event struct {
|
||||
At time.Time `json:"at,omitzero"` // zero time.Time IS omitted
|
||||
}
|
||||
```
|
||||
|
||||
### Use `strings.SplitSeq`, `strings.FieldsSeq`, `strings.Lines` _(Go 1.24+)_
|
||||
|
||||
Iterator-returning variants avoid allocating `[]string`:
|
||||
|
||||
```go
|
||||
// Before: allocates a []string
|
||||
parts := strings.Split(csv, ",")
|
||||
for _, part := range parts { process(part) }
|
||||
|
||||
// After (Go 1.24+): lazy, zero-allocation iteration
|
||||
for part := range strings.SplitSeq(csv, ",") { process(part) }
|
||||
```
|
||||
|
||||
### `t.Context()` SHOULD replace manual `context.Background()` in tests _(Go 1.24+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
func TestFoo(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
}
|
||||
|
||||
// After (Go 1.24+): auto-cancelled when test ends
|
||||
func TestFoo(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
}
|
||||
```
|
||||
|
||||
### `b.Loop()` MUST be used in benchmarks _(Go 1.24+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
func BenchmarkFoo(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ { foo() }
|
||||
}
|
||||
|
||||
// After (Go 1.24+)
|
||||
func BenchmarkFoo(b *testing.B) {
|
||||
for b.Loop() { foo() }
|
||||
}
|
||||
```
|
||||
|
||||
### Use `runtime.AddCleanup` instead of `runtime.SetFinalizer` _(Go 1.24+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
runtime.SetFinalizer(obj, func(o *Object) { o.Close() })
|
||||
|
||||
// After (Go 1.24+): more flexible, no cycle issues
|
||||
runtime.AddCleanup(obj, func(resource Resource) { resource.Close() }, obj.resource)
|
||||
```
|
||||
|
||||
### Use `weak` package for weak references _(Go 1.24+)_
|
||||
|
||||
```go
|
||||
import "weak"
|
||||
|
||||
ptr := weak.Make(obj)
|
||||
if v := ptr.Value(); v != nil {
|
||||
// object still alive
|
||||
}
|
||||
```
|
||||
|
||||
### Use `crypto/sha3`, `crypto/hkdf`, `crypto/pbkdf2` _(Go 1.24+)_
|
||||
|
||||
Replace `golang.org/x/crypto` sub-packages with standard library equivalents:
|
||||
|
||||
```go
|
||||
// Before
|
||||
import "golang.org/x/crypto/sha3"
|
||||
import "golang.org/x/crypto/hkdf"
|
||||
import "golang.org/x/crypto/pbkdf2"
|
||||
|
||||
// After (Go 1.24+)
|
||||
import "crypto/sha3"
|
||||
import "crypto/hkdf"
|
||||
import "crypto/pbkdf2"
|
||||
```
|
||||
|
||||
### Use tool directives in `go.mod` _(Go 1.24+)_
|
||||
|
||||
Use `tool` directives instead of `tools.go` blank imports.
|
||||
|
||||
```bash
|
||||
go get -tool golang.org/x/tools/cmd/stringer@latest
|
||||
go get -tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
||||
go tool stringer -type=Kind
|
||||
go tool golangci-lint run ./...
|
||||
```
|
||||
|
||||
`go.mod` shape for a module targeting Go 1.26 or newer. This is an example target, not a cap; keep the project's actual `go` directive and do not change it just to add tools.
|
||||
|
||||
```go.mod
|
||||
module example.com/project
|
||||
|
||||
go 1.26
|
||||
|
||||
tool (
|
||||
golang.org/x/tools/cmd/stringer
|
||||
github.com/golangci/golangci-lint/v2/cmd/golangci-lint
|
||||
)
|
||||
```
|
||||
|
||||
Use `go install tool` to install all module-pinned tools when needed and `go get -u tool` to update them deliberately.
|
||||
|
||||
### Use `fmt.Appendf`, `fmt.Appendln` _(Go 1.19+, often overlooked)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
buf = append(buf, fmt.Sprintf("count: %d", n)...)
|
||||
|
||||
// After (Go 1.19+)
|
||||
buf = fmt.Appendf(buf, "count: %d", n)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Go 1.25 Modernizations (August 2025)
|
||||
|
||||
Changelog: <https://go.dev/doc/go1.25>
|
||||
|
||||
### Use `sync.WaitGroup.Go` _(Go 1.25+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
process()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
// After (Go 1.25+)
|
||||
var wg sync.WaitGroup
|
||||
wg.Go(func() {
|
||||
process()
|
||||
})
|
||||
wg.Wait()
|
||||
```
|
||||
|
||||
### Use `testing/synctest` for concurrent code testing _(Go 1.25+, experimental in 1.24)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
func TestConcurrent(t *testing.T) {
|
||||
var count atomic.Int32
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
count.Add(1)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Problem: Race conditions are hard to detect, timing-dependent,
|
||||
// and flaky tests are common
|
||||
if count.Load() != 1 {
|
||||
t.Fatal("expected 1")
|
||||
}
|
||||
}
|
||||
|
||||
// After (Go 1.25+)
|
||||
func TestConcurrent(t *testing.T) {
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
var count atomic.Int32
|
||||
go func() { count.Add(1) }()
|
||||
synctest.Wait() // wait for all goroutines to park
|
||||
if count.Load() != 1 { t.Fatal("expected 1") }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Use `synctest.Test` in Go 1.25+ and Go 1.26+. Do not use the old Go 1.24 experimental `synctest.Run` API in Go 1.25+ code.
|
||||
|
||||
### Use `runtime/trace.FlightRecorder` _(Go 1.25+)_
|
||||
|
||||
Lightweight always-on ring-buffer tracing for production:
|
||||
|
||||
```go
|
||||
fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{})
|
||||
if err := fr.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
// ... later, on error:
|
||||
fr.WriteTo(file) // captures recent trace data
|
||||
```
|
||||
|
||||
### Container-aware `GOMAXPROCS` _(Go 1.25+)_
|
||||
|
||||
Go 1.25 automatically respects cgroup CPU limits on Linux. Remove manual workarounds:
|
||||
|
||||
```go
|
||||
// Before: using uber-go/automaxprocs
|
||||
import _ "go.uber.org/automaxprocs"
|
||||
|
||||
// After (Go 1.25+): built-in, remove the import
|
||||
// GOMAXPROCS is set automatically from cgroup CPU limits
|
||||
```
|
||||
|
||||
### `encoding/json/v2` (experimental) _(Go 1.25+, GOEXPERIMENT=jsonv2)_
|
||||
|
||||
Major JSON revision. **Experimental** — evaluate for new code, don't migrate production yet.
|
||||
|
||||
### Go 1.25 additions to prefer when target allows
|
||||
|
||||
- `sync.WaitGroup.Go`: simple fire-and-wait goroutines; function must not panic; no errors/cancellation.
|
||||
- `testing/synctest.Test` and `synctest.Wait`: stable deterministic concurrent/time tests. Do not use the Go 1.24 experimental `synctest.Run` in Go 1.25+.
|
||||
- `net/http.CrossOriginProtection`: stdlib helper for cross-origin / CSRF-style protection in HTTP servers.
|
||||
- `reflect.TypeAssert[T](v)`: prefer over `v.Interface().(T)` in reflection code.
|
||||
- `os.Root.FS` and additional `os.Root` methods: use for confined filesystem APIs.
|
||||
- New vet checks: `waitgroup` misuse and manual host:port formatting; prefer `net.JoinHostPort`.
|
||||
|
||||
---
|
||||
|
||||
## Go 1.26 Modernizations (February 2026)
|
||||
|
||||
Changelog: <https://go.dev/doc/go1.26>
|
||||
|
||||
### Use `errors.AsType[T]()` _(Go 1.26+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
var pathErr *os.PathError
|
||||
if errors.As(err, &pathErr) {
|
||||
fmt.Println(pathErr.Path)
|
||||
}
|
||||
|
||||
// After (Go 1.26+)
|
||||
if pathErr, ok := errors.AsType[*os.PathError](err); ok {
|
||||
fmt.Println(pathErr.Path)
|
||||
}
|
||||
```
|
||||
|
||||
### Use enhanced `new()` _(Go 1.26+)_
|
||||
|
||||
`new(expr)` now accepts a value expression and returns a pointer to it (not zero-initialized):
|
||||
|
||||
```go
|
||||
// Before: helper function needed
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
cfg := Config{Timeout: ptr(30)}
|
||||
|
||||
// After (Go 1.26+): new(expr) initializes the value — equivalent to ptr(30)
|
||||
cfg := Config{Timeout: new(30)} // *int pointing to 30, not 0
|
||||
```
|
||||
|
||||
### Use `crypto/hpke` _(Go 1.26+)_
|
||||
|
||||
Hybrid Public Key Encryption (RFC 9180) is now in the standard library.
|
||||
|
||||
### Use RSA-OAEP or HPKE instead of new PKCS#1 v1.5 encryption _(Go 1.26+)_
|
||||
|
||||
For new encryption use, avoid `crypto/rsa.EncryptPKCS1v15`. Prefer RSA-OAEP (`rsa.EncryptOAEP` / `rsa.EncryptOAEPWithOptions`) or a modern KEM/HPKE design.
|
||||
|
||||
### Green Tea GC enabled by default _(Go 1.26+)_
|
||||
|
||||
Re-evaluate GC and allocation tuning under Go 1.26 Green Tea GC using profiles and benchmarks. Remove legacy tuning only when data supports it. Keep `GOMEMLIMIT` when it represents a real container or service memory ceiling. Remove third-party `automaxprocs` workarounds unless the project has a measured reason, because Go 1.25+ makes `GOMAXPROCS` container-aware by default.
|
||||
|
||||
### Go 1.26+ test artifacts
|
||||
|
||||
Use `t.ArtifactDir()`, `b.ArtifactDir()`, and `f.ArtifactDir()` for files created by tests, benchmarks, and fuzzers that should persist for inspection.
|
||||
|
||||
### Go 1.26+ slog multi-handler
|
||||
|
||||
For simple fan-out to multiple slog handlers, prefer stdlib `slog.NewMultiHandler` before adding third-party handler-composition dependencies.
|
||||
|
||||
### Go 1.26+ ReverseProxy
|
||||
|
||||
For new reverse proxy code, prefer `httputil.ReverseProxy{Rewrite: ...}`. Do not generate new `Director`-based proxy code unless preserving old compatibility.
|
||||
|
||||
```go
|
||||
proxy := &httputil.ReverseProxy{
|
||||
Rewrite: func(pr *httputil.ProxyRequest) {
|
||||
pr.SetURL(targetURL)
|
||||
pr.SetXForwarded()
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Small Go 1.26+ API preferences
|
||||
|
||||
- Use `bytes.Buffer.Peek(n)` when you need to inspect upcoming bytes without consuming them.
|
||||
- Use reflect iterators where they simplify code:
|
||||
- `reflect.Type.Fields()`
|
||||
- `reflect.Type.Methods()`
|
||||
- `reflect.Type.Ins()`
|
||||
- `reflect.Type.Outs()`
|
||||
- `reflect.Value.Fields()`
|
||||
- `reflect.Value.Methods()`
|
||||
- Prefer these over manual `NumField`/`Field(i)` or `NumMethod`/`Method(i)` loops when the iterator form is clearer.
|
||||
|
||||
### Go 1.26+ goroutine leak profile
|
||||
|
||||
For Go 1.26 diagnostics, there is an experimental goroutine leak profile. It is useful for production-oriented leak investigation, but is gated by `GOEXPERIMENT=goroutineleakprofile`; do not rely on it as default stable behavior.
|
||||
|
||||
### Go 1.26+ documentation command
|
||||
|
||||
Use `go doc`, not `go tool doc`. Go 1.26 removed the old `cmd/doc` / `go tool doc` path.
|
||||
|
||||
### Go 1.26+ module target note
|
||||
|
||||
When using a Go 1.26 or newer toolchain, `go mod init` may create a module with an older default `go` directive. If the project intentionally targets Go 1.26+ APIs, update the directive deliberately:
|
||||
|
||||
```bash
|
||||
go mod edit -go=1.26
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
For future Go versions, use the project's intended target version. Do not use APIs newer than the module's `go` directive until the project explicitly agrees to upgrade it.
|
||||
|
||||
### Modernized `go fix` _(Go 1.26+)_
|
||||
|
||||
Go 1.26 rewrote `go fix` to apply a subset of modernize-style analyzers automatically. Check `go tool fix help` for exact coverage; some modernizations still require linting or manual review.
|
||||
|
||||
```bash
|
||||
go fix ./... # applies the enabled safe transformations
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## General Modernization (Any Version)
|
||||
|
||||
### Code MUST use `any` instead of `interface{}` _(Go 1.18+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
func process(data interface{}) interface{} { ... }
|
||||
|
||||
// After (Go 1.18+)
|
||||
func process(data any) any { ... }
|
||||
```
|
||||
|
||||
### Use generics instead of `interface{}` + type assertions _(Go 1.18+)_
|
||||
|
||||
```go
|
||||
// Before
|
||||
func Contains(slice []interface{}, item interface{}) bool { ... }
|
||||
|
||||
// After (Go 1.18+)
|
||||
func Contains[T comparable](slice []T, item T) bool { ... }
|
||||
// Or better (Go 1.21+): slices.Contains
|
||||
```
|
||||
|
||||
### Use `errors.Join` instead of multi-error libraries _(Go 1.20+)_
|
||||
|
||||
```go
|
||||
// Before: hashicorp/go-multierror or uber-go/multierr
|
||||
errs = multierror.Append(errs, err1)
|
||||
return errs.ErrorOrNil()
|
||||
|
||||
// After (Go 1.20+)
|
||||
return errors.Join(err1, err2)
|
||||
```
|
||||
|
||||
### Use `net.JoinHostPort` instead of `fmt.Sprintf` _(any version)_
|
||||
|
||||
```go
|
||||
// Before (broken for IPv6)
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
|
||||
// After (handles IPv6 correctly: [::1]:8080)
|
||||
addr := net.JoinHostPort(host, strconv.Itoa(port))
|
||||
```
|
||||
Reference in New Issue
Block a user