Claude Code Plugins

Community-maintained marketplace

Feedback

lang-go-library-dev

@aRustyDev/ai
0
0

Go-specific library/package development patterns. Use when creating Go libraries, designing public APIs with Go idioms, configuring go.mod, managing module versioning, publishing packages, or writing package documentation. Extends meta-library-dev with Go tooling and ecosystem practices.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name lang-go-library-dev
description Go-specific library/package development patterns. Use when creating Go libraries, designing public APIs with Go idioms, configuring go.mod, managing module versioning, publishing packages, or writing package documentation. Extends meta-library-dev with Go tooling and ecosystem practices.

Go Library Development

Go-specific patterns for library/package development. This skill extends meta-library-dev with Go tooling, module management, and ecosystem practices.

This Skill Extends

  • meta-library-dev - Foundational library patterns (API design, versioning, testing strategies)

For general concepts like semantic versioning, module organization principles, and testing pyramids, see the meta-skill first.

This Skill Adds

  • Go tooling: go.mod, module versioning, workspaces, package documentation
  • Go idioms: Interface design, error handling, API patterns, zero values
  • Go ecosystem: pkg.go.dev, module proxies, major version strategies

This Skill Does NOT Cover

  • General library patterns - see meta-library-dev
  • Basic Go syntax - see lang-go-dev
  • CLI development - see lang-go-cli-dev
  • Web services - see lang-go-http-dev

Quick Reference

Task Command/Pattern
New module go mod init github.com/user/repo
Add dependency go get package@version
Update dependencies go get -u ./...
Tidy dependencies go mod tidy
Run tests go test ./...
Run tests with coverage go test -cover ./...
Generate docs go doc -all
Format code go fmt ./...
Vet code go vet ./...
Build go build ./...

Module Structure

go.mod Configuration

module github.com/username/mylibrary

go 1.21  // Minimum Go version

require (
    github.com/pkg/errors v0.9.1
    golang.org/x/sync v0.5.0
)

require (
    // Indirect dependencies
    golang.org/x/sys v0.15.0 // indirect
)

Best Practices

Use semantic import versioning for v2+:

// v0 or v1
module github.com/user/lib

// v2+
module github.com/user/lib/v2

Specify minimum Go version:

// Enables use of features from that version
go 1.21

Organize indirect dependencies:

# Let Go manage indirect deps automatically
go mod tidy

Package Organization

Standard Library Structure

mylibrary/
├── go.mod
├── go.sum
├── README.md
├── LICENSE
├── doc.go              # Package-level documentation
├── library.go          # Main public API
├── types.go            # Public types
├── options.go          # Configuration/options
├── errors.go           # Error types
├── internal/           # Internal implementation
│   └── helper.go
├── testdata/           # Test fixtures
│   └── sample.json
└── example_test.go     # Example tests for godoc

Package Naming

Good package names:

  • Short, lowercase, single word: http, json, xml
  • No underscores or mixedCaps: ioutil not io_util
  • Descriptive: encoding/json not encoding/j

Avoid:

// Bad: Generic names
package util
package common
package helpers

// Good: Specific names
package httputil
package stringutil
package mathutil

Internal Packages

mylibrary/
├── library.go          # Public API
└── internal/           # Cannot be imported by external packages
    ├── parser.go
    └── validator.go

Use internal/ to prevent external dependencies on implementation details.


Public API Design

Interface Design

Keep interfaces small:

// Good: Single method interface
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Good: Composed from small interfaces
type ReadWriter interface {
    Reader
    Writer
}

// Avoid: Large interfaces
type DataStore interface {
    Read() error
    Write() error
    Delete() error
    Update() error
    // ... 10 more methods
}

Accept interfaces, return concrete types:

// Good: Accept interface (flexible)
func Process(r io.Reader) (*Result, error)

// Good: Return concrete type (clear contract)
func NewClient(url string) *Client

// Avoid: Returning interface unnecessarily
func NewClient(url string) ClientInterface

Define interfaces at point of use:

// Good: Define interface where you use it
package consumer

type DataFetcher interface {
    FetchData(id string) ([]byte, error)
}

func ProcessData(fetcher DataFetcher) error {
    // Use fetcher
}

// Not: Define interface in provider package

Functional Options Pattern

Preferred pattern for optional configuration:

type Client struct {
    baseURL    string
    timeout    time.Duration
    maxRetries int
}

type Option func(*Client)

func WithTimeout(d time.Duration) Option {
    return func(c *Client) {
        c.timeout = d
    }
}

func WithMaxRetries(n int) Option {
    return func(c *Client) {
        c.maxRetries = n
    }
}

func NewClient(baseURL string, opts ...Option) *Client {
    c := &Client{
        baseURL:    baseURL,
        timeout:    30 * time.Second,  // defaults
        maxRetries: 3,
    }
    for _, opt := range opts {
        opt(c)
    }
    return c
}

// Usage
client := NewClient("https://api.example.com",
    WithTimeout(10*time.Second),
    WithMaxRetries(5),
)

Constructor Patterns

New prefix for constructors:

// Returns pointer (can fail, stateful)
func NewClient(url string) (*Client, error) {
    if url == "" {
        return nil, errors.New("url required")
    }
    return &Client{url: url}, nil
}

// Must prefix for constructors that panic
func MustCompile(pattern string) *Regexp {
    re, err := Compile(pattern)
    if err != nil {
        panic(err)
    }
    return re
}

Error Design

Create specific error types:

// Error type
type ValidationError struct {
    Field string
    Err   error
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed for %s: %v", e.Field, e.Err)
}

func (e *ValidationError) Unwrap() error {
    return e.Err
}

// Sentinel errors
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
)

// Check with errors.Is
if errors.Is(err, ErrNotFound) {
    // Handle not found
}

// Check type with errors.As
var validErr *ValidationError
if errors.As(err, &validErr) {
    fmt.Println("Field:", validErr.Field)
}

Error wrapping:

// Wrap errors with context
if err != nil {
    return fmt.Errorf("failed to connect to %s: %w", url, err)
}

// Allows callers to unwrap
originalErr := errors.Unwrap(err)

Zero Values

Design types to have useful zero values:

// Good: Zero value is useful
type Buffer struct {
    buf []byte
}

var b Buffer
b.Write([]byte("hello"))  // Works without initialization

// Good: Document when zero value is not useful
type Client struct {
    baseURL string  // Must be set
}

func NewClient(baseURL string) *Client {
    return &Client{baseURL: baseURL}
}

Testing Patterns

Table-Driven Tests

Standard Go testing pattern:

func TestParse(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    Result
        wantErr bool
    }{
        {
            name:  "valid input",
            input: "key=value",
            want:  Result{Key: "key", Value: "value"},
        },
        {
            name:    "empty input",
            input:   "",
            wantErr: true,
        },
        {
            name:  "multiple pairs",
            input: "a=1,b=2",
            want:  Result{Pairs: map[string]string{"a": "1", "b": "2"}},
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := Parse(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if !tt.wantErr && !reflect.DeepEqual(got, tt.want) {
                t.Errorf("Parse() = %v, want %v", got, tt.want)
            }
        })
    }
}

Subtests

Organize tests hierarchically:

func TestClient(t *testing.T) {
    t.Run("GET", func(t *testing.T) {
        t.Run("success", func(t *testing.T) {
            // Test successful GET
        })
        t.Run("not found", func(t *testing.T) {
            // Test 404
        })
    })

    t.Run("POST", func(t *testing.T) {
        t.Run("success", func(t *testing.T) {
            // Test successful POST
        })
    })
}

Test Helpers

Use t.Helper() to improve error messages:

func assertNoError(t *testing.T, err error) {
    t.Helper()
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
}

func assertEqual(t *testing.T, got, want interface{}) {
    t.Helper()
    if !reflect.DeepEqual(got, want) {
        t.Errorf("got %v, want %v", got, want)
    }
}

Example Tests

Generate documentation with runnable examples:

// Example tests appear in godoc
func ExampleParse() {
    result, _ := Parse("key=value")
    fmt.Println(result.Key)
    // Output: key
}

func ExampleParse_multiple() {
    result, _ := Parse("a=1,b=2")
    fmt.Println(len(result.Pairs))
    // Output: 2
}

func ExampleClient_Get() {
    client := NewClient("https://api.example.com")
    data, _ := client.Get("/endpoint")
    fmt.Printf("Got %d bytes\n", len(data))
    // Output: Got 42 bytes
}

Test Coverage

# Run tests with coverage
go test -cover ./...

# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

# View coverage by function
go tool cover -func=coverage.out

Documentation with godoc

Package Documentation

doc.go pattern:

// Package mylibrary provides utilities for parsing configuration files.
//
// This package supports JSON, YAML, and TOML formats with automatic
// format detection based on file extension.
//
// Basic usage:
//
//	config, err := mylibrary.Load("config.yaml")
//	if err != nil {
//		log.Fatal(err)
//	}
//
// The package provides three main types:
//
//   - Config: The main configuration structure
//   - Parser: Interface for custom parsers
//   - Validator: Interface for custom validation
//
// For more examples, see the examples/ directory.
package mylibrary

Type and Function Documentation

Standard documentation format:

// Client represents an HTTP client for the API.
// It is safe for concurrent use by multiple goroutines.
type Client struct {
    baseURL string
    client  *http.Client
}

// NewClient creates a new API client with the given base URL.
// It returns an error if the URL is invalid.
//
// Example:
//
//	client, err := NewClient("https://api.example.com")
//	if err != nil {
//		log.Fatal(err)
//	}
func NewClient(baseURL string) (*Client, error) {
    // Implementation
}

// Get performs a GET request to the specified path.
// The path should not include the base URL.
//
// Get returns the response body as bytes. If the request fails
// or returns a non-2xx status code, an error is returned.
func (c *Client) Get(path string) ([]byte, error) {
    // Implementation
}

Documentation Best Practices

Practice Example
Start with type/function name // Client represents... not // This is...
Use complete sentences // NewClient creates a new client.
Document parameters // path should be relative to base URL
Document return values // Returns nil if not found
Include examples Code blocks in comments
Mark deprecated items // Deprecated: Use NewClient instead.

Versioning and Releases

Semantic Versioning

Go modules use semantic versioning:

  • v0.x.x - Initial development, no stability guarantees
  • v1.x.x - Stable API, backward compatible changes only
  • v2.x.x+ - Breaking changes require new major version

Major Version Strategy

v2+ requires module path change:

// v1
module github.com/user/lib

// v2 - add /v2 suffix
module github.com/user/lib/v2

In code:

// Import v2
import "github.com/user/lib/v2"

// Both v1 and v2 can coexist
import (
    libv1 "github.com/user/lib"
    libv2 "github.com/user/lib/v2"
)

Tagging Releases

# Tag v1 release
git tag v1.0.0
git push origin v1.0.0

# Tag v2 release (after creating /v2 module path)
git tag v2.0.0
git push origin v2.0.0

# Pre-release
git tag v1.1.0-beta.1
git push origin v1.1.0-beta.1

Retract Versions

Retract broken versions in go.mod:

module github.com/user/lib

go 1.21

retract (
    v1.0.0 // Published accidentally
    v1.0.1 // Critical bug, use v1.0.2+
    [v1.5.0, v1.7.0] // Range retraction
)

Publishing and Distribution

Making Your Module Discoverable

Requirements for pkg.go.dev:

  1. Public repository on GitHub, GitLab, or Bitbucket
  2. Valid go.mod with module path matching repo
  3. Version tags using semantic versioning
  4. LICENSE file (required)
  5. Documentation in godoc format

Trigger indexing:

# pkg.go.dev automatically indexes on first request
curl https://pkg.go.dev/github.com/user/lib@v1.0.0

LICENSE

Common licenses for Go libraries:

  • MIT - Permissive, simple
  • Apache 2.0 - Permissive, patent grant
  • BSD 3-Clause - Permissive, attribution required

Include LICENSE file in repository root.

README Best Practices

Include in README.md:

# My Library

Brief description of what the library does.

## Installation

```bash
go get github.com/user/lib

Quick Start

package main

import "github.com/user/lib"

func main() {
    // Example usage
}

Documentation

Full documentation available at pkg.go.dev.

License

MIT License - see LICENSE file.


### Go Module Proxy

**By default, Go uses proxy.golang.org:**

```bash
# Verify module is available
go list -m -versions github.com/user/lib

# Force direct access (bypass proxy)
GOPRIVATE=github.com/user/* go get github.com/user/lib

API Compatibility

Backward Compatibility Rules

Within major version v1:

OK (minor/patch):

  • Add new functions, types, methods
  • Add new fields to structs (if not used in comparisons)
  • Add new error types
  • Make unexported symbols exported

NOT OK (requires v2):

  • Remove or rename exported symbols
  • Change function signatures
  • Change error types returned
  • Remove struct fields
  • Change exported variable types

Deprecation Pattern

// Deprecated: Use NewClient instead.
// OldClient will be removed in v2.
func OldClient(url string) *Client {
    return NewClient(url)
}

// NewClient creates a new API client.
func NewClient(url string) *Client {
    // Implementation
}

Common Patterns

Config Struct with Defaults

type Config struct {
    Timeout    time.Duration
    MaxRetries int
    Debug      bool
}

// DefaultConfig returns a Config with sensible defaults.
func DefaultConfig() *Config {
    return &Config{
        Timeout:    30 * time.Second,
        MaxRetries: 3,
        Debug:      false,
    }
}

// Usage
config := DefaultConfig()
config.Debug = true

Graceful Initialization

type Manager struct {
    once sync.Once
    db   *sql.DB
}

func (m *Manager) init() {
    m.once.Do(func() {
        m.db, _ = sql.Open("postgres", "connection_string")
    })
}

func (m *Manager) Query(q string) (*Result, error) {
    m.init()  // Lazy initialization
    return m.db.Query(q)
}

Context for Cancellation

// Accept context as first parameter
func (c *Client) Fetch(ctx context.Context, url string) (*Response, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    resp, err := c.client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    // Process response
    return parseResponse(resp)
}

// Usage with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

response, err := client.Fetch(ctx, "https://api.example.com")

Anti-Patterns

1. init() Functions

// Avoid: Non-deterministic initialization
func init() {
    db = connectDatabase()  // What if this fails?
}

// Prefer: Explicit initialization
func New() (*Client, error) {
    db, err := connectDatabase()
    if err != nil {
        return nil, err
    }
    return &Client{db: db}, nil
}

2. Global State

// Avoid: Global variables
var defaultClient *Client

func Get(url string) (*Response, error) {
    return defaultClient.Get(url)
}

// Prefer: Explicit dependencies
type Client struct { }

func (c *Client) Get(url string) (*Response, error) {
    // Implementation
}

3. Interface Pollution

// Avoid: Interfaces for every type
type UserRepository interface {
    Save(u User) error
}

type userRepository struct {}

// Prefer: Concrete types unless abstraction needed
type UserRepository struct {}

func (r *UserRepository) Save(u User) error {
    // Implementation
}

4. Returning Pointers to Slices/Maps

// Avoid: Unnecessary pointer
func GetUsers() *[]User {
    users := []User{ /* ... */ }
    return &users
}

// Prefer: Return slice directly (already a reference type)
func GetUsers() []User {
    return []User{ /* ... */ }
}

Troubleshooting

Module Path Issues

Problem: go get fails with "module not found"

Solutions:

  1. Ensure module path matches repository URL
  2. Check that repository is public
  3. Verify version tag exists: git tag v1.0.0
  4. Wait for pkg.go.dev to index (can take a few minutes)

Import Cycle

Problem: "import cycle not allowed"

Solutions:

  1. Extract shared code to a separate package
  2. Use interfaces to break dependency
  3. Restructure package organization
// Before: Cycle between user and auth packages
package user
import "myapp/auth"

package auth
import "myapp/user"

// After: Extract common types to separate package
package types
type User struct { }

package user
import "myapp/types"

package auth
import "myapp/types"

Breaking API Changes

Problem: Need to make breaking change in v1

Solutions:

  1. Add new function, deprecate old
  2. Create v2 module with /v2 suffix
  3. Use options pattern for extensibility
// Instead of changing signature
func Process(data string) error

// Add new function
func ProcessWithOptions(data string, opts Options) error

References