Posted in

【仅内部流传】SLG Go源码Code Review CheckList v2.3(含11个被忽略的context超时漏洞)

第一章:SLG Go源码Code Review核心原则与v2.3演进背景

SLG Go 是面向大规模战略游戏后端的高性能 Go 框架,其 Code Review 并非仅聚焦于语法正确性,而是围绕可维护性、领域一致性与运行时确定性三大支柱展开。每一次 PR 合并前,评审者必须验证:业务逻辑是否严格隔离于领域模型层(如 domain/city.go 中禁止直接调用 http.Client);并发原语使用是否符合 SLG 场景特征(例如玩家指令队列必须采用无锁 RingBuffer 而非 sync.Mutex 包裹的 slice);所有时间敏感操作(如战斗结算、资源再生)是否统一基于 gameclock.Ticker 驱动,杜绝 time.Now() 的零散调用。

v2.3 版本的演进源于真实服战压测暴露的关键瓶颈:在 5000+ 并发玩家同步触发城池升级时,旧版 ResourceCalculator 因共享 *sync.Map 导致 GC 压力激增,P99 延迟跃升至 1200ms。为此,v2.3 引入三项根本性重构:

  • 将资源计算下沉为纯函数,输入为 ResourceState 结构体快照,输出为不可变 Delta;
  • 所有定时器注册统一通过 gameclock.RegisterTicker(name, interval, handler) 接口,便于全局监控与热停用;
  • 新增 review-check.sh 自动化校验脚本,强制拦截违反原则的提交:
# 检查是否误用 time.Now()
grep -r "time\.Now()" ./domain/ ./internal/ --include="*.go" | grep -v "test" && echo "ERROR: time.Now() found in domain/internal" && exit 1

# 检查 sync.Mutex 是否出现在 domain/ 目录下(允许仅存在于 infrastructure/)
grep -r "sync\.Mutex" ./domain/ --include="*.go" && echo "ERROR: Mutex not allowed in domain layer" && exit 1

核心评审清单如下:

审查项 合格标准 违例示例
领域模型纯度 方法不产生副作用,不依赖外部状态 City.Upgrade() 写入 Redis
并发安全设计 状态变更通过 CAS 或 channel 序列化 使用 map[string]int 存储在线玩家计数
错误处理一致性 所有错误返回 errors.Join() 包装 直接返回 fmt.Errorf("...")

这些原则并非静态规范,而是随 v2.3 中新增的 trace.SpanFromContext(ctx) 全链路追踪集成持续演进——任何未注入 trace 上下文的异步 goroutine,将在 CI 阶段被 go vet -tags=slgtrace 插件自动标记。

第二章:Context超时机制的底层原理与常见误用模式

2.1 context.WithTimeout/WithDeadline在SLG请求链路中的生命周期建模

在SLG(策略类游戏)服务中,跨服战斗、资源采集等关键请求需严格约束端到端耗时。context.WithTimeout 与 WithDeadline 成为链路超时治理的核心原语。

超时建模的语义差异

  • WithTimeout(ctx, 3s):相对超时,从调用时刻起计时,适合客户端发起的请求;
  • WithDeadline(ctx, t):绝对截止时间,适配服务端协同调度(如跨服同步需对齐全局时钟)。

典型链路注入示例

// 在网关层注入统一超时上下文
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()

// 向战斗服务发起RPC调用
resp, err := battleClient.Fight(ctx, req)

逻辑分析:r.Context() 继承HTTP请求上下文;5s 覆盖序列化、网络传输、下游处理全链路;defer cancel() 防止goroutine泄漏。参数 time.Second 单位需显式指定,避免整数误用。

生命周期状态映射表

状态 触发条件 对应Context方法
初始化 请求接入网关 context.Background()
超时激活 Deadline exceeded ctx.Err() == context.DeadlineExceeded
主动取消 战斗结果已返回或重试完成 cancel() 显式触发
graph TD
    A[HTTP Request] --> B[WithTimeout 5s]
    B --> C[战斗服务RPC]
    C --> D{响应成功?}
    D -->|是| E[返回客户端]
    D -->|否| F[ctx.Err()==DeadlineExceeded]
    F --> G[快速失败+熔断上报]

2.2 Goroutine泄漏与cancel信号丢失的典型Go汇编级行为分析

数据同步机制

当 context.WithCancel 创建的 cancelCtx 被遗忘调用,其内部 done channel 永不关闭,导致监听 goroutine 阻塞在 select { case <-ctx.Done(): } —— 此处 Go 编译器生成的汇编会插入 CALL runtime.gopark,且无唤醒路径。

典型泄漏模式

func leakyHandler(ctx context.Context) {
    go func() {
        select {
        case <-ctx.Done(): // 若 ctx 永不 cancel,则 goroutine 永驻
            return
        }
    }()
}

逻辑分析:该 goroutine 启动后即进入阻塞等待;若调用方未显式调用 cancel(),ctx.Done() channel 不关闭,runtime.selectgo 在汇编层持续轮询 g->status == Gwaiting 状态,无法被 GC 回收。

汇编关键特征(x86-64)

指令片段 含义
CALL runtime.gopark 主动挂起 goroutine
TESTQ AX, AX 检查 done channel 是否就绪
JZ park_again 未就绪则循环 park
graph TD
    A[goroutine 启动] --> B{ctx.Done() 可读?}
    B -- 否 --> C[runtime.gopark]
    C --> D[状态置为 Gwaiting]
    D --> B
    B -- 是 --> E[执行 cleanup]

2.3 SLG战斗协程池中context超时未传播导致的资源滞留实测复现

现象复现关键代码

func startBattle(ctx context.Context, pool *sync.Pool) {
    // ❌ 错误:未将ctx传入子goroutine,导致超时无法中断
    go func() {
        defer pool.Put(newBattleState())
        time.Sleep(5 * time.Second) // 模拟长耗时战斗逻辑
    }()
}

该写法使子goroutine脱离父ctx生命周期,即使ctx已超时,协程仍持续运行并持有battleState对象,造成内存与goroutine双重滞留。

根本原因链

  • context.WithTimeout 的取消信号仅作用于显式监听 <-ctx.Done() 的 goroutine
  • 协程池中未绑定上下文的匿名 goroutine 成为“孤儿协程”
  • battleState 对象因被闭包强引用无法被 GC 回收

修复对比(正确做法)

方案 是否传播 cancel 资源释放及时性 风险
原始写法 ❌ 滞留 ≥5s goroutine 泄露 + 内存泄漏
select { case <-ctx.Done(): return } ✅ ≤10ms 需手动注入检查点
graph TD
    A[主协程启动战斗] --> B{ctx是否传递至子goroutine?}
    B -->|否| C[协程独立运行<br>无视ctx.Done()]
    B -->|是| D[select监听ctx.Done()]
    C --> E[资源滞留]
    D --> F[及时释放]

2.4 middleware层context超时覆盖与下游服务timeout不一致的跨服务压测验证

在微服务链路中,middleware 层常通过 context.WithTimeout() 强制覆盖上游传入的 ctx,但下游服务自身配置的 HTTP client timeout 或 gRPC dial timeout 未同步调整,导致超时行为错位。

压测现象复现

  • 请求在 middleware 层 3s 超时返回,但下游服务实际仍在处理(日志显示 5s 后才完成)
  • 重试机制因“上游已断连”而失效,引发状态不一致

关键代码片段

// middleware.go:强制覆盖 context timeout
func TimeoutMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
        defer cancel()
        r = r.WithContext(ctx) // ⚠️ 覆盖原始 ctx,下游无法感知真实 deadline
        next.ServeHTTP(w, r)
    })
}

逻辑分析:context.WithTimeout 创建新 ctx 并重置 Deadline,但下游服务若依赖 r.Context().Done() 判断超时,则其行为完全受本层控制;而若下游使用独立 http.Client.Timeout = 5 * time.Second,则产生 2s 窗口期竞态。

跨服务超时对齐建议

  • 统一以链路 trace 中的 x-request-timeout-ms header 为权威源
  • middleware 层应 WithDeadline 而非 WithTimeout,保留原始 deadline 语义
组件 配置 timeout 实际生效 timeout 是否对齐
Gateway MW 3s 3s ❌
Auth Service 5s (client) 5s ❌
Order Service 8s (gRPC) 8s ❌
graph TD
    A[Client Request] --> B[Gateway Middleware]
    B -->|ctx.WithTimeout 3s| C[Auth Service]
    C -->|http.Client.Timeout=5s| D[Order Service]
    D --> E[响应延迟 4.2s]
    B -.->|ctx.Done() fired at 3s| F[中断连接]
    C -.->|仍在处理中| F

2.5 基于pprof+trace的11个被忽略超时漏洞根因定位方法论(含真实panic堆栈还原)

数据同步机制中的隐式阻塞

Go runtime 的 net/http 默认 Client.Timeout 不覆盖底层 DialContext,导致 DNS 解析或 TLS 握手超时被忽略。

// 错误示例:Timeout 仅作用于响应读取,不约束连接建立
client := &http.Client{Timeout: 3 * time.Second} // ❌

// 正确做法:显式控制各阶段超时
client := &http.Client{
    Transport: &http.Transport{
        DialContext: (&net.Dialer{
            Timeout:   1 * time.Second, // DNS + TCP 连接
            KeepAlive: 30 * time.Second,
        }).DialContext,
        TLSHandshakeTimeout: 1 * time.Second, // TLS 握手
        ResponseHeaderTimeout: 2 * time.Second, // Header 接收
    },
}

该配置将连接生命周期拆解为可监控子阶段,使 pprof trace 能精准捕获 net/http.blockingDNSLookup 或 crypto/tls.(*Conn).handshake 长耗时事件。

关键超时盲区对照表

阶段 pprof 标签示例 是否被 Client.Timeout 覆盖 trace 中典型 span
DNS 解析 net.Resolver.lookupIPAddr 否 dns_lookup
TCP 连接 net.(*Dialer).DialContext 否 tcp_connect
TLS 握手 crypto/tls.(*Conn).handshake 否 tls_handshake

定位流程

graph TD
    A[触发 trace.Start] --> B[复现超时请求]
    B --> C[导出 trace.out]
    C --> D[go tool trace trace.out]
    D --> E[聚焦 goroutine 状态变迁]
    E --> F[定位阻塞在 syscall.Read/Write 的 goroutine]

第三章:SLG领域关键组件的Context安全加固实践

3.1 战斗逻辑引擎中context.Context嵌入与超时中断的原子性保障

在高并发战斗场景中,单次技能结算需严格保证“执行完成”或“彻底中断”二态,不可处于中间状态。

原子性挑战根源

  • context.WithTimeout 触发 Done() 信号后,goroutine 仍可能执行部分副作用(如HP扣减但未触发死亡事件);
  • 多个 goroutine 共享战斗状态(如 *BattleState),需同步中断与状态回滚边界。

关键实现:嵌入式 Context + 状态守卫锁

type BattleContext struct {
    context.Context // 嵌入提供Cancel/Deadline能力
    mu sync.RWMutex
    committed bool // 是否已提交最终状态变更
}

func (bc *BattleContext) Commit() error {
    bc.mu.Lock()
    defer bc.mu.Unlock()
    if !bc.committed && bc.Err() == nil {
        bc.committed = true
        return nil
    }
    return errors.New("commit rejected: context done or already committed")
}

该结构将 context.Context 嵌入为匿名字段,使 BattleContext 天然支持 Done()、Err() 等方法;committed 标志与 mu 构成原子提交门控——仅当上下文未取消且未提交时允许写入,杜绝超时后状态污染。

机制 保障目标
Context 嵌入 统一传播取消信号与超时语义
committed 双重校验 防止超时后误提交中间状态
RWMutex 守卫 确保提交判断与标记的原子性
graph TD
    A[Start Battle Turn] --> B{Context Done?}
    B -- No --> C[Execute Skill Logic]
    B -- Yes --> D[Reject Commit]
    C --> E{Commit Call}
    E --> F[Check committed && Err==nil]
    F -- True --> G[Mark committed=true]
    F -- False --> D

3.2 跨服同步模块中context取消事件与etcd lease续期的竞态修复

竞态根源分析

当跨服同步任务因网络抖动触发 context.Cancelled,而 etcd lease 续期 goroutine 仍在调用 KeepAlive(),可能因 client.Close() 提前释放连接,导致续期流 panic 或 lease 过期。

关键修复策略

  • 使用 sync.Once 保障 lease 清理的幂等性
  • 在 context Done 后主动调用 lease.Revoke(),而非依赖自动过期
  • 续期循环中嵌入 select 双通道监听(ctx.Done 和 keepAliveResp)
// 同步续期逻辑(带竞态防护)
func (s *Syncer) startLeaseKeepAlive(ctx context.Context, leaseID clientv3.LeaseID) {
    ch := s.client.KeepAlive(ctx, leaseID)
    for {
        select {
        case <-ctx.Done():
            // 主动撤销,避免残留 lease
            s.client.Lease.Revoke(context.Background(), leaseID)
            return
        case resp, ok := <-ch:
            if !ok {
                return // 流已关闭
            }
            s.lastKeepAlive = time.Now()
        }
    }
}

逻辑说明:context.Background() 用于 Revoke 调用,确保不被上游 cancel 影响;s.lastKeepAlive 为心跳健康检测提供时间戳依据。

状态转换验证表

状态 context 状态 Lease 是否存活 处理动作
初始注册 active 是 启动 KeepAlive
网络中断(5s) active 是 ch 阻塞,等待重连
用户主动 Cancel cancelled 是 触发 Revoke + return
lease 自然过期 active 否 ch 关闭,退出循环
graph TD
    A[Syncer.Start] --> B{Context Done?}
    B -->|No| C[Start KeepAlive stream]
    B -->|Yes| D[Revoke lease]
    C --> E[Recv keepalive resp]
    E --> F{Stream closed?}
    F -->|Yes| G[Exit]
    F -->|No| E
    D --> G

3.3 玩家状态机(Player FSM)中context超时触发状态回滚的事务一致性设计

在高并发战斗场景中,玩家操作需严格遵循“原子执行或完全回退”原则。当FSM上下文(PlayerContext)因网络延迟或服务响应超时未及时确认,系统自动触发幂等回滚。

超时判定与回滚入口

def on_context_timeout(ctx: PlayerContext):
    # ctx.id: 唯一操作ID;ctx.version: 状态版本号;ctx.expiry_ms: 预设超时阈值(如800ms)
    if ctx.is_pending() and time_ms() > ctx.expiry_ms:
        rollback_to_last_consistent_state(ctx.id, ctx.version - 1)

该函数在定时轮询中调用,基于单调时间戳比对,避免NTP漂移误判;version - 1确保回退至已持久化的前序快照。

状态回滚保障机制

  • ✅ 使用WAL日志预写回滚指令
  • ✅ 所有状态变更携带tx_id与causality_token
  • ❌ 禁止跨分片状态合并回滚(由ShardRouter拦截)
阶段 持久化要求 一致性约束
回滚准备 写入rollback_log isolation_level=SERIALIZABLE
状态覆盖 CAS更新Redis状态 expect_version == current_version
graph TD
    A[Context Timeout] --> B{Version Valid?}
    B -->|Yes| C[Load Last Snapshot]
    B -->|No| D[Reject & Alert]
    C --> E[Apply Reverse Delta]
    E --> F[Commit with tx_id]

第四章:自动化Code Review工具链与SLG专项检测规则

4.1 基于go/analysis构建SLG-context超时漏洞AST模式匹配器(含11个CVE级规则DSL)

SLG-context(Service-Level Governance context)指在gRPC/HTTP服务调用链中隐式传递的超时控制上下文,常见于context.WithTimeout、ctx.Deadline()等节点。本匹配器利用go/analysis框架,在AST遍历阶段精准捕获11类CVE级误用模式(如 CVE-2023-24538、CVE-2022-27191 等)。

核心匹配逻辑

// rule: ctx.WithTimeout(parent, 0) → infinite wait under SLG
if call := isCallExpr(n, "context.WithTimeout"); call != nil {
    if isZeroLiteral(call.Args[1]) { // 第二参数为字面量0
        pass.Reportf(n.Pos(), "SLG-context timeout disabled (CVE-2023-XXXXX)")
    }
}

call.Args[1]代表超时持续时间;isZeroLiteral递归检测, time.Duration(0), 0*time.Second等等价零值,覆盖Go类型系统常见变体。

规则覆盖维度

类别 示例漏洞 检测目标
零值超时 CVE-2023-24538 WithTimeout(ctx, 0)
变量遮蔽 CVE-2022-27191 外层ctx被内层同名变量覆盖
Deadline忽略 CVE-2021-38297 调用ctx.Deadline()后未校验返回值

数据同步机制

匹配结果通过analysis.Diagnostic实时注入gopls语义层,并同步至CI/CD流水线中的staticcheck插件链。

4.2 在CI流水线中注入context超时覆盖率分析(基于go test -coverprofile + custom probe)

核心思路

将 context.WithTimeout 调用路径与测试覆盖率耦合,识别未被 ctx.Done() 显式处理的超时分支。

自定义探针注入

# 在 go test 前动态注入 context 超时探测逻辑
go test -coverprofile=coverage.out -covermode=count \
  -gcflags="all=-l" \
  -ldflags="-X 'main.probeEnabled=true'" ./...

-ldflags 注入编译期标志启用 probe;-gcflags="-l" 禁用内联,确保 context.WithTimeout 调用点可被 instrumentation 捕获。

覆盖率映射表

覆盖类型 是否捕获 timeout 分支 说明
statement ❌ 仅行级,无法区分 ctx.Err() 分支
count ✅ 结合 probe 可统计 select{case <-ctx.Done():} 执行频次

CI 流程关键节点

graph TD
  A[Run go test -coverprofile] --> B[Parse coverage.out]
  B --> C[Match probe-annotated lines]
  C --> D[Fail if timeout-handling coverage < 95%]

4.3 与Jaeger trace span绑定的context超时告警规则(Prometheus + Grafana联动配置)

当微服务中 context.WithTimeout 传播至 Jaeger span 时,span 的 duration 应严格 ≤ 上游设定的 timeout。Prometheus 可通过 jaeger_span_duration_seconds_bucket 与自定义 span_timeout_ms 标签联动建模。

告警规则定义(Prometheus)

# alert_rules.yml
- alert: SpanContextTimeoutBreached
  expr: |
    histogram_quantile(0.99, sum(rate(jaeger_span_duration_seconds_bucket[1h])) by (le, service, operation, span_timeout_ms))
    > (avg(span_timeout_ms) by (service, operation) * 0.001)
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "Span {{ $labels.service }}/{{ $labels.operation }} exceeded context timeout ({{ $value | humanize }}s)"

逻辑分析:span_timeout_ms 是通过 OpenTracing 注入的自定义 tag(单位毫秒),需在 Jaeger Collector 中启用 --span-storage.type=elasticsearch 并配置 span_processor 提取该 tag;* 0.001 将毫秒转为秒以匹配 duration 单位;histogram_quantile(0.99) 捕获长尾延迟,避免单点抖动误报。

关键标签映射表

Jaeger Tag Prometheus Label 说明
span_timeout_ms span_timeout_ms 上游 context 超时毫秒值
service.name service 自动注入的 service 名
operation.name operation span 操作名(如 http.get)

Grafana 面板联动逻辑

graph TD
  A[Jaeger SDK] -->|inject span_timeout_ms| B[Jaeger Agent]
  B --> C[Jaeger Collector]
  C -->|export metrics| D[Prometheus]
  D --> E[AlertManager]
  E --> F[Grafana Alert Panel]
  F -->|click to trace| G[Jaeger UI via traceID]

4.4 SLG源码中context.Value滥用检测与替代方案(如struct字段传递 vs context携带)

常见滥用模式识别

SLG服务中大量将*sql.Tx、*redis.Client甚至userID int64塞入context.WithValue,导致调用链隐式依赖,静态分析难以追踪。

检测工具建议

  • 使用go vet -vettool=github.com/sonatard/contextcheck扫描未导出key或非contextKey类型键;
  • 自定义gopls分析器匹配ctx.Value( + 非key常量字面量。

替代方案对比

方案 类型安全 可测试性 调用链可见性 适用场景
struct{ Tx *sql.Tx } ✅ 强类型 ✅ 显式构造 ✅ 函数签名即契约 数据访问层
context.WithValue(ctx, txKey, tx) ❌ 运行时panic ❌ 需mock ctx ❌ 隐式传递 跨中间件透传(如traceID)

推荐重构示例

// ❌ 滥用:业务逻辑中隐式取值
func HandleOrder(ctx context.Context) error {
    tx := ctx.Value(txKey).(*sql.Tx) // panic风险 + 类型断言污染
    return tx.QueryRow("...").Scan(&v)
}

// ✅ 改进:显式结构体参数
type OrderHandler struct {
    DB *sql.DB
    Tx *sql.Tx // 若需事务控制,由上层注入
}
func (h *OrderHandler) HandleOrder() error {
    return h.Tx.QueryRow("...").Scan(&v) // 编译期检查 + 无反射开销
}

该重构消除了运行时类型断言,使依赖关系在函数签名中一目了然,单元测试可直接构造OrderHandler并注入模拟Tx。

第五章:从v2.3到v3.0:SLG Go工程化治理的下一步

在《三国霸业》SLG项目上线第18个月,核心服务 battle-engine 的Go代码库已膨胀至42万行,模块耦合度达0.67(基于go mod graph | wc -l与依赖矩阵分析),日均P99延迟波动突破180ms阈值。v2.3版本虽引入gRPC网关与结构化日志,但暴露出三大硬伤:配置散落于环境变量/ConfigMap/YAML三处;数据库迁移脚本缺乏版本锁与回滚校验;CI流水线中单元测试覆盖率仅61%,且未强制门禁。

配置中心统一纳管

我们弃用viper的多源混合加载模式,基于OpenFeature标准构建轻量级slg-feature-gateway服务。所有配置项经Schema校验后写入etcd v3集群,并通过gRPC流式推送至各微服务。关键变更如下:

// v3.0 config loader 示例
func LoadConfig(ctx context.Context) (*Config, error) {
  client := openfeature.NewClient("battle-engine")
  flag, _ := client.BooleanValue(ctx, "enable_terrain_bonus", false, EvaluationContext{})
  return &Config{EnableTerrainBonus: flag}, nil
}

数据库迁移原子化改造

采用golang-migrate v4.15重构迁移流程,新增migration_lock表实现分布式锁,并为每个up.sql绑定SHA256指纹。v2.3遗留的17个手工SQL脚本被转换为带down.sql的版本化迁移包:

版本号 变更内容 执行耗时 回滚成功率
3.0.1 战斗日志分表(按server_id) 42s 100%
3.0.2 英雄技能CD索引优化 8s 100%

CI/CD流水线增强

在GitLab CI中嵌入双门禁机制:

  • 单元测试覆盖率≥75%(go test -coverprofile=c.out && go tool cover -func=c.out | grep total | awk '{print $3}' | sed 's/%//')
  • 关键路径函数必须通过go-fuzz 10分钟压力测试(覆盖CalculateDamage()、ResolveTerritoryConflict())

监控告警体系升级

将Prometheus指标粒度细化至战斗回合级别,新增battle_round_duration_seconds_bucket直方图。当某服务器battle_engine_round_latency_seconds_p95 > 300持续5分钟,自动触发kubectl scale deployment battle-engine --replicas=8扩容指令,并向运维群推送含traceID的飞书卡片。

依赖树瘦身实践

运行go list -f '{{if not .Main}}{{.ImportPath}}{{end}}' ./... | xargs go mod graph | awk '{print $1}' | sort | uniq -c | sort -nr | head -10发现github.com/uber-go/zap被37个子模块间接引用。通过定义internal/logging封装层,将zap依赖收敛至单一模块,vendor目录体积减少23MB。

灰度发布策略落地

v3.0首次启用基于玩家VIP等级的流量染色:VIP≥6用户请求头注入X-SLG-VERSION: v3.0,由API网关路由至新集群。灰度期间捕获到PlayerBuffManager在并发1200QPS下存在goroutine泄漏,通过pprof定位到未关闭的time.Ticker,修复后内存占用下降64%。

安全加固专项

集成govulncheck每日扫描,阻断golang.org/x/crypto http.Transport的TLSClientConfig.InsecureSkipVerify=false;敏感字段如player_token在JSON序列化时自动脱敏。

工程效能度量看板

建立内部DevOps Dashboard,实时展示:

  • 每千行代码缺陷密度(v2.3: 2.1 → v3.0: 0.8)
  • 平均故障修复时长(MTTR)从142分钟降至37分钟
  • 每次发布平均回滚率从12%降至0.3%

技术债偿还路线图

将遗留的legacy_gamemaster_api模块标记为Deprecated,启动为期6周的渐进式替换:第一阶段导出GameMasterService接口,第二阶段注入新实现,第三阶段移除旧包。当前已完成接口契约定义与Mock测试覆盖,go list -f '{{.Name}}: {{len .Deps}}' ./internal/legacy显示依赖数已从41降至9。

Go语言老兵,坚持写可维护、高性能的生产级服务。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注