《软件设计的哲学》(A Philosophy of Software Design)是一本在软件架构与设计领域颇具影响力的书籍,作者 John Ousterhout 在书中分享了诸多深刻且实用的软件设计理念。书中列举的这些设计原则,汇聚了作者丰富的实战经验与深邃的思考,给软件开发者们提供了高屋建瓴的指导,助力大家在项目中打造出更具可维护性、扩展性的软件系统。
本书名称叫做“哲学”,其实更像是一本指导书,里面的具体原则比经常听到的“强内聚,弱耦合,单一原则“等等,更加具有实践指导意义。这里列举一些重要的原则要点16条:
序号 | 原则名称 |
---|---|
1 | 复杂性是渐进的,需关注细节(Complexity is incremental; you have to sweat the small stuff) |
2 | 工作代码并非足够(Working codes isn’t enough) |
3 | 持续进行小的投入以改进系统设计(Make continual small investments to improve system design) |
4 | 模块应具有深度(Modules should be deep) |
5 | 设计接口应使常见用法尽可能简单(Interfaces should be designed to make the most common usage as simple as possible) |
6 | 模块拥有简单接口比简单实现更重要(It’s more important for a module to have a simple interface than a simple implementation) |
7 | 通用目的模块应更具深度(General-purpose modules are deeper) |
8 | 分离通用目的和特定目的的代码(Separate general-purpose and special-purpose code) |
9 | 不同层次应具有不同的抽象(Different layers should be different abstractions) |
10 | 将复杂性向下拉(Pull complexity downward) |
11 | 定义错误使其不存在(Define errors out of existence) |
12 | 设计两次(Design it twice) |
13 | 注释应描述代码中不明显的内容(Comments should describe things that are not obvious from the code) |
14 | 软件应设计为易于阅读,而非易于编写(Software should be designed for ease of reading, not ease of writing) |
15 | 软件开发的增量应是抽象,而非功能(The increments of software development should be abstraction, not features) |
16 | 区分重要事项与不重要事项,并着重关注重要的部分(Separate what matters from what doesn’t matter and emphasize the things that matter.) |
1. 复杂性是逐步累积的;你必须关注细节 (Complexity is incremental; you have to sweat the small stuff)
- 含义与理解:软件系统的复杂性并非一蹴而就,而是像滚雪球一般,从项目伊始的细微之处慢慢积攒起来。起初看似微不足道的小决策、小代码片段,随着系统不断拓展与迭代,可能引发一系列连锁反应,使整体复杂度飙升。忽略细节,就如同埋下一颗颗定时炸弹,随时可能引爆大规模的技术难题。
- 示例:开发一款餐饮外卖 APP,最开始在设计订单备注功能时,如果没仔细考虑字符长度限制、特殊字符过滤这些小细节,短期内或许不影响使用。但当业务量增大,遇到一些恶意输入或者超长备注需求时,就可能导致数据库存储出错、订单处理流程紊乱,后续修复涉及多个模块联动,复杂性大幅提升。
2. 能运行的代码还远远不够 (Working codes isn’t enough)
- 含义与理解:仅仅实现代码的基本运行功能,只是迈出软件开发的第一步。软件并非一次性产品,后续的升级、维护以及功能拓展需求源源不断。若初期只为“跑起来”而仓促编码,忽略架构的合理性、代码的可扩展性与可维护性,后续项目生命周期里,每一次优化与新增功能都将举步维艰。
- 示例:一个简单的个人博客网站,早期用最简易的代码拼凑实现了文章发布和浏览。但随着博主想要添加社交分享按钮、评论区实时互动、多设备适配等功能时,由于一开始没有采用模块化架构、分层设计,代码搅成一团乱麻,新增功能的难度呈指数级增长,甚至可能要推翻重来。
3. 持续投入小成本来改进系统设计 (Make continual small investments to improve system design)
- 含义与理解:系统设计并非一劳永逸,而是需要在开发全程,不间断地投入少量精力与资源去优化。这些微小投入看似不起眼,却能在长期积累下,让系统的架构愈发稳固、高效,从容应对业务的动态变化,避免后期因架构老化而付出高昂的重构代价。
- 示例:以一款日程管理 APP 为例,每次小版本迭代时,开发团队不只是忙着堆砌新功能,还会抽出部分时间重构数据库查询语句,优化界面渲染逻辑。虽然每次改进的效果不那么显著,但随着时间推移,当用户量持续攀升,APP 在处理海量日程数据时依然流畅,新功能也能毫无阻碍地整合进去。
4. 模块应当有深度 (Modules should be deep)
- 含义与理解:模块的深度意味着它不应仅仅提供表面、单薄的功能,而是要深入挖掘业务需求,将相关的复杂功能聚合、强化,形成一套完整且强大的能力体系。有深度的模块更具内聚性,对外输出稳定、高效的服务,能显著提升整个系统的处理能力与灵活性。
- 示例:在视频编辑软件里,特效添加模块不是简单罗列几种预设特效,而是深挖视频处理算法,融合色彩校正、光影变幻、动态跟踪等技术。无论是制作炫酷的短视频,还是专业影视剪辑,它都能深度满足创作者对画面特效的精细调控需求。
5. 接口的设计应使最常见的用法尽可能简单 (Interfaces should be designed to make the most common usage as simple as possible)
- 含义与理解:接口作为模块与外界交互的桥梁,其设计优劣直接影响系统集成的难易程度。聚焦于最普遍的使用场景,把接口设计得简洁易懂,能极大降低其他模块与之对接的成本与门槛,提升开发效率,促进整个系统各部分的协同工作。
- 示例:对于地图导航 SDK,众多开发者最常使用的功能就是定位与路径规划。优秀的 SDK 接口设计,只需开发者传入起点、终点位置信息,用极少的代码就能获取精准路线,屏蔽掉后台复杂的地图数据解析、算法优化过程,让接入过程轻松流畅。
6. 对于一个模块而言,拥有简单的接口比拥有简单的实现更重要 (It’s more important for a module to have a simple interface than a simple implementation)
- 含义与理解:模块内部实现或许涉及复杂高深的算法与逻辑,但对外部使用者而言,他们无需关心这些内部细节。提供简单、清晰的接口,能够隐藏内部复杂性,让其他模块轻松调用,实现系统的低耦合,便于不同团队并行开发,保障整体架构的灵活性与扩展性。
- 示例:在人工智能图像识别模块中,内部可能运用了深度卷积神经网络,训练过程繁杂且算力消耗巨大。但对外暴露的接口,仅需接收图片路径,就能直接返回识别结果,把复杂的运算封装起来,其他开发者不用钻研图像识别底层技术,就能快速集成该功能。
7. 通用模块更具深度 (General-purpose modules are deeper)
- 含义与理解:通用模块旨在服务多种不同的业务场景,需要应对各式各样的需求变化,因此必须深挖功能、容纳丰富逻辑,构建起强大的通用能力。通用性越强,模块内部整合的知识、技术就越密集,深度也就自然显现出来。
- 示例:在各类软件开发项目里,日志记录模块属于通用模块。它不仅要记录基本的时间、事件信息,还要支持不同级别日志分类、多线程安全写入、远程日志同步等复杂功能,以适配从桌面小程序到大型分布式系统的全方位需求。
8. 区分通用代码与专用代码 (Separate general-purpose and special-purpose code)
- 含义与理解:通用代码具备复用潜力,能在多个不同场景、模块中发光发热;专用代码则服务于特定业务需求,针对性强。明确划分二者,能让代码库条理清晰,便于维护与管理,一方面提高通用代码的复用率,另一方面精准优化专用代码,互不干扰。
- 示例:电商平台的商品展示页面,有通用的图片加载、布局渲染代码,这些可以抽离成通用模块,应用到其他商品分类甚至不同电商项目。而针对限时折扣商品特有的倒计时、闪烁特效代码,属于专用代码,单独封装,方便后续修改折扣逻辑,不影响通用展示模块。
9. 不同的层级应有不同的抽象 (Different layers should be different abstractions)
- 含义与理解:软件架构分层构建,各层级肩负不同使命,对应不同的抽象层次。底层靠近硬件或基础资源,抽象程度低,处理具体事务;高层则聚焦业务逻辑,抽象程度高,从宏观视角统筹调度。合理分层与抽象,让系统层次分明,易于理解、开发与维护。
- 示例:在企业级 ERP 系统里,底层数据库访问层直接操作数据表,执行增删改查,极为具象;中间业务逻辑层将底层操作抽象成订单处理、库存管理等业务单元;最上层的用户界面层,从用户视角抽象出便捷的操作流程与可视化界面,各层级各司其职。
10. 降低复杂性 (Pull complexity downward)
- 含义与理解:随着软件系统的成长,复杂性悄然攀升,而开发者要主动出击,运用合理的架构调整、代码重构等手段,把复杂的逻辑梳理清晰,隐藏不必要的细节,下沉复杂实现,让核心业务流程简洁明了,提升系统的稳健性与可读性。
- 示例:一款在线文档编辑工具,初始版本为兼容多种文档格式,代码充斥着大量格式转换的复杂判断与嵌套逻辑。后期重构时,把格式转换部分封装成独立底层模块,上层编辑操作只调用简洁接口,简化了编辑流程,降低了整体复杂性。
11. 将错误消灭在定义阶段 (Define errors out of existence)
- 含义与理解:在软件开发前期,无论是需求分析、架构设计,还是接口定义环节,都要严谨地梳理规则、限定边界,提前预料可能出现的错误场景,并制定防范措施。从源头上把控,远比在测试、上线后再来处理错误成本更低、效果更好。在实现过程中,也要注意,一旦出现异常,就地解决,尽量减少向外或者向上抛出,减少异常处理的链条,减少复杂性。
- 示例:开发在线考试系统,在需求定义时就明确规定答题时间格式、答案提交规则,代码编写伊始便严格校验输入合法性。如此一来,考试过程中因格式错误、非法提交引发的系统故障就能被扼杀在萌芽状态。
12. 进行二次设计 (Design it twice)
- 含义与理解:软件首次设计受限于时间、认知等因素,很难尽善尽美。在获取一定的用户反馈、积累实际运行数据后,进行二次设计,基于真实场景重新审视架构、优化流程,能让软件更贴合市场需求,弥补前期不足,延长软件生命周期。
- 示例:出行打车 APP 初次上线时,为抢时间匆忙确定了派单算法与司机乘客匹配机制。运营一段时间,收集大量数据与用户投诉后,二次设计优化算法,综合考虑距离、路况、司机服务评价等因素,大幅提升用户打车体验。
13. 注释应当描述那些从代码中看不出来的内容 (Comments should describe things that are not obvious from the code)
- 含义与理解:代码虽然是程序员之间沟通的主要方式,但有些关键决策背景、设计意图仅凭代码难以呈现。注释就起到补充说明的作用,为后续维护者、协作者点明代码背后隐藏的业务考量、技术权衡,避免他人花费大量时间去揣测代码意图。
- 示例:一段加密算法代码里,选用了一种非标准加密方式。代码里只能看到算法实现流程,而注释详细说明是因为项目特定的安全合规要求、数据传输环境限制,才采用该小众算法,让接手者瞬间明晰缘由。
14. 软件设计应着眼于便于阅读,而非便于编写 (Software should be designed for ease of reading, not ease of writing)
- 含义与理解:软件开发是团队协作活动,一份代码在项目存续期会历经多人之手。易于阅读的代码,遵循规范命名、清晰分层、简洁逻辑,能让新成员迅速融入,理解业务流程,降低沟通成本。过于追求编写时的个人便利,会牺牲代码可读性,给后续维护带来巨大困扰。
- 示例:大型开源项目代码库,变量命名全部采用语义化词汇,函数按功能模块清晰分组,代码注释详尽。即使新手加入团队,顺着代码结构与注释,也能较快搞清楚核心逻辑,投入开发工作。
15. 软件开发的增量应该是抽象,而非功能特性 (The increments of software development should be abstraction, not features)
- 含义与理解:单纯不断堆砌新功能,会让软件陷入无序扩张,臃肿不堪。以抽象作为增量,意味着从现有功能中提炼通用模式、架构模式,用更高级的抽象框架去整合功能,提升软件的柔韧性与扩展性,从容应对未来变化。
- 示例:一款音乐播放 APP,初期陆续上线本地播放、在线播放、歌单创建等功能。后续开发若以抽象为导向,提炼出媒体资源管理抽象层,就能轻松兼容新的音频格式、流媒体协议,而不是孤立地添加一个个播放功能。
16. 区分重要事项与不重要事项,并着重关注重要的部分 (Separate what matters from what doesn’t matter and emphasize the things that matter.)
- 含义与理解:软件项目资源有限,无论是开发时间、人力,还是服务器算力。精准甄别核心业务流程、关键用户需求,把资源集中投入,保障关键部分稳定、高效运行,适当弱化次要环节,才能在资源约束下实现最优产出。
- 示例:电商大促期间,电商平台最关键的是保障订单提交、支付成功、库存扣减这些核心链路顺畅无阻,页面上一些非关键的广告位更新、个性化推荐微调等非核心事务可以暂缓,集中火力服务好抢购用户。