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.
Profilregeln
Error Guards
Direkt nach einer Zuweisung an err folgt der Guard. Fehler werden nicht mit _ verworfen.
user, _ := loadUser(id)
return createSession(user), nil user, err := loadUser(id)
log.Printf("loaded %s", user.Name)
if err != nil {
return nil, err
} user, err := loadUser(id)
if err != nil {
return nil, err
}
session := createSession(user)
return session, nil Kein else nach terminierenden Zweigen
Wenn der if-Zweig mit return, break oder continue endet, ist ein else überflüssig.
if err != nil {
return nil, err
} else {
return createSession(user), nil
} if err != nil {
return nil, err
}
return createSession(user), nil 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.
u, e := load(x)
for _, v := range u.Items {
tmp := v.Price
} user session conn server packet
request ctx err wg tx func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} Kurze Receiver-Namen sind erlaubt
Receiver sind lokal, konventionell und eindeutig. Pureline erzwingt keinen langen Receiver-Namen, wenn er keine Klarheit bringt.
func (this *Server) Start() error {
}
func (self *Server) Stop() error {
} func (s *Server) Start() error {
} func (server *Server) Start() error {
} Kleine Interfaces
Interfaces beschreiben eine enge Fähigkeit. Übergroße Interfaces mit unzusammenhängenden Verantwortlichkeiten werden aufgeteilt.
type Store interface {
Load(id ID) (*User, error)
Save(user *User) error
Delete(id ID) error
Migrate() error
Backup(path string) error
Metrics() Stats
} type Storage interface {
Load(id ID) (*User, error)
Save(user *User) error
} Interfaces beim Konsumenten definieren
Interfaces entstehen dort, wo sie konsumiert werden, statt global als große, vererbungsartige Abstraktionen vorentworfen zu werden.
package storage
type UserStorage interface {
Load(id ID) (*User, error)
Save(user *User) error
}
type postgresStorage struct{} package session
type userLoader interface {
Load(id ID) (*User, error)
}
type Service struct {
users userLoader
} defer für klare Lebenszyklen
Direkt nach dem Error Guard einer geöffneten Ressource steht das passende defer.
file, err := os.Open(path)
defer file.Close()
if err != nil {
return err
} resp, err := http.Get(url)
if err != nil {
return err
}
return decode(resp.Body) file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close() Goroutines brauchen Ownership
Keine Fire-and-forget-Goroutines ohne expliziten Lebenszyklus. Langlebige Goroutines klären Abbruch, Fehlerweitergabe, Shutdown und Ownership.
go refreshCache() group, ctx := errgroup.WithContext(ctx)
group.Go(func() error {
return refreshCache(ctx)
})
return group.Wait() 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.
type ConfigLoader interface {
Load(path string) (*Config, error)
}
type configLoader struct{}
func NewConfigLoader() ConfigLoader {
return &configLoader{}
} 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: