《软件设计的哲学》阅读摘要之设计原则

devtools/2024/12/27 0:35:31/

在这里插入图片描述

软件设计的哲学》(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.)

  • 含义与理解:软件项目资源有限,无论是开发时间、人力,还是服务器算力。精准甄别核心业务流程、关键用户需求,把资源集中投入,保障关键部分稳定、高效运行,适当弱化次要环节,才能在资源约束下实现最优产出。
  • 示例:电商大促期间,电商平台最关键的是保障订单提交、支付成功、库存扣减这些核心链路顺畅无阻,页面上一些非关键的广告位更新、个性化推荐微调等非核心事务可以暂缓,集中火力服务好抢购用户。

http://www.ppmy.cn/devtools/145344.html

相关文章

细说STM32F407单片机轮询方式读写SPI FLASH W25Q16BV

目录 一、工程配置 1、时钟、DEBUG 2、GPIO 3、SPI2 4、USART6 5、NVIC 二、软件设计 1、FALSH (1)w25flash.h (2) w25flash.c 1)W25Q16基本操作指令 2)计算地址的辅助功能函数 3)器…

Java实现Excel带层级关系的数据导出功能

在Java中实现Excel带层级关系的数据导出,可以使用Apache POI库。Apache POI是一个强大的API,用于操作各种基于Office Open XML标准(OOXML)和微软的OLE 2复合文档格式(OLE2)的文件格式,包括Excel…

workman服务端开发模式-应用开发-vue-element-admin挂载websocket

一、项目根目录main.js添加全局引入 import /utils/websocket 二、在根目录app.vue 中初始化WebSocket连接 <template><div id"app"><router-view /></div> </template><script>import store from ./store export default {n…

重温设计模式--观察者模式

文章目录 观察者模式&#xff08;Observer Pattern&#xff09;概述观察者模式UML图作用&#xff1a;实现对象间的解耦支持一对多的依赖关系易于维护和扩展 观察者模式的结构抽象主题&#xff08;Subject&#xff09;&#xff1a;具体主题&#xff08;Concrete Subject&#xf…

[react]不能将类型“string | undefined”分配给类型“To”。 不能将类型“undefined”分配给类型“To”

场景, 封装组件的时候, 想通过外部传进去一个路由地址, 再用<Link to{}>跳转, 显示这个, 有四种方法解决 第一种 合并运算符 ?? ?? 是 空值合并运算符&#xff08;Nullish Coalescing Operator&#xff09;&#xff0c;它是 JavaScript 和 TypeScript 中的一种逻辑…

逆向工程实战,在反汇编工具中理解汇编与伪C

前言 今天对某外国的机器中的某个音频解码库进行反汇编&#xff0c;发现了一些有趣的知识&#xff0c;故而记录下来&#xff0c;以防以后重复遇到。 现笔者使用的反汇编工具为“Ghidra”。可在GitHub上下载&#xff1a;NationalSecurityAgency/ghidra: Ghidra is a software …

隐马尔科夫模型|前向算法|Viterbi 算法

隐马尔可夫模型 (Hidden Markov Model, HMM) HMM 是一种统计模型&#xff0c;用于表示由一个隐藏的马尔可夫链生成的观测序列。它假设每个观测值依赖于当前的隐藏状态&#xff0c;并且隐藏状态之间的转换遵循马尔可夫性质&#xff08;即未来的状态仅依赖于当前状态&#xff0c…

迪文串口屏_T5L平台_界面状态图标显示和隐藏

迪文串口屏_T5L平台_界面状态图标显示和隐藏 参考迪文官方视频教程&#xff1a;http://inforum.dwin.com.cn:20080/forum.php?modviewthread&tid7240&fromuid42572 一、软件准备&#xff08;参考上一篇文章&#xff09; 二、打开项目&#xff0c;添加变量图标显示 三…