Pureline

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.

Idiomatic Error GuardsSmall InterfacesdeferExplicit Goroutine Lifecycle
9Profile rules
15automated
Check Go code

Profile rules

PL-GO-001 Checker

Error Guards

The guard follows directly after an assignment to err. Errors are not discarded with _.

Avoid
Error discarded
user, _ := loadUser(id)
return createSession(user), nil
Guard not directly after
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

Avoid `else` After Terminating Branches

When the if branch ends with return, break, or continue, an else is unnecessary.

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

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.

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

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.

Avoid
Foreign conventions
func (this *Server) Start() error {
}

func (self *Server) Stop() error {
}
Pureline
func (s *Server) Start() error {
}
Also allowed, but not enforced
func (server *Server) Start() error {
}
PL-GO-005 Checker

Small Interfaces

Interfaces describe a narrow capability. Oversized interfaces containing unrelated responsibilities are split.

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

Define Interfaces Near Consumers

Interfaces should generally be defined where they are consumed rather than globally pre-designed as large inheritance-style abstractions.

Avoid
storage/storage.go: producer defines everything up front
package storage

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

type postgresStorage struct{}
Pureline
session/service.go: consumer defines what it needs
package session

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

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

Use `defer` for Clear Resource Lifecycles

The matching defer goes directly after the error guard of an opened resource.

Avoid
defer before the guard
file, err := os.Open(path)
defer file.Close()

if err != nil {
    return err
}
No 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 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.

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

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

return group.Wait()
PL-GO-009

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.

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 rules in the Go checker

In addition to the profile rules, the Go checker verifies these rules from the Pureline Core: