| name | writing-go-code |
| description | Apply Go coding standards when writing or modifying Go code. Use when implementing functions, using dependency injection, handling errors idiomatically, or working with interfaces. For test conventions, use the `writing-go-tests` skill instead. |
Go Development Standards
Project-specific Go coding standards for this codebase.
Companion Skills
applying-effective-go — General Go idioms from the official Effective Go documentation (naming, control flow, error handling philosophy, concurrency patterns). Complementary to this skill.
writing-go-tests — Test conventions, mock usage, assertions, naming. Always load when writing test files.
Code Organization
type MyService struct {
logger Logger
fs FileSystem
}
var _ Service = (*MyService)(nil)
func NewMyService(logger Logger, fs FileSystem) *MyService {
return &MyService{logger: logger, fs: fs}
}
Dependency Injection
Always inject dependencies via constructors. Never create dependencies internally.
func NewHandler(
logger Logger,
service Service,
validator Validator,
) *Handler {
return &Handler{
logger: logger,
service: service,
validator: validator,
}
}
func NewHandler() *Handler {
return &Handler{
logger: NewDefaultLogger(),
service: NewService(),
}
}
Testability
Every external dependency (OS calls, file I/O, command execution, environment access, network, time) must be behind an interface so unit tests can inject mocks. This is non-negotiable — code that calls os.*, exec.*, or similar directly in business logic is untestable.
The pattern:
- Define an interface describing the capability
- Create a
Default* concrete implementation that wraps the real calls
- Constructor returns the concrete type (not the interface)
- Business logic accepts the interface, never the concrete type
- The composition root (e.g.,
cmd/add.go) wires concrete types to interfaces
type FileSystem interface {
ReadFileContents(path string) ([]byte, error)
PathExists(path string) (bool, error)
}
type DefaultFileSystem struct{}
func NewDefaultFileSystem() *DefaultFileSystem {
return &DefaultFileSystem{}
}
func (fs *DefaultFileSystem) ReadFileContents(path string) ([]byte, error) {
return os.ReadFile(path)
}
func LoadConfig(fs FileSystem, path string) (Config, error) {
data, err := fs.ReadFileContents(path)
}
func runAdd(cmd *cobra.Command, args []string) error {
fs := utils.NewDefaultFileSystem()
cfg, err := config.LoadConfig(fs, configPath)
}
What must be behind an interface:
- File I/O (
os.ReadFile, os.WriteFile, os.MkdirAll, etc.) → FileSystem
- Command execution (
os/exec) → Commander
- Environment variables (
os.Getenv) → EnvironmentManager
- Time, network, or any other non-deterministic dependency
What does NOT need an interface:
- Pure functions (string manipulation, data transformation)
- Standard library types used as values (
time.Duration, filepath.Join)
- CLI framework wiring (Cobra commands, flag parsing)
Mock Generation
Mocks use mockery with moq template. To regenerate all mocks:
mockery
Mock types are prefixed with Moq (e.g., MoqLogger, MoqFileSystem). For mock usage conventions in tests, see the writing-go-tests skill.
Optional Types
Use samber/mo for safer nil handling:
import "github.com/samber/mo"
type Config struct {
Shell mo.Option[string]
}
if shell, ok := config.Shell.Get(); ok {
}
Code Formatting
- Line length: 120 characters max.
- Vertically align function arguments when there are multiple arguments.
- Insert blank lines between logical sections of code.
- Do not separate error unwrapping from related code with a blank line; treat it as part of the same section.
result, err := doSomething()
if err != nil {
return fmt.Errorf("failed to do something: %w", err)
}
processResult(result)
Documentation
End all type and function comments with a period, following Go conventions.
type MyService struct {
}
func (s *MyService) Process(ctx context.Context) error {
}
Key Rules
- Use the Go standard library whenever possible. Only use third-party libraries when necessary.
- Pre-allocate slices/maps when size is known.
- All external dependencies must be behind interfaces — see the Testability section above for the full pattern.
- All non-CLI codepaths must be unit-testable via mock injection. If a function can't be tested without hitting the real OS, it needs refactoring.
- Never edit mock files manually.