This document defines the coding conventions for projects written in Go (Golang).
These conventions apply to all Go code in this repository unless explicitly stated otherwise.
<anchor general_principles />
References:
When in doubt, idiomatic Go style takes precedence over personal preference.
Identifiers starting with an uppercase letter are exported. Identifiers starting with a lowercase letter are unexported.
Examples:
File naming examples:
Use camelCase for unexported identifiers.
var maxRetries = 3 func newUserRepo(db *sql.DB) *userRepo { return &userRepo{db: db} } type userRepo struct { db *sql.DB }
Use PascalCase for exported identifiers.
type UserService struct { Repo UserRepository } func NewUserService(repo UserRepository) *UserService { return &UserService{Repo: repo} } type UserRepository interface { FindByID(ctx context.Context, id string) (*User, error) }
Constants typically use PascalCase.
const ( DefaultTimeout = 30 * time.Second HTTPTimeout = 10 * time.Second MaxRetries = 3 )
Common acronyms should be all caps.
type HTTPServer struct{} type UserID string func NewJSONEncoder() JSONEncoder {} const HTTPTimeout = 10 * time.Second
; Types : Should be nouns or noun phrases : Examples: ``User``, ``Order``, ``UserService``
; Functions and methods : Should be verbs or verb phrases : Examples: ``CreateUser``, ``ValidateToken``
; Interfaces : Should, where appropriate, end with ``-er`` : Examples: ``Reader``, ``Writer``, ``UserRepository``
; Short-lived or small scopes : Used in loops or short blocks : Examples: ``i``, ``j``, ``n``, ``err``
; Longer-lived or wider scopes : Should use meaningful names : Examples: ``userID``, ``ctx``, ``cfg``
if condition { // ... }
Import grouping example:
import ( "context" "fmt" "net/http" "github.com/yourorg/yourproject/internal/user" )
. ├── cmd/ │ └── appname/ │ └── main.go ├── internal/ │ ├── domain/ │ ├── infrastructure/ │ ├── presentation/ │ ├── config/ │ ├── constant/ │ ├── validator/ ├── pkg/ ├── configs/ └── go.mod
Guidelines:
Options struct example:
type CreateUserOptions struct { Name string Email string Role string } func (s *UserService) CreateUser( ctx context.Context, opts CreateUserOptions, ) (*User, error) { // ... }
Prefer early returns:
if err != nil { return nil, err }
Always handle errors immediately.
user, err := s.repo.FindByID(ctx, id) if err != nil { return nil, err }
Use ``errors.Is`` and ``errors.As``:
if errors.Is(err, sql.ErrNoRows) { return nil, ErrUserNotFound }
Error message rules:
return fmt.Errorf("failed to save user: %w", err)
Every exported identifier must have a doc comment.
// UserService handles user-related operations. type UserService struct {}
Avoid comments that repeat code behavior.
Bad:
// Increase increases the counter by 1. func (c *Counter) Increase() { c.value++ }
Good:
// Increase increments the counter safely for concurrent use. func (c *Counter) Increase() {}
Centralize configuration loading and validation in ``internal/config``.
Rules:
func (s *UserService) GetByID( ctx context.Context, id string, ) (*User, error) { // ... }
Use ``log/slog`` for structured logging.
slog.InfoContext(ctx, "user created", "user_id", userID) slog.ErrorContext(ctx, "failed to create user", "error", err)
Batch jobs must implement:
Run(ctx context.Context, args []string) error
Healthcheck example:
type healthcheck struct{} func (h *healthcheck) Run(ctx context.Context, args []string) error { slog.InfoContext(ctx, "OK") return nil }
Dependency injection example:
func NewPointAddUsecase( repo IAddPointRepository, crm IYappliCRMClient, ) *PointAddUsecase { return &PointAddUsecase{ repo: repo, crm: crm, } }
gofmt -w ./...
Formats code and manages imports automatically.
go vet ./...
Configured via ``.golangci.yml``.
linters: enable: - govet - staticcheck - errcheck