后端与接口
用深模块、信息隐藏与战略性编程管理软件复杂度
在设计模块、评审 API、重构代码或做架构决策时,用一套可执行的复杂度管理原则来降低系统理解与修改成本。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
用深模块、信息隐藏与战略性编程管理软件复通用
解决思路
- 用「这个设计是增加还是减少系统整体复杂度?」来评估每个设计决策,目标不是零复杂度,而是最小化不必要的复杂度,并把必要的复杂度集中到可管理的地方。
- 识别复杂度的三种症状:变更放大(一个简单改动需要在多处修改)、认知负担(开发者必须同时记住太多信息)、未知的未知(不清楚什么需要改、哪些信息相关,这是最严重的症状)。
- 针对变更放大:集中共享知识,例如把颜色常量抽取出来,而不是在 20 个文件里硬编码 #ff0000。
- 针对认知负担:减少开发者必须知道的东西,例如用 open(path) 而不是要求调用方提供 buffer size、encoding、lock mode。
- 针对未知的未知:让依赖显式化,用类型系统和接口暴露一个改动会影响什么。
- 针对模糊性:精确命名,用 numBytesReceived 而不是 n,用 retryDelayMs 而不是 delay。
- 设计深模块:用简单接口隐藏强大功能,例如 file.read(path) 隐藏磁盘块、缓存、缓冲、编码等细节。
- 用「深度 = 提供的能力 / 强加的接口复杂度」判断模块是否值得存在;如果一个方法比你自己重新实现还难学,它就是净负值。
- 治疗 classitis:合并相关的浅类,例如把 RequestParser + RequestValidator + RequestProcessor 合并为一个 RequestHandler。
- 简化接口:减少参数和方法数量,例如用带合理默认值的 config.get(key),而不是 15 个构造函数参数。
- 隐藏信息:每个模块封装其他模块不需要知道的知识;如果两个模块共享知识,就合并它们,或新建一个模块来封装这份知识。
- 避免时间分解导致的泄漏:不要按「先读、再处理、后写」的时间顺序拆分模块,而应按知识组织代码,例如把「读取配置」和「应用配置」合并成一个配置模块。
- 集中序列化以消除格式泄漏:让一个模块负责 JSON 编码/解码,而不是到处调用 json.dumps。
- 抽象传输细节以消除协议泄漏:用 MessageBus.send(event) 隐藏 HTTP、gRPC 或队列的差异。
- 注意装饰器经常泄漏,因为它们会暴露被装饰的接口。
- 设计「适度通用」的模块:接口足够通用以支持多种用途,实现只处理当前需求;问自己「覆盖当前所有需求的最简接口是什么?」。
- 按概念而非单一用例设计 API,例如用 text.insert(position, string) 而不是 text.addBulletPoint()。
- 减少配置参数:能自动判断的行为就自动判断,例如自动检测文件编码,而不是暴露 encoding 参数。
- 避免过度特化:用一个通用方法代替多个特定方法,例如用 store(key, value, options) 而不是 storeUser()、storeProduct()、storeOrder()。
- 把复杂度向下推:让底层模块处理困难情况,使上层保持简单。
- 把注释当作设计文档:注释应描述代码中不明显的内容,包括设计意图、抽象理由、不变量和假设。
- 写四类注释:接口注释(最重要,定义抽象)、数据结构成员注释、实现注释、跨模块注释。
- 先写注释(注释驱动设计),在写代码前先厘清思路。
- 不要重复代码已经说清楚的内容;注释紧挨着它描述的代码,并与代码一起更新。
- 如果一条注释很难写,可能说明设计太复杂。
- 接口注释描述抽象而非实现,例如「返回距离 position 最近的 widget,若阈值内没有则返回 null」。
- 数据结构注释解释不变量,例如「列表按优先级降序排序;相同优先级按插入顺序」。
- 实现注释解释为什么而不是做什么,例如「// 二分查找:列表始终有序,可能容纳 10 万条以上」。
- 跨模块注释链接相关决策,例如「// 此超时必须与 RetryPolicy.java 中的重试间隔一致」。
- 采用战略性编程:多投入 10-20% 的精力在良好设计上,把每次改动都当作改进结构的机会。
- 抵制战术性陷阱:不要为「就这一个特殊情况」添加布尔参数,不要做快速而肮脏的修补。
- 在开发功能时改进结构,例如在添加功能的同时重构别扭的模块接口。
- 在设计评审中评估结构而不只是正确性,问「这是否让系统更简单?」而不只是「它能工作吗?」。
- 把重构当作每个功能的一部分,而不是特殊事件;每次改动都让代码比之前好一点。
- 避免直通方法:只把参数转发给另一个方法的方法只增加接口而不提供功能,应把它合并进调用方或被调用方。
- 用快速诊断表自检:能否用一句话描述每个模块?接口是否比实现简单?能否在不影响调用方的情况下改变实现?接口注释是否描述了抽象?代码评审是否讨论设计?每个模块是否隐藏了一个重要设计决策?新人能否在不读实现的情况下理解模块边界?是否花 10-20% 时间改进设计?
- 按八项快速诊断逐项打分(每项约 1.25 分),给出当前得分、未通过的诊断项,以及每项达到 10/10 所需的具体改动。
适用边界 · 注意事项
- 当任务只是修复局部 bug 或做一次性脚本、原型验证,且不打算长期维护时,不必强行套用完整的设计评审流程。
- 当系统规模很小、复杂度预算充足,或团队尚未建立设计评审习惯时,不要为了追求深模块而过度重构,以免拖慢交付。
- 不要把「适度通用」误解为提前为尚未出现的用例做抽象;当前需求不要求的通用性是投机性复杂度。