agentic-team-templates 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -14
- package/package.json +1 -1
- package/src/index.js +4 -0
- package/src/index.test.js +1 -0
- package/templates/golang-expert/.cursorrules/concurrency.md +290 -0
- package/templates/golang-expert/.cursorrules/error-handling.md +199 -0
- package/templates/golang-expert/.cursorrules/interfaces-and-types.md +255 -0
- package/templates/golang-expert/.cursorrules/overview.md +139 -0
- package/templates/golang-expert/.cursorrules/performance.md +234 -0
- package/templates/golang-expert/.cursorrules/production-patterns.md +320 -0
- package/templates/golang-expert/.cursorrules/stdlib-and-tooling.md +276 -0
- package/templates/golang-expert/.cursorrules/testing.md +326 -0
- package/templates/golang-expert/CLAUDE.md +361 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# Go Interfaces and Type Design
|
|
2
|
+
|
|
3
|
+
Go's type system is intentionally simple. Interfaces are implicit. Composition replaces inheritance. The goal is clarity, not cleverness.
|
|
4
|
+
|
|
5
|
+
## Interface Design
|
|
6
|
+
|
|
7
|
+
### Keep Interfaces Small
|
|
8
|
+
|
|
9
|
+
```go
|
|
10
|
+
// Good: Single-method interfaces are the most powerful abstraction in Go
|
|
11
|
+
type Reader interface {
|
|
12
|
+
Read(p []byte) (n int, err error)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Writer interface {
|
|
16
|
+
Write(p []byte) (n int, err error)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Compose small interfaces into larger ones when needed
|
|
20
|
+
type ReadWriter interface {
|
|
21
|
+
Reader
|
|
22
|
+
Writer
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Bad: Large interfaces that force implementers to satisfy methods they don't need
|
|
26
|
+
type Repository interface {
|
|
27
|
+
Create(ctx context.Context, entity Entity) error
|
|
28
|
+
GetByID(ctx context.Context, id string) (*Entity, error)
|
|
29
|
+
Update(ctx context.Context, entity Entity) error
|
|
30
|
+
Delete(ctx context.Context, id string) error
|
|
31
|
+
List(ctx context.Context, filter Filter) ([]Entity, error)
|
|
32
|
+
Count(ctx context.Context, filter Filter) (int, error)
|
|
33
|
+
// 6 methods — any mock must implement all 6
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Better: Separate interfaces by consumer need
|
|
37
|
+
type EntityReader interface {
|
|
38
|
+
GetByID(ctx context.Context, id string) (*Entity, error)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type EntityWriter interface {
|
|
42
|
+
Create(ctx context.Context, entity Entity) error
|
|
43
|
+
Update(ctx context.Context, entity Entity) error
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Define Interfaces at the Consumer, Not the Producer
|
|
48
|
+
|
|
49
|
+
```go
|
|
50
|
+
// Good: The consumer defines what it needs
|
|
51
|
+
// In package "handler":
|
|
52
|
+
type UserFinder interface {
|
|
53
|
+
FindByID(ctx context.Context, id string) (*User, error)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type Handler struct {
|
|
57
|
+
users UserFinder // Only needs one method
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// In package "postgres":
|
|
61
|
+
// *UserRepo satisfies handler.UserFinder implicitly
|
|
62
|
+
type UserRepo struct { db *sql.DB }
|
|
63
|
+
|
|
64
|
+
func (r *UserRepo) FindByID(ctx context.Context, id string) (*User, error) { ... }
|
|
65
|
+
func (r *UserRepo) Create(ctx context.Context, u *User) error { ... }
|
|
66
|
+
func (r *UserRepo) Delete(ctx context.Context, id string) error { ... }
|
|
67
|
+
|
|
68
|
+
// Bad: Producer defines an interface and forces consumers to accept all of it
|
|
69
|
+
// package "postgres"
|
|
70
|
+
type UserRepository interface {
|
|
71
|
+
FindByID(ctx context.Context, id string) (*User, error)
|
|
72
|
+
Create(ctx context.Context, u *User) error
|
|
73
|
+
Delete(ctx context.Context, id string) error
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Interface Naming
|
|
78
|
+
|
|
79
|
+
```go
|
|
80
|
+
// Single-method interfaces: method name + "er"
|
|
81
|
+
type Reader interface { Read(p []byte) (int, error) }
|
|
82
|
+
type Closer interface { Close() error }
|
|
83
|
+
type Stringer interface { String() string }
|
|
84
|
+
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
|
|
85
|
+
|
|
86
|
+
// Multi-method interfaces: descriptive noun
|
|
87
|
+
type ReadCloser interface {
|
|
88
|
+
Reader
|
|
89
|
+
Closer
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Struct Design
|
|
94
|
+
|
|
95
|
+
### Functional Options
|
|
96
|
+
|
|
97
|
+
```go
|
|
98
|
+
// For constructors with many optional parameters
|
|
99
|
+
type Server struct {
|
|
100
|
+
addr string
|
|
101
|
+
readTimeout time.Duration
|
|
102
|
+
writeTimeout time.Duration
|
|
103
|
+
logger *slog.Logger
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type Option func(*Server)
|
|
107
|
+
|
|
108
|
+
func WithReadTimeout(d time.Duration) Option {
|
|
109
|
+
return func(s *Server) { s.readTimeout = d }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func WithWriteTimeout(d time.Duration) Option {
|
|
113
|
+
return func(s *Server) { s.writeTimeout = d }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func WithLogger(l *slog.Logger) Option {
|
|
117
|
+
return func(s *Server) { s.logger = l }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func NewServer(addr string, opts ...Option) *Server {
|
|
121
|
+
s := &Server{
|
|
122
|
+
addr: addr,
|
|
123
|
+
readTimeout: 30 * time.Second, // Sensible defaults
|
|
124
|
+
writeTimeout: 30 * time.Second,
|
|
125
|
+
logger: slog.Default(),
|
|
126
|
+
}
|
|
127
|
+
for _, opt := range opts {
|
|
128
|
+
opt(s)
|
|
129
|
+
}
|
|
130
|
+
return s
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Usage
|
|
134
|
+
srv := NewServer(":8080",
|
|
135
|
+
WithReadTimeout(10*time.Second),
|
|
136
|
+
WithLogger(logger),
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Embedding for Composition
|
|
141
|
+
|
|
142
|
+
```go
|
|
143
|
+
// Good: Embed to compose behavior
|
|
144
|
+
type LoggingMiddleware struct {
|
|
145
|
+
next http.Handler
|
|
146
|
+
logger *slog.Logger
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Good: Embed mutex to protect struct fields
|
|
150
|
+
type SafeMap struct {
|
|
151
|
+
mu sync.RWMutex
|
|
152
|
+
m map[string]string
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Bad: Embedding to "inherit" — Go doesn't have inheritance
|
|
156
|
+
type Animal struct { Name string }
|
|
157
|
+
type Dog struct {
|
|
158
|
+
Animal // This is composition, not inheritance — don't treat it as inheritance
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Method Receivers
|
|
163
|
+
|
|
164
|
+
```go
|
|
165
|
+
// Use pointer receivers when:
|
|
166
|
+
// - The method mutates the receiver
|
|
167
|
+
// - The struct is large (avoid copying)
|
|
168
|
+
// - Consistency: if any method needs a pointer, use pointers for all
|
|
169
|
+
func (s *Server) Start() error { ... }
|
|
170
|
+
|
|
171
|
+
// Use value receivers when:
|
|
172
|
+
// - The struct is small and immutable (time.Time, etc.)
|
|
173
|
+
// - The method doesn't modify the receiver
|
|
174
|
+
func (p Point) Distance(other Point) float64 { ... }
|
|
175
|
+
|
|
176
|
+
// Never mix pointer and value receivers on the same type
|
|
177
|
+
// Pick one and be consistent
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Type Aliases and Definitions
|
|
181
|
+
|
|
182
|
+
```go
|
|
183
|
+
// Type definitions create a new, distinct type
|
|
184
|
+
type UserID string
|
|
185
|
+
type Celsius float64
|
|
186
|
+
|
|
187
|
+
// Prevents mixing incompatible values
|
|
188
|
+
func GetUser(id UserID) (*User, error) { ... }
|
|
189
|
+
// GetUser("raw-string") won't compile — must be UserID("raw-string")
|
|
190
|
+
|
|
191
|
+
// Type aliases are for migration, not abstraction
|
|
192
|
+
type OldName = NewName // Both are the same type — useful for gradual refactors
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Generics (Go 1.18+)
|
|
196
|
+
|
|
197
|
+
```go
|
|
198
|
+
// Use generics when the logic is identical across types
|
|
199
|
+
// and the type parameter adds real value
|
|
200
|
+
|
|
201
|
+
// Good: Generic data structures and algorithms
|
|
202
|
+
func Map[T, U any](s []T, f func(T) U) []U {
|
|
203
|
+
result := make([]U, len(s))
|
|
204
|
+
for i, v := range s {
|
|
205
|
+
result[i] = f(v)
|
|
206
|
+
}
|
|
207
|
+
return result
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
func Filter[T any](s []T, predicate func(T) bool) []T {
|
|
211
|
+
var result []T
|
|
212
|
+
for _, v := range s {
|
|
213
|
+
if predicate(v) {
|
|
214
|
+
result = append(result, v)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return result
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Good: Type constraints for compile-time safety
|
|
221
|
+
type Ordered interface {
|
|
222
|
+
~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
|
223
|
+
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
|
|
224
|
+
~float32 | ~float64 | ~string
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
func Max[T Ordered](a, b T) T {
|
|
228
|
+
if a > b {
|
|
229
|
+
return a
|
|
230
|
+
}
|
|
231
|
+
return b
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Bad: Generics for the sake of generics
|
|
235
|
+
// If there's only one concrete type, don't make it generic
|
|
236
|
+
// If an interface works, prefer the interface
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Anti-Patterns
|
|
240
|
+
|
|
241
|
+
```go
|
|
242
|
+
// Never: Empty interface (any) as a parameter when the type is known
|
|
243
|
+
func Process(data any) { ... } // What is data? No one knows without reading the implementation.
|
|
244
|
+
|
|
245
|
+
// Never: Interface pollution — defining interfaces you don't consume
|
|
246
|
+
// If only one type implements the interface, you probably don't need the interface
|
|
247
|
+
|
|
248
|
+
// Never: Returning interfaces from constructors (hides information)
|
|
249
|
+
func New() MyInterface { return &myImpl{} } // BAD — return *myImpl
|
|
250
|
+
|
|
251
|
+
// Never: Stuttering — the package name is part of the identifier
|
|
252
|
+
package user
|
|
253
|
+
type UserService struct{} // user.UserService stutters
|
|
254
|
+
type Service struct{} // user.Service reads cleanly
|
|
255
|
+
```
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Go Expert
|
|
2
|
+
|
|
3
|
+
Guidelines for principal-level Go engineering. Idiomatic Go, production systems, and deep runtime knowledge.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
This ruleset applies to:
|
|
8
|
+
- HTTP services and gRPC servers
|
|
9
|
+
- CLI tools and system utilities
|
|
10
|
+
- Libraries and shared packages
|
|
11
|
+
- Distributed systems and microservices
|
|
12
|
+
- Data pipelines and stream processing
|
|
13
|
+
- Infrastructure tooling and platform code
|
|
14
|
+
|
|
15
|
+
## Core Philosophy
|
|
16
|
+
|
|
17
|
+
Go is a language of restraint. The best Go code is boring Go code.
|
|
18
|
+
|
|
19
|
+
- **Simplicity is the highest virtue.** If a junior engineer can't read it, it's too clever.
|
|
20
|
+
- **The standard library is your first dependency.** Reach for third-party packages only when the stdlib genuinely falls short.
|
|
21
|
+
- **Errors are values, not exceptions.** Handle them explicitly at every call site.
|
|
22
|
+
- **Concurrency is a tool, not a default.** Don't use goroutines because you can — use them because the problem demands it.
|
|
23
|
+
- **Interfaces are discovered, not designed.** Accept interfaces, return structs.
|
|
24
|
+
- **If you don't know, say you don't know.** Guessing at behavior you haven't verified is worse than admitting uncertainty.
|
|
25
|
+
|
|
26
|
+
## Key Principles
|
|
27
|
+
|
|
28
|
+
### 1. Effective Go Is the Baseline
|
|
29
|
+
|
|
30
|
+
The official [Effective Go](https://go.dev/doc/effective_go) document and the [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments) wiki are not suggestions — they are the floor. Everything in this guide builds on top of those foundations.
|
|
31
|
+
|
|
32
|
+
### 2. Package Design
|
|
33
|
+
|
|
34
|
+
```go
|
|
35
|
+
// A package should have a single, clear purpose.
|
|
36
|
+
// The package name IS the documentation.
|
|
37
|
+
|
|
38
|
+
// Good: package names that describe what they do
|
|
39
|
+
package httputil // HTTP utility functions
|
|
40
|
+
package authz // Authorization logic
|
|
41
|
+
package sqlmigrate // SQL migration runner
|
|
42
|
+
|
|
43
|
+
// Bad: package names that describe nothing
|
|
44
|
+
package util // Util of what?
|
|
45
|
+
package common // Common to whom?
|
|
46
|
+
package helpers // Helping with what?
|
|
47
|
+
package base // Base of what?
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 3. Accept Interfaces, Return Structs
|
|
51
|
+
|
|
52
|
+
```go
|
|
53
|
+
// Good: Accept the narrowest interface that satisfies the need
|
|
54
|
+
func ProcessRecords(r io.Reader) error { ... }
|
|
55
|
+
|
|
56
|
+
// Good: Return concrete types — let callers decide abstraction
|
|
57
|
+
func NewServer(cfg Config) *Server { ... }
|
|
58
|
+
|
|
59
|
+
// Bad: Returning an interface hides information and prevents extension
|
|
60
|
+
func NewServer(cfg Config) ServerInterface { ... }
|
|
61
|
+
|
|
62
|
+
// Bad: Accepting a concrete type when only one method is needed
|
|
63
|
+
func ProcessRecords(f *os.File) error { ... }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 4. Zero Values Are Useful
|
|
67
|
+
|
|
68
|
+
```go
|
|
69
|
+
// Design types so zero values are valid and usable
|
|
70
|
+
type Buffer struct {
|
|
71
|
+
buf []byte
|
|
72
|
+
// No constructor needed — zero value is an empty, ready-to-use buffer
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// sync.Mutex zero value is an unlocked mutex — no Init() needed
|
|
76
|
+
var mu sync.Mutex
|
|
77
|
+
|
|
78
|
+
// bytes.Buffer zero value is an empty buffer ready for writes
|
|
79
|
+
var buf bytes.Buffer
|
|
80
|
+
buf.WriteString("hello")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 5. Error Handling Is Not Boilerplate
|
|
84
|
+
|
|
85
|
+
```go
|
|
86
|
+
// Every error check is a conscious decision about what happens next.
|
|
87
|
+
// If you find yourself writing the same error handling repeatedly,
|
|
88
|
+
// the API design needs work — not the error handling.
|
|
89
|
+
|
|
90
|
+
// Good: Add context at every level
|
|
91
|
+
result, err := db.QueryContext(ctx, query, args...)
|
|
92
|
+
if err != nil {
|
|
93
|
+
return fmt.Errorf("querying users by email %q: %w", email, err)
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Project Structure
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
project/
|
|
101
|
+
├── cmd/ # Main applications
|
|
102
|
+
│ └── myapp/
|
|
103
|
+
│ └── main.go # Minimal — wiring only
|
|
104
|
+
├── internal/ # Private application code
|
|
105
|
+
│ ├── domain/ # Core business types and logic
|
|
106
|
+
│ ├── service/ # Application services / use cases
|
|
107
|
+
│ ├── repository/ # Data access implementations
|
|
108
|
+
│ ├── handler/ # HTTP/gRPC handlers
|
|
109
|
+
│ └── platform/ # Infrastructure concerns (logging, metrics, config)
|
|
110
|
+
├── pkg/ # Public library code (use sparingly)
|
|
111
|
+
├── api/ # API definitions (OpenAPI, protobuf)
|
|
112
|
+
├── migrations/ # Database migrations
|
|
113
|
+
├── testdata/ # Test fixtures
|
|
114
|
+
├── go.mod
|
|
115
|
+
├── go.sum
|
|
116
|
+
└── Makefile
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Layout Rules
|
|
120
|
+
|
|
121
|
+
- `cmd/` contains only `main.go` files that wire dependencies and call `run()`
|
|
122
|
+
- `internal/` is your private code — the compiler enforces this boundary
|
|
123
|
+
- `pkg/` is for genuinely reusable library code intended for other projects — most projects don't need it
|
|
124
|
+
- Never put business logic in `cmd/` or `handler/`
|
|
125
|
+
- Co-locate tests with the code they test: `foo.go` and `foo_test.go` in the same package
|
|
126
|
+
|
|
127
|
+
## Definition of Done
|
|
128
|
+
|
|
129
|
+
A Go feature is complete when:
|
|
130
|
+
- [ ] `go vet ./...` reports zero issues
|
|
131
|
+
- [ ] `staticcheck ./...` reports zero issues
|
|
132
|
+
- [ ] `golangci-lint run` passes with the project's config
|
|
133
|
+
- [ ] `go test -race ./...` passes with no race conditions
|
|
134
|
+
- [ ] Test coverage covers meaningful behavior (not just lines)
|
|
135
|
+
- [ ] Error paths are tested, not just happy paths
|
|
136
|
+
- [ ] Documentation comments on all exported identifiers
|
|
137
|
+
- [ ] No `TODO` or `FIXME` without an associated issue
|
|
138
|
+
- [ ] Context propagation is correct (no `context.Background()` in request paths)
|
|
139
|
+
- [ ] Resource cleanup verified (deferred closes, connection pools)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Go Performance
|
|
2
|
+
|
|
3
|
+
Go is fast by default. The garbage collector, runtime scheduler, and compiler do heavy lifting. Your job is to not get in the way — and to know how to profile when something is slow.
|
|
4
|
+
|
|
5
|
+
## Profile First
|
|
6
|
+
|
|
7
|
+
Never optimize without profiling data. Intuition about bottlenecks is wrong more often than it's right.
|
|
8
|
+
|
|
9
|
+
```go
|
|
10
|
+
// CPU profiling
|
|
11
|
+
import "runtime/pprof"
|
|
12
|
+
|
|
13
|
+
f, _ := os.Create("cpu.prof")
|
|
14
|
+
pprof.StartCPUProfile(f)
|
|
15
|
+
defer pprof.StopCPUProfile()
|
|
16
|
+
|
|
17
|
+
// Memory profiling
|
|
18
|
+
f, _ := os.Create("mem.prof")
|
|
19
|
+
pprof.WriteHeapProfile(f)
|
|
20
|
+
|
|
21
|
+
// In HTTP servers — import for side effects
|
|
22
|
+
import _ "net/http/pprof"
|
|
23
|
+
// Then: go tool pprof http://localhost:6060/debug/pprof/heap
|
|
24
|
+
|
|
25
|
+
// Analyze
|
|
26
|
+
// go tool pprof cpu.prof
|
|
27
|
+
// (pprof) top10
|
|
28
|
+
// (pprof) web # Opens in browser
|
|
29
|
+
// (pprof) list FunctionName
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Memory Allocation
|
|
33
|
+
|
|
34
|
+
### Preallocate Slices
|
|
35
|
+
|
|
36
|
+
```go
|
|
37
|
+
// Good: Preallocate when length is known
|
|
38
|
+
users := make([]User, 0, len(ids))
|
|
39
|
+
for _, id := range ids {
|
|
40
|
+
u, err := getUser(id)
|
|
41
|
+
if err != nil {
|
|
42
|
+
return nil, err
|
|
43
|
+
}
|
|
44
|
+
users = append(users, u)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Bad: Growing slice dynamically
|
|
48
|
+
var users []User // Repeated reallocation as it grows
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Avoid Unnecessary Allocations
|
|
52
|
+
|
|
53
|
+
```go
|
|
54
|
+
// Good: Reuse buffers
|
|
55
|
+
var bufPool = sync.Pool{
|
|
56
|
+
New: func() any { return new(bytes.Buffer) },
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func process(data []byte) string {
|
|
60
|
+
buf := bufPool.Get().(*bytes.Buffer)
|
|
61
|
+
defer func() {
|
|
62
|
+
buf.Reset()
|
|
63
|
+
bufPool.Put(buf)
|
|
64
|
+
}()
|
|
65
|
+
|
|
66
|
+
buf.Write(data)
|
|
67
|
+
// ... process ...
|
|
68
|
+
return buf.String()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Good: strings.Builder for string concatenation
|
|
72
|
+
var b strings.Builder
|
|
73
|
+
for _, s := range parts {
|
|
74
|
+
b.WriteString(s)
|
|
75
|
+
}
|
|
76
|
+
result := b.String()
|
|
77
|
+
|
|
78
|
+
// Bad: String concatenation in a loop
|
|
79
|
+
result := ""
|
|
80
|
+
for _, s := range parts {
|
|
81
|
+
result += s // Allocates a new string every iteration
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Pointer vs Value Semantics
|
|
86
|
+
|
|
87
|
+
```go
|
|
88
|
+
// Small structs (< ~64 bytes): pass by value — avoids heap allocation
|
|
89
|
+
type Point struct{ X, Y float64 }
|
|
90
|
+
func Distance(a, b Point) float64 { ... } // Copies are cheap, stays on stack
|
|
91
|
+
|
|
92
|
+
// Large structs or structs that must be shared: pass by pointer
|
|
93
|
+
type Config struct { /* many fields */ }
|
|
94
|
+
func NewServer(cfg *Config) *Server { ... }
|
|
95
|
+
|
|
96
|
+
// Slices, maps, and channels are already reference types — don't pass pointers to them
|
|
97
|
+
func Process(items []Item) { ... } // Good
|
|
98
|
+
func Process(items *[]Item) { ... } // Bad (unless you need to modify the slice header)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Concurrency Performance
|
|
102
|
+
|
|
103
|
+
### Minimize Lock Contention
|
|
104
|
+
|
|
105
|
+
```go
|
|
106
|
+
// Use sync.RWMutex for read-heavy workloads
|
|
107
|
+
type Cache struct {
|
|
108
|
+
mu sync.RWMutex
|
|
109
|
+
data map[string]Entry
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func (c *Cache) Get(key string) (Entry, bool) {
|
|
113
|
+
c.mu.RLock()
|
|
114
|
+
defer c.mu.RUnlock()
|
|
115
|
+
e, ok := c.data[key]
|
|
116
|
+
return e, ok
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func (c *Cache) Set(key string, entry Entry) {
|
|
120
|
+
c.mu.Lock()
|
|
121
|
+
defer c.mu.Unlock()
|
|
122
|
+
c.data[key] = entry
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// For high-contention counters, use atomic operations
|
|
126
|
+
var requestCount atomic.Int64
|
|
127
|
+
requestCount.Add(1)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Avoid Goroutine Leaks
|
|
131
|
+
|
|
132
|
+
```go
|
|
133
|
+
// Every goroutine must have a clear exit condition
|
|
134
|
+
// Use context cancellation to signal shutdown
|
|
135
|
+
|
|
136
|
+
func startWorkers(ctx context.Context, n int) {
|
|
137
|
+
var wg sync.WaitGroup
|
|
138
|
+
for range n {
|
|
139
|
+
wg.Add(1)
|
|
140
|
+
go func() {
|
|
141
|
+
defer wg.Done()
|
|
142
|
+
for {
|
|
143
|
+
select {
|
|
144
|
+
case <-ctx.Done():
|
|
145
|
+
return
|
|
146
|
+
default:
|
|
147
|
+
doWork()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}()
|
|
151
|
+
}
|
|
152
|
+
wg.Wait()
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Compiler Optimizations
|
|
157
|
+
|
|
158
|
+
```go
|
|
159
|
+
// Inlining: Small functions are automatically inlined
|
|
160
|
+
// Check: go build -gcflags="-m" ./...
|
|
161
|
+
|
|
162
|
+
// Escape analysis: The compiler decides stack vs heap
|
|
163
|
+
// Check: go build -gcflags="-m" ./...
|
|
164
|
+
// "moved to heap" = allocation — consider if avoidable
|
|
165
|
+
|
|
166
|
+
// Bounds check elimination: The compiler removes bounds checks
|
|
167
|
+
// when it can prove safety
|
|
168
|
+
s := data[0:10]
|
|
169
|
+
for i := range s { // Compiler knows i is in bounds — no check needed
|
|
170
|
+
process(s[i])
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Database Performance
|
|
175
|
+
|
|
176
|
+
```go
|
|
177
|
+
// Always use connection pooling
|
|
178
|
+
db, err := sql.Open("postgres", connString)
|
|
179
|
+
db.SetMaxOpenConns(25) // Limit concurrent connections
|
|
180
|
+
db.SetMaxIdleConns(5) // Keep some connections warm
|
|
181
|
+
db.SetConnMaxLifetime(5 * time.Minute) // Rotate connections
|
|
182
|
+
|
|
183
|
+
// Use prepared statements for repeated queries
|
|
184
|
+
stmt, err := db.PrepareContext(ctx, "SELECT * FROM users WHERE id = $1")
|
|
185
|
+
defer stmt.Close()
|
|
186
|
+
|
|
187
|
+
// Use batch operations for bulk inserts
|
|
188
|
+
// Use COPY protocol (pgx) for large data loads
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Benchmarking Best Practices
|
|
192
|
+
|
|
193
|
+
```go
|
|
194
|
+
func BenchmarkJSON(b *testing.B) {
|
|
195
|
+
data := loadTestData()
|
|
196
|
+
|
|
197
|
+
b.ResetTimer() // Exclude setup time
|
|
198
|
+
b.ReportAllocs() // Report memory allocations
|
|
199
|
+
|
|
200
|
+
for b.Loop() {
|
|
201
|
+
var result MyStruct
|
|
202
|
+
if err := json.Unmarshal(data, &result); err != nil {
|
|
203
|
+
b.Fatal(err)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Compare implementations
|
|
209
|
+
func BenchmarkJSON_StdLib(b *testing.B) { ... }
|
|
210
|
+
func BenchmarkJSON_Sonic(b *testing.B) { ... }
|
|
211
|
+
|
|
212
|
+
// Run: go test -bench=. -benchmem -count=5 ./...
|
|
213
|
+
// Use benchstat to compare: benchstat old.txt new.txt
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Anti-Patterns
|
|
217
|
+
|
|
218
|
+
```go
|
|
219
|
+
// Never: Premature optimization
|
|
220
|
+
// Profile first. The bottleneck is almost never where you think it is.
|
|
221
|
+
|
|
222
|
+
// Never: sync.Pool for small, short-lived objects
|
|
223
|
+
// The GC handles these efficiently. Pool is for large buffers.
|
|
224
|
+
|
|
225
|
+
// Never: Manual memory management tricks
|
|
226
|
+
// Go's GC is highly optimized. Fight the runtime and you'll lose.
|
|
227
|
+
|
|
228
|
+
// Never: Caching without bounds
|
|
229
|
+
cache := make(map[string]Result) // Grows forever — OOM eventually
|
|
230
|
+
// Use an LRU cache with a max size.
|
|
231
|
+
|
|
232
|
+
// Never: Blocking operations in hot paths without timeouts
|
|
233
|
+
resp, err := http.Get(url) // No timeout! Use http.Client with Timeout set.
|
|
234
|
+
```
|