返回 Skill 列表
extension
分类: 开发与工程无需 API Key

lang-go-dev

涵盖类型、接口、goroutines、通道和常用惯用法的基础Go模式。在编写Go代码、理解Go的并发模型或需要指导使用哪种专门的Go技能时使用。这是Go开发的入门点。

person作者: jakexiaohubgithub

Go Fundamentals

Foundational Go patterns and core language features. This skill serves as both a reference for common patterns and an index to specialized Go skills.

Overview

┌─────────────────────────────────────────────────────────────────┐
│                       Go Skill Hierarchy                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                    ┌─────────────────┐                          │
│                    │   lang-go-dev   │ ◄── You are here         │
│                    │  (foundation)   │                          │
│                    └────────┬────────┘                          │
│                             │                                   │
│         ┌───────────────────┼───────────────────┐               │
│         │                   │                   │               │
│         ▼                   ▼                   ▼               │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │ concurrency │    │   testing   │    │   modules   │         │
│  │  patterns   │    │  patterns   │    │  packages   │         │
│  └─────────────┘    └─────────────┘    └─────────────┘         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

This skill covers:

  • Basic types and zero values
  • Structs, interfaces, and embedding
  • Slices, maps, and iteration
  • Error handling patterns
  • Goroutines and channels basics
  • Common idioms and conventions

This skill does NOT cover (see specialized skills):

  • Advanced concurrency patterns → go-concurrency-patterns
  • Module management and versioning → future skill
  • Testing strategies → future skill
  • Performance optimization → future skill

Quick Reference

| Task | Syntax | |------|--------| | Variable declaration | var x int or x := 0 | | Constant | const Pi = 3.14 | | Function | func name(x int) int { return x } | | Multiple return | func f() (int, error) | | Struct | type User struct { Name string } | | Interface | type Reader interface { Read() } | | Slice | []int{1, 2, 3} | | Map | map[string]int{"a": 1} | | Goroutine | go func() { ... }() | | Channel | ch := make(chan int) | | Defer | defer file.Close() |


Skill Routing

Use this table to find the right specialized skill:

| When you need to... | Use this skill | |---------------------|----------------| | Complex concurrency (worker pools, fan-out) | go-concurrency-patterns | | Module versioning, go.mod management | future: lang-go-modules-dev | | Testing strategies, mocking | future: lang-go-testing-dev | | HTTP servers, middleware | future: lang-go-http-dev |


Types and Variables

Basic Types

// Boolean
var active bool = true

// Numeric types
var i int = 42           // Platform-dependent size
var i64 int64 = 42       // Explicit 64-bit
var f float64 = 3.14     // Default float type
var c complex128 = 1+2i  // Complex numbers

// String (immutable UTF-8)
var s string = "hello"

// Byte and rune
var b byte = 'A'         // Alias for uint8
var r rune = '世'        // Alias for int32 (Unicode code point)

Zero Values

Every type has a zero value - no uninitialized variables in Go:

| Type | Zero Value | |------|------------| | bool | false | | int, float64 | 0 | | string | "" (empty string) | | pointer, slice, map, chan, func | nil | | struct | All fields zero-valued |

Variable Declaration

// Explicit type
var name string = "Alice"

// Type inference
var name = "Alice"

// Short declaration (inside functions only)
name := "Alice"

// Multiple variables
var x, y int = 1, 2
a, b := 1, "hello"

// Block declaration
var (
    name   string = "Alice"
    age    int    = 30
    active bool   = true
)

Constants

// Typed constant
const Pi float64 = 3.14159

// Untyped constant (more flexible)
const MaxSize = 1024

// Constant block with iota
const (
    Sunday = iota  // 0
    Monday         // 1
    Tuesday        // 2
)

// Iota patterns
const (
    _  = iota             // Skip 0
    KB = 1 << (10 * iota) // 1024
    MB                    // 1048576
    GB                    // 1073741824
)

Functions

Basic Functions

// Simple function
func greet(name string) string {
    return "Hello, " + name
}

// Multiple parameters of same type
func add(x, y int) int {
    return x + y
}

// Multiple return values
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Named return values
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return // Naked return
}

Variadic Functions

// Accept any number of arguments
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// Usage
sum(1, 2, 3)
sum([]int{1, 2, 3}...) // Spread slice

Function Values and Closures

// Functions are first-class values
var fn func(int) int
fn = func(x int) int { return x * 2 }

// Closure (captures outer variable)
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

c := counter()
c() // 1
c() // 2

Defer

// Deferred calls execute in LIFO order when function returns
func process(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close() // Guaranteed to run

    // Process file...
    return nil
}

// Arguments evaluated immediately, call deferred
func demo() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i) // Prints 2, 1, 0
    }
}

Structs

Definition and Instantiation

// Define struct type
type User struct {
    ID        int
    Name      string
    Email     string
    CreatedAt time.Time
}

// Create instances
u1 := User{ID: 1, Name: "Alice", Email: "alice@example.com"}
u2 := User{1, "Alice", "alice@example.com", time.Now()} // Positional (fragile)
u3 := User{Name: "Bob"} // Other fields get zero values

// Pointer to struct
u4 := &User{Name: "Charlie"}

// Anonymous struct (one-off use)
point := struct {
    X, Y int
}{10, 20}

Methods

// Value receiver (copy)
func (u User) FullName() string {
    return u.Name
}

// Pointer receiver (can modify, avoids copy)
func (u *User) UpdateEmail(email string) {
    u.Email = email
}

// Usage
user := User{Name: "Alice"}
user.UpdateEmail("new@example.com") // Automatic &user

When to Use Pointer vs Value Receiver

| Use Pointer Receiver | Use Value Receiver | |---------------------|-------------------| | Method modifies the receiver | Method only reads | | Struct is large | Struct is small | | Consistency with other methods | Immutability desired |

Embedding (Composition)

type Person struct {
    Name string
    Age  int
}

func (p Person) Greet() string {
    return "Hello, " + p.Name
}

type Employee struct {
    Person              // Embedded (not named)
    EmployeeID string
    Department string
}

// Employee "inherits" Person's fields and methods
emp := Employee{
    Person:     Person{Name: "Alice", Age: 30},
    EmployeeID: "E001",
    Department: "Engineering",
}

emp.Name        // Promoted field
emp.Greet()     // Promoted method
emp.Person.Name // Explicit access

Interfaces

Definition and Implementation

// Define interface
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// Compose interfaces
type ReadWriter interface {
    Reader
    Writer
}

// Implement implicitly (no "implements" keyword)
type FileReader struct {
    data []byte
    pos  int
}

func (f *FileReader) Read(p []byte) (int, error) {
    n := copy(p, f.data[f.pos:])
    f.pos += n
    return n, nil
}

// FileReader now implements Reader

Empty Interface

// interface{} accepts any type (like any in TS)
func printAnything(v interface{}) {
    fmt.Println(v)
}

// Go 1.18+: 'any' is alias for interface{}
func printAnything(v any) {
    fmt.Println(v)
}

Type Assertions

// Assert specific type
var i interface{} = "hello"

s := i.(string)        // Panics if wrong type
s, ok := i.(string)    // Safe: ok is false if wrong type

if s, ok := i.(string); ok {
    fmt.Println(s)
}

Type Switch

func describe(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Integer: %d\n", v)
    case string:
        fmt.Printf("String: %s\n", v)
    case bool:
        fmt.Printf("Boolean: %t\n", v)
    default:
        fmt.Printf("Unknown type: %T\n", v)
    }
}

Common Interfaces

| Interface | Methods | Use | |-----------|---------|-----| | io.Reader | Read([]byte) (int, error) | Read data | | io.Writer | Write([]byte) (int, error) | Write data | | io.Closer | Close() error | Clean up resources | | fmt.Stringer | String() string | Custom string representation | | error | Error() string | Error type |


Collections

Arrays

// Fixed size, rarely used directly
var arr [5]int              // Zero-valued
arr := [5]int{1, 2, 3, 4, 5}
arr := [...]int{1, 2, 3}    // Size inferred (3)

// Arrays are values, not references
arr2 := arr // Full copy

Slices

// Dynamic, reference type (view into array)
var s []int                 // nil slice
s := []int{1, 2, 3}         // Literal
s := make([]int, 5)         // Length 5, capacity 5
s := make([]int, 0, 10)     // Length 0, capacity 10

// Slice operations
len(s)                      // Length
cap(s)                      // Capacity
s[1:3]                      // Sub-slice [1, 3)
s[:3]                       // [0, 3)
s[1:]                       // [1, len)

// Append (may reallocate)
s = append(s, 4)            // Single element
s = append(s, 4, 5, 6)      // Multiple elements
s = append(s, other...)     // Another slice

// Copy
dst := make([]int, len(src))
copy(dst, src)

Slice Gotchas

// Slices share underlying array
original := []int{1, 2, 3, 4, 5}
slice := original[1:3] // [2, 3]
slice[0] = 99          // Modifies original!

// Safe copy
safeCopy := make([]int, len(original[1:3]))
copy(safeCopy, original[1:3])

Maps

// Create maps
var m map[string]int        // nil map (read ok, write panics)
m := map[string]int{}       // Empty map
m := make(map[string]int)   // Empty map
m := map[string]int{        // With values
    "alice": 30,
    "bob":   25,
}

// Operations
m["key"] = 42               // Set
val := m["key"]             // Get (zero value if missing)
val, ok := m["key"]         // Check existence
delete(m, "key")            // Delete
len(m)                      // Size

// Iterate (order is random!)
for key, value := range m {
    fmt.Println(key, value)
}

Iteration

// Range over slice
for i, v := range slice {
    fmt.Println(i, v)
}

// Index only
for i := range slice {
    fmt.Println(i)
}

// Value only
for _, v := range slice {
    fmt.Println(v)
}

// Range over map
for k, v := range m {
    fmt.Println(k, v)
}

// Range over string (runes)
for i, r := range "hello" {
    fmt.Printf("%d: %c\n", i, r)
}

// Range over channel
for msg := range ch {
    fmt.Println(msg)
}

Error Handling

The error Interface

// error is a built-in interface
type error interface {
    Error() string
}

// Create errors
err := errors.New("something went wrong")
err := fmt.Errorf("failed to process %s: %w", name, originalErr)

Error Handling Pattern

// Check errors immediately
result, err := doSomething()
if err != nil {
    return err // Or handle appropriately
}
// Use result...

// Multiple operations
f, err := os.Open(filename)
if err != nil {
    return fmt.Errorf("open file: %w", err)
}
defer f.Close()

data, err := io.ReadAll(f)
if err != nil {
    return fmt.Errorf("read file: %w", err)
}

Custom Errors

// Simple custom error
type ValidationError struct {
    Field   string
    Message string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Message)
}

// Usage
return ValidationError{Field: "email", Message: "invalid format"}

Error Wrapping (Go 1.13+)

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

// Check error type
if errors.Is(err, os.ErrNotExist) {
    // Handle file not found
}

// Extract wrapped error
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    fmt.Println("Path:", pathErr.Path)
}

Panic and Recover

// Panic for unrecoverable errors (avoid in library code)
func mustParse(s string) int {
    n, err := strconv.Atoi(s)
    if err != nil {
        panic(err)
    }
    return n
}

// Recover from panic
func safeCall(fn func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic: %v", r)
        }
    }()
    fn()
    return nil
}

Concurrency Basics

Goroutines

// Start goroutine
go doWork()

// Anonymous goroutine
go func() {
    fmt.Println("In goroutine")
}()

// With arguments (captured by value)
for i := 0; i < 5; i++ {
    go func(n int) {
        fmt.Println(n)
    }(i)
}

Channels

// Create channel
ch := make(chan int)        // Unbuffered
ch := make(chan int, 10)    // Buffered (capacity 10)

// Send and receive
ch <- 42        // Send (blocks if full/unbuffered)
val := <-ch     // Receive (blocks if empty)

// Close channel
close(ch)

// Check if closed
val, ok := <-ch
if !ok {
    fmt.Println("Channel closed")
}

// Range over channel (until closed)
for val := range ch {
    fmt.Println(val)
}

Channel Directions

// Send-only channel
func producer(ch chan<- int) {
    ch <- 42
}

// Receive-only channel
func consumer(ch <-chan int) {
    val := <-ch
}

Select

// Wait on multiple channels
select {
case msg := <-ch1:
    fmt.Println("From ch1:", msg)
case msg := <-ch2:
    fmt.Println("From ch2:", msg)
case ch3 <- 42:
    fmt.Println("Sent to ch3")
default:
    fmt.Println("No communication ready")
}

// Timeout pattern
select {
case result := <-ch:
    fmt.Println(result)
case <-time.After(time.Second):
    fmt.Println("Timeout")
}

WaitGroup

import "sync"

var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Println(n)
    }(i)
}

wg.Wait() // Block until all done

Mutex

import "sync"

type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// RWMutex for read-heavy workloads
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.data[key]
}

Common Patterns

Options Pattern (Functional Options)

type Server struct {
    host    string
    port    int
    timeout time.Duration
}

type Option func(*Server)

func WithPort(port int) Option {
    return func(s *Server) {
        s.port = port
    }
}

func WithTimeout(d time.Duration) Option {
    return func(s *Server) {
        s.timeout = d
    }
}

func NewServer(host string, opts ...Option) *Server {
    s := &Server{
        host:    host,
        port:    8080,           // Default
        timeout: time.Minute,    // Default
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage
server := NewServer("localhost",
    WithPort(9000),
    WithTimeout(30*time.Second),
)

Constructor Pattern

type User struct {
    id   int
    name string
}

// Constructor function (New prefix convention)
func NewUser(id int, name string) *User {
    return &User{
        id:   id,
        name: name,
    }
}

// MustX for constructors that can fail
func MustCompile(pattern string) *regexp.Regexp {
    re, err := regexp.Compile(pattern)
    if err != nil {
        panic(err)
    }
    return re
}

Table-Driven Tests

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 1, 2, 3},
        {"negative", -1, -2, -3},
        {"zero", 0, 0, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

Context for Cancellation

import "context"

func doWork(ctx context.Context) error {
    select {
    case <-time.After(time.Hour):
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

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

if err := doWork(ctx); err != nil {
    log.Println("Work cancelled:", err)
}

// Usage with cancellation
ctx, cancel := context.WithCancel(context.Background())
go func() {
    time.Sleep(time.Second)
    cancel() // Signal cancellation
}()

Go Conventions

Naming

| Type | Convention | Example | |------|------------|---------| | Exported (public) | PascalCase | UserService | | Unexported (private) | camelCase | userCache | | Acronyms | All caps | HTTPClient, xmlParser | | Interfaces | -er suffix | Reader, Stringer | | Getters | No Get prefix | Name() not GetName() | | Package names | lowercase, single word | http, strconv |

Code Organization

// Standard file structure
package main

import (
    "fmt"           // Standard library
    "net/http"

    "github.com/pkg/errors"  // Third-party

    "myproject/internal/user"  // Internal packages
)

// Constants
const MaxRetries = 3

// Package-level variables (minimize these)
var defaultClient = &http.Client{}

// Types
type Server struct { ... }

// Functions
func NewServer() *Server { ... }

// Methods
func (s *Server) Start() error { ... }

Error Strings

// Lowercase, no punctuation
errors.New("connection refused")  // Good
errors.New("Connection refused.") // Bad

// With context
fmt.Errorf("open %s: %w", filename, err)

Troubleshooting

"declared and not used"

// Go requires all variables to be used
x := 5  // Error if x is never used

// Use blank identifier to ignore
x, _ := someFuncReturningTwo()

"cannot use X as type Y"

// Types must match exactly
var x int64 = 5
var y int = x    // Error: int64 != int

// Explicit conversion required
var y int = int(x)

"nil pointer dereference"

// Check for nil before dereferencing
var u *User
fmt.Println(u.Name)  // Panic!

if u != nil {
    fmt.Println(u.Name)
}

"all goroutines are asleep - deadlock!"

// Common cause: unbuffered channel with no receiver
ch := make(chan int)
ch <- 1  // Blocks forever, no receiver

// Fix: use buffered channel or ensure receiver exists
ch := make(chan int, 1)
ch <- 1  // OK

"race condition detected"

// Run with race detector
// go run -race main.go

// Fix: use sync primitives
var mu sync.Mutex
mu.Lock()
shared++
mu.Unlock()

Module System

Go uses a module system for dependency management and package organization.

Package Basics

// Every .go file starts with package declaration
package main    // Executable (has main function)
package user    // Library package

// Package names should be:
// - lowercase, single word
// - match the directory name
// - not generic (utils, common, misc)

Import Paths

import (
    // Standard library
    "fmt"
    "net/http"
    "encoding/json"

    // Third-party packages
    "github.com/gorilla/mux"
    "github.com/pkg/errors"

    // Internal packages
    "myproject/internal/config"
    "myproject/pkg/utils"
)

// Aliased imports
import (
    "fmt"
    myfmt "myproject/fmt"  // Avoid name collision
)

// Blank import (side effects only)
import _ "github.com/lib/pq"  // Register database driver

// Dot import (avoid in production)
import . "fmt"  // Allows Println() instead of fmt.Println()

Visibility

// Uppercase = exported (public)
// Lowercase = unexported (private)

package user

type User struct {       // Exported
    Name  string        // Exported
    email string        // Unexported (package-private)
}

func NewUser() *User {   // Exported
    return &User{}
}

func validate() bool {   // Unexported
    return true
}

Project Structure

myproject/
├── go.mod              # Module definition
├── go.sum              # Dependency checksums
├── main.go             # Entry point (package main)
├── cmd/                # Multiple executables
│   ├── server/
│   │   └── main.go
│   └── cli/
│       └── main.go
├── pkg/                # Public library code
│   └── utils/
│       └── strings.go
├── internal/           # Private packages (enforced by Go)
│   └── database/
│       └── conn.go
└── api/                # API definitions (proto, OpenAPI)

Internal Packages

// Packages under 'internal/' are only importable by code
// within the parent of 'internal/'

// myproject/internal/database/conn.go
// Can be imported by: myproject/...
// Cannot be imported by: other projects

// This is enforced by the Go compiler

Serialization

Go uses struct tags for serialization configuration.

JSON Basics

import "encoding/json"

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
    Age   int    `json:"age"`
}

// Serialize (Marshal)
user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
data, err := json.Marshal(user)
// {"name":"Alice","email":"alice@example.com","age":30}

// Pretty print
data, err := json.MarshalIndent(user, "", "  ")

// Deserialize (Unmarshal)
var user User
err := json.Unmarshal([]byte(jsonStr), &user)

// Stream encoding/decoding
encoder := json.NewEncoder(writer)
encoder.Encode(user)

decoder := json.NewDecoder(reader)
decoder.Decode(&user)

Struct Tags

type Config struct {
    // Rename field
    Name string `json:"name"`

    // Omit if empty/zero value
    Email string `json:"email,omitempty"`

    // Ignore field (never serialize)
    Password string `json:"-"`

    // String encoding for numbers
    Count int `json:"count,string"`

    // Multiple tags
    CreatedAt time.Time `json:"created_at" db:"created_at"`
}

Optional Fields

type User struct {
    Name  string  `json:"name"`
    Email *string `json:"email,omitempty"` // Pointer = nullable
    Age   int     `json:"age,omitempty"`   // Omit if 0
}

// Check for null
if user.Email != nil {
    fmt.Println(*user.Email)
}

Custom Marshal/Unmarshal

type Status int

const (
    StatusPending Status = iota
    StatusActive
    StatusDone
)

func (s Status) MarshalJSON() ([]byte, error) {
    var str string
    switch s {
    case StatusPending:
        str = "pending"
    case StatusActive:
        str = "active"
    case StatusDone:
        str = "done"
    default:
        return nil, fmt.Errorf("unknown status: %d", s)
    }
    return json.Marshal(str)
}

func (s *Status) UnmarshalJSON(data []byte) error {
    var str string
    if err := json.Unmarshal(data, &str); err != nil {
        return err
    }
    switch str {
    case "pending":
        *s = StatusPending
    case "active":
        *s = StatusActive
    case "done":
        *s = StatusDone
    default:
        return fmt.Errorf("unknown status: %s", str)
    }
    return nil
}

Validation

// Using go-playground/validator
import "github.com/go-playground/validator/v10"

type User struct {
    Name  string `json:"name" validate:"required,min=1,max=100"`
    Email string `json:"email" validate:"required,email"`
    Age   int    `json:"age" validate:"gte=0,lte=150"`
}

var validate = validator.New()

func (u *User) Validate() error {
    return validate.Struct(u)
}

// Usage
if err := user.Validate(); err != nil {
    // Handle validation errors
    for _, err := range err.(validator.ValidationErrors) {
        fmt.Printf("%s: %s\n", err.Field(), err.Tag())
    }
}

Other Formats

// YAML: gopkg.in/yaml.v3
import "gopkg.in/yaml.v3"
data, err := yaml.Marshal(config)
err = yaml.Unmarshal(data, &config)

// TOML: github.com/BurntSushi/toml
import "github.com/BurntSushi/toml"
_, err := toml.DecodeFile("config.toml", &config)

// XML: encoding/xml (standard library)
import "encoding/xml"
data, err := xml.Marshal(config)

See also: patterns-serialization-dev for cross-language serialization patterns


Build and Dependencies

Go uses Go Modules for dependency management.

go.mod

// go mod init myproject
module github.com/user/myproject

go 1.21

require (
    github.com/gorilla/mux v1.8.0
    github.com/pkg/errors v0.9.1
)

require (
    // Indirect dependencies (automatically managed)
    github.com/some/transitive v1.0.0 // indirect
)

// Replace directive (local development)
replace github.com/original/pkg => ../local/pkg

// Exclude problematic versions
exclude github.com/broken/pkg v1.0.0

Dependency Commands

| Command | Purpose | |---------|---------| | go mod init <module> | Initialize new module | | go mod tidy | Add missing, remove unused deps | | go mod download | Download dependencies | | go mod verify | Verify dependency checksums | | go mod why <pkg> | Explain why package is needed | | go mod graph | Print dependency graph | | go mod vendor | Copy deps to vendor/ |

Adding Dependencies

# Add dependency (latest version)
go get github.com/gorilla/mux

# Add specific version
go get github.com/gorilla/mux@v1.8.0

# Add latest of major version
go get github.com/gorilla/mux@v1

# Add from branch
go get github.com/gorilla/mux@main

# Update dependency
go get -u github.com/gorilla/mux

# Update all dependencies
go get -u ./...

# Remove dependency
go mod tidy  # After removing imports

Build Commands

| Command | Purpose | |---------|---------| | go build | Compile package | | go build -o app | Compile with output name | | go build ./... | Build all packages | | go install | Compile and install | | go run main.go | Compile and run | | go clean | Remove build artifacts |

Build Flags

# Cross-compilation
GOOS=linux GOARCH=amd64 go build -o app-linux

# Static binary (no CGO)
CGO_ENABLED=0 go build -o app

# Strip debug info (smaller binary)
go build -ldflags="-s -w" -o app

# Inject version info
go build -ldflags="-X main.Version=1.0.0" -o app

# Race detector
go build -race -o app

# Embed version
var Version = "dev"  // Set by -ldflags

Workspaces (Go 1.18+)

# For multi-module development
go work init ./module1 ./module2

# go.work file
go 1.21

use (
    ./module1
    ./module2
)

Environment Variables

| Variable | Purpose | |----------|---------| | GOPATH | Workspace path (legacy) | | GOBIN | Binary install location | | GOPROXY | Module proxy URL | | GOPRIVATE | Private module patterns | | GONOPROXY | Skip proxy for patterns | | GONOSUMDB | Skip checksum DB |

# Private repos
go env -w GOPRIVATE=github.com/mycompany/*

# Use default proxy with fallback
go env -w GOPROXY=https://proxy.golang.org,direct

Testing

Go has built-in testing support with the testing package.

Basic Tests

// user_test.go (must end with _test.go)
package user

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

func TestAddNegative(t *testing.T) {
    result := Add(-1, 1)
    if result != 0 {
        t.Errorf("Add(-1, 1) = %d; want 0", result)
    }
}

Table-Driven Tests

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 1, 2, 3},
        {"negative numbers", -1, -2, -3},
        {"zero", 0, 0, 0},
        {"mixed", -1, 1, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

Test Helpers

// Helper function
func setupTestDatabase(t *testing.T) *Database {
    t.Helper()  // Marks as helper (better error locations)
    db, err := NewTestDatabase()
    if err != nil {
        t.Fatalf("setup failed: %v", err)
    }
    t.Cleanup(func() {  // Cleanup after test
        db.Close()
    })
    return db
}

func TestUser(t *testing.T) {
    db := setupTestDatabase(t)
    // Test using db...
}

Subtests and Parallel

func TestUser(t *testing.T) {
    // Subtests
    t.Run("Create", func(t *testing.T) {
        t.Parallel()  // Run in parallel
        // Test create...
    })

    t.Run("Update", func(t *testing.T) {
        t.Parallel()
        // Test update...
    })
}

Running Tests

# Run all tests
go test ./...

# Run specific package
go test ./pkg/user

# Run specific test
go test -run TestAdd ./...

# Run with verbose output
go test -v ./...

# Run with coverage
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

# Run with race detector
go test -race ./...

# Run benchmarks
go test -bench=. ./...

# Timeout
go test -timeout 30s ./...

# Skip long tests
go test -short ./...

Benchmarks

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

func BenchmarkAddParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Add(1, 2)
        }
    })
}

// With setup
func BenchmarkProcess(b *testing.B) {
    data := setupLargeData()
    b.ResetTimer()  // Don't count setup time

    for i := 0; i < b.N; i++ {
        Process(data)
    }
}

Mocking with Interfaces

// Define interface for dependencies
type UserStore interface {
    GetUser(id int) (*User, error)
    SaveUser(user *User) error
}

// Real implementation
type PostgresStore struct { ... }

// Mock implementation
type MockStore struct {
    users map[int]*User
}

func (m *MockStore) GetUser(id int) (*User, error) {
    user, ok := m.users[id]
    if !ok {
        return nil, ErrNotFound
    }
    return user, nil
}

// Test with mock
func TestService(t *testing.T) {
    mock := &MockStore{
        users: map[int]*User{
            1: {ID: 1, Name: "Alice"},
        },
    }

    service := NewUserService(mock)
    user, err := service.GetUser(1)
    // Assert...
}

Example Tests

// Example tests appear in documentation
func ExampleAdd() {
    sum := Add(2, 3)
    fmt.Println(sum)
    // Output: 5
}

func ExampleUser_FullName() {
    u := User{FirstName: "Alice", LastName: "Smith"}
    fmt.Println(u.FullName())
    // Output: Alice Smith
}

TestMain

func TestMain(m *testing.M) {
    // Setup before all tests
    setup()

    // Run tests
    code := m.Run()

    // Cleanup after all tests
    teardown()

    os.Exit(code)
}

Metaprogramming

Go has limited metaprogramming capabilities compared to languages with macros. The primary approach is code generation.

Code Generation

//go:generate stringer -type=Status

type Status int

const (
    StatusPending Status = iota
    StatusActive
    StatusDone
)

// Run: go generate ./...
// Generates: status_string.go with String() method

Common Generators

| Tool | Purpose | |------|---------| | stringer | Generate String() for enums | | mockgen | Generate mock implementations | | protoc | Generate from Protocol Buffers | | go-bindata | Embed files in binary | | sqlc | Generate from SQL | | ent | Generate ORM code |

Build Tags

// +build linux
// +build !windows

package mypackage

// This file only compiles on Linux, not on Windows
//go:build linux && amd64

package mypackage

// Go 1.17+ syntax

Reflection (Runtime)

import "reflect"

func PrintFields(v interface{}) {
    t := reflect.TypeOf(v)
    val := reflect.ValueOf(v)

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := val.Field(i)
        tag := field.Tag.Get("json")

        fmt.Printf("%s (%s): %v [json:%s]\n",
            field.Name, field.Type, value.Interface(), tag)
    }
}

// Use reflection sparingly - it's slow and unsafe

See also: patterns-metaprogramming-dev for cross-language comparison


Cross-Cutting Patterns

For cross-language comparison and translation patterns, see:

  • patterns-concurrency-dev - Goroutines, channels, select patterns
  • patterns-serialization-dev - JSON, struct tags, validation
  • patterns-metaprogramming-dev - Code generation, build tags

References