Go Conventions¶
All Amadla tools and libraries follow idiomatic Go coding conventions.
Naming¶
Interfaces¶
Interfaces use descriptive concept names — no I prefix:
type Git interface {
Clone(url string) error
Pull() error
}
type File interface {
Read(path string) ([]byte, error)
Write(path string, data []byte) error
}
Structs¶
Implementation structs are unexported — no S prefix:
Constructors¶
Constructor functions follow the New() pattern and return the interface type. Use New() when there is one primary type per package:
This pattern enables dependency injection and mocking.
Mocks¶
Mocks are generated by mockery and follow the Mock* naming with mock_*.go files:
Dependency Injection¶
Package-Level Function Variables¶
OS and system calls are assigned to package-level variables so tests can replace them:
// Production code
var osOpen = os.Open
var execCommand = exec.Command
func readFile(path string) ([]byte, error) {
f, err := osOpen(path)
// ...
}
// Test code
func TestReadFile(t *testing.T) {
osOpen = func(name string) (*os.File, error) {
return nil, errors.New("mock error")
}
defer func() { osOpen = os.Open }()
// ...
}
Interface-Based Injection¶
Services accept interfaces in constructors, enabling mock injection:
type entity struct {
git Git
cache Cache
schema Schema
}
func New(git Git, cache Cache, schema Schema) Entity {
return &entity{git: git, cache: cache, schema: schema}
}
Error Handling¶
- Use typed errors in
message/packages - Wrap errors with context:
fmt.Errorf("failed to clone repo: %w", err) - Return errors up the call stack; handle at command level
Code Organization¶
- One interface + implementation per file (named after the interface)
- Tests alongside implementation:
git.go+git_test.go - Mocks generated in-package:
mock_*.go