名称: golang 描述: 用于编写地道、生产质量Go代码的Go语言专业知识。适用于Go开发、并发模式、错误处理、测试和模块管理。触发词: go, golang, goroutine, channel, interface, struct, pointer, slice, map, defer, context, error, gin, echo, fiber, cobra, viper, gorm, sqlx, go mod, go test, effective go, errgroup, sync, mutex, waitgroup。
Go语言专业知识
概述
此技能提供编写地道、高效、生产质量Go代码的指导。它涵盖Go的并发模型、错误处理模式、测试实践和遵循Effective Go原则的模块系统。
关键概念
错误处理
import (
"errors"
"fmt"
)
// 定义哨兵错误
var (
ErrNotFound = errors.New("资源未找到")
ErrUnauthorized = errors.New("未授权访问")
)
// 带上下文的自定义错误类型
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("字段 %s 的验证错误: %s", e.Field, e.Message)
}
// 错误包装以添加上下文
func fetchUser(id string) (*User, error) {
user, err := db.GetUser(id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("用户 %s: %w", id, ErrNotFound)
}
return nil, fmt.Errorf("获取用户 %s: %w", id, err)
}
return user, nil
}
// 使用Is和As检查错误
func handleError(err error) {
if errors.Is(err, ErrNotFound) {
// 处理未找到情况
}
var validErr *ValidationError
if errors.As(err, &validErr) {
// 处理验证错误,可访问Field和Message
}
}
并发模式
// 工作池模式
func workerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
wg.Wait()
close(results)
}
// 用于取消和超时的上下文
func fetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// 多通道选择
func multiplex(ctx context.Context, ch1, ch2 <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for {
select {
case v, ok := <-ch1:
if !ok {
ch1 = nil
continue
}
out <- v
case v, ok := <-ch2:
if !ok {
ch2 = nil
continue
}
out <- v
case <-ctx.Done():
return
}
if ch1 == nil && ch2 == nil {
return
}
}
}()
return out
}
// 互斥锁用于共享状态
type SafeCounter struct {
mu sync.RWMutex
count map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.count[key]++
}
func (c *SafeCounter) Get(key string) int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.count[key]
}
接口和嵌入
// 小而专注的接口
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
// 接受接口,返回结构体
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
Create(ctx context.Context, user *User) error
}
type userService struct {
repo UserRepository
cache Cache
logger *slog.Logger
}
func NewUserService(repo UserRepository, cache Cache, logger *slog.Logger) *userService {
return &userService{
repo: repo,
cache: cache,
logger: logger,
}
}
// 嵌入用于组合
type Base struct {
ID string
CreatedAt time.Time
UpdatedAt time.Time
}
type User struct {
Base
Email string
Name string
}
函数选项模式
type Server struct {
host string
port int
timeout time.Duration
logger *slog.Logger
}
type Option func(*Server)
func WithHost(host string) Option {
return func(s *Server) {
s.host = host
}
}
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(d time.Duration) Option {
return func(s *Server) {
s.timeout = d
}
}
func WithLogger(logger *slog.Logger) Option {
return func(s *Server) {
s.logger = logger
}
}
func NewServer(opts ...Option) *Server {
s := &Server{
host: "localhost",
port: 8080,
timeout: 30 * time.Second,
logger: slog.Default(),
}
for _, opt := range opts {
opt(s)
}
return s
}
// 使用示例
server := NewServer(
WithHost("0.0.0.0"),
WithPort(9000),
WithTimeout(60*time.Second),
)
使用Cobra的CLI应用程序
// cmd/root.go
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
cfgFile string
verbose bool
)
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "您的应用程序的简短描述",
Long: `一个跨越多行的较长描述,可能包含使用您应用程序的示例和用法。`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "配置文件(默认为 $HOME/.myapp.yaml)")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "详细输出")
viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
cobra.CheckErr(err)
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".myapp")
}
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "使用配置文件:", viper.ConfigFileUsed())
}
}
// cmd/serve.go
var serveCmd = &cobra.Command{
Use: "serve",
Short: "启动HTTP服务器",
RunE: func(cmd *cobra.Command, args []string) error {
port := viper.GetInt("port")
return startServer(cmd.Context(), port)
},
}
func init() {
rootCmd.AddCommand(serveCmd)
serveCmd.Flags().IntP("port", "p", 8080, "监听的端口")
viper.BindPFlag("port", serveCmd.Flags().Lookup("port"))
}
HTTP服务器
// 标准库HTTP服务器
package server
import (
"context"
"errors"
"log/slog"
"net/http"
"time"
)
type Server struct {
httpServer *http.Server
logger *slog.Logger
}
func New(addr string, handler http.Handler, logger *slog.Logger) *Server {
return &Server{
httpServer: &http.Server{
Addr: addr,
Handler: handler,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
},
logger: logger,
}
}
func (s *Server) Start() error {
s.logger.Info("启动服务器", slog.String("addr", s.httpServer.Addr))
if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
}
func (s *Server) Shutdown(ctx context.Context) error {
s.logger.Info("关闭服务器")
return s.httpServer.Shutdown(ctx)
}
// 中间件模式
func loggingMiddleware(logger *slog.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
logger.Info("请求",
slog.String("方法", r.Method),
slog.String("路径", r.URL.Path),
slog.Duration("耗时", time.Since(start)),
)
})
}
}
func recoveryMiddleware(logger *slog.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
logger.Error("恐慌恢复",
slog.Any("错误", err),
slog.String("路径", r.URL.Path),
)
http.Error(w, "内部服务器错误", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
}
// Go 1.22+ 模式的路由器设置
func setupRoutes(mux *http.ServeMux, handler *Handler) {
mux.HandleFunc("GET /api/users/{id}", handler.GetUser)
mux.HandleFunc("POST /api/users", handler.CreateUser)
mux.HandleFunc("PUT /api/users/{id}", handler.UpdateUser)
mux.HandleFunc("DELETE /api/users/{id}", handler.DeleteUser)
mux.HandleFunc("GET /health", handler.Health)
}
// Gin框架示例
import "github.com/gin-gonic/gin"
func setupGinRouter(handler *Handler) *gin.Engine {
r := gin.New()
r.Use(gin.Recovery())
r.Use(gin.Logger())
api := r.Group("/api")
{
users := api.Group("/users")
{
users.GET("/:id", handler.GetUser)
users.POST("/", handler.CreateUser)
users.PUT("/:id", handler.UpdateUser)
users.DELETE("/:id", handler.DeleteUser)
}
}
r.GET("/health", handler.Health)
return r
}
数据库访问模式
// 使用sqlx进行更简洁的数据库操作
import (
"context"
"database/sql"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type User struct {
ID string `db:"id"`
Email string `db:"email"`
Name string `db:"name"`
CreatedAt time.Time `db:"created_at"`
}
type UserRepository struct {
db *sqlx.DB
}
func NewUserRepository(db *sqlx.DB) *UserRepository {
return &UserRepository{db: db}
}
func (r *UserRepository) GetByID(ctx context.Context, id string) (*User, error) {
var user User
query := `SELECT id, email, name, created_at FROM users WHERE id = $1`
if err := r.db.GetContext(ctx, &user, query, id); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return nil, fmt.Errorf("获取用户: %w", err)
}
return &user, nil
}
func (r *UserRepository) Create(ctx context.Context, user *User) error {
query := `
INSERT INTO users (id, email, name, created_at)
VALUES (:id, :email, :name, :created_at)
`
_, err := r.db.NamedExecContext(ctx, query, user)
if err != nil {
return fmt.Errorf("创建用户: %w", err)
}
return nil
}
func (r *UserRepository) List(ctx context.Context, limit, offset int) ([]*User, error) {
var users []*User
query := `
SELECT id, email, name, created_at
FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
if err := r.db.SelectContext(ctx, &users, query, limit, offset); err != nil {
return nil, fmt.Errorf("列出用户: %w", err)
}
return users, nil
}
// 事务助手
func (r *UserRepository) WithTx(ctx context.Context, fn func(*sqlx.Tx) error) error {
tx, err := r.db.BeginTxx(ctx, nil)
if err != nil {
return fmt.Errorf("开始事务: %w", err)
}
if err := fn(tx); err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
return fmt.Errorf("回滚事务: %v (原始错误: %w)", rbErr, err)
}
return err
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("提交事务: %w", err)
}
return nil
}
最佳实践
代码组织
myproject/
├── cmd/
│ └── myapp/
│ └── main.go
├── internal/
│ ├── service/
│ ├── repository/
│ └── handler/
├── pkg/
│ └── utils/
├── go.mod
└── go.sum
模块管理
// go.mod
module github.com/user/myproject
go 1.22
require (
github.com/lib/pq v1.10.9
golang.org/x/sync v0.5.0
)
使用slog的结构化日志
import "log/slog"
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
logger.Info("请求处理",
slog.String("方法", r.Method),
slog.String("路径", r.URL.Path),
slog.Duration("延迟", time.Since(start)),
)
// 向日志添加上下文
logger = logger.With(slog.String("request_id", requestID))
常见模式
表驱动测试
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"正数", 2, 3, 5},
{"负数", -1, -2, -3},
{"零", 0, 0, 0},
{"混合", -1, 5, 4},
}
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; 期望 %d", tt.a, tt.b, result, tt.expected)
}
})
}
}
// 并行子测试
func TestFetch(t *testing.T) {
tests := []struct {
name string
url string
}{
{"google", "https://google.com"},
{"github", "https://github.com"},
}
for _, tt := range tests {
tt := tt // 捕获范围变量
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// 测试实现
})
}
}
基准测试
func BenchmarkFibonacci(b *testing.B) {
for i := 0; i < b.N; i++ {
Fibonacci(20)
}
}
func BenchmarkFibonacciParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Fibonacci(20)
}
})
}
// 带子基准测试
func BenchmarkSort(b *testing.B) {
sizes := []int{100, 1000, 10000}
for _, size := range sizes {
b.Run(fmt.Sprintf("大小-%d", size), func(b *testing.B) {
data := generateData(size)
b.ResetTimer()
for i := 0; i < b.N; i++ {
Sort(data)
}
})
}
}
HTTP处理器
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := r.PathValue("id") // Go 1.22+
user, err := h.service.GetUser(ctx, id)
if err != nil {
if errors.Is(err, ErrNotFound) {
http.Error(w, "用户未找到", http.StatusNotFound)
return
}
h.logger.Error("获取用户失败", slog.String("错误", err.Error()))
http.Error(w, "内部错误", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(user); err != nil {
h.logger.Error("编码响应失败", slog.String("错误", err.Error()))
}
}
反模式
避免这些做法
// 错误:忽略错误
result, _ := doSomething()
// 正确:始终处理错误
result, err := doSomething()
if err != nil {
return fmt.Errorf("做某事: %w", err)
}
// 错误:Goroutine泄漏
func fetch(urls []string) []Result {
results := make(chan Result)
for _, url := range urls {
go func(u string) {
results <- fetchURL(u) // 如果没有人读取,永远阻塞
}(url)
}
return collectResults(results)
}
// 正确:使用上下文和适当清理
func fetch(ctx context.Context, urls []string) ([]Result, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([]Result, len(urls))
for i, url := range urls {
i, url := i, url
g.Go(func() error {
r, err := fetchURL(ctx, url)
if err != nil {
return err
}
results[i] = r
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
// 错误:返回接口
func NewService() ServiceInterface {
return &service{}
}
// 正确:返回具体类型
func NewService() *Service {
return &Service{}
}
// 错误:大接口
type Repository interface {
GetUser(id string) (*User, error)
CreateUser(user *User) error
UpdateUser(user *User) error
DeleteUser(id string) error
ListUsers() ([]*User, error)
GetOrder(id string) (*Order, error)
// ... 更多20个方法
}
// 正确:小而专注的接口
type UserGetter interface {
GetUser(ctx context.Context, id string) (*User, error)
}
// 错误:长函数中的裸返回
func process(data []byte) (result string, err error) {
// 50行代码
result = string(data)
return // 返回什么?
}
// 正确:显式返回
func process(data []byte) (string, error) {
// 处理逻辑
return string(data), nil
}
// 错误:使用init()进行复杂初始化
func init() {
db = connectToDatabase()
cache = initCache()
}
// 正确:在main中显式初始化
func main() {
db, err := connectToDatabase()
if err != nil {
log.Fatal(err)
}
defer db.Close()
cache := initCache()
// ...
}