软件的多纬度视角
- 按阶段划分:BuildTime以及RunTime
- 按动态性划分:Moment以及Period
- 按构造对象的层次划分:Code以及Component
在对不同阶段进行详细阐述之前,让我们先来理清几个概念:
- 什么是Code-Level:即Source Code,代码如何被Logically organized.与其相关的名词:Functions, Classes, Methods, Interfaces
- 什么是Component-Level:即源码如何被physically organized。我所认为的physically,即源码如何在硬盘上以文件的形式进行组织,于其相关的名词有:Files, directories, packages, libraries
- 什么是Moment View:在某时刻,源码和组件的状态。与其相关联的名词:snap shot
- 什么是Period View:对象如何进化、改变,即多个Moment构成Period。于其相关联的名词有:版本控制,代码改变。
- 什么是Build Time:即在写源码的过程。
- 什么是Run Time:即程序变成进程的过程。
下面我们将分阶段对其进行阐述:
1. Build-Time, Moment and Code-level
源码如何以程序块的形式进行组织,比如函数,类,方法以及接口。与其相关联的概念有:
- 基于词汇的半结构化源代码
- 语法驱动的程序框架——AST,语法分析树,半结构化的源码可以被表示为一棵树。AST使得代码彻底结构化,将源码变成一棵树,对树的操作就是对源代码的修改。AST树的写法:同层次原则。比如下面的代码:
while(a ≠ b){
if( a > b)
a = a - b;
else
b = b - a;
}
return a;
如果想要把它表示为AST,首先,先看最外侧同层次的内容:while以及return。那么,我们首先把这两个写出来。值得注意的是:叶子节点必须是变量!就比如说return,它是一个内部节点,后面必须有一个叶子节点a,也就是它的返回值。对于while,它有两部分组成:condition以及循环体Body。condition是一个比较操作≠,这个可以算作是一个内部节点,它连接两个叶子节点,进行比较。至于循环体,它是一个branch,又进行层次划分:if和else是同一层次,并列写出。if后面的condition,是>比较,也可以作为一个内部节点,下面连接两个变量。对于if以及else,它都是assign,连接一个变量以及一个表达式,表达式是一个-操作,下面连接着两个变量。
- 基于语义的程序结构。所谓语义,就是代码具有什么含义,这通常与需求,设计进行联系。
2.Bulid-time, period, and code-level view
从moment变为period,实际上就是代码的改变——code churn。在Github上我们经常见到code churn。
3. Build-time, moment, and component-level view
现在我们来到了组件视角。组件实际上就是源码在硬盘上的组织形式。源码在硬盘上被组织为文件,目录以及包。同时,可用组件被组织为库。
- 关于静态链接:在csapp上已经学过,在链接器链接的时候,把库中需要的目标文件与源代码形成一个整体文件。静态链接是在构造阶段,动态链接是在运行阶段。
4. Build-time, period, and component-level view
即软件实体(文件,目录,库)如何随时间变化而变化?与之想干的名词:SCI(配置项)、版本。
- 注意:这里和code level的区别:code level在构造阶段的period体现为代码的改变,即某行的改变,而这里组件视角,是以整个代码被组织成更大的文件、目录以及库之后,这些更宏观的实体是如何改变的。
- 与其相关的名词:版本控制,演进图。
在进入Run time level之前,我们先来看看到底什么是运行时视角,及其相关的概念
- 运行时视角:就是从程序变成进程的过程。代码层面:逻辑实体(变量)在内存中呈现的状态。构件层面:物理实体(文件、包、库)在物理层面如何呈现。
- 可执行程序:即csapp中学到的可执行目标文件,CPU可以直接执行的机器代码。
- 库:包含可重定位目标文件,不可以直接被加载执行,必须先被链接。
- 配置以及数据文件:不可直接被执行,他们是帮助程序加载入内存的。
- 分布式程序:一个软件包含多个相互协作的程序。
- 程序完全解释执行:类似于C的翻译器,将源代码最终翻译为机器可以执行的机器代码
- 预编译过的解释型字节码:类似于原生机器码,但是不可以直接被CPU执行,只能借助JVM以及JIT(Compiler)将其翻译成原生机器码。
但是Perl以及Python,是解释型语言,使用字节码来运行,不需要编译。 - 动态链接:非常熟悉了,在Build阶段仅仅获取库的符号表等,在运行时才加载内存。
- 配置以及数据文件:当程序需要用到磁盘上文件时,需要借助他们来取得那些文件。
- 分布式程序:多个运行程序部署在多个计算机(比如server以及client)
5. Run-time, moment, and code-level view
可以想象成debug,每一个变量都在某一时刻有一个值,这也就是代码快照图。
与之相关联的概念有:内存信息转储:就是当进程运行完毕后,在磁盘上留下一个记录此进程内存等信息的文件。调试器Debugger通常要使用它。
6. Run-time, period, and code-level view
- Sequence diagram
- 执行跟踪:以日志的方式记录程序的执行。
7. Run-time, moment, and component-level view
- Deployment diagram
8. Run-time, period, and component-level view
- 系统层面上的事件日志。与执行跟踪的区别主要是:实践日志通常是系统层面的,高层次的内容(比如某软件安装失败了),而执行跟踪是开发者使用的,低层次的内容(比如代码运行出错了)
9.小结
软件设计就是不同维度的转换
外部因素以及内部因素
- 外部因素重要,和客户用户相关
- 内部因素影响软件本身以及开发者
- 但是内部因素决定外部因素
外部因素 - 正确性:永远不可能被折中,首要目标。保证正确性:调试,防御式编程(写程序就确保正确性,不再debug了)以及形式化验证正确性
- 鲁棒性:当用户输入规约之外的输入时,优雅退出。正确性是在规约之内。
- 可拓展性:应对变化。规模越大,可拓展性越低。通常有:简约主义以及去中心化(分离主义)
- 复用性:DRY,不要重复造轮子。
- 兼容性:Compatibility,不同软件系统之间可以集成。需要保持设计同构性,遵循标准做事。
- 效率:过早优化是万恶之源。
- 可移植性:不仅仅指硬件系统,还包括操作系统。
- 易用性:给详细指南
- 功能性:功能越多,程序越臃肿。
- 及时性:及早发布,抢占客户。
- 可验证性:用简单的步骤证明程序正确性。
- 完整性
- 可修复性
- 经济性
内部因素
- LOC:代码行数
- 循环复杂性
- 耦合度
- 内聚性
- 可读性
- 可理解性
- 清洁度、体积
学会在不同性能指标之间折中
唯一例外的是:正确性坚决不可以被折中。
- 重要的几个因素:正确性以及鲁棒性:可靠。可拓展性以及易用性:模块化。
- OOP能够提高质量:
五大质量目标
Understandability:
Reusability:
Maintainability and Adaptability:
Robustness:
Performance