lolly/docs/superpowers/plans/2026-06-03-eliminate-code-redundancy.md
xfy ebeb258c58 docs(benchmark): add v0.4.0 baseline summary and update gitignore
- Collect baseline benchmark summary across all core modules
- Save key results to benchmarks/v0.4.0/summary.txt
- Update .gitignore to track benchmark summaries/reports
- Include performance optimization design docs and plan
2026-06-11 13:43:28 +08:00

23 KiB
Raw Permalink Blame History

消除代码冗余实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 消除 lolly 项目中的代码冗余:删除 8 处死代码、重构 2 处源文件重复模式、提取测试辅助函数减少 184 处配置字面量重复。

Architecture: 分三阶段实施:阶段 1 删除未使用的死代码(零风险);阶段 2 提取路由注册和 DEBUG 日志辅助函数(低风险重构);阶段 3 创建测试辅助函数包并迁移重复代码(逐步替换)。

Tech Stack: Go 1.22+, golangci-lint, dupl/unused linters


文件结构

创建:

  • internal/testutil/proxy.go - 测试辅助函数ProxyConfig、Target 创建)

修改:

  • internal/config/validate.go - 删除 validateStatic() 函数
  • internal/config/validate_test.go - 删除 TestValidateStatic 测试
  • internal/http2/server.go - 删除 connectionPool.get()connectionPool.count()
  • internal/middleware/bodylimit/bodylimit.go - 删除 formatSize() 函数
  • internal/middleware/bodylimit/bodylimit_test.go - 删除 TestFormatSize 测试
  • internal/middleware/security/headers.go - 删除 3 个 security headers 函数
  • internal/middleware/security/headers_test.go - 删除 3 个对应测试
  • internal/ssl/ocsp.go - 删除 extractCertificates() 函数
  • internal/ssl/ocsp_test.go - 删除 2 个对应测试
  • internal/server/router.go - 提取 registerRoute 辅助函数
  • internal/proxy/proxy.go - 提取 proxyDebugLog 辅助函数

阶段 1死代码删除

Task 1: 删除 validateStatic 函数及其测试

Files:

  • Modify: internal/config/validate.go:475-484

  • Modify: internal/config/validate_test.go:752-809

  • Step 1: 删除 validateStatic 函数

删除 internal/config/validate.go 第 475-484 行:

// validateStatic 验证静态文件配置。
//
// 参数:
//   - s: 静态文件配置对象
//
// 返回值:
//   - error: 验证失败时返回错误信息,成功返回 nil
func validateStatic(s *StaticConfig) error {
	// 静态文件根目录非空时验证路径有效性
	if s.Root != "" {
		// 路径安全检查:不允许包含 ".."
		if err := ValidatePathTraversal(s.Root, "根目录路径"); err != nil {
			return err
		}
	}
	return nil
}
  • Step 2: 删除对应的单元测试

删除 internal/config/validate_test.go 第 752-809 行的 TestValidateStatic 函数:

func TestValidateStatic(t *testing.T) {
	t.Parallel()
	// TestValidateStatic 测试静态文件配置验证。
	tests := []struct {
		name    string
		errMsg  string
		config  StaticConfig
		wantErr bool
	}{
		{
			name:    "空配置有效",
			config:  StaticConfig{},
			wantErr: false,
		},
		{
			name: "有效根目录",
			config: StaticConfig{
				Root: "/var/www/html",
			},
			wantErr: false,
		},
		{
			name: "根目录含..路径遍历",
			config: StaticConfig{
				Root: "/var/www/../etc",
			},
			wantErr: true,
			errMsg:  "根目录路径不能包含 '..'",
		},
		{
			name: "根目录含多个..",
			config: StaticConfig{
				Root: "/var/../www/../html",
			},
			wantErr: true,
			errMsg:  "根目录路径不能包含 '..'",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateStatic(&tt.config)
			if tt.wantErr {
				if err == nil {
					t.Errorf("validateStatic() 期望返回错误,但返回 nil")
					return
				}
				if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
					t.Errorf("validateStatic() 错误消息不匹配,期望包含 %q实际 %q", tt.errMsg, err.Error())
				}
			} else {
				if err != nil {
					t.Errorf("validateStatic() 期望返回 nil但返回错误: %v", err)
				}
			}
		})
	}
}
  • Step 3: 运行测试确认通过

Run: go test ./internal/config/... -run TestValidateStatic -v Expected: 无此测试(因为已删除)

Run: go test ./internal/config/... -v Expected: PASS

  • Step 4: Commit
git add internal/config/validate.go internal/config/validate_test.go
git commit -m "refactor: remove unused validateStatic function and its test"

Task 2: 删除 connectionPool 未使用的方法

Files:

  • Modify: internal/http2/server.go:575-587

  • Step 1: 删除 getcount 方法

删除 internal/http2/server.go 第 575-587 行:

// get 获取连接。
func (p *connectionPool) get(key string) []net.Conn {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.conns[key]
}

// count 获取连接数。
func (p *connectionPool) count(key string) int {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return len(p.conns[key])
}
  • Step 2: 运行测试确认通过

Run: go test ./internal/http2/... -v Expected: PASS

  • Step 3: Commit
git add internal/http2/server.go
git commit -m "refactor: remove unused connectionPool.get and connectionPool.count methods"

Task 3: 删除 bodylimit.formatSize 函数及其测试

Files:

  • Modify: internal/middleware/bodylimit/bodylimit.go:279-305

  • Modify: internal/middleware/bodylimit/bodylimit_test.go:36-72

  • Step 1: 删除 formatSize 函数

删除 internal/middleware/bodylimit/bodylimit.go 第 279-305 行:

// formatSize 将字节数格式化为人类可读的字符串。
//
// 根据大小自动选择合适的单位b、kb、mb、gb
//
// 参数:
//   - size: 字节数
//
// 返回值:
//   - string: 格式化后的字符串,如 "1.00mb"、"10.00kb"
func formatSize(size int64) string {
	const (
		KB = 1024
		MB = 1024 * KB
		GB = 1024 * MB
	)

	switch {
	case size >= GB:
		return fmt.Sprintf("%.2fgb", float64(size)/GB)
	case size >= MB:
		return fmt.Sprintf("%.2fmb", float64(size)/MB)
	case size >= KB:
		return fmt.Sprintf("%.2fkb", float64(size)/KB)
	default:
		return fmt.Sprintf("%db", size)
	}
}
  • Step 2: 删除对应的单元测试

删除 internal/middleware/bodylimit/bodylimit_test.go 第 36-72 行的 TestFormatSize 函数:

func TestFormatSize(t *testing.T) {
	tests := []struct {
		input    int64
		expected string
	}{
		{512, "512b"},
		{1024, "1.00kb"},
		{1024 * 1024, "1.00mb"},
		{1024 * 1024 * 1024, "1.00gb"},
		{1536, "1.50kb"},
	}

	for _, tt := range tests {
		t.Run(formatSize(tt.input), func(t *testing.T) {
			got := formatSize(tt.input)
			if got != tt.expected {
				t.Errorf("formatSize(%d) = %s, want %s", tt.input, got, tt.expected)
			}
		})
	}
}
  • Step 3: 运行测试确认通过

Run: go test ./internal/middleware/bodylimit/... -v Expected: PASS

  • Step 4: Commit
git add internal/middleware/bodylimit/bodylimit.go internal/middleware/bodylimit/bodylimit_test.go
git commit -m "refactor: remove unused bodylimit.formatSize function and test"

Task 4: 删除 security headers 未使用的函数及其测试

Files:

  • Modify: internal/middleware/security/headers.go:291-331

  • Modify: internal/middleware/security/headers_test.go:184-215

  • Step 1: 删除 3 个 security headers 函数

删除 internal/middleware/security/headers.go 第 291-331 行:

// defaultSecurityHeaders 返回安全的安全头默认配置。
//
// 返回值:
//   - *config.SecurityHeaders: 包含安全默认值的配置对象
func defaultSecurityHeaders() *config.SecurityHeaders {
	return &config.SecurityHeaders{
		XFrameOptions:       "DENY",
		XContentTypeOptions: "nosniff",
		ReferrerPolicy:      "strict-origin-when-cross-origin",
	}
}

// strictSecurityHeaders 返回严格模式的安全头配置。
//
// 适用于高安全要求的应用场景,包含严格的 CSP 和权限策略。
//
// 返回值:
//   - *config.SecurityHeaders: 包含严格安全值的配置对象
func strictSecurityHeaders() *config.SecurityHeaders {
	return &config.SecurityHeaders{
		XFrameOptions:         "DENY",
		XContentTypeOptions:   "nosniff",
		ContentSecurityPolicy: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; frame-ancestors 'none'",
		ReferrerPolicy:        "no-referrer",
		PermissionsPolicy:     "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()",
	}
}

// developmentSecurityHeaders 返回开发环境使用的宽松安全头配置。
//
// 警告:请勿在生产环境使用此配置,安全性较低。
//
// 返回值:
//   - *config.SecurityHeaders: 包含宽松安全值的配置对象
func developmentSecurityHeaders() *config.SecurityHeaders {
	return &config.SecurityHeaders{
		XFrameOptions:       "SAMEORIGIN",
		XContentTypeOptions: "nosniff",
		ReferrerPolicy:      "strict-origin-when-cross-origin",
	}
}
  • Step 2: 删除对应的单元测试

删除 internal/middleware/security/headers_test.go 第 184-215 行:

func TestDefaultSecurityHeaders(t *testing.T) {
	cfg := defaultSecurityHeaders()

	if cfg.XFrameOptions != "DENY" {
		t.Errorf("Expected default X-Frame-Options 'DENY', got %s", cfg.XFrameOptions)
	}
	if cfg.XContentTypeOptions != "nosniff" {
		t.Errorf("Expected default X-Content-Type-Options 'nosniff', got %s", cfg.XContentTypeOptions)
	}
}

func TestStrictSecurityHeaders(t *testing.T) {
	cfg := strictSecurityHeaders()

	if cfg.XFrameOptions != "DENY" {
		t.Errorf("Expected X-Frame-Options 'DENY', got %s", cfg.XFrameOptions)
	}
	if cfg.ReferrerPolicy != "no-referrer" {
		t.Errorf("Expected Referrer-Policy 'no-referrer', got %s", cfg.ReferrerPolicy)
	}
	if cfg.ContentSecurityPolicy == "" {
		t.Error("Expected non-empty CSP for strict config")
	}
}

func TestDevelopmentSecurityHeaders(t *testing.T) {
	cfg := developmentSecurityHeaders()

	if cfg.XFrameOptions != "SAMEORIGIN" {
		t.Errorf("Expected X-Frame-Options 'SAMEORIGIN' for dev, got %s", cfg.XFrameOptions)
	}
}
  • Step 3: 运行测试确认通过

Run: go test ./internal/middleware/security/... -v Expected: PASS

  • Step 4: Commit
git add internal/middleware/security/headers.go internal/middleware/security/headers_test.go
git commit -m "refactor: remove unused security header preset functions and tests"

Task 5: 删除 extractCertificates 函数及其测试

Files:

  • Modify: internal/ssl/ocsp.go:482-514

  • Modify: internal/ssl/ocsp_test.go:311-335

  • Step 1: 删除 extractCertificates 函数

删除 internal/ssl/ocsp.go 第 482-514 行:

// extractCertificates 解析 PEM 数据并返回证书列表。
//
// 参数:
//   - pemData: PEM 编码的证书数据
//
// 返回值:
//   - []*x509.Certificate: 解析后的证书列表
//   - error: 解析失败时返回错误
func extractCertificates(pemData []byte) ([]*x509.Certificate, error) {
	var certs []*x509.Certificate
	rest := pemData

	for {
		block, remaining := pem.Decode(rest)
		if block == nil {
			break
		}
		if block.Type == "CERTIFICATE" {
			cert, err := x509.ParseCertificate(block.Bytes)
			if err != nil {
				return nil, fmt.Errorf("failed to parse certificate: %w", err)
			}
			certs = append(certs, cert)
		}
		rest = remaining
	}

	if len(certs) == 0 {
		return nil, errors.New("no certificates found in PEM data")
	}

	return certs, nil
}
  • Step 2: 删除对应的单元测试

删除 internal/ssl/ocsp_test.go 第 311-335 行:

func TestExtractCertificates(t *testing.T) {
	// Create valid PEM data
	certPEM, _ := generateTestCertWithOCSP(t, nil)

	certs, err := extractCertificates(certPEM)
	if err != nil {
		t.Fatalf("extractCertificates() failed: %v", err)
	}

	if len(certs) == 0 {
		t.Error("Expected at least one certificate")
	}
}

func TestExtractCertificatesInvalidPEM(t *testing.T) {
	invalidPEM := []byte("not valid pem data")

	certs, err := extractCertificates(invalidPEM)
	if err == nil {
		t.Error("Expected error for invalid PEM data")
	}
	if certs != nil {
		t.Error("Expected nil certs for invalid PEM data")
	}
}
  • Step 3: 运行测试确认通过

Run: go test ./internal/ssl/... -v Expected: PASS

  • Step 4: Commit
git add internal/ssl/ocsp.go internal/ssl/ocsp_test.go
git commit -m "refactor: remove unused extractCertificates function and tests"

阶段 2源文件重复模式重构

Task 6: 提取路由注册辅助函数

Files:

  • Modify: internal/server/router.go:84-124internal/server/router.go:190-220internal/server/router.go:390-420

  • Step 1: 添加 registerRoute 辅助函数

internal/server/router.goconfigureProxyRoutes 函数之前添加:

// registerRoute 根据位置类型注册路由
func (s *Server) registerRoute(
	locType string,
	path string,
	handler fasthttp.RequestHandler,
	internal bool,
	source string,
) error {
	var err error
	switch locType {
	case matcher.LocationTypeExact:
		err = s.locationEngine.AddExact(path, handler, internal)
	case matcher.LocationTypePrefixPriority:
		err = s.locationEngine.AddPrefixPriority(path, handler, internal)
	case matcher.LocationTypeRegex:
		err = s.locationEngine.AddRegex(path, handler, false, internal)
	case matcher.LocationTypeRegexCaseless:
		err = s.locationEngine.AddRegex(path, handler, true, internal)
	case matcher.LocationTypeNamed:
		err = s.locationEngine.AddNamed(path, handler)
	default:
		err = s.locationEngine.AddPrefix(path, handler, internal)
	}
	if err != nil {
		return s.handleRegistrationError(source, path, err)
	}
	return nil
}
  • Step 2: 重构 proxy 路由注册

internal/server/router.go 第 84-124 行的 switch 语句替换为:

		switch locType {
		case matcher.LocationTypeExact:
			if err := s.registerRoute(locType, proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal, "proxy"); err != nil {
				return err
			}
		case matcher.LocationTypePrefixPriority:
			if err := s.registerRoute(locType, proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal, "proxy"); err != nil {
				return err
			}
		case matcher.LocationTypeRegex, matcher.LocationTypeRegexCaseless:
			caseInsensitive := locType == matcher.LocationTypeRegexCaseless
			if err := s.registerRoute(locType, proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal, "proxy"); err != nil {
				return err
			}
		case matcher.LocationTypeNamed:
			if proxyCfg.LocationName != "" {
				if err := s.registerRoute(locType, "@"+proxyCfg.LocationName, p.ServeHTTP, false, "proxy"); err != nil {
					return err
				}
			}
		case matcher.LocationTypePrefix:
			if err := s.registerRoute(locType, proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal, "proxy"); err != nil {
				return err
			}
		default:
			if err := s.registerRoute(locType, proxyCfg.Path, p.ServeHTTP, proxyCfg.Internal, "proxy"); err != nil {
				return err
			}
		}
  • Step 3: 重构 static 路由注册

internal/server/router.go 第 190-220 行的类似代码替换为 registerRoute 调用。

  • Step 4: 重构 lua 路由注册

internal/server/router.go 第 390-420 行的类似代码替换为 registerRoute 调用。

  • Step 5: 运行测试确认通过

Run: go test ./internal/server/... -v Expected: PASS

  • Step 6: Commit
git add internal/server/router.go
git commit -m "refactor: extract registerRoute helper to reduce repetition"

Task 7: 提取 DEBUG 日志辅助函数

Files:

  • Modify: internal/proxy/proxy.go:470-476 和类似位置

  • Step 1: 添加 proxyDebugLog 辅助函数

internal/proxy/proxy.goServeHTTP 方法之前添加:

// proxyDebugLog 在 DEBUG 级别记录代理日志
func proxyDebugLog(msg string, kv ...interface{}) {
	if !logging.Debug().Enabled() {
		return
	}
	event := logging.Debug()
	for i := 0; i < len(kv)-1; i += 2 {
		key, ok := kv[i].(string)
		if !ok {
			continue
		}
		switch v := kv[i+1].(type) {
		case string:
			event = event.Str(key, v)
		case int:
			event = event.Int(key, v)
		case bool:
			event = event.Bool(key, v)
		}
	}
	event.Msg(msg)
}
  • Step 2: 替换第一个 DEBUG 日志

将第 470-476 行:

	if logging.Debug().Enabled() {
		logging.Debug().
			Str("path", b2s(ctx.Path())).
			Str("host", b2s(ctx.Host())).
			Str("method", b2s(ctx.Method())).
			Msg("[PROXY] 收到请求")
	}

替换为:

	proxyDebugLog("[PROXY] 收到请求",
		"path", b2s(ctx.Path()),
		"host", b2s(ctx.Host()),
		"method", b2s(ctx.Method()),
	)
  • Step 3: 替换其余 4 个 DEBUG 日志

重复 Step 2 的模式,替换第 536-540、555-559、627-631、715-719 行的 DEBUG 日志。

  • Step 4: 运行测试确认通过

Run: go test ./internal/proxy/... -v Expected: PASS

  • Step 5: Commit
git add internal/proxy/proxy.go
git commit -m "refactor: extract proxyDebugLog helper for repeated debug logging"

阶段 3测试辅助函数

Task 8: 创建测试辅助函数包

Files:

  • Create: internal/testutil/proxy.go

  • Step 1: 创建测试辅助函数文件

创建 internal/testutil/proxy.go

package testutil

import (
	"time"

	"rua.plus/lolly/internal/config"
	"rua.plus/lolly/internal/loadbalance"
)

// NewTestProxyConfig 创建测试用的代理配置
//
// 参数:
//   - path: 代理路径
//   - targetURLs: 后端目标 URL 列表
//
// 返回值:
//   - *config.ProxyConfig: 配置好的代理配置
func NewTestProxyConfig(path string, targetURLs ...string) *config.ProxyConfig {
	cfg := &config.ProxyConfig{
		Path:        path,
		LoadBalance: "round_robin",
		Timeout: config.ProxyTimeout{
			Connect: 5 * time.Second,
			Read:    30 * time.Second,
			Write:   30 * time.Second,
		},
	}

	if len(targetURLs) > 0 {
		cfg.Targets = make([]config.ProxyTargetConfig, len(targetURLs))
		for i, url := range targetURLs {
			cfg.Targets[i] = config.ProxyTargetConfig{URL: url}
		}
	}

	return cfg
}

// NewTestProxyConfigWithCache 创建带缓存的测试代理配置
func NewTestProxyConfigWithCache(path string, maxAge time.Duration, targetURLs ...string) *config.ProxyConfig {
	cfg := NewTestProxyConfig(path, targetURLs...)
	cfg.Cache = config.ProxyCacheConfig{
		Enabled: true,
		MaxAge:  maxAge,
	}
	return cfg
}

// NewTestTarget 创建测试用的代理目标
//
// 参数:
//   - url: 目标 URL
//
// 返回值:
//   - *loadbalance.Target: 测试目标
func NewTestTarget(url string) *loadbalance.Target {
	return &loadbalance.Target{URL: url}
}

// NewTestTargets 批量创建测试目标
func NewTestTargets(urls ...string) []*loadbalance.Target {
	targets := make([]*loadbalance.Target, len(urls))
	for i, url := range urls {
		targets[i] = NewTestTarget(url)
	}
	return targets
}

// NewTestHealthyTarget 创建已标记为健康的测试目标
//
// 参数:
//   - url: 目标 URL
//
// 返回值:
//   - *loadbalance.Target: 已标记为健康的测试目标
func NewTestHealthyTarget(url string) *loadbalance.Target {
	t := NewTestTarget(url)
	t.Healthy.Store(true)
	return t
}

// NewTestHealthyTargets 批量创建健康测试目标
func NewTestHealthyTargets(urls ...string) []*loadbalance.Target {
	targets := make([]*loadbalance.Target, len(urls))
	for i, url := range urls {
		targets[i] = NewTestHealthyTarget(url)
	}
	return targets
}
  • Step 2: 编写辅助函数测试

创建 internal/testutil/proxy_test.go

package testutil

import (
	"testing"
	"time"
)

func TestNewTestProxyConfig(t *testing.T) {
	cfg := NewTestProxyConfig("/api", "http://localhost:8080")

	if cfg.Path != "/api" {
		t.Errorf("expected path /api, got %s", cfg.Path)
	}
	if len(cfg.Targets) != 1 {
		t.Errorf("expected 1 target, got %d", len(cfg.Targets))
	}
	if cfg.Timeout.Connect != 5*time.Second {
		t.Errorf("expected 5s connect timeout, got %v", cfg.Timeout.Connect)
	}
}

func TestNewTestHealthyTarget(t *testing.T) {
	target := NewTestHealthyTarget("http://localhost:8080")

	if target.URL != "http://localhost:8080" {
		t.Errorf("expected URL http://localhost:8080, got %s", target.URL)
	}
	if !target.Healthy.Load() {
		t.Error("expected target to be healthy")
	}
}

func TestNewTestHealthyTargets(t *testing.T) {
	targets := NewTestHealthyTargets("http://localhost:8080", "http://localhost:8081")

	if len(targets) != 2 {
		t.Errorf("expected 2 targets, got %d", len(targets))
	}
	for i, target := range targets {
		if !target.Healthy.Load() {
			t.Errorf("expected target %d to be healthy", i)
		}
	}
}
  • Step 3: 运行测试确认通过

Run: go test ./internal/testutil/... -v Expected: PASS

  • Step 4: Commit
git add internal/testutil/
git commit -m "feat: add testutil package for proxy config helpers"

Task 9: 迁移 proxy 测试使用辅助函数

Files:

  • Modify: internal/proxy/proxy_test.go

  • Modify: internal/integration/proxy_integration_test.go

  • Step 1: 修改 internal/proxy/proxy_test.go 导入

添加导入:

import (
	"rua.plus/lolly/internal/testutil"
)
  • Step 2: 替换重复的 ProxyConfig 创建

将测试中的重复模式替换为:

// 替换前:
cfg := &config.ProxyConfig{
    Path:        "/api",
    LoadBalance: "round_robin",
    Timeout: config.ProxyTimeout{
        Connect: 5 * time.Second,
        Read:    30 * time.Second,
        Write:   30 * time.Second,
    },
}

// 替换后:
cfg := testutil.NewTestProxyConfig("/api")
  • Step 3: 替换重复的 Target 创建

将:

targets := []*loadbalance.Target{{URL: "http://localhost:8080"}}
targets[0].Healthy.Store(true)

替换为:

targets := testutil.NewTestHealthyTargets("http://localhost:8080")
  • Step 4: 运行测试确认通过

Run: go test ./internal/proxy/... -v Expected: PASS

  • Step 5: Commit
git add internal/proxy/proxy_test.go internal/integration/proxy_integration_test.go
git commit -m "refactor: use testutil helpers in proxy tests"

Task 10: 迁移 server 测试使用辅助函数

Files:

  • Modify: internal/server/*_test.go

  • Step 1: 批量替换 server 测试中的重复代码

使用与 Task 9 相同的模式,替换 internal/server/ 下所有测试文件中的重复 ProxyConfig 和 Target 创建。

  • Step 2: 运行测试确认通过

Run: go test ./internal/server/... -v Expected: PASS

  • Step 3: Commit
git add internal/server/
git commit -m "refactor: use testutil helpers in server tests"

验收检查

Task 11: 最终验证

  • Step 1: 运行 unused linter

Run: golangci-lint run --enable=unused ./... Expected: 无 unused 错误

  • Step 2: 运行 dupl linter

Run: golangci-lint run --enable=dupl ./... Expected: 源文件无 dupl 错误(测试文件允许)

  • Step 3: 运行完整测试套件

Run: go test ./... Expected: 全部 PASS

  • Step 4: 统计代码行数变化

Run: git diff --stat Expected: 总行数净减少 >200 行

  • Step 5: 最终 Commit
git commit -m "chore: eliminate code redundancy - dead code removal, pattern extraction, test helpers"

Self-Review Checklist

  1. Spec coverage: 所有 3 个阶段都有详细任务 ✓
  2. Placeholder scan: 无 TBD、TODO 或模糊描述 ✓
  3. Type consistency: registerRouteproxyDebugLog 签名与使用处一致 ✓
  4. File paths: 所有路径均为绝对路径,与代码库匹配 ✓
  5. Commands: 每个测试步骤都有明确的运行命令和预期输出 ✓