Skip to content

Commit 69239ec

Browse files
authored
docs: add Go development guide (#60)
* docs: add go development guide Add comprehensive Go guide following project style conventions: - Concise, actionable format matching other guides - Focus on Go idioms and common pitfalls - Anti-pattern examples with ❌/✅ format - Reference projects for best practices - Covers project structure, testing, concurrency, and security * docs: remove emojis from go guide to match project style Replace emoji-based anti-patterns (❌/✅) with text-based comments following the style used in python.md and other guides. Use inline comments like "Don't do this" and "Do this" instead. * docs: add go guide to readme
1 parent 89561ef commit 69239ec

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ Always ensure you specify a profile (e.g. `--profile <projectname>`) when runnin
267267

268268
- [Makefiles](./guides/make.md)
269269
- [Python](./guides/python.md)
270+
- [Go](./guides/go.md)
270271
- [Shell Scripts](./guides/shell-scripts.md)
271272

272273
## Pattern Specific Guides

guides/go.md

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# Go
2+
3+
Follow Go idioms and conventions. Write idiomatic Go, not Java or Python in Go syntax.
4+
5+
## Project Structure
6+
7+
Standard Go layout:
8+
- `/cmd` - main application entry points (one subdirectory per executable)
9+
- `/internal` - private code that cannot be imported by other projects
10+
- `/pkg` - public libraries safe for external use
11+
- NO `/src` directory (not idiomatic in Go)
12+
- Keep `main` packages minimal; put logic in importable packages
13+
14+
```
15+
project/
16+
├── cmd/
17+
│ ├── server/main.go
18+
│ └── cli/main.go
19+
├── internal/
20+
│ ├── auth/
21+
│ └── handler/
22+
└── pkg/
23+
└── models/
24+
```
25+
26+
Reference: https://github.com/golang-standards/project-layout
27+
28+
## Naming
29+
30+
- **Exported**: PascalCase (e.g., `UserService`, `Config`)
31+
- **Unexported**: camelCase (e.g., `userData`, `handleRequest`)
32+
- **Acronyms**: All caps (e.g., `HTTPServer`, `IDToken`, not `HttpServer`, `IdToken`)
33+
- **Getters**: No "Get" prefix (e.g., `user.Name()`, not `user.GetName()`)
34+
- **Interfaces**: Use `-er` suffix for single-method interfaces (e.g., `Reader`, `Writer`, `Stringer`)
35+
- **Packages**: Short, lowercase, singular form (e.g., `time`, `http`, not `times`, `utils`)
36+
37+
## Error Handling
38+
39+
Always check and handle errors. Never ignore them with `_`.
40+
41+
```go
42+
// Don't do this - ignoring errors:
43+
data, _ := ioutil.ReadFile("file.txt")
44+
45+
// Do this - always handle errors:
46+
data, err := ioutil.ReadFile("file.txt")
47+
if err != nil {
48+
return fmt.Errorf("reading config: %w", err)
49+
}
50+
```
51+
52+
- Use `fmt.Errorf` with `%w` to wrap errors with context
53+
- Return errors rather than using `panic` for normal flows
54+
- Error strings should be lowercase and not end with punctuation
55+
- Create custom error types for specific cases
56+
- Use `errors.Is()` and `errors.As()` for error comparison
57+
- Return early on errors to avoid deep nesting
58+
59+
## Testing
60+
61+
- Write table-driven tests for multiple scenarios
62+
- Use subtests with `t.Run()` for organizing related cases
63+
- Test both success and error paths
64+
- Use `testify/assert` or `testify/require` for cleaner assertions
65+
- Place tests in same package with `_test.go` suffix
66+
- Aim for 80%+ test coverage
67+
68+
```go
69+
func TestParseConfig(t *testing.T) {
70+
tests := []struct {
71+
name string
72+
input string
73+
wantErr bool
74+
}{
75+
{"valid config", `{"port": 8080}`, false},
76+
{"invalid json", `{broken`, true},
77+
{"empty input", ``, true},
78+
}
79+
80+
for _, tt := range tests {
81+
t.Run(tt.name, func(t *testing.T) {
82+
_, err := ParseConfig(tt.input)
83+
if (err != nil) != tt.wantErr {
84+
t.Errorf("got error=%v, wantErr=%v", err, tt.wantErr)
85+
}
86+
})
87+
}
88+
}
89+
```
90+
91+
## Concurrency
92+
93+
- Use channels for communication between goroutines
94+
- Use `sync.Mutex` for shared memory access
95+
- Always use `context.Context` for cancellation and timeouts
96+
- Beware of goroutine leaks - ensure all goroutines can exit
97+
- Use `sync.WaitGroup` for waiting on multiple goroutines
98+
- Use `errgroup` for handling errors from multiple goroutines
99+
100+
```go
101+
// Don't do this - goroutine leak (never exits):
102+
go func() {
103+
for {
104+
doWork()
105+
}
106+
}()
107+
108+
// Do this - provide exit mechanism:
109+
go func() {
110+
for {
111+
select {
112+
case <-ctx.Done():
113+
return
114+
case work := <-workCh:
115+
doWork(work)
116+
}
117+
}
118+
}()
119+
```
120+
121+
## Code Organization
122+
123+
- Organize by domain functionality, not by layer (avoid `/models`, `/controllers` structure)
124+
- Keep functions focused on single responsibility (< 50 lines)
125+
- Limit parameters (aim for ≤ 3; use structs or options pattern for more)
126+
- Use function options pattern for complex constructors
127+
- Prefer composition over embedding
128+
- Keep exported API surface minimal
129+
130+
## Performance
131+
132+
- Profile before optimizing (`pprof`)
133+
- Use `strings.Builder` for string concatenation, not `+=`
134+
- Pre-allocate slices when size is known: `make([]T, 0, capacity)`
135+
- Use pointers judiciously; prefer values for small structs (< 64 bytes)
136+
- Use `sync.Pool` for frequently allocated objects
137+
- Batch database operations
138+
139+
## Dependencies
140+
141+
- Use Go modules (`go.mod`, `go.sum`)
142+
- Run `go mod tidy` to clean up unused dependencies
143+
- Pin versions for stability in production
144+
- Keep dependencies minimal
145+
- Regularly update for security fixes (`go get -u`)
146+
- Document external dependencies in README
147+
148+
## Code Quality
149+
150+
Use `golangci-lint` with these essential linters:
151+
- `gofmt` / `goimports` - standard formatting (always run before commit)
152+
- `govet` - detect suspicious constructs
153+
- `staticcheck` - advanced static analysis
154+
- `errcheck` - ensure errors are handled
155+
- `gosec` - security issues
156+
- `revive` - style checks
157+
158+
Group imports: stdlib, external packages, internal packages (with blank lines between).
159+
160+
```go
161+
import (
162+
"context"
163+
"fmt"
164+
165+
"github.com/pkg/errors"
166+
"go.uber.org/zap"
167+
168+
"github.com/myorg/myproject/internal/config"
169+
)
170+
```
171+
172+
## Documentation
173+
174+
- Every exported symbol MUST have a doc comment
175+
- Comments start with the symbol name: `// User represents...`
176+
- Use full sentences with proper punctuation
177+
- Package-level comment at top of one file explains package purpose
178+
- Use godoc format for documentation
179+
- Include examples for complex functions
180+
181+
```go
182+
// Package auth provides user authentication and authorization.
183+
package auth
184+
185+
// Authenticate verifies credentials and returns a session token.
186+
// Returns an error if authentication fails.
187+
func Authenticate(email, password string) (string, error) {
188+
// implementation
189+
}
190+
```
191+
192+
## Security
193+
194+
- Never hardcode secrets; use environment variables or secret managers
195+
- Use `crypto/rand` for random generation, not `math/rand`
196+
- Use prepared statements for SQL queries (prevent injection)
197+
- Set timeouts on HTTP clients and servers
198+
- Validate and sanitize all user input
199+
- Use `crypto/subtle` for constant-time comparisons of sensitive data
200+
- Use TLS for network communications
201+
202+
## Common Pitfalls
203+
204+
**Range loop variable capture**:
205+
```go
206+
// Don't do this - all goroutines see last value:
207+
for _, v := range items {
208+
go func() {
209+
process(v) // Wrong!
210+
}()
211+
}
212+
213+
// Do this - pass value explicitly:
214+
for _, v := range items {
215+
go func(item Item) {
216+
process(item)
217+
}(v)
218+
}
219+
```
220+
221+
**Pointer to loop variable**:
222+
```go
223+
// Don't do this - all pointers point to same variable:
224+
for _, v := range items {
225+
results = append(results, &v)
226+
}
227+
228+
// Do this - shadow variable first:
229+
for _, v := range items {
230+
v := v
231+
results = append(results, &v)
232+
}
233+
```
234+
235+
## Reference Projects
236+
237+
Excellent examples of idiomatic Go:
238+
- [Kubernetes](https://github.com/kubernetes/kubernetes) - Large-scale project structure
239+
- [Docker](https://github.com/moby/moby) - CLI patterns and architecture
240+
- [Terraform](https://github.com/hashicorp/terraform) - Plugin architecture
241+
- [Hugo](https://github.com/gohugoio/hugo) - Fast build tool patterns
242+
- [CockroachDB](https://github.com/cockroachdb/cockroach) - Database patterns

0 commit comments

Comments
 (0)