Pureline

Pureline Go

Idiomatisches Go, das bereits linear denkt.

Pureline Go bleibt idiomatisches Go. Pureline versucht nicht, Go wie Java aussehen zu lassen. Die Error Guards von Go passen ohnehin genau zur Guard-First-Philosophie.

Idiomatic Error GuardsSmall InterfacesdeferExplicit Goroutine Lifecycle
9Profilregeln
15automatisiert
Go-Code prüfen

Profilregeln

PL-GO-001 Checker

Error Guards

Direkt nach einer Zuweisung an err folgt der Guard. Fehler werden nicht mit _ verworfen.

Avoid
Fehler verworfen
user, _ := loadUser(id)
return createSession(user), nil
Guard nicht direkt danach
user, err := loadUser(id)
log.Printf("loaded %s", user.Name)

if err != nil {
    return nil, err
}
Pureline
user, err := loadUser(id)

if err != nil {
    return nil, err
}

session := createSession(user)
return session, nil
PL-GO-002 Checker

Kein else nach terminierenden Zweigen

Wenn der if-Zweig mit return, break oder continue endet, ist ein else überflüssig.

Avoid
if err != nil {
    return nil, err
} else {
    return createSession(user), nil
}
Pureline
if err != nil {
    return nil, err
}

return createSession(user), nil
PL-GO-003 Checker

Idiomatische Namen

Go-übliche Kurzformen wie ctx, err, wg, tx und conn sind etabliert. Bedeutungslose Ein-Buchstaben-Namen sind es nicht, außer bei starken lokalen Konventionen wie w http.ResponseWriter und r *http.Request.

Avoid
u, e := load(x)
for _, v := range u.Items {
    tmp := v.Price
}
Pureline
user   session   conn   server   packet
request   ctx   err   wg   tx
Etablierte Konvention
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
PL-GO-004

Kurze Receiver-Namen sind erlaubt

Receiver sind lokal, konventionell und eindeutig. Pureline erzwingt keinen langen Receiver-Namen, wenn er keine Klarheit bringt.

Avoid
Fremde Konventionen
func (this *Server) Start() error {
}

func (self *Server) Stop() error {
}
Pureline
func (s *Server) Start() error {
}
Auch erlaubt, aber nicht erzwungen
func (server *Server) Start() error {
}
PL-GO-005 Checker

Kleine Interfaces

Interfaces beschreiben eine enge Fähigkeit. Übergroße Interfaces mit unzusammenhängenden Verantwortlichkeiten werden aufgeteilt.

Avoid
type Store interface {
    Load(id ID) (*User, error)
    Save(user *User) error
    Delete(id ID) error
    Migrate() error
    Backup(path string) error
    Metrics() Stats
}
Pureline
type Storage interface {
    Load(id ID) (*User, error)
    Save(user *User) error
}
PL-GO-006

Interfaces beim Konsumenten definieren

Interfaces entstehen dort, wo sie konsumiert werden, statt global als große, vererbungsartige Abstraktionen vorentworfen zu werden.

Avoid
storage/storage.go: Produzent definiert alles vorab
package storage

type UserStorage interface {
    Load(id ID) (*User, error)
    Save(user *User) error
}

type postgresStorage struct{}
Pureline
session/service.go: Konsument definiert, was er braucht
package session

type userLoader interface {
    Load(id ID) (*User, error)
}

type Service struct {
    users userLoader
}
PL-GO-007 Checker

defer für klare Lebenszyklen

Direkt nach dem Error Guard einer geöffneten Ressource steht das passende defer.

Avoid
defer vor dem Guard
file, err := os.Open(path)
defer file.Close()

if err != nil {
    return err
}
Kein defer
resp, err := http.Get(url)

if err != nil {
    return err
}

return decode(resp.Body)
Pureline
file, err := os.Open(path)

if err != nil {
    return err
}

defer file.Close()
PL-GO-008 Checker

Goroutines brauchen Ownership

Keine Fire-and-forget-Goroutines ohne expliziten Lebenszyklus. Langlebige Goroutines klären Abbruch, Fehlerweitergabe, Shutdown und Ownership.

Avoid
go refreshCache()
Pureline
group, ctx := errgroup.WithContext(ctx)

group.Go(func() error {
    return refreshCache(ctx)
})

return group.Wait()
PL-GO-009

Keine voreilige Interface-Abstraktion

Ein Interface entsteht nicht, weil ein konkreter Typ theoretisch später eine zweite Implementierung bekommen könnte, sondern wenn ein Konsument es wirklich braucht.

Avoid
type ConfigLoader interface {
    Load(path string) (*Config, error)
}

type configLoader struct{}

func NewConfigLoader() ConfigLoader {
    return &configLoader{}
}
Pureline
func LoadConfig(path string) (*Config, error) {
}

Core-Regeln im Go-Checker

Zusätzlich zu den Profilregeln prüft der Checker für Go diese Regeln aus dem Pureline Core: