读书笔记《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高级征程:《大型网站技术架构》读书笔记系列
一.此书到底何方神圣? <大型网站技术架构:核心原理与案例分析>通过梳理大型网站技术发展历程,剖析大型网站技术架构模式,深入讲述大型互联网架构设计的核心原理,并通过一组典型网站技术架构设计 ...
随机推荐
- 若依代码生成的一个大坑 You have an error in your SQL syntax; check the manual that corresponds to your MySQL s
报错如下所示:显示我的xml文件的SQL语句有错 ### Error querying database. Cause: java.sql.SQLSyntaxErrorException: You h ...
- SDN实验环境安装配置
- IDEA远程部署项目到Docker
前言 最近在写东西部署到服务器,结构是springboot工程配合docker部署. 但是每次部署都3个步骤: 本地构建jar 复制jar到远程服务器 用DockerFile构建镜像 部署次数一多,我 ...
- Nginx反代服务器基础配置实践案例
转载自:https://www.bilibili.com/read/cv16149433?spm_id_from=333.999.0.0 方式1: 轮询 RR(默认轮询)每个请求按时间顺序逐一分配到不 ...
- Elasticsearch:top_hits aggregation
top_hits指标聚合器跟踪要聚合的最相关文档. 该聚合器旨在用作子聚合器,以便可以按存储分区汇总最匹配的文档. top_hits聚合器可以有效地用于通过存储桶聚合器按某些字段对结果集进行分组. 一 ...
- PAT (Basic Level) Practice 1007 素数对猜想 分数 20
让我们定义dn为:dn=pn+1−pn,其中pi是第i个素数.显然有d1=1,且对于n>1有dn是偶数."素数对猜想"认为"存在无穷多对相邻且差为2的 ...
- 邻接矩阵bfs
#include<bits/stdc++.h> using namespace std; int a[11][11]; bool visited[11]; void store_graph ...
- 20个超棒的jQuery bootstrap 插件
1. Bootstrap File Input Bootstrap3.x 的一个增强版的HTML 5 文件选择控件,可以对图片文件和文本文件进行预览,以及其他功能.该插件增强了这些插件,并且将组件的初 ...
- 常用RE对照表——敬请期待!!!
.* #任意长度任意字符
- Redis 02: redis基础知识 + 5种数据结构 + 基础操作命令
Redis基础知识 1).测试redis服务的性能: redis-benchmark 2).查看redis服务是否正常运行: ping 如果正常---pong 3).查看redis服务器的统计信息: ...