This skill should be used when the user is working with Go code, Go modules, or files like ".go", "go.mod", "go.sum", "Makefile" in Go projects. Provides Go development conventions including error handling, testing, project structure, interfaces, concurrency, and naming.
| Layer | Technology |
|---|---|
| Language | Go (latest stable) |
| Formatting | gofmt / goimports |
| Linting | golangci-lint |
| Testing | testing (stdlib), testify (optional) |
| Integration Testing | testcontainers-go |
| HTTP | net/http (stdlib), chi, gin, or echo |
| Logging | slog (stdlib) or zerolog |
| Config | envconfig or environment variables |
project/
├── cmd/
│ └── server/
│ └── main.go # Entry point
├── internal/ # Private application code
│ ├── handler/ # HTTP handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ ├── model/ # Domain types
│ └── middleware/ # HTTP middleware
├── pkg/ # Public reusable packages (optional)
├── go.mod
├── go.sum
├── Makefile
└── README.md
internal/ is enforced by Go compiler — cannot be imported externallycmd/ for application entry points; each subdirectory is a binarypkg/ only for truly reusable packages intended for external importGo's (T, error) return pattern is the natural Result pattern:
func GetUser(id string) (*User, error) {
user, err := repo.FindByID(id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
_fmt.Errorf("operation context: %w", err)errors.Is(err, target) for sentinel errorserrors.As(err, &target) for typed errorspanic in library code — panic only for truly unrecoverable programmer errorstype NotFoundError struct {
Resource string
ID string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s %s not found", e.Resource, e.ID)
}
Use the testing package with table-driven tests:
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive", 1, 2, 3},
{"negative", -1, -2, -3},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, result, tt.expected)
}
})
}
}
*_test.go in same packageTestFuncName_Condition or TestFuncName with subtestst.Run() for subteststestify/assert or testify/require if present in project (don't add without reason)testcontainers-go for integration tests with real databasest.Helper() in test helper functionst.Parallel() for independent testsGetUser, UserService)getUserByID, parseConfig)HTTPClient, UserID, JSONAPI — not HttpClient, UserId)Reader, Writer, Storer — not IReader)type Closer interface { Close() error })user, http, config — not users, utils)func (s *Server) Start())// Defined by the consumer, not the implementor
type UserRepository interface {
FindByID(ctx context.Context, id string) (*model.User, error)
}
type UserService struct {
repo UserRepository // Accepts interface
}
context.Context as first parameter for cancellation and timeout propagationerrgroup.Group for parallel work with error collectioncontext.WithCancel)chan<-, <-chan) in function signaturessync.Mutex for shared state; keep critical sections smallinit()gofmt / goimports — non-negotiablegolangci-lint with project config (.golangci.yml)context.Context for request-scoped values and cancellationinit() overuse (hard to test, implicit ordering)panic in library code (return errors instead)_utils or helperscontext.Context for cancellationFor detailed patterns, consult:
references/patterns.md — HTTP handlers, middleware, database, logging, config, graceful shutdown