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

930 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 消除代码冗余实施计划
> **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 行:
```go
// 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` 函数:
```go
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**
```bash
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: 删除 `get` 和 `count` 方法**
删除 `internal/http2/server.go` 第 575-587 行:
```go
// 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**
```bash
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 行:
```go
// 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` 函数:
```go
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**
```bash
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 行:
```go
// 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 行:
```go
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**
```bash
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 行:
```go
// 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 行:
```go
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**
```bash
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-124``internal/server/router.go:190-220``internal/server/router.go:390-420`
- [ ] **Step 1: 添加 `registerRoute` 辅助函数**
`internal/server/router.go``configureProxyRoutes` 函数之前添加:
```go
// 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 语句替换为:
```go
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**
```bash
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.go``ServeHTTP` 方法之前添加:
```go
// 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 行:
```go
if logging.Debug().Enabled() {
logging.Debug().
Str("path", b2s(ctx.Path())).
Str("host", b2s(ctx.Host())).
Str("method", b2s(ctx.Method())).
Msg("[PROXY] 收到请求")
}
```
替换为:
```go
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**
```bash
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`
```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`
```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**
```bash
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` 导入**
添加导入:
```go
import (
"rua.plus/lolly/internal/testutil"
)
```
- [ ] **Step 2: 替换重复的 ProxyConfig 创建**
将测试中的重复模式替换为:
```go
// 替换前:
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 创建**
将:
```go
targets := []*loadbalance.Target{{URL: "http://localhost:8080"}}
targets[0].Healthy.Store(true)
```
替换为:
```go
targets := testutil.NewTestHealthyTargets("http://localhost:8080")
```
- [ ] **Step 4: 运行测试确认通过**
Run: `go test ./internal/proxy/... -v`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
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**
```bash
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**
```bash
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**: `registerRoute``proxyDebugLog` 签名与使用处一致 ✓
4. **File paths**: 所有路径均为绝对路径,与代码库匹配 ✓
5. **Commands**: 每个测试步骤都有明确的运行命令和预期输出 ✓