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

在这里插入图片描述

《软件设计的哲学》(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.rhkb.cn/news/495189.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Centos7.9安装openldap+phpldapadmin+grafana配置LDAP登录最详细步骤 亲测100%能行

一、部署LDAP 1、安装LDAP yum install -y openldap-servers openldap-clients openldap openldap-devel compat-openldap openldap-servers-sql systemctl start slapd systemctl enable slapd2、创建第一个管理账号密码(设置为ldapadmin) slappass…

【MySQL基础篇】多表查询(隐式/显式内连接、左/右外连接、自连接查询、联合查询、标量/列/行/表子查询)

Hiヽ(゜▽゜ )-欢迎来到蓝染Aizen的CSDN博客~ 🔥 博客主页: 【✨蓝染 の Blog😘】 💖感谢大家点赞👍 收藏⭐ 评论✍ 文章目录 MySQL基础篇-多表查询一、多表关系1. 一对多2. 多对多3. 一对一 二、多表查询…

【踩坑记录】C编程变量未初始化导致的程序异常

1、在编程的时候养成良好的习惯,定义变量以后记得给变量初始化,不然可能会产生一些意想不到的Bug。 2、比如下面的例子,如果定义的变量没有被初始化就有可能是一个随机值。如果代码少还好,很容易排查出来。但如果是一个比较大的项…

如何查看pad的console输出,以便我们更好的进行调试,查看并了解实际可能的问题。

1、以下是baidu AI回复: 2、说明: 1)如果小伙伴们经常做android开发的话,这个不陌生,因为调试都是要开启这个开发者模式。并启用USB调试模式。 2)需要连上USB线,有的时候会忘记,然…

c++ [spdlog 配置与使用]

一、 下载spdlog https://codeload.github.com/gabime/spdlog/zip/refs/heads/v1.x spdlog链接 二、配置工程编译,和eigen库类似spdlog无需单独编译 拷贝到工程目录下 配置目录 稍微封装一下符合qDebug() 使用习惯 /* ** File name: LogSystem.h ** Auth…

leetcode-80.删除有序数组的重复项II-day12

总结:不必过于死磕一道题目,二十分钟没做出来就可参考题解

ES已死,文本检索永生

长期以来,混合查询(Hybrid Search)一直是提升 RAG(Retrieval-Augmented Generation)搜索质量的重要手段。尽管基于密集向量(Dense Embedding)的搜索技术随着模型规模和预训练数据集的不断扩展&a…

【Web】2024“国城杯”网络安全挑战大赛决赛题解(全)

最近在忙联通的安全准入测试,很少有时间看CTF了,今晚抽点时间回顾下上周线下的题(期末还没开始复习😢) 感觉做渗透测试一半的时间在和甲方掰扯&水垃圾洞,没啥惊喜感,还是CTF有意思 目录 Mountain ez_zhuawa 图…

VS2022 中的 /MT /MTd /MD /MDd 选项

我们有时编译时,需要配置这个 运行库,指定C/C++运行时库的链接方式。 如下图 那么这些选项的含义是什么? /MT:静态链接多线程库 /MT选项代表“Multi-threaded Static”,即多线程静态库。选择此选项时,编译器会从运行时库中选择多线程静态连接库来解释程序中的代码,…

前端常用算法集合

使用前介绍 不借助临时变量,交换整数 加减乘除法 注意:如果是浮点数,对于加减乘除法需要注意浮点数的精度丢失问题。 对象法 数组法 数组去重 方法1(filter,推荐使用) 方法2(新数组法&#xff…

保护模式基本概念

CPU 架构 RISC(Reduced Instruction Set Computer) 中文即"精简指令集计算机”。RISC构架的指令格式和长度通常是固定的(如ARM是32位的指令)、且指令和寻址方式少而简单、大多数指令在一个周期内就可以执行完毕 CISC&…

62.基于SpringBoot + Vue实现的前后端分离-驾校预约学习系统(项目+论文)

项目介绍 伴随着信息技术与互联网技术的不断发展,人们进到了一个新的信息化时代,传统管理技术性没法高效率、容易地管理信息内容。为了实现时代的发展必须,提升管理高效率,各种各样管理管理体系应时而生,各个领域陆续进…

【零基础保姆级教程】制作自己的数据集(二)——Labelme的安装与使用及常见的报错解决方法

前段时间安装了Labelimg,网上有些博客写着Labelme能进行语义分割的标注,但UI窗口就那么大找不着选项,只能打矩形框,为了能够标注自己的分割数据集,遂写下该教程以供参考。 采用Labelimg进行目标检测标注的教程如下。 …

详解磁盘IO、网络IO、零拷贝IO、BIO、NIO、AIO、IO多路复用(select、poll、epoll)

1、什么是I/O 在计算机操作系统中,所谓的I/O就是输入(Input)和输出(Output),也可以理解为读(Read)和写(Write),针对不同的对象,I/O模式可以划分为…

无人设备遥控器之定向天线篇

一、定义与功能 定向天线,顾名思义,是通过改变天线的辐射方向,实现信号发射、接收和增强的天线。它可以让信号以更高的功率、更远的距离传输到指定区域,同时也能够降低与周围天线之间的干扰。在无人设备遥控器中,定向天…

老旧小区用电安全保护装置#限流式防火保护器参数介绍#

摘要 随着居民住宅区用电负荷的增加,用电安全问题日益突出,火灾隐患频繁发生。防火限流式保护器作为一种新型电气安全设备,能够有效预防因电气故障引发的火灾事故。本文介绍了防火限流式保护器的工作原理、技术特点及其在居民住宅区用电系统…

dify的ChatFlow自定义上传图片并通过HTTP请求到SpringBoot后端

前情提要 交互场景:dify的ChatFlow上传文件(本示例是单张图片),通过HTTP请求至SpringBoot后端dify版本:0.13.2python版本:3.12.7 1. 自定义上传变量 在【开始】节点自定义变量单文件上传变量file 2. 下接HTTP请求节点 BODY要…

【幼儿园识物】比大小启蒙资料PDF

下载链接::huanxigou-uihttp://yiwub.natapp1.cc/zyweb/#/source/viewPdf?id9

帧缓存的分配

帧缓存实际上就是一块内存。在 Android 系统中分配与回收帧缓存,使用的是一个叫 ION 的内核模块,App 使用 ioctl 系统调用后,会在内核内存中分配一块符合要求的内存,用户态会拿到一个 fd(有的地方也称之为 handle&…

SDMTSP:黑翅鸢算法(Black-winged kite algorithm,BKA)求解单仓库多旅行商问题,可以更改数据集和起点(MATLAB代码)

一、黑翅鸢算法BKA 黑翅鸢算法(Black-winged kite algorithm,BKA)由Wang Jun等人于2024年提出,该算法受黑翅鸢的迁徙和掠食行为启发而得。BKA集成了柯西突变策略和领导者策略,增强了算法的全局搜索能力,提…