agentic-team-templates 0.11.0 → 0.12.0
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/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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-team-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "AI coding assistant templates for Cursor IDE. Pre-configured rules and guidelines that help AI assistants write better code. - use at your own risk",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cursor",
|
package/src/index.js
CHANGED
|
@@ -44,6 +44,10 @@ const TEMPLATES = {
|
|
|
44
44
|
description: 'Full-stack web applications (Next.js, Nuxt, SvelteKit, Remix)',
|
|
45
45
|
rules: ['api-contracts.md', 'architecture.md', 'overview.md', 'shared-types.md', 'testing.md']
|
|
46
46
|
},
|
|
47
|
+
'golang-expert': {
|
|
48
|
+
description: 'Principal-level Go engineering (concurrency, stdlib, production patterns, testing)',
|
|
49
|
+
rules: ['concurrency.md', 'error-handling.md', 'interfaces-and-types.md', 'overview.md', 'performance.md', 'production-patterns.md', 'stdlib-and-tooling.md', 'testing.md']
|
|
50
|
+
},
|
|
47
51
|
'javascript-expert': {
|
|
48
52
|
description: 'Principal-level JavaScript engineering across Node.js, React, vanilla JS, and testing',
|
|
49
53
|
rules: ['language-deep-dive.md', 'node-patterns.md', 'overview.md', 'performance.md', 'react-patterns.md', 'testing.md', 'tooling.md']
|
package/src/index.test.js
CHANGED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Go Concurrency
|
|
2
|
+
|
|
3
|
+
Concurrency is Go's defining feature. It is also the easiest way to write bugs that are impossible to reproduce. Use it deliberately, not by default.
|
|
4
|
+
|
|
5
|
+
## Fundamental Rules
|
|
6
|
+
|
|
7
|
+
### 1. Don't Start a Goroutine You Can't Stop
|
|
8
|
+
|
|
9
|
+
```go
|
|
10
|
+
// Good: Goroutine respects context cancellation
|
|
11
|
+
func (w *Worker) Run(ctx context.Context) error {
|
|
12
|
+
for {
|
|
13
|
+
select {
|
|
14
|
+
case <-ctx.Done():
|
|
15
|
+
return ctx.Err()
|
|
16
|
+
case job := <-w.jobs:
|
|
17
|
+
if err := w.process(ctx, job); err != nil {
|
|
18
|
+
return fmt.Errorf("processing job: %w", err)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Bad: Goroutine runs forever with no way to stop it
|
|
25
|
+
go func() {
|
|
26
|
+
for {
|
|
27
|
+
doWork() // No context, no signal, no way out
|
|
28
|
+
}
|
|
29
|
+
}()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. The Caller Decides Concurrency
|
|
33
|
+
|
|
34
|
+
```go
|
|
35
|
+
// Good: Synchronous function — caller decides whether to use goroutine
|
|
36
|
+
func FetchUser(ctx context.Context, id string) (*User, error) {
|
|
37
|
+
// ...
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Caller makes it concurrent when needed
|
|
41
|
+
go func() {
|
|
42
|
+
user, err := FetchUser(ctx, id)
|
|
43
|
+
results <- result{user, err}
|
|
44
|
+
}()
|
|
45
|
+
|
|
46
|
+
// Bad: Function that forces concurrency on the caller
|
|
47
|
+
func FetchUserAsync(id string) <-chan *User {
|
|
48
|
+
ch := make(chan *User)
|
|
49
|
+
go func() {
|
|
50
|
+
// Caller can't pass context, can't handle errors properly
|
|
51
|
+
}()
|
|
52
|
+
return ch
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Share Memory by Communicating
|
|
57
|
+
|
|
58
|
+
```go
|
|
59
|
+
// Good: Channels for ownership transfer
|
|
60
|
+
func generator(ctx context.Context) <-chan int {
|
|
61
|
+
ch := make(chan int)
|
|
62
|
+
go func() {
|
|
63
|
+
defer close(ch)
|
|
64
|
+
for i := 0; ; i++ {
|
|
65
|
+
select {
|
|
66
|
+
case <-ctx.Done():
|
|
67
|
+
return
|
|
68
|
+
case ch <- i:
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}()
|
|
72
|
+
return ch
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Good: Mutex when protecting shared state with simple access patterns
|
|
76
|
+
type SafeCounter struct {
|
|
77
|
+
mu sync.Mutex
|
|
78
|
+
v map[string]int
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func (c *SafeCounter) Inc(key string) {
|
|
82
|
+
c.mu.Lock()
|
|
83
|
+
defer c.mu.Unlock()
|
|
84
|
+
c.v[key]++
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Channel Patterns
|
|
89
|
+
|
|
90
|
+
### Pipeline
|
|
91
|
+
|
|
92
|
+
```go
|
|
93
|
+
func pipeline(ctx context.Context, input <-chan int) <-chan int {
|
|
94
|
+
out := make(chan int)
|
|
95
|
+
go func() {
|
|
96
|
+
defer close(out)
|
|
97
|
+
for v := range input {
|
|
98
|
+
select {
|
|
99
|
+
case <-ctx.Done():
|
|
100
|
+
return
|
|
101
|
+
case out <- v * 2:
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}()
|
|
105
|
+
return out
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Fan-Out, Fan-In
|
|
110
|
+
|
|
111
|
+
```go
|
|
112
|
+
func fanOut(ctx context.Context, input <-chan Job, workers int) <-chan Result {
|
|
113
|
+
results := make(chan Result)
|
|
114
|
+
var wg sync.WaitGroup
|
|
115
|
+
|
|
116
|
+
for range workers {
|
|
117
|
+
wg.Add(1)
|
|
118
|
+
go func() {
|
|
119
|
+
defer wg.Done()
|
|
120
|
+
for job := range input {
|
|
121
|
+
select {
|
|
122
|
+
case <-ctx.Done():
|
|
123
|
+
return
|
|
124
|
+
case results <- process(job):
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
go func() {
|
|
131
|
+
wg.Wait()
|
|
132
|
+
close(results)
|
|
133
|
+
}()
|
|
134
|
+
|
|
135
|
+
return results
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Bounded Concurrency with Semaphore
|
|
140
|
+
|
|
141
|
+
```go
|
|
142
|
+
func processAll(ctx context.Context, items []Item, maxConcurrent int) error {
|
|
143
|
+
sem := make(chan struct{}, maxConcurrent)
|
|
144
|
+
g, ctx := errgroup.WithContext(ctx)
|
|
145
|
+
|
|
146
|
+
for _, item := range items {
|
|
147
|
+
g.Go(func() error {
|
|
148
|
+
select {
|
|
149
|
+
case sem <- struct{}{}:
|
|
150
|
+
defer func() { <-sem }()
|
|
151
|
+
case <-ctx.Done():
|
|
152
|
+
return ctx.Err()
|
|
153
|
+
}
|
|
154
|
+
return process(ctx, item)
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return g.Wait()
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## sync Package
|
|
163
|
+
|
|
164
|
+
### sync.WaitGroup
|
|
165
|
+
|
|
166
|
+
```go
|
|
167
|
+
// Always Add before launching the goroutine
|
|
168
|
+
var wg sync.WaitGroup
|
|
169
|
+
for _, item := range items {
|
|
170
|
+
wg.Add(1)
|
|
171
|
+
go func() {
|
|
172
|
+
defer wg.Done()
|
|
173
|
+
process(item)
|
|
174
|
+
}()
|
|
175
|
+
}
|
|
176
|
+
wg.Wait()
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### sync.Once
|
|
180
|
+
|
|
181
|
+
```go
|
|
182
|
+
// For lazy, thread-safe initialization
|
|
183
|
+
type Client struct {
|
|
184
|
+
initOnce sync.Once
|
|
185
|
+
conn *grpc.ClientConn
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func (c *Client) getConn() *grpc.ClientConn {
|
|
189
|
+
c.initOnce.Do(func() {
|
|
190
|
+
c.conn = dial() // Only called once, even from many goroutines
|
|
191
|
+
})
|
|
192
|
+
return c.conn
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### sync.Map
|
|
197
|
+
|
|
198
|
+
```go
|
|
199
|
+
// Use sync.Map ONLY when:
|
|
200
|
+
// 1. Keys are stable (write-once, read-many)
|
|
201
|
+
// 2. Disjoint goroutines access disjoint key sets
|
|
202
|
+
// Otherwise, a regular map with sync.Mutex is simpler and often faster.
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## errgroup
|
|
206
|
+
|
|
207
|
+
```go
|
|
208
|
+
import "golang.org/x/sync/errgroup"
|
|
209
|
+
|
|
210
|
+
func fetchAll(ctx context.Context, urls []string) ([]Response, error) {
|
|
211
|
+
g, ctx := errgroup.WithContext(ctx)
|
|
212
|
+
responses := make([]Response, len(urls))
|
|
213
|
+
|
|
214
|
+
for i, url := range urls {
|
|
215
|
+
g.Go(func() error {
|
|
216
|
+
resp, err := fetch(ctx, url)
|
|
217
|
+
if err != nil {
|
|
218
|
+
return fmt.Errorf("fetching %s: %w", url, err)
|
|
219
|
+
}
|
|
220
|
+
responses[i] = resp // Safe: each goroutine writes to its own index
|
|
221
|
+
return nil
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if err := g.Wait(); err != nil {
|
|
226
|
+
return nil, err
|
|
227
|
+
}
|
|
228
|
+
return responses, nil
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Context
|
|
233
|
+
|
|
234
|
+
```go
|
|
235
|
+
// Context is the first parameter, always
|
|
236
|
+
func DoWork(ctx context.Context, id string) error { ... }
|
|
237
|
+
|
|
238
|
+
// Never store context in a struct
|
|
239
|
+
// Bad:
|
|
240
|
+
type Server struct {
|
|
241
|
+
ctx context.Context // Don't do this
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Derive child contexts for scoped deadlines
|
|
245
|
+
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
246
|
+
defer cancel() // Always defer cancel to avoid leaks
|
|
247
|
+
|
|
248
|
+
// Use context values sparingly — only for request-scoped data
|
|
249
|
+
// that transits process boundaries (trace IDs, auth tokens)
|
|
250
|
+
// Never use context for passing optional parameters
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Race Condition Prevention
|
|
254
|
+
|
|
255
|
+
```go
|
|
256
|
+
// Always run tests with -race
|
|
257
|
+
// go test -race ./...
|
|
258
|
+
|
|
259
|
+
// Common race: closing over loop variable (fixed in Go 1.22+)
|
|
260
|
+
// Pre-1.22, you needed:
|
|
261
|
+
for _, item := range items {
|
|
262
|
+
item := item // Shadow the loop variable
|
|
263
|
+
go process(item)
|
|
264
|
+
}
|
|
265
|
+
// Go 1.22+ loop variables are per-iteration — no shadowing needed
|
|
266
|
+
|
|
267
|
+
// Common race: reading and writing a map concurrently
|
|
268
|
+
// Maps are NOT safe for concurrent use — use sync.Mutex or sync.Map
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Anti-Patterns
|
|
272
|
+
|
|
273
|
+
```go
|
|
274
|
+
// Never: Goroutine without ownership
|
|
275
|
+
go doSomething() // Who waits for this? Who handles the error?
|
|
276
|
+
|
|
277
|
+
// Never: time.Sleep for synchronization
|
|
278
|
+
time.Sleep(100 * time.Millisecond) // Hoping goroutine finishes — use sync primitives
|
|
279
|
+
|
|
280
|
+
// Never: Unbounded goroutine spawning
|
|
281
|
+
for _, item := range millionItems {
|
|
282
|
+
go process(item) // OOM or file descriptor exhaustion
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Never: Closing a channel from the receiver side
|
|
286
|
+
// Only the sender closes. Receivers check with range or ok idiom.
|
|
287
|
+
|
|
288
|
+
// Never: Sending on a closed channel (panics)
|
|
289
|
+
// Design your channel lifecycle so ownership is clear
|
|
290
|
+
```
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Go Error Handling
|
|
2
|
+
|
|
3
|
+
Errors in Go are values. They are the primary control flow mechanism for failure cases. Treat them with the same rigor as your business logic.
|
|
4
|
+
|
|
5
|
+
## Fundamental Rules
|
|
6
|
+
|
|
7
|
+
### Always Handle Errors
|
|
8
|
+
|
|
9
|
+
```go
|
|
10
|
+
// Good: Every error is handled
|
|
11
|
+
f, err := os.Open(filename)
|
|
12
|
+
if err != nil {
|
|
13
|
+
return fmt.Errorf("opening config file: %w", err)
|
|
14
|
+
}
|
|
15
|
+
defer f.Close()
|
|
16
|
+
|
|
17
|
+
// Bad: Silently discarding errors
|
|
18
|
+
f, _ := os.Open(filename) // What happens when this fails?
|
|
19
|
+
|
|
20
|
+
// Bad: Logging and continuing as if nothing happened
|
|
21
|
+
f, err := os.Open(filename)
|
|
22
|
+
if err != nil {
|
|
23
|
+
log.Println(err) // Then what? f is nil. You'll panic below.
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Wrap With Context Using %w
|
|
28
|
+
|
|
29
|
+
```go
|
|
30
|
+
// Good: Each layer adds context about what it was doing
|
|
31
|
+
func (s *UserService) GetByEmail(ctx context.Context, email string) (*User, error) {
|
|
32
|
+
user, err := s.repo.FindByEmail(ctx, email)
|
|
33
|
+
if err != nil {
|
|
34
|
+
return nil, fmt.Errorf("getting user by email %q: %w", email, err)
|
|
35
|
+
}
|
|
36
|
+
return user, nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The error chain reads like a stack trace:
|
|
40
|
+
// "getting user by email "bob@example.com": querying users table: sql: connection refused"
|
|
41
|
+
|
|
42
|
+
// Bad: Wrapping without adding useful context
|
|
43
|
+
if err != nil {
|
|
44
|
+
return fmt.Errorf("error: %w", err) // "error:" adds nothing
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Bad: Wrapping with redundant "failed to" prefix
|
|
48
|
+
if err != nil {
|
|
49
|
+
return fmt.Errorf("failed to get user: %w", err) // "failed to" is implied — it's an error
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Sentinel Errors and Custom Types
|
|
54
|
+
|
|
55
|
+
```go
|
|
56
|
+
// Use sentinel errors for conditions callers need to check
|
|
57
|
+
var (
|
|
58
|
+
ErrNotFound = errors.New("not found")
|
|
59
|
+
ErrAlreadyExists = errors.New("already exists")
|
|
60
|
+
ErrUnauthorized = errors.New("unauthorized")
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
// Check with errors.Is
|
|
64
|
+
if errors.Is(err, ErrNotFound) {
|
|
65
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Use custom error types when you need to carry structured data
|
|
70
|
+
type ValidationError struct {
|
|
71
|
+
Field string
|
|
72
|
+
Message string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func (e *ValidationError) Error() string {
|
|
76
|
+
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check with errors.As
|
|
80
|
+
var ve *ValidationError
|
|
81
|
+
if errors.As(err, &ve) {
|
|
82
|
+
// Access ve.Field, ve.Message
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### When to Use %w vs %v
|
|
87
|
+
|
|
88
|
+
```go
|
|
89
|
+
// Use %w when callers should be able to unwrap and inspect the cause
|
|
90
|
+
return fmt.Errorf("querying database: %w", err)
|
|
91
|
+
|
|
92
|
+
// Use %v when you want to break the error chain
|
|
93
|
+
// (hide implementation details across API boundaries)
|
|
94
|
+
return fmt.Errorf("internal error: %v", err)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Error Handling Patterns
|
|
98
|
+
|
|
99
|
+
### Errors in Main
|
|
100
|
+
|
|
101
|
+
```go
|
|
102
|
+
func main() {
|
|
103
|
+
if err := run(); err != nil {
|
|
104
|
+
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
105
|
+
os.Exit(1)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func run() error {
|
|
110
|
+
cfg, err := loadConfig()
|
|
111
|
+
if err != nil {
|
|
112
|
+
return fmt.Errorf("loading config: %w", err)
|
|
113
|
+
}
|
|
114
|
+
// ...
|
|
115
|
+
return nil
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Errors in HTTP Handlers
|
|
120
|
+
|
|
121
|
+
```go
|
|
122
|
+
// Define an application handler that returns errors
|
|
123
|
+
type appHandler func(w http.ResponseWriter, r *http.Request) error
|
|
124
|
+
|
|
125
|
+
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
126
|
+
if err := fn(w, r); err != nil {
|
|
127
|
+
var ve *ValidationError
|
|
128
|
+
switch {
|
|
129
|
+
case errors.As(err, &ve):
|
|
130
|
+
http.Error(w, ve.Message, http.StatusBadRequest)
|
|
131
|
+
case errors.Is(err, ErrNotFound):
|
|
132
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
133
|
+
case errors.Is(err, ErrUnauthorized):
|
|
134
|
+
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
135
|
+
default:
|
|
136
|
+
// Log the full error, return generic message to client
|
|
137
|
+
slog.Error("internal error", "error", err, "path", r.URL.Path)
|
|
138
|
+
http.Error(w, "internal server error", http.StatusInternalServerError)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Deferred Cleanup Errors
|
|
145
|
+
|
|
146
|
+
```go
|
|
147
|
+
// Capture close errors from deferred calls
|
|
148
|
+
func writeFile(path string, data []byte) (err error) {
|
|
149
|
+
f, err := os.Create(path)
|
|
150
|
+
if err != nil {
|
|
151
|
+
return fmt.Errorf("creating file: %w", err)
|
|
152
|
+
}
|
|
153
|
+
defer func() {
|
|
154
|
+
if closeErr := f.Close(); closeErr != nil && err == nil {
|
|
155
|
+
err = fmt.Errorf("closing file: %w", closeErr)
|
|
156
|
+
}
|
|
157
|
+
}()
|
|
158
|
+
|
|
159
|
+
if _, err := f.Write(data); err != nil {
|
|
160
|
+
return fmt.Errorf("writing data: %w", err)
|
|
161
|
+
}
|
|
162
|
+
return nil
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Multi-Error Collection
|
|
167
|
+
|
|
168
|
+
```go
|
|
169
|
+
// Collect multiple errors (Go 1.20+ with errors.Join)
|
|
170
|
+
func validateUser(u User) error {
|
|
171
|
+
var errs []error
|
|
172
|
+
if u.Name == "" {
|
|
173
|
+
errs = append(errs, &ValidationError{Field: "name", Message: "required"})
|
|
174
|
+
}
|
|
175
|
+
if u.Email == "" {
|
|
176
|
+
errs = append(errs, &ValidationError{Field: "email", Message: "required"})
|
|
177
|
+
}
|
|
178
|
+
return errors.Join(errs...)
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Anti-Patterns
|
|
183
|
+
|
|
184
|
+
```go
|
|
185
|
+
// Never: panic for expected error conditions
|
|
186
|
+
func MustParse(s string) Config {
|
|
187
|
+
// Only acceptable in init() or test helpers, never in request paths
|
|
188
|
+
panic("don't do this in production code paths")
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Never: Checking error strings
|
|
192
|
+
if err.Error() == "not found" { } // Fragile — use errors.Is or errors.As
|
|
193
|
+
|
|
194
|
+
// Never: Returning both a value and an error that are both valid
|
|
195
|
+
// If err != nil, the other return values should be zero values
|
|
196
|
+
|
|
197
|
+
// Never: Using naked returns in functions with error handling
|
|
198
|
+
// Named returns for error capture in defer is the one acceptable use
|
|
199
|
+
```
|