2026-05-21 10:29:13 +00:00

9.5 KiB

Go Patterns for Skill Scripts

This reference provides battle-tested Go patterns for common operations in Claude Code skills.

File Processing Patterns

Reading Files Line by Line

func processFileLineByLine(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return fmt.Errorf("failed to open file: %w", err)
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    lineNum := 0
    
    for scanner.Scan() {
        lineNum++
        line := scanner.Text()
        
        // Process line
        if err := processLine(line); err != nil {
            return fmt.Errorf("error on line %d: %w", lineNum, err)
        }
    }
    
    if err := scanner.Err(); err != nil {
        return fmt.Errorf("scanner error: %w", err)
    }
    
    return nil
}

Streaming Large Files

func streamProcess(input, output string) error {
    inFile, err := os.Open(input)
    if err != nil {
        return err
    }
    defer inFile.Close()

    outFile, err := os.Create(output)
    if err != nil {
        return err
    }
    defer outFile.Close()

    reader := bufio.NewReader(inFile)
    writer := bufio.NewWriter(outFile)
    defer writer.Flush()

    buf := make([]byte, 4096)
    for {
        n, err := reader.Read(buf)
        if err != nil && err != io.EOF {
            return err
        }
        if n == 0 {
            break
        }
        
        // Process chunk
        processed := processChunk(buf[:n])
        
        if _, err := writer.Write(processed); err != nil {
            return err
        }
    }
    
    return nil
}

Concurrent Processing Patterns

Worker Pool

func processParallel(items []string, workers int) error {
    jobs := make(chan string, len(items))
    results := make(chan error, len(items))
    
    // Start workers
    var wg sync.WaitGroup
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for item := range jobs {
                if err := processItem(item); err != nil {
                    results <- err
                } else {
                    results <- nil
                }
            }
        }()
    }
    
    // Send jobs
    for _, item := range items {
        jobs <- item
    }
    close(jobs)
    
    // Wait for completion
    wg.Wait()
    close(results)
    
    // Check for errors
    for err := range results {
        if err != nil {
            return err
        }
    }
    
    return nil
}

Rate Limited Processing

func processWithRateLimit(items []string, requestsPerSecond int) error {
    limiter := time.NewTicker(time.Second / time.Duration(requestsPerSecond))
    defer limiter.Stop()
    
    for i, item := range items {
        <-limiter.C
        
        if *verbose {
            log.Printf("Processing %d/%d: %s", i+1, len(items), item)
        }
        
        if err := processItem(item); err != nil {
            return fmt.Errorf("failed to process %s: %w", item, err)
        }
    }
    
    return nil
}

Progress Reporting

Simple Progress Bar

func showProgress(current, total int) {
    if total == 0 {
        return
    }
    
    percent := float64(current) / float64(total) * 100
    filled := int(percent / 2) // 50 chars max
    
    fmt.Fprintf(os.Stderr, "\r[")
    for i := 0; i < 50; i++ {
        if i < filled {
            fmt.Fprintf(os.Stderr, "=")
        } else {
            fmt.Fprintf(os.Stderr, " ")
        }
    }
    fmt.Fprintf(os.Stderr, "] %.1f%% (%d/%d)", percent, current, total)
    
    if current == total {
        fmt.Fprintln(os.Stderr)
    }
}

Timed Progress Updates

type ProgressTracker struct {
    total       int
    current     int
    lastUpdate  time.Time
    updateEvery time.Duration
}

func NewProgressTracker(total int) *ProgressTracker {
    return &ProgressTracker{
        total:       total,
        updateEvery: 500 * time.Millisecond,
        lastUpdate:  time.Now(),
    }
}

func (p *ProgressTracker) Update(current int) {
    p.current = current
    
    if time.Since(p.lastUpdate) < p.updateEvery && current < p.total {
        return
    }
    
    p.lastUpdate = time.Now()
    showProgress(p.current, p.total)
}

Error Handling Patterns

Recoverable vs Fatal Errors

type ProcessResult struct {
    Processed int
    Failed    int
    Errors    []error
}

func processWithRecovery(items []string) (*ProcessResult, error) {
    result := &ProcessResult{}
    
    for _, item := range items {
        if err := processItem(item); err != nil {
            // Check if error is recoverable
            if isRecoverable(err) {
                result.Failed++
                result.Errors = append(result.Errors, 
                    fmt.Errorf("%s: %w", item, err))
                continue
            } else {
                // Fatal error
                return result, fmt.Errorf("fatal error processing %s: %w", item, err)
            }
        }
        result.Processed++
    }
    
    return result, nil
}

func isRecoverable(err error) bool {
    // Define what errors are recoverable
    return errors.Is(err, os.ErrNotExist) || 
           errors.Is(err, os.ErrPermission)
}

CLI Patterns

Multiple Input Sources

func getInput() (io.Reader, func() error, error) {
    if flag.NArg() > 0 {
        // File input
        filename := flag.Arg(0)
        file, err := os.Open(filename)
        if err != nil {
            return nil, nil, err
        }
        return file, file.Close, nil
    } else {
        // Stdin input
        return os.Stdin, func() error { return nil }, nil
    }
}

func main() {
    // ... flag parsing ...
    
    input, cleanup, err := getInput()
    if err != nil {
        log.Fatal(err)
    }
    defer cleanup()
    
    if err := process(input); err != nil {
        log.Fatal(err)
    }
}

Configuration with Defaults

type Config struct {
    Workers     int
    BufferSize  int
    Timeout     time.Duration
    OutputDir   string
}

func loadConfig() *Config {
    cfg := &Config{
        Workers:    4,
        BufferSize: 4096,
        Timeout:    30 * time.Second,
        OutputDir:  "output",
    }
    
    flag.IntVar(&cfg.Workers, "workers", cfg.Workers, "Number of worker goroutines")
    flag.IntVar(&cfg.BufferSize, "buffer", cfg.BufferSize, "Buffer size in bytes")
    flag.DurationVar(&cfg.Timeout, "timeout", cfg.Timeout, "Operation timeout")
    flag.StringVar(&cfg.OutputDir, "output", cfg.OutputDir, "Output directory")
    
    flag.Parse()
    
    return cfg
}

Data Processing Patterns

CSV Processing

func processCSV(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    reader := csv.NewReader(file)
    reader.TrimLeadingSpace = true
    
    // Read header
    header, err := reader.Read()
    if err != nil {
        return fmt.Errorf("failed to read header: %w", err)
    }
    
    // Process rows
    for {
        record, err := reader.Read()
        if err == io.EOF {
            break
        }
        if err != nil {
            return fmt.Errorf("failed to read row: %w", err)
        }
        
        // Convert to map for easy access
        row := make(map[string]string)
        for i, value := range record {
            if i < len(header) {
                row[header[i]] = value
            }
        }
        
        if err := processRow(row); err != nil {
            return err
        }
    }
    
    return nil
}

JSON Streaming

func processJSONStream(r io.Reader) error {
    decoder := json.NewDecoder(r)
    
    // Expect array of objects
    // Read opening bracket
    if _, err := decoder.Token(); err != nil {
        return err
    }
    
    // Process objects
    for decoder.More() {
        var obj YourType
        if err := decoder.Decode(&obj); err != nil {
            return err
        }
        
        if err := processObject(&obj); err != nil {
            return err
        }
    }
    
    // Read closing bracket
    if _, err := decoder.Token(); err != nil {
        return err
    }
    
    return nil
}

Testing Patterns

Table-Driven Tests

func TestProcess(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    string
        wantErr bool
    }{
        {
            name:  "valid input",
            input: "test.txt",
            want:  "output.txt",
        },
        {
            name:    "invalid input",
            input:   "nonexistent.txt",
            wantErr: true,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := process(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("process() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if got != tt.want {
                t.Errorf("process() = %v, want %v", got, tt.want)
            }
        })
    }
}

Best Practices Summary

  1. Always handle errors explicitly - Never ignore errors
  2. Close resources - Use defer for cleanup
  3. Validate inputs early - Fail fast with clear messages
  4. Use buffered I/O - For file operations
  5. Progress feedback - For long operations
  6. Graceful degradation - Separate recoverable from fatal errors
  7. Configurable behavior - Use flags for common parameters
  8. Stream large data - Don't load entire files into memory
  9. Parallel processing - Use goroutines for independent operations
  10. Test thoroughly - Use table-driven tests