Background
internal/messaging/ 共享层包含 21 个源文件(~4,759 行),负责平台适配器基类、Session bridge、交互管理、斜杠命令解析、文本格式化。Slack 和 Feishu 子模块各自嵌入 BaseAdapter 实现平台特定逻辑。模块已有 issue 257 跟踪子模块间 DRY 问题。本次为 首次从 SOLID + coupling 角度分析共享层本身。
Scope: solid, dry, coupling — cycle 3 (模块分析通过 1)
Key files: platform_adapter.go, bridge.go, control_command.go, interaction.go, command_executor.go
Finding Summary
| Category |
Critical |
High |
Medium |
Low |
| SOLID/SRP |
0 |
0 |
2 |
0 |
| SOLID/OCP |
0 |
0 |
2 |
0 |
| SOLID/ISP |
0 |
0 |
1 |
0 |
| DRY |
0 |
1 |
1 |
0 |
| Coupling |
0 |
1 |
1 |
0 |
| 合计 |
0 |
2 |
7 |
0 |
Findings
Coupling
platformadapter-concrete-bridge-dependency
Severity: High | Confidence: High | ROI: High
Location: platform_adapter.go:132 (bridge *Bridge)
Problem: PlatformAdapter 持有具体 *Bridge 指针而非接口。所有平台适配器(Slack/Feishu)通过嵌入的 PlatformAdapter 间接依赖具体 *messaging.Bridge。这意味着:
- 测试任何适配器必须构造真实 Bridge
- Bridge API 变更波及所有适配器
- 与
HubInterface/HandlerInterface(已使用接口)的模式不一致
Current Pattern:
// platform_adapter.go:126-142 — concrete *Bridge
type PlatformAdapter struct {
Log *slog.Logger
hub HubInterface // interface — good
sm SessionManager // empty interface — bad (see I1)
handler HandlerInterface // interface — good
bridge *Bridge // concrete type — inconsistent
Dedup *Dedup
Gate *Gate
Interactions *InteractionManager
// ...
}
Proposed Fix: 定义 BridgeInterface 并在 PlatformAdapter 中使用:
type BridgeInterface interface {
Handle(ctx context.Context, env *events.Envelope, pc PlatformConn) error
JoinSession(ctx context.Context, sessionID string, pc PlatformConn) error
Platform() PlatformType
SetAdapter(adapter PlatformAdapterInterface)
}
type PlatformAdapter struct {
bridge BridgeInterface // interface, not concrete
// ...
}
Estimated Impact: 适配器可独立测试,与 HubInterface/HandlerInterface 模式一致
Acceptance Criteria:
DRY
four-parallel-command-maps
Severity: High | Confidence: High | ROI: High
Location: control_command.go:19, 31, 105, 120
Problem: 4 个概念相同的命令映射使用相同 lookup 模式但独立定义:
slashCommandMap (line 19) — /gc → ControlAction
naturalLanguageMap (line 31) — $休眠 → ControlAction
workerSlashMap (line 105) — /model → WorkerCommand
workerNLMap (line 120) — $模型 → WorkerCommand
ParseControlCommand 和 ParseWorkerCommand 也是结构相同的方法(normalize → lookup → return),仅返回类型不同。
Current Pattern:
// control_command.go:19-30 — ControlAction maps
var slashCommandMap = map[string]ControlAction{"/gc": ActionGC, "/reset": ActionReset, ...}
var naturalLanguageMap = map[string]ControlAction{"$休眠": ActionGC, "$重置": ActionReset, ...}
// control_command.go:105-118 — WorkerCommand maps
var workerSlashMap = map[string]WorkerCommand{"/model": CmdSetModel, "/skills": CmdSkills, ...}
var workerNLMap = map[string]WorkerCommand{"$模型": CmdSetModel, "$技能": CmdSkills, ...}
// Both parse functions: normalize(text) → map[text] → return(result, true)
Proposed Fix: 泛型命令映射类型:
type CommandMap[T any] struct {
slash map[string]T
natural map[string]T
}
func NewCommandMap[T any](slash, natural map[string]T) *CommandMap[T] { ... }
func (m *CommandMap[T]) Parse(text string) (T, bool) {
normalized := strings.ToLower(strings.TrimSpace(text))
if v, ok := m.slash[normalized]; ok { return v, true }
if v, ok := m.natural[normalized]; ok { return v, true }
var zero T; return zero, false
}
Estimated Impact: ~30 行减少,新增命令只需添加 map entry
Acceptance Criteria:
SOLID: SRP
platform-adapter-go-mixed-responsibilities
Severity: Medium | Confidence: High | ROI: Medium
Location: platform_adapter.go (215 lines)
Problem: 单文件混合 6 种职责:(1) PlatformType 类型+常量, (2) ExtractPlatformKeys switch, (3) PlatformAdapterInterface 接口, (4) Register/New 注册表, (5) PlatformAdapter 基础 struct, (6) HubInterface/HandlerInterface/SessionStarter 网关接口。新增平台类型需同时修改常量、switch、registry。
Current Pattern:
// platform_adapter.go — all in one file:
type PlatformType string // type definition (line 14)
const (PlatformSlack, PlatformFeishu) // constants (line 16-20)
func (p PlatformType) ExtractPlatformKeys(...) { switch p { ... } } // OCP violation (line 23)
type PlatformAdapterInterface interface { ... } // interface (line 58)
func Register(...) { } // registry (line 82)
func New(...) { } // factory (line 99)
type PlatformAdapter struct { ... } // base struct (line 126)
type HubInterface interface { ... } // gateway interface (line 145)
Proposed Fix: 按职责拆分:
platform_types.go — PlatformType + constants + ExtractPlatformKeys
platform_interfaces.go — HubInterface, HandlerInterface, SessionStarter, BridgeInterface
platform_registry.go — Register, New, RegisteredTypes
platform_adapter.go — PlatformAdapter struct + PlatformAdapterInterface
Estimated Impact: 新增平台只需修改 types.go,不影响 adapter/registry/interfaces
Acceptance Criteria:
bridge-platform-specific-envelopes
Severity: Medium | Confidence: High | ROI: Medium
Location: bridge.go:155-184 (MakeSlackEnvelope, MakeFeishuEnvelope)
Problem: Bridge 是平台无关的路由层,但包含 Slack 和 Feishu 特定的 envelope 构建方法。新增平台需要修改此文件。
Current Pattern:
// bridge.go:155-184 — platform-specific methods on platform-agnostic Bridge
func (b *Bridge) MakeSlackEnvelope(sessionID, channelID, threadTS string, env *events.Envelope) *events.Envelope {
return b.MakeEnvelope(sessionID, map[string]string{
"channel_id": channelID, "thread_ts": threadTS,
}, env)
}
func (b *Bridge) MakeFeishuEnvelope(sessionID, chatID, msgID string, env *events.Envelope) *events.Envelope {
return b.MakeEnvelope(sessionID, map[string]string{
"chat_id": chatID, "message_id": msgID,
}, env)
}
Proposed Fix: 只保留通用的 MakeEnvelope,平台适配器自行构建 platformKey map:
// Bridge only provides the generic method
func (b *Bridge) MakeEnvelope(sessionID string, platformKey map[string]string, env *events.Envelope) *events.Envelope {
// ... existing logic
}
// Slack adapter calls:
envelope := bridge.MakeEnvelope(sessionID, map[string]string{"channel_id": chID, "thread_ts": ts}, env)
Estimated Impact: 消除 Bridge 对平台细节的依赖
Acceptance Criteria:
SOLID: OCP
extractplatformkeys-switch-chain
Severity: Medium | Confidence: High | ROI: Medium
Location: platform_adapter.go:23-55
Problem: PlatformType.ExtractPlatformKeys() 用 switch 分发到 Slack/Feishu 特定的字段提取逻辑。新增平台必须修改此方法。这与 adapter registration pattern(Register/New)矛盾 — registry 是开放的但 key extraction 是封闭的。
Current Pattern:
// platform_adapter.go:23-55
func (p PlatformType) ExtractPlatformKeys(conn any) (map[string]string, error) {
switch p {
case PlatformSlack:
sc, ok := conn.(*SlackConn)
// extract channel_id, thread_ts
case PlatformFeishu:
fc, ok := conn.(*FeishuConn)
// extract chat_id, message_id
default:
return nil, fmt.Errorf("unsupported platform: %s", p)
}
}
Proposed Fix: 将 key extraction 注册到 adapter registry:
type AdapterBuilder struct {
NewFunc func(log *slog.Logger) PlatformAdapterInterface
ExtractKeys func(conn any) (map[string]string, error)
}
func Register(pt PlatformType, builder AdapterBuilder) { ... }
Estimated Impact: 新增平台只需调用 Register,无需修改任何共享层代码
Acceptance Criteria:
SOLID: ISP
sessionmanager-empty-interface
Severity: Medium | Confidence: High | ROI: High
Location: platform_adapter.go:157
Problem: type SessionManager any 是空接口 — 没有定义任何方法。它被存储在 PlatformAdapter.sm 和 AdapterConfig 中,但从未通过此类型调用任何方法。实际的 session 操作通过 SessionStarter 接口完成。这个空接口:
- 增加 coupling weight(所有 adapter 构造需传入)
- 提供 zero compile-time safety
- 误导读者以为 session 操作通过此类型进行
Current Pattern:
// platform_adapter.go:157
type SessionManager any // empty interface!
// platform_adapter.go:131 — stored but never used for method calls
type PlatformAdapter struct {
sm SessionManager // dead weight
// ...
}
// config.go:10 — required in config
type AdapterConfig struct {
SM SessionManager // must be passed but serves no purpose
// ...
}
Proposed Fix: 移除 SessionManager 类型和所有引用:
type PlatformAdapter struct {
// sm SessionManager — REMOVED
hub HubInterface
handler HandlerInterface
bridge BridgeInterface
// ...
}
type AdapterConfig struct {
// SM SessionManager — REMOVED
Hub HubInterface
Handler HandlerInterface
Bridge *Bridge
// ...
}
Estimated Impact: 消除 dead dependency,简化 adapter 构造
Acceptance Criteria:
Coupling
bidirectional-bridge-platformadapter-dependency
Severity: Medium | Confidence: High | ROI: Medium
Location: platform_adapter.go:132 + bridge.go:27,61-68
Problem: Bridge 和 PlatformAdapter 存在双向依赖:
PlatformAdapter 持有 *Bridge(直接引用)
Bridge 通过 atomic.Value 持有 PlatformAdapterInterface(延迟设置)
PlatformAdapter.ConfigureWith 接收 *Bridge 作为参数
Bridge.SetAdapter 设置 adapter 到 atomic.Value
初始化顺序是:创建 Adapter → ConfigureWith(Bridge) → Bridge.SetAdapter(Adapter)。如果顺序错误,会产生 nil pointer panic。这是通过约定而非类型系统保证的脆弱初始化。
Current Pattern:
// platform_adapter.go:169 — receives *Bridge
func (pa *PlatformAdapter) ConfigureWith(cfg AdapterConfig) {
pa.bridge = cfg.Bridge // concrete *Bridge
}
// bridge.go:61-68 — stores adapter later
func (b *Bridge) SetAdapter(adapter PlatformAdapterInterface) {
b.adapter.Store(adapter) // delayed, must be called after ConfigureWith
}
Proposed Fix: 配合 C1 修复(引入 BridgeInterface),将双向依赖改为单向:
// PlatformAdapter only knows about BridgeInterface
// Bridge knows about PlatformAdapterInterface (via atomic.Value)
// Both depend on abstractions, not concretions
Estimated Impact: 消除 fragile initialization order
Acceptance Criteria:
Implementation Priority
| Finding |
Priority |
Effort |
Risk |
Impact |
| sessionmanager-empty-interface |
P1 |
Small |
Low |
消除 dead dependency,简化构造 |
| four-parallel-command-maps |
P1 |
Small |
Low |
~30 行减少,泛型统一 |
| platformadapter-concrete-bridge-dependency |
P2 |
Medium |
Medium |
解锁 mock 测试 |
| extractplatformkeys-switch-chain |
P2 |
Medium |
Low |
新平台零改动共享层 |
| bridge-platform-specific-envelopes |
P3 |
Small |
Low |
Bridge 完全平台无关 |
| bidirectional-bridge-platformadapter |
P3 |
Medium |
Low |
消除 fragile init |
| platform-adapter-go-mixed |
P3 |
Small |
Low |
文件拆分,职责清晰 |
Recommended starting point: sessionmanager-empty-interface — 最小改动(删除 dead code),最高回报
Out of Scope
- Slack/Feishu 子模块间 interaction/command 重复 — 已在 issue 257 跟踪
- control_command.go parsing+help 混合 — Low ROI,help text 是自然附属
- AdapterConfig.Extras untyped map — Low ROI,platform-specific 配置
- interaction.go 不一致 extraction — Low ROI,3 个函数差异足够大
Verification
Background
internal/messaging/共享层包含 21 个源文件(~4,759 行),负责平台适配器基类、Session bridge、交互管理、斜杠命令解析、文本格式化。Slack 和 Feishu 子模块各自嵌入BaseAdapter实现平台特定逻辑。模块已有 issue 257 跟踪子模块间 DRY 问题。本次为 首次从 SOLID + coupling 角度分析共享层本身。Scope: solid, dry, coupling — cycle 3 (模块分析通过 1)
Key files:
platform_adapter.go,bridge.go,control_command.go,interaction.go,command_executor.goFinding Summary
Findings
Coupling
platformadapter-concrete-bridge-dependency
Severity: High | Confidence: High | ROI: High
Location:
platform_adapter.go:132(bridge *Bridge)Problem:
PlatformAdapter持有具体*Bridge指针而非接口。所有平台适配器(Slack/Feishu)通过嵌入的PlatformAdapter间接依赖具体*messaging.Bridge。这意味着:HubInterface/HandlerInterface(已使用接口)的模式不一致Current Pattern:
Proposed Fix: 定义
BridgeInterface并在 PlatformAdapter 中使用:Estimated Impact: 适配器可独立测试,与 HubInterface/HandlerInterface 模式一致
Acceptance Criteria:
BridgeInterface定义在platform_adapter.goPlatformAdapter.bridge类型从*Bridge改为BridgeInterfaceBridge实现BridgeInterfaceDRY
four-parallel-command-maps
Severity: High | Confidence: High | ROI: High
Location:
control_command.go:19, 31, 105, 120Problem: 4 个概念相同的命令映射使用相同 lookup 模式但独立定义:
slashCommandMap(line 19) —/gc→ControlActionnaturalLanguageMap(line 31) —$休眠→ControlActionworkerSlashMap(line 105) —/model→WorkerCommandworkerNLMap(line 120) —$模型→WorkerCommandParseControlCommand和ParseWorkerCommand也是结构相同的方法(normalize → lookup → return),仅返回类型不同。Current Pattern:
Proposed Fix: 泛型命令映射类型:
Estimated Impact: ~30 行减少,新增命令只需添加 map entry
Acceptance Criteria:
CommandMap[T]泛型类型定义ParseControlCommand和ParseWorkerCommand使用CommandMap.LookupSOLID: SRP
platform-adapter-go-mixed-responsibilities
Severity: Medium | Confidence: High | ROI: Medium
Location:
platform_adapter.go(215 lines)Problem: 单文件混合 6 种职责:(1) PlatformType 类型+常量, (2) ExtractPlatformKeys switch, (3) PlatformAdapterInterface 接口, (4) Register/New 注册表, (5) PlatformAdapter 基础 struct, (6) HubInterface/HandlerInterface/SessionStarter 网关接口。新增平台类型需同时修改常量、switch、registry。
Current Pattern:
Proposed Fix: 按职责拆分:
Estimated Impact: 新增平台只需修改 types.go,不影响 adapter/registry/interfaces
Acceptance Criteria:
platform_adapter.go拆分为 4 个文件make test零回归bridge-platform-specific-envelopes
Severity: Medium | Confidence: High | ROI: Medium
Location:
bridge.go:155-184(MakeSlackEnvelope,MakeFeishuEnvelope)Problem: Bridge 是平台无关的路由层,但包含 Slack 和 Feishu 特定的 envelope 构建方法。新增平台需要修改此文件。
Current Pattern:
Proposed Fix: 只保留通用的
MakeEnvelope,平台适配器自行构建 platformKey map:Estimated Impact: 消除 Bridge 对平台细节的依赖
Acceptance Criteria:
MakeSlackEnvelope和MakeFeishuEnvelope从 Bridge 中移除MakeEnvelope并传入 platformKeymake test零回归SOLID: OCP
extractplatformkeys-switch-chain
Severity: Medium | Confidence: High | ROI: Medium
Location:
platform_adapter.go:23-55Problem:
PlatformType.ExtractPlatformKeys()用 switch 分发到 Slack/Feishu 特定的字段提取逻辑。新增平台必须修改此方法。这与 adapter registration pattern(Register/New)矛盾 — registry 是开放的但 key extraction 是封闭的。Current Pattern:
Proposed Fix: 将 key extraction 注册到 adapter registry:
Estimated Impact: 新增平台只需调用
Register,无需修改任何共享层代码Acceptance Criteria:
ExtractPlatformKeys逻辑移入各平台 adaptermake test零回归SOLID: ISP
sessionmanager-empty-interface
Severity: Medium | Confidence: High | ROI: High
Location:
platform_adapter.go:157Problem:
type SessionManager any是空接口 — 没有定义任何方法。它被存储在PlatformAdapter.sm和AdapterConfig中,但从未通过此类型调用任何方法。实际的 session 操作通过SessionStarter接口完成。这个空接口:Current Pattern:
Proposed Fix: 移除
SessionManager类型和所有引用:Estimated Impact: 消除 dead dependency,简化 adapter 构造
Acceptance Criteria:
SessionManager类型定义移除PlatformAdapter.sm字段移除AdapterConfig.SM字段移除make test零回归Coupling
bidirectional-bridge-platformadapter-dependency
Severity: Medium | Confidence: High | ROI: Medium
Location:
platform_adapter.go:132+bridge.go:27,61-68Problem: Bridge 和 PlatformAdapter 存在双向依赖:
PlatformAdapter持有*Bridge(直接引用)Bridge通过atomic.Value持有PlatformAdapterInterface(延迟设置)PlatformAdapter.ConfigureWith接收*Bridge作为参数Bridge.SetAdapter设置 adapter 到 atomic.Value初始化顺序是:创建 Adapter → ConfigureWith(Bridge) → Bridge.SetAdapter(Adapter)。如果顺序错误,会产生 nil pointer panic。这是通过约定而非类型系统保证的脆弱初始化。
Current Pattern:
Proposed Fix: 配合 C1 修复(引入 BridgeInterface),将双向依赖改为单向:
Estimated Impact: 消除 fragile initialization order
Acceptance Criteria:
PlatformAdapter.bridge类型改为BridgeInterfacemake test零回归Implementation Priority
Recommended starting point: sessionmanager-empty-interface — 最小改动(删除 dead code),最高回报
Out of Scope
Verification
make test通过,无回归make lint不产生新警告go test -race ./internal/messaging/...通过