Table of Contents
GOLANG CODING CONVENTION
This document defines the coding conventions for projects written in Go (Golang).
Goals
- Improve readability and maintainability of the codebase
- Ensure a consistent coding style across the team
- Reduce friction in code reviews and onboarding
- Leverage Go’s ecosystem and tools effectively
These conventions apply to all Go code in this repository unless explicitly stated otherwise.
Table of Contents
1. General Principles
<anchor general_principles />
- Prefer clarity over cleverness
- Keep functions, types, and packages small and focused
- Follow idiomatic Go practices
References:
- Go standard library (fmt, net/http, context, time, strings, errors, io, etc.)
When in doubt, idiomatic Go style takes precedence over personal preference.
2. Naming Conventions
Identifiers starting with an uppercase letter are exported. Identifiers starting with a lowercase letter are unexported.
2.1 Package Names
- Use lowercase names
- Do not use CamelCase
- Avoid underscores unless absolutely necessary
Examples:
- ``package user``
- ``package auth``
- ``package config``
File naming examples:
- ``user_service.go``
- ``user_repository.go``
- ``http_handler.go``
2.2 Variables, Functions, and Methods
Use camelCase for unexported identifiers.
var maxRetries = 3 func newUserRepo(db *sql.DB) *userRepo { return &userRepo{db: db} } type userRepo struct { db *sql.DB }
2.3 Structs, Interfaces, and Exported Symbols
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) }
2.4 Constants and Acronyms
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
2.5 General Naming Guidelines
; 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``
3. Formatting
- Always format code using ``gofmt`` or ``goimports``
- Tabs are used for indentation
- Opening braces must be on the same line
if condition { // ... }
Import grouping example:
import ( "context" "fmt" "net/http" "github.com/yourorg/yourproject/internal/user" )
4. Project Structure
. ├── cmd/ │ └── appname/ │ └── main.go ├── internal/ │ ├── domain/ │ ├── infrastructure/ │ ├── presentation/ │ ├── config/ │ ├── constant/ │ ├── validator/ ├── pkg/ ├── configs/ └── go.mod
Guidelines:
- ``cmd``: application entry points and wiring
- ``internal``: non-public application code
- ``pkg``: reusable libraries (if needed)
5. Functions and Methods
- Functions should have a single responsibility
- Avoid very long functions (over ~100 lines)
- Prefer no more than 3 parameters
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 }
6. Error Handling
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:
- Start with a lowercase letter
- Do not end with a period
- Be short and descriptive
return fmt.Errorf("failed to save user: %w", err)
7. Comments and Documentation
7.1 Doc Comments
Every exported identifier must have a doc comment.
// UserService handles user-related operations. type UserService struct {}
7.2 Comment the Why
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() {}
8. Configuration, Context and Logging
8.1 Configuration
Centralize configuration loading and validation in ``internal/config``.
8.2 context.Context
Rules:
- Must be the first parameter
- Must not be stored in struct fields
- Must never be nil
func (s *UserService) GetByID( ctx context.Context, id string, ) (*User, error) { // ... }
8.3 Logging
Use ``log/slog`` for structured logging.
slog.InfoContext(ctx, "user created", "user_id", userID) slog.ErrorContext(ctx, "failed to create user", "error", err)
9. Batch Coding
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 }
10. Testing
- Use the ``testing`` package
- Prefer table-driven tests
- Use ``testify/assert`` for assertions
- Use ``gomock`` for mocking
- Suppress logs in tests
Dependency injection example:
func NewPointAddUsecase( repo IAddPointRepository, crm IYappliCRMClient, ) *PointAddUsecase { return &PointAddUsecase{ repo: repo, crm: crm, } }
11. Tools and Static Analysis
11.1 gofmt
gofmt -w ./...
11.2 goimports
Formats code and manages imports automatically.
11.3 go vet
go vet ./...
11.4 golangci-lint
Configured via ``.golangci.yml``.
linters: enable: - govet - staticcheck - errcheck
