阅读了本书的最后的部分,下面的是我的收获:
8.1 文档的类型
使用系统的规格说明文档(system specification document), 详细描述系统的目标、系统的功能需求、管理和技术上的限制、以及成本和日程等要素,了解所阅读代码的运行环境.
软件需求规格说明(software requirements specification)提供对用户需求和系统总体结构的高层描述,并且详细记述系统的功能和非功能性需求,比如数据处理、外部接口、数据库的逻辑模式以及设计上的各种约束。由于软硬件环境和用户需求的不断改变,文档中还可能描述预期的系统演化。是阅读和评估代码的基准.
系统的设计规格说明(system design specification)一般描述系统的构架、数据和代码结构,还有不同模块之间的接口。面向对象的设计会勾画出系统的基本类型以及公开方法。细化的设计规格一般还包括每个模块(或类)的具体信息,比如它执行的处理任务、提供的接口,以及与其他模块或类之间的关系。另外,设计规格说明还会描述系统采用的数据结构,适用的数据库模式等。系统的设计规格说明作为认知代码结构的路线图, 阅读具体代码的指引.
系统测试规格说明(system test specification)包括测试计划、具体的测试过程、以及实际的测试结果。每个测试过程都会详细说明它所针对的模块以及测试用例使用的数据(从中可以得知特定的输入由哪个模块处理)。利用测试规格说明文档为我们提供可以用来预演正在阅读的代码.
用户文档(user documentation),这些文档包括功能描、安装说明、介绍性的引导、参考手册和管理员手册。
在接触一个未知系统时, 功能性的描述和用户指南可以提供重要的背景信息,从而更好地理解阅读的代码所处的上下文.
从用户参考手册中, 我们可以快速地获取, 应用程序在外观与逻辑上的背景知识, 从管理员手册中可以得知代码的接口|文件格式和错误消息的详细信息.
8.2 阅读文档
利用文档可以快捷地获取系统的概况, 了解提供特定特性的代码.
文档经常能够反映和提示出系统的底层结构.
文档有助于理解复杂的算法和数据结构.
算法的文字描述能够使不透明(晦涩, 难以理解)的代码变得可以理解.
文档常常能够阐明源代码中标识符的含义.
文档能够提供非功能性需求背后的理论基础.
文档还会说明内部编程接口.
由于文档很少像实际的程序代码那样进行测试, 并受人关注, 所以它常常可能存在错误|不完整或过时.
文档也提供测试用例, 以及实际应用的例子.
文档常常还会包括已知的实现问题或bug.
环境中已知的缺点一般都会记录在源代码中.
文档的变更能够标出那些故障点.一般命名为ChangeLog
对同一段源代码重复或互相冲突的更改, 常常表示存在根本性的设计缺陷, 从而使得维护人员需要用一系列的修补程序来修复.
相似的修复应用到源代码的不同部分, 常常表示一种易犯的错误或疏忽, 它们同样可能会在其他地方存在.
8.3 文档存在的问题
文档常常会提供不恰当的信息, 误导我们对源代码的理解. 两种不同类型的歪曲是未记录的特性(undocumented feature)和理想化表述(idealized presentation)
要警惕那些未归档的特性: 将每个实例归类为合理|疏忽或有害, 相应地决定是否应该修复代码或文档.
有时, 文档在描述系统时, 并非按照已完成的实现, 而是系统应该的样子或将来的实现即理想化表述(idealized presentation).
在源代码文档中, 单词gork的意思一般是指”理解”.
如果未知的或特殊用法的单词阻碍了对代码的理解, 可以试着在文档的术语表(如果存在的话)|New Hacker’s Dictionary[Ray96]|或在Web搜索引擎中查找它们.
8.4 其他文档来源
总是要以批判的态度来看待文档, 注意非传统的来源, 比如注释|标准|出版物|测试用例|邮件列表|新闻组|修订日志|问题跟踪数据库|营销材料|源代码本身.
总是要以批判的态度来看待文档; 由于文档永远不会执行, 对文档的测试和正式复查也很少达到对代码的同样水平, 所以文档常常会误导读者, 或者完全错误.
对于那些有缺陷的代码, 我们可以从中推断出它的真实意图.
8.5 常见的开发源代码文档格式
文档两种类型:
1 二进制文件:他们的生成和阅读都要使用专利产品,如:Microsoft Word或Adobe FrameMaker;
2 文本文件:其中包含标记语言(markup language)形式的结构和格式化命令。
2.1 troff 2.2 Texinfo 2.3 DocBook 2.4 javadoc 2.5 Doxygen 2.6 Unix手册页 2.7 javadoc注释 2.8 Texinfo文档 2.9 DocBook
在阅读大型系统的文档时, 首先要熟悉文档的总体结构和约定.
在对付体积庞大的文档时, 可以使用工具, 或将文本输出到高品质输出设备上, 比如激光打印机, 来提高阅读的效率.
8.6 进阶读物
第三章 系统构架(原书:第九章: 系统构架)、设计模式、编码惯例
9.1 系统的结构
9.1.1 集中式存储库(centralized repository)和分布式方案(distributed Scheme)
一个系统可以(在重大的系统中也确实如此)同时展示出多种不同的构架类型. 以不同的方式检查同一系统、分析系统的不同部分、或使用不同级别的分解, 都有可能发现不同的构架类型.
协同式的应用程序, 或者需要协同访问共享信息或资源的半自治进程, 一般会采用集中式储存库构架.
黑板系统使用集中式的储存库, 存储非结构化的键/值对, 作为大量不同代码元件之间的通信集线器.
9.1.2 数据流构架(data-flow)
当处理过程可以建模|设计和实现成一系列的数据变换时, 常常会使用数据流(或管道—过滤器)构架.
在批量进行自动数据处理的环境中, 经常会采用数据流构架, 在对数据工具提供大量支持的平台上尤其如此.
数据流构架的一个明显征兆是: 程序中使用临时文件或流水线(pipeline)在不同进程间进行通信.
9.1.3 面向对象的结构(object-oriented)
使用图示来建模面向对象构架中类的关系.
可以将源代码输入到建模工具中, 逆向推导出系统的构架.
9.1.4 分层构架(layered)
拥有大量同级子系统的系统, 常常按照分层构架进行组织.
分层构架一般通过堆叠拥有标准化接口的软件组件来实现.
系统中每个层可以将下面的层看作抽象实体, 并且(只要该层满足它的需求说明)不关心上面的层如何使用它.
层的接口既可以是支持特定概念的互补函数族, 也可以是一系列支持同一抽象接口不同底层实现的可互换函数.
用C语言实现的系统, 常常用函数指针的数组, 表达层接口的多路复用操作.
用面向对象的语言实现的系统, 使用虚方法调用直接表达对层接口的多路复用操作.
9.1.5 层次(hierarchical decompositon)
196.系统可以使用不同的、独特的层次分解模型跨各种坐标轴进行组织.
系统的层次结构:
源代码在目录中的安排
静态或动态过程调用图
namespace(C++),package(Ada,Java,Perl),或标识符名称
类和接口的继承关系
结构化黑板项(blackboard entry)的命名
内部类和嵌套过程
异构数据结构和对象关联中的导航
9.1.6 切片(slicing)
使用程序切片技术, 可以将程序中的数据和控制之间依赖关系集中到一起.切片可以对量模块内聚性。
9.2 控制模型
系统的控制模型描述构成系统的各个子系统之间如何进行交互。
9.2.1 事件驱动的系统(event-driven architecture)
9.2.2 系统管理器(system manager control model)
在并发系统中, 一个单独的系统组件起到集中式管理器的作用, 负责启动、停止和协调其他系统进程和任务的执行.
许多现实的系统都会博采众家之长. 当处理此类系统时, 不要徒劳地寻找无所不包的构架图; 应该将不同构架风格作为独立但相关的实体来进行定位|识别并了解.
9.2.3 状态变迁(state transition model)
状态变迁模型(state transition model)通过操作数据----系统的状态,来管理系统的控制流程。状态数据决定执行控制权应该走向何处,对状态数据的更改可以重定向
执行的目标。采用状态变迁控制模型的系统(更常见的是子系统)一般以状态机(state machine)的形式进行建模和构造。
状态机(state machine)有一组有穷的状态和从一种状态变到另一种状态时执行处理和变迁的规则来定义。两种特殊情况,初始状态(initial state)和最终状态(final state),
分别规定状态机的起始点和结束条件。 状态机的实现一般是一个switch语句的循环,switch语句的分支对应状态机的状态;每个case语句执行特定状态的处理工作、
更改状态、并将控制权返回到状态机的顶端。
状态机一般用来实现简单的反应系统(simple reactive system)(用户界面、恒温器和电动机控制、以及处理自动化应用程序)、虚拟机、解释器、正则表达式模式匹配器和词法分析器。
状态变迁图常常有助于理清状态机的动作.
9.3 元素封装
9.3.1 模块
在处理大量的代码时, 了解将代码分解成单独单元的机制极为重要.
大多数情况下, 模块的物理边界是单个文件、组织到一个目录中的多个文件或拥有统一前缀的文件的集合.
9.3.2 命名空间
模块的一个重要思想就是信息隐藏(information hiding)的原则,它规定与模块相关的所有信息都应该为私有,除非它被特别声明为公开。
9.3.3 对象
C中的模块, 由提供模块公开接口的头文件和提供对应实现的源文件组成.
对象的构造函数经常用来分配与对象相关的资源, 并初始化对象的状态. 函数一般用来释放对象在生命期中占用的资源.
对象方法经常使用类字段来存储控制所有方法运作的数据(比如查找表或字典)或维护类运作的状态信息(例如, 赋给每个对象一个标识符的计数器).
在设计良好的类中, 所有的字段都应在声明为private, 并用公开的访问方法提供对它们的访问.
在遇到friend声明时, 要停下来分析一下, 看看绕过类封装在设计上的理由.
可以有节制地用运算符增强特定类的可用性, 但用运算符重载, 将类实现为拥有内建算术类型相关的全部功能的类实体, 是不恰当的.
9.3.4 泛型实现(generic implementation)
数据结构或算法的泛型实现(generic implementation) 的设计目的是能够在任意数据类型上工作。
编写特定概念的泛型实现需要切实的智力劳动。因此,泛型实现最常用在,通过对实现的广泛应用能够获得相当好处的情况下。
典型的应用包括(1)容器(经常作为抽象数据类型来实现) (2)相关的算法,比如查找、排序和其他作用在集合和有序区间上的操作 (3)泛型数值算法
使用系统的规格说明文档(system specification document), 详细描述系统的目标、系统的功能需求、管理和技术上的限制、以及成本和日程等要素,了解所阅读代码的运行环境.
软件需求规格说明(software requirements specification)提供对用户需求和系统总体结构的高层描述,并且详细记述系统的功能和非功能性需求,比如数据处理、外部接口、数据库的逻辑模式以及设计上的各种约束。由于软硬件环境和用户需求的不断改变,文档中还可能描述预期的系统演化。是阅读和评估代码的基准.
系统的设计规格说明(system design specification)一般描述系统的构架、数据和代码结构,还有不同模块之间的接口。面向对象的设计会勾画出系统的基本类型以及公开方法。细化的设计规格一般还包括每个模块(或类)的具体信息,比如它执行的处理任务、提供的接口,以及与其他模块或类之间的关系。另外,设计规格说明还会描述系统采用的数据结构,适用的数据库模式等。系统的设计规格说明作为认知代码结构的路线图, 阅读具体代码的指引.
系统测试规格说明(system test specification)包括测试计划、具体的测试过程、以及实际的测试结果。每个测试过程都会详细说明它所针对的模块以及测试用例使用的数据(从中可以得知特定的输入由哪个模块处理)。利用测试规格说明文档为我们提供可以用来预演正在阅读的代码.
用户文档(user documentation),这些文档包括功能描、安装说明、介绍性的引导、参考手册和管理员手册。
在接触一个未知系统时, 功能性的描述和用户指南可以提供重要的背景信息,从而更好地理解阅读的代码所处的上下文.
从用户参考手册中, 我们可以快速地获取, 应用程序在外观与逻辑上的背景知识, 从管理员手册中可以得知代码的接口|文件格式和错误消息的详细信息.
8.2 阅读文档
利用文档可以快捷地获取系统的概况, 了解提供特定特性的代码.
文档经常能够反映和提示出系统的底层结构.
文档有助于理解复杂的算法和数据结构.
算法的文字描述能够使不透明(晦涩, 难以理解)的代码变得可以理解.
文档常常能够阐明源代码中标识符的含义.
文档能够提供非功能性需求背后的理论基础.
文档还会说明内部编程接口.
由于文档很少像实际的程序代码那样进行测试, 并受人关注, 所以它常常可能存在错误|不完整或过时.
文档也提供测试用例, 以及实际应用的例子.
文档常常还会包括已知的实现问题或bug.
环境中已知的缺点一般都会记录在源代码中.
文档的变更能够标出那些故障点.一般命名为ChangeLog
对同一段源代码重复或互相冲突的更改, 常常表示存在根本性的设计缺陷, 从而使得维护人员需要用一系列的修补程序来修复.
相似的修复应用到源代码的不同部分, 常常表示一种易犯的错误或疏忽, 它们同样可能会在其他地方存在.
8.3 文档存在的问题
文档常常会提供不恰当的信息, 误导我们对源代码的理解. 两种不同类型的歪曲是未记录的特性(undocumented feature)和理想化表述(idealized presentation)
要警惕那些未归档的特性: 将每个实例归类为合理|疏忽或有害, 相应地决定是否应该修复代码或文档.
有时, 文档在描述系统时, 并非按照已完成的实现, 而是系统应该的样子或将来的实现即理想化表述(idealized presentation).
在源代码文档中, 单词gork的意思一般是指”理解”.
如果未知的或特殊用法的单词阻碍了对代码的理解, 可以试着在文档的术语表(如果存在的话)|New Hacker’s Dictionary[Ray96]|或在Web搜索引擎中查找它们.
8.4 其他文档来源
总是要以批判的态度来看待文档, 注意非传统的来源, 比如注释|标准|出版物|测试用例|邮件列表|新闻组|修订日志|问题跟踪数据库|营销材料|源代码本身.
总是要以批判的态度来看待文档; 由于文档永远不会执行, 对文档的测试和正式复查也很少达到对代码的同样水平, 所以文档常常会误导读者, 或者完全错误.
对于那些有缺陷的代码, 我们可以从中推断出它的真实意图.
8.5 常见的开发源代码文档格式
文档两种类型:
1 二进制文件:他们的生成和阅读都要使用专利产品,如:Microsoft Word或Adobe FrameMaker;
2 文本文件:其中包含标记语言(markup language)形式的结构和格式化命令。
2.1 troff 2.2 Texinfo 2.3 DocBook 2.4 javadoc 2.5 Doxygen 2.6 Unix手册页 2.7 javadoc注释 2.8 Texinfo文档 2.9 DocBook
在阅读大型系统的文档时, 首先要熟悉文档的总体结构和约定.
在对付体积庞大的文档时, 可以使用工具, 或将文本输出到高品质输出设备上, 比如激光打印机, 来提高阅读的效率.
8.6 进阶读物
第三章 系统构架(原书:第九章: 系统构架)、设计模式、编码惯例
9.1 系统的结构
9.1.1 集中式存储库(centralized repository)和分布式方案(distributed Scheme)
一个系统可以(在重大的系统中也确实如此)同时展示出多种不同的构架类型. 以不同的方式检查同一系统、分析系统的不同部分、或使用不同级别的分解, 都有可能发现不同的构架类型.
协同式的应用程序, 或者需要协同访问共享信息或资源的半自治进程, 一般会采用集中式储存库构架.
黑板系统使用集中式的储存库, 存储非结构化的键/值对, 作为大量不同代码元件之间的通信集线器.
9.1.2 数据流构架(data-flow)
当处理过程可以建模|设计和实现成一系列的数据变换时, 常常会使用数据流(或管道—过滤器)构架.
在批量进行自动数据处理的环境中, 经常会采用数据流构架, 在对数据工具提供大量支持的平台上尤其如此.
数据流构架的一个明显征兆是: 程序中使用临时文件或流水线(pipeline)在不同进程间进行通信.
9.1.3 面向对象的结构(object-oriented)
使用图示来建模面向对象构架中类的关系.
可以将源代码输入到建模工具中, 逆向推导出系统的构架.
9.1.4 分层构架(layered)
拥有大量同级子系统的系统, 常常按照分层构架进行组织.
分层构架一般通过堆叠拥有标准化接口的软件组件来实现.
系统中每个层可以将下面的层看作抽象实体, 并且(只要该层满足它的需求说明)不关心上面的层如何使用它.
层的接口既可以是支持特定概念的互补函数族, 也可以是一系列支持同一抽象接口不同底层实现的可互换函数.
用C语言实现的系统, 常常用函数指针的数组, 表达层接口的多路复用操作.
用面向对象的语言实现的系统, 使用虚方法调用直接表达对层接口的多路复用操作.
9.1.5 层次(hierarchical decompositon)
196.系统可以使用不同的、独特的层次分解模型跨各种坐标轴进行组织.
系统的层次结构:
源代码在目录中的安排
静态或动态过程调用图
namespace(C++),package(Ada,Java,Perl),或标识符名称
类和接口的继承关系
结构化黑板项(blackboard entry)的命名
内部类和嵌套过程
异构数据结构和对象关联中的导航
9.1.6 切片(slicing)
使用程序切片技术, 可以将程序中的数据和控制之间依赖关系集中到一起.切片可以对量模块内聚性。
9.2 控制模型
系统的控制模型描述构成系统的各个子系统之间如何进行交互。
9.2.1 事件驱动的系统(event-driven architecture)
9.2.2 系统管理器(system manager control model)
在并发系统中, 一个单独的系统组件起到集中式管理器的作用, 负责启动、停止和协调其他系统进程和任务的执行.
许多现实的系统都会博采众家之长. 当处理此类系统时, 不要徒劳地寻找无所不包的构架图; 应该将不同构架风格作为独立但相关的实体来进行定位|识别并了解.
9.2.3 状态变迁(state transition model)
状态变迁模型(state transition model)通过操作数据----系统的状态,来管理系统的控制流程。状态数据决定执行控制权应该走向何处,对状态数据的更改可以重定向
执行的目标。采用状态变迁控制模型的系统(更常见的是子系统)一般以状态机(state machine)的形式进行建模和构造。
状态机(state machine)有一组有穷的状态和从一种状态变到另一种状态时执行处理和变迁的规则来定义。两种特殊情况,初始状态(initial state)和最终状态(final state),
分别规定状态机的起始点和结束条件。 状态机的实现一般是一个switch语句的循环,switch语句的分支对应状态机的状态;每个case语句执行特定状态的处理工作、
更改状态、并将控制权返回到状态机的顶端。
状态机一般用来实现简单的反应系统(simple reactive system)(用户界面、恒温器和电动机控制、以及处理自动化应用程序)、虚拟机、解释器、正则表达式模式匹配器和词法分析器。
状态变迁图常常有助于理清状态机的动作.
9.3 元素封装
9.3.1 模块
在处理大量的代码时, 了解将代码分解成单独单元的机制极为重要.
大多数情况下, 模块的物理边界是单个文件、组织到一个目录中的多个文件或拥有统一前缀的文件的集合.
9.3.2 命名空间
模块的一个重要思想就是信息隐藏(information hiding)的原则,它规定与模块相关的所有信息都应该为私有,除非它被特别声明为公开。
9.3.3 对象
C中的模块, 由提供模块公开接口的头文件和提供对应实现的源文件组成.
对象的构造函数经常用来分配与对象相关的资源, 并初始化对象的状态. 函数一般用来释放对象在生命期中占用的资源.
对象方法经常使用类字段来存储控制所有方法运作的数据(比如查找表或字典)或维护类运作的状态信息(例如, 赋给每个对象一个标识符的计数器).
在设计良好的类中, 所有的字段都应在声明为private, 并用公开的访问方法提供对它们的访问.
在遇到friend声明时, 要停下来分析一下, 看看绕过类封装在设计上的理由.
可以有节制地用运算符增强特定类的可用性, 但用运算符重载, 将类实现为拥有内建算术类型相关的全部功能的类实体, 是不恰当的.
9.3.4 泛型实现(generic implementation)
数据结构或算法的泛型实现(generic implementation) 的设计目的是能够在任意数据类型上工作。
编写特定概念的泛型实现需要切实的智力劳动。因此,泛型实现最常用在,通过对实现的广泛应用能够获得相当好处的情况下。
典型的应用包括(1)容器(经常作为抽象数据类型来实现) (2)相关的算法,比如查找、排序和其他作用在集合和有序区间上的操作 (3)泛型数值算法