effective-go

jurabek's avatarfrom jurabek

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

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 3, 2026

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.
nameeffective-go
descriptionIdiomatic 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 *T
  • make(T, args) - slices/maps/channels only, returns initialized T
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)
}