重点摘要
1. 复杂性是软件设计挑战的根源
复杂性源于依赖关系和模糊性的积累。
复杂性逐步积累。 随着软件系统的增长,由于组件之间依赖关系和模糊代码段的逐渐积累,系统往往变得更加复杂。这种复杂性主要体现在三个方面:
- 变更放大:小的变更需要在许多地方进行修改
- 认知负荷:开发人员需要理解大量信息才能进行修改
- 未知的未知:不清楚需要修改哪些代码或哪些信息是相关的
简化是解药。 为了对抗复杂性,软件设计师应专注于创建简单、明显的设计,尽量减少依赖关系和模糊性。这包括:
- 模块化设计:将系统划分为独立的模块
- 信息隐藏:将实现细节封装在模块内
- 清晰的抽象:提供隐藏底层复杂性的简单接口
2. 战略编程优于战术方法
最好的方法是持续进行大量的小投资。
长期思维带来更好的结果。 战略编程专注于创建一个恰好能工作的优秀设计,而不仅仅是让代码工作。这种方法包括:
- 在设计上投入时间
- 持续进行小改进
- 重构代码以保持设计的清洁
战术编程导致技术债务。 虽然战术方法在短期内看起来更快,但它们通常会导致:
- 快速修复和黑客手段的积累
- 随着时间的推移,修改变得越来越困难
- 更高的长期开发成本
通过采用战略思维,开发人员可以创建更易于维护和演化的系统,从而在长远上节省时间和精力。
3. 模块应当深而不浅
最好的模块是那些提供强大功能但接口简单的模块。
深度创造抽象。 深度模块在简单接口背后隐藏了显著的实现复杂性。这种方法:
- 减少模块用户的认知负荷
- 使实现的修改更容易
- 促进信息隐藏和封装
浅层模块增加复杂性。 相对于其功能,接口复杂的模块被认为是浅层的。这些模块:
- 增加了整体系统的复杂性
- 暴露了不必要的实现细节
- 使系统更难理解和修改
为了创建深度模块,专注于设计简单、直观的接口,抽象掉底层复杂性。努力最大化功能与接口复杂性的比率。
4. 良好的接口是管理复杂性的关键
模块的接口包含两种信息:正式的和非正式的。
设计良好的接口简化系统。 良好的接口提供了模块功能的清晰抽象,而不暴露不必要的细节。它们应当:
- 简单且直观易用
- 隐藏实现复杂性
- 提供正式(如方法签名)和非正式(如高层行为描述)信息
接口应当谨慎演化。 修改现有代码时:
- 考虑对模块接口的影响
- 避免暴露实现细节
- 努力维护或改进接口提供的抽象
通过专注于创建和维护良好的接口,开发人员可以管理复杂性,使系统更加模块化和易于理解。
5. 注释对于创建抽象至关重要
注释是完全捕捉抽象的唯一方式,而良好的抽象是良好系统设计的基础。
注释完成抽象。 虽然代码可以表达实现细节,但注释对于捕捉:
- 高层设计决策
- 选择背后的理由
- 期望和约束
- 从代码中不明显的抽象
先写注释。 通过在实现代码之前编写注释:
- 你可以澄清对设计的思考
- 你可以早期评估和改进抽象
- 你可以确保文档始终是最新的
关注什么和为什么,而不是如何。 良好的注释应当:
- 描述代码中不明显的内容
- 解释代码的目的和高层行为
- 避免仅仅重复代码的功能
通过优先编写清晰、信息丰富的注释,开发人员可以创建更好的抽象,改进系统的整体设计。
6. 一致的命名和格式增强可读性
好的名字是一种文档形式:它们使代码更易于理解。
一致性减少认知负荷。 通过建立和遵循命名和格式约定,开发人员可以:
- 使代码更可预测和易读
- 减少理解代码所需的脑力劳动
- 突出可能表明错误或设计问题的不一致性
仔细选择名字。 好的名字应当:
- 精确且明确
- 创建被命名实体的清晰形象
- 在整个代码库中一致使用
格式很重要。 一致的格式有助于:
- 使代码结构更明显
- 视觉上分组相关元素
- 强调重要信息
通过关注命名和格式,开发人员可以显著提高代码的可读性和可维护性。
7. 持续改进对于保持设计清洁至关重要
如果你想要一个干净的软件结构,使你能够长期高效工作,那么你必须在前期花一些额外的时间来创建这个结构。
设计是一个持续的过程。 干净的软件设计需要:
- 定期重构以改进现有代码
- 持续评估设计决策
- 随着系统演化愿意做出改变
投资于改进。 为了保持设计清洁:
- 分配时间进行清理和重构
- 及时解决设计问题,防止它们积累
- 将每次代码更改视为改进整体设计的机会
平衡完美和进步。 在追求干净设计时:
- 认识到有时需要妥协
- 专注于进行渐进改进
- 优先考虑带来最大收益的更改
通过将设计视为一个持续改进的过程,开发人员可以在系统增长和演化时保持其清洁和可管理性。
8. 错误处理应当简化而非繁殖
消除异常处理复杂性的最佳方法是定义你的API,使其没有需要处理的异常:将错误定义为不存在。
减少异常情况。 为了简化错误处理:
- 设计API以最小化异常条件
- 使用默认行为处理常见边缘情况
- 考虑异常是否真的必要
聚合错误处理。 当异常不可避免时:
- 尽可能在一个地方处理多个异常
- 使用异常层次结构简化相关错误的处理
- 避免捕获你无法有意义处理的异常
使正常情况变得简单。 专注于使代码中的常见、无错误路径尽可能简单和明显。这种方法:
- 减少开发人员的认知负荷
- 最小化引入错误的可能性
- 使代码更易于理解和维护
通过简化错误处理,开发人员可以创建更健壮且更易于理解的系统。
9. 通用代码通常优于专用解决方案
即使你以专用方式使用一个类,构建它为通用方式的工作量更少。
通用性促进重用。 通用代码:
- 可以应用于更广泛的问题
- 通常更简单和抽象
- 往往具有更清晰的接口
避免过早专用化。 在设计新功能时:
- 从某种程度上的通用方法开始
- 抵制过早优化特定用例的冲动
- 根据实际使用模式演化设计
平衡通用性和简洁性。 在追求通用解决方案时:
- 避免过度设计或增加不必要的复杂性
- 确保通用设计仍然易于用于常见情况
- 在真正必要时愿意创建专用解决方案
通过偏向通用设计,开发人员可以创建更灵活和可维护的系统,更好地应对未来的需求。
10. 编写易读的代码,而不是易写的代码
软件应当设计为易读,而不是易写。
优先考虑长期可维护性。 在编写代码时:
- 专注于使其易于未来的读者理解
- 避免使代码目的模糊的捷径或巧妙技巧
- 投资时间创建清晰的抽象和文档
使代码显而易见。 努力编写:
- 可以快速理解且脑力劳动最小的代码
- 使用清晰且一致的命名约定
- 具有逻辑且易于遵循的结构
为清晰性重构。 定期审查和改进现有代码:
- 寻找简化复杂部分的机会
- 将长方法分解为更小、更专注的部分
- 消除重复和不一致
通过优先考虑可读性而非易写性,开发人员可以创建更易于维护、调试和扩展的系统。这种方法可能需要初期更多的努力,但在减少长期复杂性和提高团队生产力方面会有所回报。
最后更新日期:
评论
《软件设计哲学,第二版》收到了褒贬不一的评价。许多人赞扬其在管理复杂性和设计深层模块方面的见解,而有些人则批评其对注释的强调以及在某些领域缺乏深度。读者们欣赏其清晰的写作和实用的建议,特别是对于新手开发者。然而,一些有经验的程序员认为它过于基础,或者不同意某些建议。书中对面向对象编程的关注和学术视角得到了注意,有些人希望能看到更多样化的语言示例和实际应用。