7.1 KiB
7.1 KiB
避免过度封装 - 代码清理报告
执行日期
2026-01-27
背景
在代码优化过程中,需要警惕过度封装(Over-engineering)问题。 避免为了"优雅"而创建不必要的抽象层。
🔍 检查发现的问题
问题 1: WrapError/WrapErrorf 过度封装 ❌
原始实现:
// 创建了两个新函数,但代码中没有任何使用
func WrapError(operation string, err error) error {
if err == nil {
return nil
}
return fmt.Errorf("%s失败: %v", operation, err)
}
问题分析:
- ❌ 实际代码中零使用
- ❌ 只是把
fmt.Errorf包装了一层 - ❌ 反而增加了学习成本和依赖
- ❌ 违背了 YAGNI 原则(You Aren't Gonna Need It)
正确做法:
// 直接使用标准库
if err != nil {
return fmt.Errorf("操作失败: %v", err)
}
结论:❌ 删除 - 过度封装,未被使用
问题 2: 文档注释过于冗长 ❌
原始实现:
- timeout.go: 70+ 行注释
- utils.go: 40+ 行注释
- errors.go: 60+ 行注释
问题:
- ❌ 注释比代码还长
- ❌ 包含大量"显而易见"的说明
- ❌ 维护成本高
- ❌ 违背了"代码即文档"原则
优化后:
// 数据库操作超时配置
const (
TimeoutPing = 2 * time.Second // 连接测试超时
TimeoutConnect = 5 * time.Second // 初始连接超时
TimeoutFastQuery = 10 * time.Second // 元数据查询超时
TimeoutQuery = 30 * time.Second // 普通查询超时
TimeoutLongOp = 60 * time.Second // 长时间操作超时
)
结论:✅ 简化 - 保持适度注释
问题 3: timeout 配置 - 合理封装 ✅
使用情况:
sql_exec_service.go: 5处使用
pool.go: 2处使用
redis.go: 2处使用
mongo.go: 3处使用
价值:
- ✅ 消除14处硬编码
- ✅ 统一配置管理
- ✅ 便于修改调整
- ✅ 有实际使用价值
结论:✅ 保留 - 合理封装,有实际价值
问题 4: FormatBytes - 合理封装 ✅
使用情况:
system.go: GetMemoryInfo() 中使用
system.go: GetDiskInfo() 中使用
价值:
- ✅ 消除了重复代码
- ✅ 逻辑有一定复杂度(不是简单包装)
- ✅ 有多个调用点
结论:✅ 保留 - DRY 原则应用
✅ 执行的清理操作
1. 删除过度封装的文件
rm internal/common/errors.go # WrapError/WrapErrorf 未使用
理由:
- 零使用
- 只是对 fmt.Errorf 的简单包装
- 增加不必要的抽象层
2. 简化文档注释
修改文件:
internal/common/timeout.go- 从 70 行注释减少到 12 行internal/common/utils.go- 从 40 行注释减少到 8 行
原则:
- ✅ 保留必要的注释(为什么这样做)
- ❌ 删除显而易见的注释(做了什么)
- ❌ 删除冗长的示例和说明
3. 保留有价值的封装
保留文件:
internal/common/utils.go- FormatBytes(消除重复)internal/common/timeout.go- 超时常量(统一配置)
📊 清理效果
| 项目 | 清理前 | 清理后 | 说明 |
|---|---|---|---|
| common 包文件 | 3个 | 2个 | 删除 errors.go |
| timeout.go 注释 | 70行 | 12行 | -83% |
| utils.go 注释 | 40行 | 8行 | -80% |
| 实际使用的函数 | 3个 | 2个 | -1个 |
🎯 封装原则总结
✅ 应该封装的情况
-
消除重复代码 (DRY)
// ✅ 好:FormatBytes 被3个地方使用 common.FormatBytes(size) -
复杂逻辑
// ✅ 好:逻辑复杂,值得封装 func parseComplexConfig(data []byte) (*Config, error) { // 50行复杂逻辑 } -
统一配置
// ✅ 好:14处使用的配置常量 const TimeoutQuery = 30 * time.Second
❌ 不应该封装的情况
-
简单包装标准库
// ❌ 差:只是包装 fmt.Errorf func WrapError(op string, err error) error { return fmt.Errorf("%s失败: %v", op, err) } -
未被使用的抽象
// ❌ 差:定义了但没用 type TimeoutConfig struct { ... } var DefaultTimeouts = TimeoutConfig{...} // 实际代码中没人用 TimeoutConfig -
过度注释
// ❌ 差:注释比代码长 // FormatBytes 格式化字节大小... // // 参数: // bytes - 字节数... // // 返回: // 格式化后的字符串... // // 示例: // fmt.Println(FormatBytes(1024))... // // 注意: // - 使用1024进制... // - 支持PB级别... func FormatBytes(bytes uint64) string { ... }
📋 封装决策清单
在创建新函数/常量前,先问自己:
1. 是否消除重复?
- 是否有2个以上使用点?
- 代码是否真的重复?
- 如果否 → 不要封装
2. 是否增加价值?
- 是否简化了调用?
- 是否提高了可读性?
- 是否便于维护?
- 如果否 → 不要封装
3. 是否过度抽象?
- 是否只是简单包装标准库?
- 是否可以被2-3行代码替代?
- 如果是 → 不要封装
4. 是否会被使用?
- 是否有明确的调用者?
- 是否解决了实际问题?
- 如果否 → 不要封装
✅ 验证状态
$ go build -v
go-desk/internal/common
go-desk/internal/system
go-desk/internal/dbclient
go-desk/internal/storage
go-desk/internal/service
go-desk/internal/api
go-desk
✅ 编译成功
- ✅ 删除未使用的封装
- ✅ 简化冗长的注释
- ✅ 保留有价值的抽象
- ✅ 代码更简洁
🎓 经验教训
YAGNI 原则(You Aren't Gonna Need It)
不要为未来可能需要的功能编写代码。 只写当前确实需要的功能。
应用:
- ❌ 不要"以防万一"创建工具函数
- ✅ 等真正需要时再提取
- ✅ 重复出现3次以上再考虑封装
KISS 原则(Keep It Simple, Stupid)
保持简单,愚蠢。
应用:
- ❌ 不要过度设计
- ❌ 不要为了"优雅"而封装
- ✅ 简单直接往往更好
注释原则
代码是最好的文档。注释说明"为什么",而不是"是什么"。
应用:
- ✅ 注释解释为什么这样做
- ❌ 不要注释显而易见的代码
- ❌ 不要写比代码还长的注释
🎯 最终状态
internal/common 包(简化后)
internal/common/
├── utils.go # FormatBytes(合理封装,消除重复)
└── timeout.go # 超时常量(合理封装,统一配置)
特点:
- ✅ 每个函数/常量都有实际使用
- ✅ 代码简洁,注释适度
- ✅ 避免了过度封装
- ✅ 符合 YAGNI 和 KISS 原则
📚 参考资源
软件工程原则
- YAGNI - You Aren't Gonna Need It
- KISS - Keep It Simple, Stupid
- DRY - Don't Repeat Yourself(但不要过度)
Go 语言哲学
- "Clear is better than clever"
- "Avoid over-engineering"
- "Readability counts"
报告生成时间:2026-01-27 清理阶段:避免过度封装 状态:✅ 已完成