Loading...
Loading...
Defensive programming patterns in Go including interface verification, slice/map copying at boundaries, time handling, avoiding globals, and defer for cleanup. Use when writing robust, production-quality Go code.
npx skill4agent add cxuu/golang-skills go-defensiveSource: Uber Go Style Guide
type Handler struct{}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
}type Handler struct{}
var _ http.Handler = (*Handler)(nil)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ...
}nil{}Source: Uber Go Style Guide
func (d *Driver) SetTrips(trips []Trip) {
d.trips = trips // caller can still modify d.trips
}func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
return s.counters // exposes internal state!
}func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
result := make(map[string]int, len(s.counters))
for k, v := range s.counters {
result[k] = v
}
return result
}Source: Uber Go Style Guide, Effective Go
deferp.Lock()
if p.count < 10 {
p.Unlock()
return p.count
}
p.count++
newCount := p.count
p.Unlock()
return newCount // easy to miss unlocksp.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.countdefer f.Close()func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // Close sits near Open - much clearer
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...)
if err != nil {
if err == io.EOF {
break
}
return "", err // f will be closed
}
}
return string(result), nil // f will be closed
}deferfor i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
// Prints: 4 3 2 1 0 (LIFO order, values captured at defer time)func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a")) // trace() runs now, un() runs at return
fmt.Println("in a")
}
// Output: entering: a, in a, leaving: aSource: Uber Go Style Guide
const (
Add Operation = iota // Add=0, zero value looks valid
Subtract
Multiply
)const (
Add Operation = iota + 1 // Add=1, zero value = uninitialized
Subtract
Multiply
)LogToStdout = iotaSource: Uber Go Style Guide
timeintfunc isActive(now, start, stop int) bool {
return start <= now && now < stop
}func isActive(now, start, stop time.Time) bool {
return (start.Before(now) || start.Equal(now)) && now.Before(stop)
}func poll(delay int) {
time.Sleep(time.Duration(delay) * time.Millisecond)
}
poll(10) // seconds? milliseconds?func poll(delay time.Duration) {
time.Sleep(delay)
}
poll(10 * time.Second)time.Durationtype Config struct {
Interval int `json:"interval"`
}type Config struct {
IntervalMillis int `json:"intervalMillis"`
}Source: Uber Go Style Guide
var _timeNow = time.Now
func sign(msg string) string {
now := _timeNow()
return signWithTime(msg, now)
}
// Test requires save/restore of global
func TestSign(t *testing.T) {
oldTimeNow := _timeNow
_timeNow = func() time.Time { return someFixedTime }
defer func() { _timeNow = oldTimeNow }()
assert.Equal(t, want, sign(give))
}type signer struct {
now func() time.Time
}
func newSigner() *signer {
return &signer{now: time.Now}
}
func (s *signer) Sign(msg string) string {
now := s.now()
return signWithTime(msg, now)
}
// Test injects dependency cleanly
func TestSigner(t *testing.T) {
s := newSigner()
s.now = func() time.Time { return someFixedTime }
assert.Equal(t, want, s.Sign(give))
}Source: Uber Go Style Guide
type ConcreteList struct {
*AbstractList
}type ConcreteList struct {
list *AbstractList
}
func (l *ConcreteList) Add(e Entity) {
l.list.Add(e)
}
func (l *ConcreteList) Remove(e Entity) {
l.list.Remove(e)
}Source: Uber Go Style Guide
type Stock struct {
Price int
Name string
}type Stock struct {
Price int `json:"price"`
Name string `json:"name"`
// Safe to rename Name to Symbol
}Source: Go Wiki CodeReviewComments (Normative)
math/randmath/rand/v2Time.Nanoseconds()crypto/randimport (
"crypto/rand"
)
func Key() string {
return rand.Text()
}crypto/rand.Textencoding/hexencoding/base64Source: Effective Go
panicrecoverfunc safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}init()| Pattern | Rule |
|---|---|
| Interface compliance | |
| Receiving slices/maps | Copy before storing |
| Returning slices/maps | Return a copy |
| Resource cleanup | Use |
| Defer argument timing | Evaluated at defer, not call time |
| Enums | Start at |
| Time instants | Use |
| Time durations | Use |
| Mutable globals | Use dependency injection |
| Type embedding | Use explicit delegation |
| Serialization | Always use field tags |
| Key generation | Use |
| Panic usage | Only for truly unrecoverable situations |
| Recover pattern | Use in defer; convert to error at API boundary |
go-style-corego-concurrencygo-error-handling