The following is a list of coding guidelines and recommandations for go (golang) development. They have been compiled from multiple sources. See credits at the end of the document.
-
Packages should contain code that has a single purpose: cmd, archive, crypto. Avoid catchall packages like util, helpers, common. Catchall packages often cause problems with testing and are a common cause of circular dependencies
-
Different implementations for a common set of functionality should be grouped under a single parent. See the contents of the package encoding
-
Package names should be short but descriptive. Take advantage of the parent package name and don't repeat in children. encoding/base64. Prefer "transport" over "transportmechanism"
-
When building an application, the executable should be separate from the other packages
-
Application packages should center around Domain Types and Services
-
The package containing domain types should also defined the interfaces that define what you can do with the domain types: ProductService, AuthenticationService, UserStorage
-
The domain types package should be the root of the application. This makes it clear to everyone what the application does. It should not contain any external dependecies.
-
The implementation of the interfaces should be in separate packages organized by dependency
-
There should be a single package per dependency: data source, transport protocol, etc. This makes it easier to test/mock, substitute and prevents circular dependencies
-
Inside a package separate code into logical concerns. If the package deals with multiple types, keep the lgoic for each type in its own source file:
package: postgres
orders.go
products.go
customers.go
- In the package that defines your domain objects, define the types and interfaces for each object in the same source file:
package: inventory
orders.go
-- contains Orders type
and OrdersStorage interface
-
When naming variables use camelCase and not snake_case
-
Use single letter variables for indices
for i:=0; i < 100; i++ {}
- Use short but descriptive variable names for everythign else
var count int
var cust Customer
-
Take scope of the variable into consideration. The farther away from declaration it is used, the longer the name should be
-
Use repeated letters to represent a collection/slice/array outside of a loop/range and a single letter inside:
var tt []*Thing
for i, t:= range tt {}
These conventions are common inside of Go's own source code
- Avoid package-level functions that repeat the package name:
GOOD: log.Info()
BAD: log.LogInfo()
- Go doesn't have setters and getters:
GOOD: custSvc.Customer()
BAD: custSvc.GetCustomer()
- If the interface has only a single function, append "-er" to the function name:
type Stringer interface{
String() string
}
- If the interface has more than one function, use a name to represent its functionality:
type CustomerStorage interface {
Customer(id int) (*Customer, error)
Save(c *Customer) error
Delete(id int) error
}
- Make comments in full sentences always:
// An order represents an order from a customer
type Order struct {}
-
Use
goimports
to manage import and they will always appear in canonical order. Standard libs first, external next. -
Avoid
else
clause. Especially in error handling
if err != nil {
//error handling
return // or continue, etc.
}
// normal code
-
Avoid broad interfaces. Functions should only accept interfaces that require the methods they need. Functions should not accept broad interfaces when a narror one would work.
-
Compose broad interfaces made from narrow ones:
type File interface {
io.Closer
io.Reader
io.ReaderAt
io.Seeker
io.Writer
io.WriterAt
}
-
Do not overuse methods. Methods should only be use if they modify the object/struct state that they belong to. They define the behavior of a type. They use state and are logically connected. They are bound to a specific type.
-
Functions should be used when they don't depend on state: Same inputs always returning same outputs. They can be used with interfaces.
-
When deciding between values and pointers consider if shared access is needed (not performance). Pointers should be used when the value is to be shared with a method or function. Value (copy) should be used when we don't want to share it.
-
When sharing a value with it's method, use a pointer receiver. This is a common usage when managing state. But it is not safe for concurrent access.
-
Use values when not sharing or using with an empty struct that is stateless with only behavior. Values are safe for concurrent access.
-
Don't forget to treat errors as interfaces and not just strings. You can create custom errors to provide additional context.
-
Data structures are not safe for concurrent access. Values aren't safe. You need to create safe behavior around them.
- Sync package provides behavior to make a value safe (Atomic/Mutex)
- Channels coordinate values across go routines by permitting one go routine to access at a time
- Safety comes at a cost. It imposes behavior on the consumer. Safety is often unnecessary.
- Proper API allows consumers to add safety as needed (using channels and mutexes)
- Maps are unsafe by design. They enable consumers to implement safety as needed.
- Review Go StdLib for examples of good Go conventions.
- Brian Ketelsen @bketelsen
- Ben Johnson (https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1)
- Steve Francia @spf13