con un clic
go-developer
Go development best practices and patterns
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Go development best practices and patterns
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
| name | go-developer |
| description | Go development best practices and patterns |
| license | MIT |
| compatibility | opencode |
| metadata | {"audience":"go-developers","workflow":"development"} |
I guide Go development with idiomatic patterns, enforcing clean architecture, testability, and conventional Go style.
Use this skill when:
go.mod) or editing .go filesAlways use the clean repository pattern for data access:
// domain/user.go - interface lives where it is consumed
type UserRepository interface {
FindByID(ctx context.Context, id int) (*User, error)
Save(ctx context.Context, u *User) error
}
// mysql/user_repository.go - concrete implementation
type userRepository struct {
db *sql.DB
}
func NewUserRepository(db *sql.DB) domain.UserRepository {
return &userRepository{db: db}
}
func (r *userRepository) FindByID(ctx context.Context, id int) (*domain.User, error) {
// ...
}
Organize all edge cases for a function under t.Run subtests inside the parent TestX function. Pair with a table-driven struct slice for clarity and scalability.
func TestFindByID(t *testing.T) {
tests := []struct {
name string
id int
want *User
wantErr bool
}{
{
name: "returns user when found",
id: 1,
want: &User{ID: 1, Name: "Alice"},
},
{
name: "returns error when not found",
id: 999,
wantErr: true,
},
{
name: "returns error on invalid id",
id: -1,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := repo.FindByID(context.Background(), tt.id)
if (err != nil) != tt.wantErr {
t.Fatalf("wantErr=%v, got err=%v", tt.wantErr, err)
}
if !tt.wantErr && !reflect.DeepEqual(got, tt.want) {
t.Errorf("want %+v, got %+v", tt.want, got)
}
})
}
}
Always return errors explicitly — never swallow them. Wrap errors with context using fmt.Errorf and the %w verb so callers can use errors.Is / errors.As.
// Good
func (r *userRepository) FindByID(ctx context.Context, id int) (*User, error) {
u, err := r.db.QueryRowContext(ctx, query, id).Scan(...)
if err != nil {
return nil, fmt.Errorf("userRepository.FindByID id=%d: %w", id, err)
}
return u, nil
}
// Bad - never do this
func (r *userRepository) FindByID(ctx context.Context, id int) *User {
u, _ := r.db.QueryRowContext(ctx, query, id).Scan(...)
return u
}
Pass context.Context as the first parameter to any function that performs I/O, network calls, database queries, or long-running work. Never store a context in a struct.
// Good
func (s *UserService) GetUser(ctx context.Context, id int) (*User, error) {
return s.repo.FindByID(ctx, id)
}
// Bad - context stored in struct
type UserService struct {
ctx context.Context // never do this
repo UserRepository
}
Define interfaces in the package that uses them, not in the package that implements them. This keeps implementations decoupled and avoids import cycles.
// service/user.go - consumer defines the interface it needs
package service
type userRepo interface {
FindByID(ctx context.Context, id int) (*domain.User, error)
}
type UserService struct {
repo userRepo
}
u *User, r *userRepositoryHTTPClient, URLParser, UserID, not HttpClient, UrlParser, UserIdNew — NewUserService, NewUserRepositoryErr — ErrNotFound, ErrInvalidIDtype HTTPClient struct { ... } // acronym all-caps
type userRepository struct { ... } // unexported concrete type
var ErrNotFound = errors.New("not found")
func (r *userRepository) save(...) {} // short receiver name
func NewUserRepository(...) UserRepository { ... }
Use interface-based mocks for testing. Prefer hand-written mocks for simple cases; use testify/mock for complex interaction verification.
// Hand-written mock for simple cases
type mockUserRepo struct {
findByIDFn func(ctx context.Context, id int) (*User, error)
}
func (m *mockUserRepo) FindByID(ctx context.Context, id int) (*User, error) {
return m.findByIDFn(ctx, id)
}
// Usage in test
repo := &mockUserRepo{
findByIDFn: func(ctx context.Context, id int) (*User, error) {
return &User{ID: id, Name: "Alice"}, nil
},
}
svc := NewUserService(repo)
Use a Config struct or option functions when a constructor takes more than 3 parameters. Avoid long positional argument lists.
// Config struct approach
type ServerConfig struct {
Host string
Port int
Timeout time.Duration
Logger *slog.Logger
}
func NewServer(cfg ServerConfig) *Server {
return &Server{cfg: cfg}
}
// Option function approach
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}
func NewServer(host string, port int, opts ...Option) *Server {
s := &Server{host: host, port: port}
for _, o := range opts {
o(s)
}
return s
}
Before submitting Go code, verify:
t.Run inside TestXtests []struct{...} slicefmt.Errorf("...: %w", err)_ on error returns)context.Context is first param on I/O functionsConfig struct or option funcs when > 3 params