CommunityCodierung & Entwicklunggithub.com

viccom/golang-design-patterns

Idiomatische Go-Design-Patterns für produktionsreife Anwendungen – funktionale Optionen, Konstruktoren, Fehlerbehandlung, Ressourcenmanagement, Graceful Shutdown, Resilienz, Architektur, Dependency Injection, Datenverarbeitung, Streaming und mehr. Anwenden, wenn explizit zwischen Architekturmustern gewählt wird, funktionale Optionen implementiert, Konstruktor-APIs entworfen, Graceful Shutdown eingerichtet, Resilienzmuster angewendet oder gefragt wird, welches idiomatische Go-Pattern für ein spezifisches Problem passt.

Was ist golang-design-patterns?

Diese Skill-Bibliothek bietet Go-Architekten und Entwicklern einen umfassenden Leitfaden für idiomatische Design-Patterns in Go, die auf Produktionsreife und einfache Wartbarkeit abzielen. Sie deckt zentrale Bereiche wie Konstruktoren mit funktionalen Optionen (statt Builder), explizite Initialisierung (kein `init()`), Enum-Definitionen, Fehlerbehandlung, Ressourcenmanagement (z.B. `defer Close()` unmittelbar nach dem Öffnen), Resilienz (Timeouts, Retry mit Context-Checks) und Datenverarbeitung (String vs. Byte, Iteratoren, Streaming) ab. Die Skill ist für Claude Code, Cursor, Codex und andere KI-Assistenten optimiert und wird in zwei Modi bereitgestellt: **Design-Modus** (neue APIs/Strukturen) und **Review-Modus** (Code-Audit). Sie verweist auf verwandte Skills (z.B. für Fehlerbehandlung, Context) und fördert eine pragmatische Philosophie: Abstraktion nur dann, wenn sie ein konkretes Problem löst, minimale Abhängigkeiten und testbare Schnittstellen. Die detaillierten Referenzen umfassen Architekturmuster (Clean, Hexagonal, DDD) und konkrete Codebeispiele.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/viccom/viccom-agent-skills/tree/HEAD/skills/golang-design-patterns

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Persona: Du bist ein Go-Architekt, der Einfachheit und Eindeutigkeit schätzt. Du wendest Patterns nur an, wenn sie ein echtes Problem lösen – nicht um Sophistikation zu demonstrieren – und du lehnst vorzeitige Abstraktion ab.

Modi:

  • Design-Modus — beim Erstellen neuer APIs, Pakete oder Anwendungsstrukturen: Frage den Entwickler nach seiner Architekturpräferenz, bevor du Patterns vorschlägst; bevorzuge das kleinste Pattern, das die Anforderung erfüllt.
  • Review-Modus — beim Prüfen vorhandenen Codes auf Design-Probleme: Scanne nach init()-Missbrauch, unbegrenzten Ressourcen, fehlenden Timeouts und implizitem globalem Zustand; berichte Ergebnisse, bevor du Refactorings vorschlägst.

Community-Standard. Ein Company-Skill, der samber/cc-skills-golang@golang-design-patterns explizit überschreibt, hat Vorrang.

Go-Design-Patterns & -Idiome

Idiomatische Go-Patterns für produktionsreifen Code. Details zur Fehlerbehandlung findest du im Skill samber/cc-skills-golang@golang-error-handling; zur Kontextweitergabe im Skill samber/cc-skills-golang@golang-context; zum Struct/Interface-Design im Skill samber/cc-skills-golang@golang-structs-interfaces.

Best Practices – Zusammenfassung

  1. Konstruktoren SOLLTEN funktionale Optionen verwenden – sie skalieren besser, wenn APIs sich weiterentwickeln (eine Funktion pro Option, keine Breaking Changes)
  2. Funktionale Optionen MÜSSEN einen Fehler zurückgeben, wenn die Validierung fehlschlagen kann – fange schlechte Konfiguration beim Bauen, nicht zur Laufzeit
  3. Vermeide init() – läuft implizit, kann keine Fehler zurückgeben, macht Tests unberechenbar. Verwende explizite Konstruktoren
  4. Enums SOLLTEN bei 1 beginnen (oder Unknown-Sentinel bei 0) – der Go-Nullwert wird stillschweigend als erstes Enum-Mitglied durchgereicht
  5. Fehlerfälle MÜSSEN zuerst behandelt werden mit frühem Return – halte den Happy Path flach
  6. Panic ist für Bugs, nicht für erwartete Fehler – Aufrufer können zurückgegebene Fehler behandeln; Panics legen den Prozess lahm
  7. defer Close() unmittelbar nach dem Öffnen – spätere Codeänderungen könnten versehentlich die Bereinigung überspringen
  8. runtime.AddCleanup statt runtime.SetFinalizer – Finalizer sind unberechenbar und können Objekte wiederbeleben
  9. Jeder externe Aufruf SOLLTE ein Timeout haben – ein langsames Upstream hängt deine Goroutine unbegrenzt auf
  10. Begrenze alles (Pool-Größen, Warteschlangentiefen, Puffer) – unbegrenzte Ressourcen wachsen, bis sie crashen
  11. Retry-Logik MUSS Context-Cancellation zwischen Versuchen prüfen
  12. Verwende strings.Builder für Verkettung in Schleifen → siehe samber/cc-skills-golang@golang-code-style
  13. string vs []byte: verwende []byte für Mutation und I/O, string für Anzeige und Keys – Konvertierungen allozieren
  14. Iteratoren (Go 1.23+): verwende für faule Auswertung – vermeide, alles in den Speicher zu laden
  15. Streame große Übertragungen – das Laden von Millionen von Zeilen verursacht OOM; Streaming hält den Speicher konstant
  16. //go:embed für statische Assets – bettet zur Compile-Zeit ein, eliminiert Laufzeit-I/O-Fehler
  17. Verwende crypto/rand für Keys/Tokens – math/rand ist vorhersagbar → siehe samber/cc-skills-golang@golang-security
  18. Regexp MUSS einmal auf Paketebene kompiliert werden – Kompilierung ist O(n) und alloziert
  19. Compile-Zeit-Interface-Checks: var _ Interface = (*Type)(nil)
  20. Ein wenig Neucodierung > eine große Abhängigkeit – jede Abhängigkeit vergrößert die Angriffsfläche und den Wartungsaufwand
  21. Design für Testbarkeit – akzeptiere Interfaces, injiziere Abhängigkeiten

Konstruktor-Patterns: Funktionale Optionen vs Builder

Funktionale Optionen (Bevorzugt)

type Server struct {
    addr         string
    readTimeout  time.Duration
    writeTimeout time.Duration
    maxConns     int
}

type Option func(*Server)

func WithReadTimeout(d time.Duration) Option {
    return func(s *Server) { s.readTimeout = d }
}

func WithWriteTimeout(d time.Duration) Option {
    return func(s *Server) { s.writeTimeout = d }
}

func WithMaxConns(n int) Option {
    return func(s *Server) { s.maxConns = n }
}

func NewServer(addr string, opts ...Option) *Server {
    // Standardoptionen
    s := &Server{
        addr:         addr,
        readTimeout:  5 * time.Second,
        writeTimeout: 10 * time.Second,
        maxConns:     100,
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Verwendung
srv := NewServer(":8080",
    WithReadTimeout(30*time.Second),
    WithMaxConns(500),
)

Konstruktoren SOLLTEN funktionale Optionen verwenden – sie skalieren besser mit der API-Entwicklung und benötigen weniger Code. Verwende das Builder-Pattern nur, wenn du komplexe Validierung zwischen Konfigurationsschritten benötigst.

Konstruktoren & Initialisierung

Vermeide init() und veränderliche globale Variablen

init() läuft implizit, erschwert Tests und erzeugt versteckte Abhängigkeiten:

  • Mehrere init()-Funktionen laufen in der Deklarationsreihenfolge, dateiübergreifend in alphabetischer Reihenfolge der Dateinamen – fragil
  • Kann keine Fehler zurückgeben – Fehler müssen panic oder log.Fatal auslösen
  • Läuft vor main() und Tests – Seiteneffekte machen Tests unberechenbar
// Schlecht – versteckter globaler Zustand
var db *sql.DB

func init() {
    var err error
    db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
    if err != nil {
        log.Fatal(err)
    }
}

// Gut – explizite Initialisierung, injizierbar
func NewUserRepository(db *sql.DB) *UserRepository {
    return &UserRepository{db: db}
}

Enums: Bei 1 beginnen

Nullwerte sollten einen ungültigen/nicht gesetzten Zustand repräsentieren:

type Status int

const (
    StatusUnknown Status = iota // 0 = ungültig/nicht gesetzt
    StatusActive                // 1
    StatusInactive              // 2
    StatusSuspended             // 3
)

Regexp einmal kompilieren

// Gut – einmal auf Paketebene kompiliert
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

func ValidateEmail(email string) bool {
    return emailRegex.MatchString(email)
}

//go:embed für statische Assets verwenden

import "embed"

//go:embed templates/*
var templateFS embed.FS

//go:embed version.txt
var version string

Compile-Zeit-Interface-Checks

→ Siehe samber/cc-skills-golang@golang-structs-interfaces für das var _ Interface = (*Type)(nil)-Pattern.

Fehlerfluss-Patterns

Fehlerfälle MÜSSEN zuerst mit frühem Return behandelt werden – halte den Happy Path auf minimaler Einrückungsebene. → Siehe samber/cc-skills-golang@golang-code-style für das vollständige Pattern und Beispiele.

Wann Panic vs Fehler zurückgeben

  • Fehler zurückgeben: Netzwerkfehler, Datei nicht gefunden, ungültige Eingabe – alles, was ein Aufrufer behandeln kann
  • Panic: Nil-Zeiger an einer Stelle, die unmöglich sein sollte, verletzte Invariante, Must*-Konstruktoren, die zur Initialisierungszeit verwendet werden
  • .Close() / Flush()-Fehler: schreibgeschützte Bereinigung kann oft defer f.Close() verwenden, aber Schreib-/Flush-Ressourcen müssen Close- oder Flush-Fehler melden, wenn Haltbarkeit wichtig ist

Datenverarbeitung

string vs []byte vs []rune

TypStandard fürVerwende, wenn
stringAllesUnveränderlich, sicher, UTF-8
[]byteI/OSchreiben in io.Writer, Strings aufbauen, Mutationen
[]runeUnicode-Operationenlen() muss Zeichen bedeuten, nicht Bytes

Vermeide wiederholte Konvertierungen – jede alloziert. Bleibe in einem Typ, bis du den anderen benötigst.

Iteratoren & Streaming für große Datenmengen

Verwende Iteratoren (Go 1.23+) und Streaming-Patterns, um große Datensätze zu verarbeiten, ohne alles in den Speicher zu laden. Für große Übertragungen zwischen Diensten (z.B. 1M Zeilen DB zu HTTP) streamen, um OOM zu verhindern.

Codebeispiele findest du unter Datenverarbeitungs-Patterns.

Ressourcenmanagement

defer Close() unmittelbar nach dem Öffnen – nicht warten, nicht vergessen:

f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close() // genau hier, nicht 50 Zeilen später

rows, err := db.QueryContext(ctx, query)
if err != nil {
    return err
}
defer rows.Close()

Details zu Graceful Shutdown, Ressourcenpools und runtime.AddCleanup findest du unter Ressourcenmanagement.

Resilienz & Grenzen

Timeout bei jedem externen Aufruf

ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

resp, err := httpClient.Do(req.WithContext(ctx))

Retry & Context-Prüfungen

Retry-Logik MUSS ctx.Err() zwischen den Versuchen prüfen und exponentiellen/linearen Backoff mittels select auf ctx.Done() verwenden. Lange Schleifen MÜSSEN periodisch ctx.Err() prüfen. → Siehe Skill samber/cc-skills-golang@golang-context.

Datenbank-Patterns

→ Siehe Skill samber/cc-skills-golang@golang-database für sqlx/pgx, Transaktionen, nullable Spalten, Connection-Pools, Repository-Interfaces, Testing.

Architektur

Frage den Entwickler, welche Architektur er bevorzugt: Clean Architecture, Hexagonal, DDD oder flaches Layout. Erzwinge keine komplexe Architektur für ein kleines Projekt.

Kernprinzipien unabhängig von der Architektur:

  • Halte die Domäne rein – keine Framework-Abhängigkeiten in der Domänenschicht
  • Fail fast – validiere an den Grenzen, vertraue internem Code
  • Mache ungültige Zustände nicht darstellbar – verwende Typen, um Invarianten durchzusetzen
  • Respektiere die 12-Faktor-App-Prinzipien – → siehe samber/cc-skills-golang@golang-project-layout

Detaillierte Anleitungen

AnleitungUmfang
Architektur-PatternsPrinzipien auf hoher Ebene, wann welche Architektur passt
Clean ArchitectureUse Cases, Abhängigkeitsregel, geschichtete Adapter
Hexagonale ArchitekturPorts und Adapter, Isolierung des Domänenkerns
Domain-Driven DesignAggregate, Value Objects, Bounded Contexts

Code-Philosophie

  • Vermeide sich wiederholenden Code – aber abstrahiere nicht vorzeitig
  • Minimiere Abhängigkeiten – ein wenig Neucodierung > eine große Abhängigkeit
  • Design für Testbarkeit – akzeptiere Interfaces, injiziere Abhängigkeiten, halte Funktionen rein

Querverweise

  • → Siehe Skill samber/cc-skills-golang@golang-data-structures für Datenstruktur-Auswahl, Interna und container/-Pakete
  • → Siehe Skill samber/cc-skills-golang@golang-error-handling für Error Wrapping, Sentinel Errors und die Single-Handling-Regel
  • → Siehe Skill samber/cc-skills-golang@golang-structs-interfaces für Interface-Design und Komposition
  • → Siehe Skill samber/cc-skills-golang@golang-concurrency für Goroutine-Lebenszyklus und Graceful Shutdown
  • → Siehe Skill samber/cc-skills-golang@golang-context für Timeout- und Cancellation-Patterns
  • → Siehe Skill samber/cc-skills-golang@golang-project-layout für Architektur und Verzeichnisstruktur
  • → Siehe Skill samber/cc-skills-golang@golang-refactoring für die sichere Migration hin zu einem dieser Patterns (Options-Struct, DI, konsumentenseitige Interfaces) in einer bestehenden Codebasis

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Verwandte Skills