effective-go
Idiomatic Go patterns and conventions from official Effective Go guide. Use when writing Go code to ensure proper naming, error handling, concurrency patterns, interface design, and Go-specific idioms. Triggers on Go file creation/editing, code review, or when user asks for idiomatic Go. If you cannot find any info visit to https://go.dev/doc/effective_go
When & Why to Use This Skill
This Claude skill serves as an expert companion for writing idiomatic Go code, strictly adhering to the official 'Effective Go' guidelines. It helps developers master Go-specific patterns, from proper naming and formatting to advanced concurrency and error handling, ensuring codebase consistency, readability, and performance.
Use Cases
- Automated Code Review: Evaluate Go source code against official conventions to identify and fix non-idiomatic patterns in naming, control structures, and error handling.
- Idiomatic Refactoring: Transform legacy or C-style Go code into clean, idiomatic Go using features like defer, multiple return values, and the comma-ok idiom.
- Concurrency Implementation: Guide the development of safe and efficient concurrent programs using goroutines, channels, and the semaphore pattern.
- Interface & Struct Design: Assist in designing small, focused interfaces and utilizing embedding for effective composition over inheritance.
- Onboarding & Training: Help developers new to Go learn the 'Go way' of programming by providing real-time feedback based on official documentation.
| name | effective-go |
|---|---|
| description | Idiomatic Go patterns and conventions from official Effective Go guide. Use when writing Go code to ensure proper naming, error handling, concurrency patterns, interface design, and Go-specific idioms. Triggers on Go file creation/editing, code review, or when user asks for idiomatic Go. If you cannot find any info visit to https://go.dev/doc/effective_go |
Effective Go
Guidelines for writing clear, idiomatic Go code.
Formatting
Use gofmt. No debates.
- Tabs for indentation
- No line length limit (wrap long lines, indent with extra tab)
- Fewer parentheses than C/Java
Names
Visibility
First character uppercase = exported. This makes naming semantically critical.
Packages
- Lowercase, single-word, no underscores/mixedCaps
- Short names (everyone types them)
- Use package name to avoid stutter:
bufio.Reader // not bufio.BufReader
ring.New // not ring.NewRing
Getters/Setters
No "Get" prefix:
owner := obj.Owner() // not GetOwner()
obj.SetOwner(user)
Interfaces
One-method interfaces: method name + "-er":
Reader, Writer, Formatter, Stringer
MixedCaps
Always MixedCaps or mixedCaps, never underscores.
Control Structures
If
Initialization statements reduce scope:
if err := file.Chmod(0664); err != nil {
return err
}
Omit else when body ends with return/break/continue:
f, err := os.Open(name)
if err != nil {
return err
}
codeUsing(f)
For
Three forms:
for init; condition; post { } // C-style
for condition { } // while
for { } // infinite
Range over collections:
for key, value := range m { }
for key := range m { } // key only
for _, value := range m { } // value only
Switch
No automatic fallthrough. Expressions need not be constants:
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
}
Multiple cases:
case ' ', '?', '&', '=':
return true
Type Switch
switch t := value.(type) {
case string:
return t
case Stringer:
return t.String()
}
Functions
Multiple Returns
func (f *File) Write(b []byte) (n int, err error)
Named Results
func ReadFull(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
}
Defer
Cleanup near allocation, guaranteed execution:
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close()
// ... read file
}
Arguments evaluated at defer time, executed LIFO.
Data
new vs make
new(T)- allocates zeroed memory, returns*Tmake(T, args)- slices/maps/channels only, returns initializedT
p := new([]int) // *p == nil
v := make([]int, 100) // v is 100-element slice
Composite Literals
return &File{fd: fd, name: name}
Zero Values
Design structs so zero value is useful:
var buf bytes.Buffer // ready to use
var mu sync.Mutex // unlocked
Slices over Arrays
Arrays are values (copy on assign). Slices are references. Prefer slices.
func process(data []byte) { } // not [100]byte
Maps
m := map[string]int{"one": 1}
// Comma-ok idiom
if val, ok := m[key]; ok {
// key exists
}
delete(m, key) // safe if absent
Methods
Pointer vs Value Receivers
- Pointer: can modify receiver, avoids copy
- Value: safe for concurrent use
func (p *ByteSlice) Write(data []byte) (int, error)
Rule: value methods callable on pointers/values; pointer methods only on pointers.
Interfaces
Design
Small, focused interfaces:
type Reader interface {
Read(p []byte) (n int, err error)
}
Define interfaces where used, not where implemented.
Export interfaces, not types
func NewReader() io.Reader // not *myReader
Type Assertions
str, ok := value.(string)
if !ok {
// not a string
}
Embedding
Composition over inheritance:
type ReadWriter struct {
*Reader
*Writer
}
Methods promoted automatically. Receiver is inner type.
Concurrency
Core Principle
Do not communicate by sharing memory; share memory by communicating.
Goroutines
go func() {
// concurrent work
}()
Channels
ch := make(chan int) // unbuffered
ch := make(chan int, 100) // buffered
Synchronization:
done := make(chan bool)
go func() {
work()
done <- true
}()
<-done
Select
select {
case msg := <-ch1:
handle(msg)
case ch2 <- val:
// sent
default:
// non-blocking
}
Semaphore Pattern
var sem = make(chan struct{}, MaxWorkers)
func handle(r *Request) {
sem <- struct{}{}
defer func() { <-sem }()
process(r)
}
Errors
Explicit Handling
Always check and handle:
f, err := os.Open(name)
if err != nil {
return fmt.Errorf("opening %s: %w", name, err)
}
Custom Errors
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
Panic/Recover
Panic for unrecoverable errors only:
func safelyDo(work func()) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
work()
}
Blank Identifier
_, err := os.Stat(path) // discard value
_ = unusedVar // silence compiler (temp)
import _ "net/http/pprof" // side-effect import
var _ json.Marshaler = (*T)(nil) // compile-time interface check
Initialization
Constants with iota
const (
_ = iota
KB = 1 << (10 * iota)
MB
GB
)
init Functions
Run after variable declarations, before main:
func init() {
if user == "" {
log.Fatal("$USER not set")
}
}
Printing
fmt.Printf("%v", val) // default format
fmt.Printf("%+v", val) // struct with field names
fmt.Printf("%#v", val) // Go syntax
fmt.Printf("%T", val) // type
Custom String method:
func (t T) String() string {
return fmt.Sprintf("T{%d}", t.val)
}