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-patternsexplizit ü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
- Konstruktoren SOLLTEN funktionale Optionen verwenden – sie skalieren besser, wenn APIs sich weiterentwickeln (eine Funktion pro Option, keine Breaking Changes)
- Funktionale Optionen MÜSSEN einen Fehler zurückgeben, wenn die Validierung fehlschlagen kann – fange schlechte Konfiguration beim Bauen, nicht zur Laufzeit
- Vermeide
init()– läuft implizit, kann keine Fehler zurückgeben, macht Tests unberechenbar. Verwende explizite Konstruktoren - Enums SOLLTEN bei 1 beginnen (oder Unknown-Sentinel bei 0) – der Go-Nullwert wird stillschweigend als erstes Enum-Mitglied durchgereicht
- Fehlerfälle MÜSSEN zuerst behandelt werden mit frühem Return – halte den Happy Path flach
- Panic ist für Bugs, nicht für erwartete Fehler – Aufrufer können zurückgegebene Fehler behandeln; Panics legen den Prozess lahm
defer Close()unmittelbar nach dem Öffnen – spätere Codeänderungen könnten versehentlich die Bereinigung überspringenruntime.AddCleanupstattruntime.SetFinalizer– Finalizer sind unberechenbar und können Objekte wiederbeleben- Jeder externe Aufruf SOLLTE ein Timeout haben – ein langsames Upstream hängt deine Goroutine unbegrenzt auf
- Begrenze alles (Pool-Größen, Warteschlangentiefen, Puffer) – unbegrenzte Ressourcen wachsen, bis sie crashen
- Retry-Logik MUSS Context-Cancellation zwischen Versuchen prüfen
- Verwende
strings.Builderfür Verkettung in Schleifen → siehesamber/cc-skills-golang@golang-code-style - string vs []byte: verwende
[]bytefür Mutation und I/O,stringfür Anzeige und Keys – Konvertierungen allozieren - Iteratoren (Go 1.23+): verwende für faule Auswertung – vermeide, alles in den Speicher zu laden
- Streame große Übertragungen – das Laden von Millionen von Zeilen verursacht OOM; Streaming hält den Speicher konstant
//go:embedfür statische Assets – bettet zur Compile-Zeit ein, eliminiert Laufzeit-I/O-Fehler- Verwende
crypto/randfür Keys/Tokens –math/randist vorhersagbar → siehesamber/cc-skills-golang@golang-security - Regexp MUSS einmal auf Paketebene kompiliert werden – Kompilierung ist O(n) und alloziert
- Compile-Zeit-Interface-Checks:
var _ Interface = (*Type)(nil) - Ein wenig Neucodierung > eine große Abhängigkeit – jede Abhängigkeit vergrößert die Angriffsfläche und den Wartungsaufwand
- 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.Fatalauslö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 oftdefer f.Close()verwenden, aber Schreib-/Flush-Ressourcen müssen Close- oder Flush-Fehler melden, wenn Haltbarkeit wichtig ist
Datenverarbeitung
string vs []byte vs []rune
| Typ | Standard für | Verwende, wenn |
|---|---|---|
string | Alles | Unveränderlich, sicher, UTF-8 |
[]byte | I/O | Schreiben in io.Writer, Strings aufbauen, Mutationen |
[]rune | Unicode-Operationen | len() 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
| Anleitung | Umfang |
|---|---|
| Architektur-Patterns | Prinzipien auf hoher Ebene, wann welche Architektur passt |
| Clean Architecture | Use Cases, Abhängigkeitsregel, geschichtete Adapter |
| Hexagonale Architektur | Ports und Adapter, Isolierung des Domänenkerns |
| Domain-Driven Design | Aggregate, 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-structuresfür Datenstruktur-Auswahl, Interna und container/-Pakete - → Siehe Skill
samber/cc-skills-golang@golang-error-handlingfür Error Wrapping, Sentinel Errors und die Single-Handling-Regel - → Siehe Skill
samber/cc-skills-golang@golang-structs-interfacesfür Interface-Design und Komposition - → Siehe Skill
samber/cc-skills-golang@golang-concurrencyfür Goroutine-Lebenszyklus und Graceful Shutdown - → Siehe Skill
samber/cc-skills-golang@golang-contextfür Timeout- und Cancellation-Patterns - → Siehe Skill
samber/cc-skills-golang@golang-project-layoutfür Architektur und Verzeichnisstruktur - → Siehe Skill
samber/cc-skills-golang@golang-refactoringfür die sichere Migration hin zu einem dieser Patterns (Options-Struct, DI, konsumentenseitige Interfaces) in einer bestehenden Codebasis