Pureline Go
Idiomatic Go that already thinks linearly.
Pureline Go must remain idiomatic Go. Pureline does not try to make Go look like Java. Go's error guards already match the guard-first philosophy.
Profile rules
Error Guards
The guard follows directly after an assignment to err. Errors are not discarded with _.
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 Avoid `else` After Terminating Branches
When the if branch ends with return, break, or continue, an else is unnecessary.
if err != nil {
return nil, err
} else {
return createSession(user), nil
} if err != nil {
return nil, err
}
return createSession(user), nil Idiomatic Naming
Common Go short forms such as ctx, err, wg, tx, and conn are established. Meaningless one-letter names are not, unless they are strongly established local conventions such as w http.ResponseWriter and 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) {
} Short Receiver Names Are Allowed
Receiver names are local, conventional, and unambiguous. Pureline does not force a long receiver name when it adds no clarity.
func (this *Server) Start() error {
}
func (self *Server) Stop() error {
} func (s *Server) Start() error {
} func (server *Server) Start() error {
} Small Interfaces
Interfaces describe a narrow capability. Oversized interfaces containing unrelated responsibilities are split.
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
} Define Interfaces Near Consumers
Interfaces should generally be defined where they are consumed rather than globally pre-designed as large inheritance-style abstractions.
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
} Use `defer` for Clear Resource Lifecycles
The matching defer goes directly after the error guard of an opened resource.
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 Need Ownership
Avoid fire-and-forget goroutines without an explicit lifecycle. A long-lived goroutine should have clear handling for cancellation, error propagation, shutdown, and ownership.
go refreshCache() group, ctx := errgroup.WithContext(ctx)
group.Go(func() error {
return refreshCache(ctx)
})
return group.Wait() Avoid Premature Interface Abstraction
Do not create an interface merely because a concrete type might theoretically have another implementation later. Create abstractions when there is a real consumer need.
type ConfigLoader interface {
Load(path string) (*Config, error)
}
type configLoader struct{}
func NewConfigLoader() ConfigLoader {
return &configLoader{}
} func LoadConfig(path string) (*Config, error) {
} Core rules in the Go checker
In addition to the profile rules, the Go checker verifies these rules from the Pureline Core: