读书笔记《A Philosophy of Software Design - John Ousterhout 软件设计哲学》
软件设计哲学这本书很薄,值得一读。这本书将大家平时碰到的很多软件问题从更深刻的层面进行了抽象分析,同时又给出了具体的解决方案。可以说既有理论高度,又能贴近实践。
但针对软件问题,这本书并没有提出太多与众不同的解决方案,讲的绝大多数方案都是大家比较熟悉的。也就是说技术大家都有,没有做好只是缺少追求卓越的态度。当然造成这种后果是整个环境造成的,并不完全是开发人员的问题。
这本书唯一跟大家平时理解有点偏差的地方就是本书认为模块应该是“深的”,即简单的接口,复杂的实现。这个思路从理论层面很显然是对的,但实际上很难做到。因为软件要想达到足够的灵活性,很难不出现“浅”的模块,比如框架、各种设计模式几乎都是“浅”的。而且模块复杂后,很难不通过拆分、解耦等方式分割模块,使模块没有原来那么“深”。这样降低了复杂模块的复杂性,但很有可能提升了整个系统的复杂性。
2 复杂性的本质
2.1 复杂性的定义
2.2 复杂性的症状
- 变更放大:看似简单的变更需要在许多不同的地方进行代码修改。
- 认知负载:开发人员需要知道多少才能完成任务。较高的认知负荷意味着开发人员必须花费更多的时间学习所需的信息,并且由于遗漏了重要的东西,因此出现错误的风险更大。认知负载以多种方式出现,例如具有多种方法的API、全局变量、不一致性和模块之间的依赖性。
- 未知的未知数:不清楚要完成一个任务,必须修改哪些代码,或者开发人员成功地执行该任务,必须包含哪些信息。未知的未知是最糟糕的。
2.3 复杂性的产生原因
2.4 复杂性是递增的
3 战略与战术编程
4 模块应该是深的
模块化设计的目标是最小化模块之间的依赖关系。
5 信息隐藏
6 通用模块是更深的
7 不同层次,不同抽象
8 把复杂性往下拉
9 合并还是分离
- 细分通常会导致更多的接口,并且每个新接口都会增加复杂性。
- 细分需要产生额外的代码来管理组件。
- 细分产生分离:细分的组件将比细分前更远地分开。分离使得开发人员很难同时看到组件,甚至意识到它们的存在。如果组件是真正独立的,那么分离是好的:它允许开发人员一次只关注单个组件,而不会被其他组件分散注意力。另一方面,如果组件之间有依赖关系,那么分离就不好了:开发人员最终会在组件之间来回切换。更糟糕的是,他们可能不知道依赖关系,这可能导致错误。
- 细分会导致重复:细分之前在单个实例中存在的代码可能需要存在于每个细分组件中。
- 它们共享信息
- 它们一起使用,使用其中一段代码的任何人都可能使用另一段代码,注意指的是双向关系
- 它们在概念上重叠,有一个包含两个代码段的简单高级类别
- 不看另一段代码就很难理解其中一段代码
如果同一段代码(或几乎相同的代码)反复出现,那就是你还没有找到正确的抽象。
10 异常处理
异常不成比例地增加复杂性,不应该简单的抛出异常给调用者,将复杂性转移出去,应该减少必须处理异常的地方的数量,在许多情况下,可以更改操作的语义,以便正常行为处理所有情况,并且没有例外条件可以报告。
- 定义没有异常的API,重定义语义或将异常在API内部处理掉。比如一个unset命令,如果语义是删除一个变量时,如果这个变量不存在,那么必然应该产生异常,但如果语义是确保一个变量无效时,如果变量不存在直接返回即可。
- 异常屏蔽,在底层检测和处理异常,上层就不用感知异常。比如TCP丢包重发。
- 异常聚合,不是为许多单个异常编写不同的处理程序,而是使用单个处理程序在一个地方处理它们。比如更上层捕获异常,但要注意封装与信息隐藏,即上层的异常处理是通用设计,是用一个可以处理多种情况的单一通用机制来代替几个专门针对特定情况的机制。
- 崩溃应用程序,针对一些不值得尝试处理的错误。
- 定义没有特殊场景的API,非异常的一些特殊状态也跟异常类似,比如需要判断是否跳过某个状态,如果把这个状态转换成跟其它状态统一的逻辑(比如指定个无影响的值或变量),那么就不需要判断。
- 注意上面的方案都是在异常可以不成为接口的前提下,如果上层必须知道是否发生了异常,那么即使增加了复杂性,也必须暴露异常。
11 设计两次
12 为什么写注释
注释背后的总体思想是捕获设计者头脑中无法在代码中表示的信息。这些信息从底层的细节(如激发特别棘手的代码的硬件问题)到高级概念(如类的基本原理)都有。当其他开发人员稍后前来进行修改时,这些注释将允许他们更快、更准确地工作。
13 注释应该描述从代码中看不出的东西
注释的最重要原因之一是抽象,它包含许多从代码中看不出来的信息。抽象的概念是提供一种简单的思考方式,但是代码非常详细,仅仅通过阅读代码很难看到抽象。注释可以提供更简单、更高级别的视图。
14 选择名称
15 先写注释
16 修改现有代码
- 保持战略性,见第三章,及时重构而不是快速修复。
- 维护注释,让注释尽量靠近代码,这样修改代码时就容易看到注释并修改。
- 注释属于代码而不是提交日志,修改代码时的一个常见错误是将有关更改的详细信息放在提交消息中提供给源代码存储库,而不是将其记录在代码中。
- 避免注释重复,这样很难找到所有的注释全部修改。
- 检查注释差异,提交前花几分钟扫描该提交的所有更改,确保每个更改都适当地反映在文档中。
- 高级别的注释更易于维护。
17 一致性
一致性体现在名字、编程规范、抽象接口、设计模式等。
18 代码应该是显而易见的
- 事件驱动,应该辅助额外的文档或注释,非显性代码如果通过快速阅读无法理解代码的含义和行为,这通常意味着,对于阅读代码的人来说,有一些重要的信息是不能立即清楚的;
- 通用容器,封装成更有意义的专用容器,一般规则:软件的设计应该是为了便于阅读,而不是便于写作;
- 声明和分配的类型不同;
- 违反读者期望的代码,如果代码符合读者所期望的约定,那么代码就非常明显;如果代码不符合读者所期望的约定,那么记录这种行为就很重要,这样读者就不会感到困惑。
19 软件趋势
- 面向对象,不推荐实现继承,依赖严重
- 敏捷开发,更倾向于战术性编程,应该更注重抽象和设计,即使推迟设计决策,但一旦需要抽象时,也应该花时间设计更合理的通用机制
- 单元测试,对重构极为重要
- 测试驱动开发,不推荐,战术性编程
- 设计模式,一致性,通用解决方案,不要过度使用
- Getter&Setter,不推荐,浅层函数
最重要的软件设计原则:
- 复杂性是递增的:你必须为小事出汗(见第11页)
- 仅能工作的代码是不够的(见第14页)
- 持续小投入,改善系统设计(见第15页)
- 模块应是深的(见第22页)
- 接口的设计应满足大部分的普通场景下应用更简单(见第27页)
- 模块拥有简单的接口比简单的实现更为重要(见第55页、第71页)
- 通用模块应该更深(见第39页)
- 分离通用和特殊用途代码(见第62页)
- 不同的层次应有不同的抽象(见第45页)
- 向下拉复杂性(见第55页)
- 定义不存在的错误(和特殊情况)(见第79页)
- 设计两次(见第91页)
- 注释应说明代码中不明显的内容(见第101页)
- 软件的设计应便于阅读,而不是便于书写(见第149页)
- 软件开发的增量应当是抽象的,而不是特性(见第154页)
红旗摘要,系统出现任何这些症状都表明系统设计存在问题:
- 浅模块:类或方法的接口并不比它的实现简单多少(见第25页、第110页)
- 信息泄漏:一个设计决定反映在多个模块中(见第31页)
- 时间分解:代码结构基于执行操作的顺序,而不是基于信息隐藏(见第32页)
- 过度曝光:API强制调用者注意很少使用的特性,以便使用常用的特性(见第36页)
- 传递方法:一个方法除了将其参数传递给另一个具有类似签名的方法之外,几乎什么也不做(见第46页)
- 重复:一段代码被反复地重复(见第62页)
- 特殊通用混合码:特殊用途代码与通用代码没有完全分离(见第65页)
- 联合方法:两种方法有如此多的依赖性,以至于在不理解另一种方法的实现的情况下很难理解一种方法的实现(见第72页)
- 注释重复代码:注释的所有信息从紧挨着注释的代码中可以很容易的看出(见第104页)
- 实现文档污染接口:接口注释描述用户不需要知道的实现细节(见第114页)
- 模糊名称:变量或方法的名称非常不精确,不能传达很多有用的信息(见第123页)
- 难以选择名称:很难为实体提供一个精确而直观的名称(见第125页)
- 难以描述:为了完整,一个变量或方法的文档必须很长。(见第131页)
- 非显性代码:一段代码的行为或意义不容易理解。(见第148页)
读书笔记《A Philosophy of Software Design - John Ousterhout 软件设计哲学》的更多相关文章
- csapp读书笔记-并发编程
这是基础,理解不能有偏差 如果线程/进程的逻辑控制流在时间上重叠,那么就是并发的.我们可以将并发看成是一种os内核用来运行多个应用程序的实例,但是并发不仅在内核,在应用程序中的角色也很重要. 在应用级 ...
- CSAPP 读书笔记 - 2.31练习题
根据等式(2-14) 假如w = 4 数值范围在-8 ~ 7之间 2^w = 16 x = 5, y = 4的情况下面 x + y = 9 >=2 ^(w-1) 属于第一种情况 sum = x ...
- CSAPP读书笔记--第八章 异常控制流
第八章 异常控制流 2017-11-14 概述 控制转移序列叫做控制流.目前为止,我们学过两种改变控制流的方式: 1)跳转和分支: 2)调用和返回. 但是上面的方法只能控制程序本身,发生以下系统状态的 ...
- CSAPP 并发编程读书笔记
CSAPP 并发编程笔记 并发和并行 并发:Concurrency,只要时间上重叠就算并发,可以是单处理器交替处理 并行:Parallel,属于并发的一种特殊情况(真子集),多核/多 CPU 同时处理 ...
- 读书笔记汇总 - SQL必知必会(第4版)
本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...
- 读书笔记--SQL必知必会18--视图
读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...
- 《C#本质论》读书笔记(18)多线程处理
.NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...
- C#温故知新:《C#图解教程》读书笔记系列
一.此书到底何方神圣? 本书是广受赞誉C#图解教程的最新版本.作者在本书中创造了一种全新的可视化叙述方式,以图文并茂的形式.朴实简洁的文字,并辅之以大量表格和代码示例,全面.直观地阐述了C#语言的各种 ...
- C#刨根究底:《你必须知道的.NET》读书笔记系列
一.此书到底何方神圣? <你必须知道的.NET>来自于微软MVP—王涛(网名:AnyTao,博客园大牛之一,其博客地址为:http://anytao.cnblogs.com/)的最新技术心 ...
- Web高级征程:《大型网站技术架构》读书笔记系列
一.此书到底何方神圣? <大型网站技术架构:核心原理与案例分析>通过梳理大型网站技术发展历程,剖析大型网站技术架构模式,深入讲述大型互联网架构设计的核心原理,并通过一组典型网站技术架构设计 ...
随机推荐
- @EqualsAndHashCode(callSuper = false) 解释
当我们的pojo使用@Data注解时,@Data默认包含的是:@EqualsAndHashCode(callSuper = false),但是我们的pojo有继承父类,我们可能需要重新定义这个注解为: ...
- Elastic:用 Docker 部署 Elastic Stack
文章转载自:https://elasticstack.blog.csdn.net/article/details/100919273 前提条件 首选需要在主机上安装好docker和docker-com ...
- rpm,docker,k8s三种方式安装部署GitLab服务
rpm方式 源地址:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/ wget https://mirrors.tuna.tsinghua ...
- FastDFS 分布式文件系统的安装与使用---两台服务器搭建FastDFS环境
写在前面 有不少小伙伴在实际工作中,对于如何存储文件(图片.视频.音频等)没有一个很好的解决思路.都明白不能将文件存储在单台服务器的磁盘上,也知道需要将文件进行副本备份.如果自己手动写文件的副本机制, ...
- 实现fastdfs防盗链功能
目录 1.背景 2.实现原理 2.1 开启防盗链 2.2 重启 nginx 2.3 Java代码生成token 1.token生成规则 2.java生成token 3.测试 3.1 带正确token访 ...
- httpd常用配置之虚拟主机
httpd常用配置 目录 httpd常用配置 虚拟主机: 相同IP不同端口 不同IP相同端口 相同IP相同端口不同域名 切换使用MPM(编辑/etc/httpd/conf.modules.d/00-m ...
- Goland Socket 服务
客户端发送消息 并接收服务端消息 package main import ( "fmt" "net" ) func main() { // conn, err ...
- Nebula Graph介绍和SpringBoot环境连接和查询
Nebula Graph介绍和SpringBoot环境连接和查询 转载请注明来源 https://www.cnblogs.com/milton/p/16784098.html 说明 当前Nebula ...
- MyBatisPlus分页插件在SpringBoot中的使用
文章目录 1.目录结构 2.新增配置 3.编写测试类 4.测试结果 5.数据库中的表 文件的创建: https://blog.csdn.net/weixin_43304253/article/deta ...
- Redis—问题(1)
写在前面 Redis 是一种 NoSQL 数据库,包含多种数据结构.支持网络.基于内存.可选持久性的键值对存储数据库,在我们的日常开发中会经常使用 Redis 来解决许多问题,比如排行榜.消息队列系统 ...