工具类
VSCode
显示所有命令:ctrl+shift+P
转到文件: ctrl+P
在文件中查找:ctrl+shift+F
开始调试: F5
切换终端: ctrl+`
资源管理器: ctrl+shift+E
源代码管理: ctrl+shift+G
运行和调试: ctrl+shift+D
扩展管理: ctrl+shift+X
查看错误: ctrl+shift+M
打开文件: ctrl+O 关闭文件夹: ctrl+K F
新建文件: ctrl+N 关闭文件: ctrl+W
关闭所有文件:ctrl+K W 关闭已保存文件:ctrl+K U
- 进程隔离插件模型
- UI渲染与业务逻辑隔离
- LSP和DAP协议
Git
基本用法
git init
,初始化一个本地版本库git status
,查看当前工作区(workspace)的状态git add [FILES]
,把文件添加到暂存区(Index)git commit -m "wrote a commit log infro”
,把暂存区里的文件提交到仓库git log
,查看当前HEAD之前的提交记录,便于回到过去git reset —hard HEAD^^/HEAD~100/commit-id/commit-id
的头几个字符,回退git reflog
,可以查看当前HEAD之后的提交记录,便于回到未来git reset —hard commit-id/commit-id
的头几个字符,回退git clone
,克隆一个存储库到一个新的目录下。git fetch
,即下载一个远程存储库数据对象等信息到本地存储库。git push
,将本地存储库的相关数据对象更新到远程存储库。git merge
,即合并两个或多个开发历史记录。git pull
,从其他存储库或分支抓取并合并到当前存储库的当前分支。- 默认的合并方式为"快进式合并"(fast-forward merge),会将分支里commit 合并到主分支里,合并成一条时间线 ,与我们期望的呈现为一段独立的分 支线段不符,因此合并时需要使用-- no-ff参数关闭"快进式合并"
git rebase -i [startpoint] [endpoint]
,如果不指定[endpoint]
,则该区间的终点默认是当前分支的HEAD
,一般只指定[startpoint]
git rebase -i HEAD^^^
git rebase —abort
git rebase --continue
Vim
- 移动光标:
hjkl
对应←↓↑→
,使用数字加方向移动对应次数 n<space>
,向右移动这行的n个字符0
或[Home]
,移动到这一行的最前面字符处 (常用)$
或[End]
,移动到这一行的最后面字符处(常用)G,
移动到这个档案的最后一行(常用)gg,
移动到这个档案的第一行,相当于 1G (常用)n<enter>
,光标向下移动 n 行(常用)x
,X
,在一行字当中,x
为向后删除一个字符 (相当于[del]
按键),X
为向前删除一个字符(相当于[backspace]
) (常用)dd
,删除游标所在的那一整行(常用)ndd
,删除光标所在的向下 n 行,例如20dd
则是删除 20 行 (常用)yy
,复制游标所在的那一行(常用)nyy
,复制光标所在的向下 n 行,例如20yy
则是复制 20 行(常用)p
,P
,p
为将已复制的数据在光标下一行贴上,P
则为贴在游标上一行u
,复原前一个动作。(常用)[Ctrl]+r
,重做上一个动作。(常用)/word
,向光标之下寻找一个名称为word
的字符串。例如要在档案内搜寻vbird
这个字符串, 就输入/vbird
即可 (常用)?word
,向光标之上寻找一个字符串名称为word的字符串。:n1,n2s/word1/word2/g
,在第 n1 与 n2 行之间寻找word1
这个字符串,并将 该字符串取代为word2
:1,$s/word1/word2/g
或:%s/word1/word2/g
从第一行到最后一行寻找word1
字符串,并将该字符串取代为word2
(常用)- g表示全局替换;c表示操作时需要确认;i表示不区分大小写
正则表达式
.
表示任意1个字符;?
表示前一个字符存在0/1次;+
表示前一个字符存在1-多次;*
表示前一个字符存在0-多次- 使用
{n1,n2}
表示出现n1-n2次 - 使用
[字符集]
表示希望匹配的字符,-
表示连续字符省略,内部^
开头表示排除字符 \w
匹配[A-Za-z0-9_]
;\W
匹配\w
的反向\d
匹配[0-9]
;\D
查找\d
的反向[]
外的^
表示字符串开头,$
表示末尾\s
搜索空格- 用
()
定义捕获组(重复子串),并使用从1开始的\1
表示从第一个开始的捕获组
工程化编程
- 代码风格的原则:简明、易读、无二义性
编写高质量代码的基本方法
- 通过控制结构简化代码
- 通过数据结构简化代码
- 一定要有错误处理
- 注意性能优先策略背后隐藏的代价
- 当软件工程师的人力成本远大于所消耗的计算资源成本时,提高代码编写的工作效率将更有价值;
- 质量保证的人力成本和质量保证的成效也比所消耗的计算资源成本更有价值;
- 性能优先的策略往往会让代码很难理解,结果需要消耗更多的工时;
- 面向机器的代码修改起来更困难,可扩展性差,同样会消耗更多工时。
- 拒绝修修补补要不断重构代码
模块化软件设计
基本原理
模块化(Modularity)是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。这个做法背后的基本原理是关注点的分离 (SoC, Separation of Concerns)分解成易解决的小问题,降低思考负担。
每个模块只有一个功能,易于开发,并且bug会集中在少数几个模块内,容易定位软件缺陷,也更加容易维护。
软件设计中的模块化程度便成为了软件设计有多好的一个重要指标,一般我们使用耦合度 (Coupling)和内聚度(Cohesion)来衡量软件模块化的程度。
耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。
一般追求松散耦合。 内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。 理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性(Feather)。
基本写法
KISS(keep it simple&stupid)原则
- 一行代码只做一件事
- 一个块代码只做一件事
- 一个函数只做一件事
- 一个软件模块只做一件事
使用本地化外部接口来提高代码的适应能力
- 不要和陌生人说话原则
先写伪代码的代码结构更好一些
可重用软件设计
接口
基本概念
接口就是互相联系的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。 在面向过程的编程中,接口一般定义了数据结构及操作这些数据结构的函数;
在面向对象的编程中,接口是对象对外开放(public)的一组属性和方法的集合。函数或方法具体包括名称、参数和返回值等。
基本要素
- 接口的目的;
- 接口的前置条件;
- 接口的协议规范;(如http协议,png图片格式,json数据格式定义etc…)
- 接口的后置条件;
- 接口的质量属性。(如响应时间)
微服务接口/RESTful API
- 系统中的各微服务是分布式管理的,各微服务之间非常强调隔离性,互相之间无 耦合或者极为松散的耦合,系统通过前端应用或API网关来聚合各微服务完成整体 系统的业务功能。
- GET用来获取资源
- POST用来新建资源(也可以用于更新资源)
- PUT用来更新资源
- DELETE用来删除资源
耦合度
- 公共耦合
- 当软件模块之间共享数据区或变量名的软件模块之间即是公共耦合,显然两个软件模块之间的接口定义不是通过显式的调用方式,而是隐式的共享了共享了数据区或变量名。
- 数据耦合
- 在软件模块之间仅通过显式的调用传递基本数据类型即为数据耦合。
- 标记耦合
- 在软件模块之间仅通过显式的调用传递复杂的数据结构(结构化数据)即为标记耦合,这时数据的结构成为调用双方软件模块隐含的规格约定,因此耦合度要比数据耦合高。但相比公共耦合没有经过显式的调用传递数据的方式耦合度要低。
通用接口定义的基本方法
- 参数化上下文(使用参数传递信息,不依赖上下文环境,即不使用闭包函数)
- 移除前置条件(sum函数中使用数组传递参数,不再限定参数个数)
- 简化后置条件(移除参数之间的关系,使sum返回的是数组全部元素的和)
软件质量的三个角度
- 产品的角度,也就是软件产品本身内在的质量特点;
- 用户的角度,也就是软件产品从外部来看是不是对用户有帮助,是不是有良好的用户体验;
- 商业的角度,也就是商业环境下软件产品的商业价值,比如投资回报或开发软件产品的其他驱 动因素。
需求分析到软件设计
需求类型
- 功能需求:根据所需的活动描述所需的行为
- 质量需求或非功能需求:描述软件必须具备的一些质量特性
- 设计约束: 设计决策,例如选择平台或接口组件
- 过程约束: 对可用于构建系统的技术或资源的限制
高质量的需求
- 需求可测试
- 定量
- 明确代词
- 明确定义
- 解决冲突
- 区分优先级:必须;需要但不必须;可能但最终没有的
- 需求的特点
- 正确、一致、无二义性
- 完整、可行、与目标相关
- 可测试、可追溯
需求分析建模
需求分析的两类基本方法
- 原型化方法
- 可以很好地整理出用户接口方式(UI,User Interface),比如界面布局和交互操作过程。
- 建模的方法
- 可以快速给出有关事件发生顺序或活动同步约束的问题,能够在逻辑上形成模型来整顿繁杂的需求细节。
用例建模
基本概念
用例(Use Case)的核心概念中首先它是一个业务过程(business process),经过逻辑整理抽象 出来的一个业务过程,这是用例的实质。什么是业务过程?在待开发软件所处的业务领域内完成特定业务任务(business task)的一系列活动就是业务过程。
基本步骤
- 从需求表述中找出用例
- 描述用例开始和结束状态
- 对用例按照子系统或不同的方面进行分类,描述用例与用例、用例与参与者之间的上下文关系,并画出用例图
- 进一步逐一分析用例与参与者的详细交互过程,完成一个两列的表格将参与者和待开发软件系统之间从用例开始到用例结束的所有交互步骤都列举出来扩展用例。
四个必要条件
- 必要条件一 :它是不是一个业务过程?
- 必要条件二:它是不是由某个参与者触发开始?
- 必要条件三:它是不是显式地或隐式地终止于某个参与者?
- 必要条件四: 它是不是为某个参与者完成了有用的业务工作?
准确提取用例的基本方法
- 从需求中寻找业务领域相关动名词/短语
- 验证用例必要条件
- 识别参与者、系统和子系统
三个抽象层级
- 抽象用例(Abstract use case)。只要用一个干什么、做什么或完成什么业务任务的动名词短语,就可以非常精简地指明一个用例。
- 高层用例(High level use case)。需要给用例的范围划定一个边界,也就是用例在什么时候什么地方开始,以及在什么时候什么地方结束;
- 扩展用例(Expanded use case)。需要将参与者和待开发软件系统为了完成用例所规定的业务任务的交互过程一步一步详细地描述出来,一般我们使用一个两列的表格将参与者和待开发软件系统之间从用例开始到用例结束的所有交互步骤都列举出来。 扩展用例最后可以用两列表格描述。
软件设计
统一过程
统一过程(UP,Unified Process)的核心要义是 用例驱动
(Use case driven)、 以架构为中心
(Architecture centric)、 增量且迭代
(Incremental and Iterative)的过程。用例驱动就是我们前文中用例建模得到的用例作为驱动软件开发的目标;以架构为中心的架构是后续软件设计的结果, 就是保持软件架构相对稳定,减小软件架构层面的重构造成的混乱;增量且迭代体现在下图中。
敏捷统一过程的四个关键步骤
- 第一,确定需求
- 第二,通过用例的方式来满足这些需求
- 第三,分配这些用例到各增量阶段
- 第四,具体完成各增量阶段所计划的任务
- 显然,第一到第三步主要是计划阶段的工作,第四步是接下来要进一步详述的增量阶段的工作。
增量阶段五个步骤
- 在每一次增量阶段的迭代过程中,都要进行从需求分析到软件设计实现的过程, 具体敏捷统一过程将增量阶段分为五个步骤:
- 用例建模(Use case modeling)
- 业务领域建模(Domain modeling)
- 对象交互建模(Object Interaction modeling),使用剧情描述来建模,最后转换为剧情描述表;
- 形成设计类图(design class diagram);
- 软件的编码实现和软件应用部署;
对象交互建模
步骤
- 第一步,在扩展用例中右侧一列中找出关键步骤(nontrivial steps)。关键步骤是那些需要在背后进行业务过程处理的步骤,而不是仅仅在表现层(presentation layer, /GUI)与参与者进行用户接口层面交互的琐碎步骤。
- 第二步,对于每一个关键步骤,从关键步骤在扩展用例两列表格中的左侧作为开始,完成剧情描述( scenario),描述一步一步的对象交互过程,直到执行完该关键步骤。
- 第三步,如果需要的话,将剧情描述(scenario)进一步转换成剧情描述表(scenario table)。
- 组织编排剧情的基本指导原则
- KISS原则(Keep it simple and stupid),保持剧情足够简洁,将细节问题留给编码阶段;
- 优先描述正常剧情,假定所有事情都按预期进行,将异常处理留给编码阶段;
- 如果需要的话描述正常剧情的多个不同可选流程,以增强设计和编码阶段的灵活性;
- 有时需要为正常剧情构建一个原型来验证设计的流程是否可行。
- 第四步,将剧情描述(scenario)或剧情描述表(scenario table)转换成序列图。
- 对象交互建模的四个基本步骤以某个用例的扩展用例为输入,中间借助业务领域知识及业务领域建模中的相关对象、属性等,最终产出结果为序列图。
软件科学基础
回调函数
回调函数是一个面向过程的概念,是代码执行过程的一种特殊流程。回调函数就 是一个通过函数指针调用的函数。把函数的指针(地址)作为参数传递给另一个 函数,当这个指针调用其所指向的函数时,就称这是回调函数。回调函数不是该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的, 用于对该事件或条件进行响应。
/* 回调函数例子
* Search a LinkTableNode from LinkTable
* int Conditon(tLinkTableNode * pNode);
*/
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args) {
if(pLinkTable == NULL || Conditon == NULL) {
return NULL;
}
tLinkTableNode * pNode = pLinkTable->pHead;
while(pNode != NULL) {
if(Conditon(pNode,args) == SUCCESS) {
return pNode;
}
pNode = pNode->pNext;
}
return NULL;
}
int SearchConditon(tLinkTableNode * pLinkTableNode,void * arg) {
char * cmd = (char*)arg;
tDataNode * pNode = (tDataNode *)pLinkTableNode;
if(strcmp(pNode->cmd, cmd) == 0) {
return SUCCESS;
}
return FAILURE;
}
int main(){
//...
//传递回调函数,查找结点
tDataNode *p = (tDataNode*)SearchLinkTableNode(head,SearchConditon,(void*)argv[0]);
}
多态
在面向对象语言中,接口的多 种不同的实现方式即为多态。多态是实例化变量可以指向不同的实例对象,这样同一个实例化变量在不同的实例对象上下文环境中执行不同的代码表现出不同的行为状态 ,而通过实例化变量调用实例对象的方法的那一块代码却是完全相同的,这就顾名思义,同一段代码执行时却表现出不同的行为状态,因而叫多态。
闭包
- 闭包是变量作用域的一种特殊情形,一般用在将函数作为返回值时,该函数执行所需的上下文环境也作为返回的函数对象的一部分,这样该函数对象就是一个闭包。
- 函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript中 ,每当函数被创建,就会在函数生成时生成闭包 。
异步调用
Promise
对象可以将异步调用以同步调用的流程表达出来,避免了通过嵌套回调函数实现异步调用。- 所谓
Promise
对象,就是代表了未来某个将要发生的事件,通常是一个异步操作。Promise
对象提供了一整套完整的接口,使得可以更加容易地控制异步调用。 Promise
对象实际上是对回调函数机制的封装,也就是通过then
方法定义的函数与resolve/reject
函数绑定,简化了回调函数传入的接口实现,在逻辑上也更加通顺,看起来像是个同步接口。
匿名函数
lamda
函数是函数式编程中的高阶函数,在我们常见的命令式编程语言中常常以匿名函数的形式出现- 比如无参数的代码块
{ code }
,或者箭头函数{ x => code }
三个系统类型
- S系统:有规范定义,可从规范派生
- 矩阵操纵矩阵运算
- P系统:需求基于问题的近似解,但现实世界保持稳定
- 象棋程序
- E系统:嵌入现实世界并随着世界的变化而变化(大多数软件都属于这个类型)
- 预测经济运行方式的软件(但经济尚未被完全理解)
- 软件具有复杂性和易变性,从而难以达成概念的完整性与一致性。(需求的达成永远赶不上需求的变化)
设计模式
- 设计模式的本质是面向对象设计原则的实际运用总结出的经验模型。
- 目的是包容变化,即通过使用设计模式和多态等特殊机制,将变化的部分和不变的部分进行适当隔离。(高内聚,低耦合)
- 正确使用设计模式具有以下优点:
- 可以提高程序员的思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
- 设计模式由四个部分组成:
- 名称;
- 目的,即该设计模式要解决什么样的问题;
- 解决方案;
- 解决方案有哪些约束和限制条件。
- 根据作用对象分两类:
- 类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。比如模板方法模式等属于类模式。
- 对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。由于组合关系或聚合关系比继承关系耦合度低,因此多数设计模式都是对象模式。
- 根据任务类型分为三类:
- 创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。比如单例模式、原型模式、建造者模式等属于创建型模式。
- 结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,比如代理模式、适配器模式、桥接模式 、装饰模式、外观模式、享元模式、组合模式等属于结构型模式。
- 行为型模式:用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。比如模板方法模式、策略模式、命令模式、职责链模式、观察者模式等属于行为型模式。
职责链
public class ChainOfResponsibilityPattern {
public static void main(String[] args) {
//组装责任链
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
handler1.setNext(handler2);
//提交请求
handler1.handleRequest("two");
}
}
//抽象处理者角色
abstract class Handler {
private Handler next;
public void setNext(Handler next) {
this.next = next;
}
public Handler getNext() {
return next;
}
//处理请求的方法
public abstract void handleRequest(String request);
}
//具体处理者角色1
class ConcreteHandler1 extends Handler {
public void handleRequest(String request) {
if (request.equals("one")) {
System.out.println("具体处理者1负责处理该请求!");
} else {
if (getNext() != null) {
getNext().handleRequest(request);
} else {
System.out.println("没有人处理该请求!");
}
}
}
}
//具体处理者角色2
class ConcreteHandler2 extends Handler {
public void handleRequest(String request) {
if (request.equals("two")) {
System.out.println("具体处理者2负责处理该请求!");
} else {
if (getNext() != null) {
getNext().handleRequest(request);
} else {
System.out.println("没有人处理该请求!");
}
}
}
}
设计原则
开闭原则(Open Closed Principle,OCP)
- 软件应当对扩展开放,对修改关闭
- 统一过程以架构为中心增量且迭代的过程和开闭原则具有内在的一致性,它们都追求软件结构上的稳定性。
- 在设计上包容软件结构本身的变化,以利于软件设计结构上的不断重构(Refactoring),才能适应软件结构本质上的不稳定性特点。
Liskov替换原则(Liskov Substitution Principle,LSP)
- 继承必须确保超类所拥有的性质在子类中仍然成立,子类可以扩展父类的功能,但不能改变父类原有的功能。
依赖倒置原则(Dependence Inversion Principle,DIP)
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
- 其核心思想是:要面向接口编程,不要面向实现编程。
- 在模块化设计中降低模块之间的耦合度和加强模块的抽象封装提高模块的内聚度上具有普遍的指导意义
单一职责原则(Single Responsibility Principle,SRP)
- 一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分
- 核心是控制类的粒度大小、提高其内聚度。
迪米特法则(Law of Demeter,LoD)
- 如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。
- 其目的是降低类之间的耦合度,提高模块的相对独立性。
合成复用原则(Composite Reuse Principle,CRP)
- 组合/聚合复用原则
- 要求在软件复用时,要尽量先使用组合或者聚合关系来实现,其次才考虑使用继承关系来实现。如果要使用继承关系,则必须严格遵循Liskov替换原则。
- 通常类的复用分为继承复用和对象组合复用两种。
MVC架构
- 模型-视图-控制器,MVC是一种设计模式
- Model(模型)代表一个存取数据的对象及其数据模型。
- View(视图)代表模型包含的数据的表达方式,一般表达为可视化的界面接口。
- Controller(控制器)作用于模型和视图上,控制数据流向模型对象,并在数据变化时 更新视图。控制器可以使视图与模型分离开解耦合
和三层架构区别
- 模型和视图有着业务层面的业务数据紧密耦合关系,控制器的核心工作就是业务逻辑处理。
- 为了体现它们的区别和联系,在MVC的结构示意图中将模型和视图上下垂直对齐表示它们内在的业务层次及业务数据的对应关系,而将控制器放在左侧表示控制器处于优先重要位置,放在模型和视图的中间位置是为了与三层架构对应与业务逻辑层处于相似的层次。
MVVM架构
- MVVM即
Model-View-ViewModel
优点
- 主要目的是分离视图(
View
)和模型(Model
) - 低耦合。视图(
View
)可以独立于Model
变化和修改,一个ViewModel
可以绑定到不同的 "View
"上,当View
变化的时候Model
可以不变,当Model
变化的时候View
也可以不变。 - 可重用性。你可以把一些视图逻辑放在一个
ViewModel
里面,让很多View
重用这段视图逻辑。 - 独立开发。开发人员可以专注于业务逻辑和数据的开发(
ViewModel
),设计人员可以专注于页面设计。 - 可测试。界面素来是比较难于测试的,测试可以针对
ViewModel
来写。
与MVC
主要区别在于:
- MVC中,用户对于M的操作是通过C传递的,然后C将改变传给V,并且M将在发生变化时通知V,然后V通过C获取变化;在MVVM中,用户直接与V交互,通过VM将变化传递给M,然后M改变之后通过VM将数据传递给V,从而实现解耦。
- 另一个区别是:M的数据需要进行解析后V才能使用时,C若承担解析的任务,就会变得很臃肿;在MVVM中,VM层承担了数据解析的工作,这时C就只需要持有VM,而不需要直接持有M了,从而完成了数据的解耦。
vue.js的观察者模式
软件架构
软件架构复用
- 克隆,完整地借鉴相似项目的设计方案,甚至代码,只需要完成一些细枝末节处的修改适配工作。
- 重构,构建软件架构模型的基本方法,通过指引我们如何进行系统分解,并在参考已有的软件架构模型的基础上逐步形成系统软件架构的一种基本建模方法。
架构分解方法
- 面向功能的分解方法,用例建模即是一种面向功能的分解方法;
- 面向特征的分解方法,根据数量众多的某种系统显著特征在不同抽象层次上划分模块的方法;
- 面向数据的分解方法,在业务领域建模中形成概念业务数据模型即应用了面向数据的分解方法;
- 面向并发的分解方法,在一些系统中具有多种并发任务的特点,那么我们可以将系统分解到不同的并发任务中(进程或线程),并描述并发任务的时序交互过程;
- 面向事件的分解方法,当系统中需要处理大量的事件,而且往往事件会触发复杂的状态转换关系,这时系统就要考虑面向事件的分解方法,并内在状态转换关系进行清晰的描述;
- 面向对象的分解方法,是一种通用的分析设计范式,是基于系统中抽象的对象元素在不同抽象层次上分解的系统的方法。
架构风格与策略
管道-过滤器
- 面向数据流的软件体系结构,最典型的应用是编译系统。
客户-服务
- 客户代码通过请求和应答的方式访问或者调用服务代码
- 客户是主动的,服务是被动的。
- 具有典型的模块化特征,降低了系统中客户和服务构件之间耦合 度,提高了服务构件的可重用性。
P2P
- 客户-服务模式的一种特殊情形,P2P架构中每一个构件既是客户端又是服务端,即每一个构件都有一种接口,该接口不仅定义了构件提供的服务,同时也指定了向同类构件发送的服务请求。这样众多构件一起形成了一种对等的网络结构
发布-订阅
- 有两类构件:发布者和订阅者。如果订阅者订阅了某一事件,则该事件一旦发生,发布者就会发布通知给该订阅者。
- 由于订阅者数量庞大,往往在消息推送时采用消息队列的方式延时推送。
- 观察者模式体现了发布-订阅架构的基本结构。
CRUD
- 是创建(Create)、 读取(Read) 、更新(Update)和删除(Delete)四种数据库持久化信息的基本操作的助记符, 表示对于存储的信息可以进行这四种持久化操作。
- CRUD也代表了一种围绕中心化管理系统关键数据的软件架构风格。
层次化
- 每一层为它的上一层提供服务,同时又作为下一层的客户。
架构的描述方法
分解视图 Decomposition View
- 呈现为较为明晰的分解结构特点
- 分解是构建软件架构模型的关键步骤
- 分解视图是描述软件架构模型的关键视图
- 用软件模块勾划出系统结构,往往会通过不同抽象层级的软件模块形成层次化的结构。
依赖视图 Dependencies View
- 展现了软件模块之间的依赖关系
- 根据依赖关系 确定开发和测试软件模块的先后次序。
泛化视图 Generalization View
- 展现了软件模块之间的一般化或具体化的关系
- 面向对象分析和设计方法中类之间的继承关系。
执行视图 Execution View
- 展示了系统运行时的时序结构特点,比如流程图、时序图等 。
- 执行视图中的每一个执行实体,一般称为组件(Component),都是不同于其他组件的执行实体。如果有相同或相似的执行实体那么就把它们合并成一个。
- 可以最终分解到软件的基本元素和软件的基本结构,因而与软件代码具有比较直接的映射关系。
实现视图 Implementation View
- 描述软件架构与源文件之间的映射关系。
- 比如软件架构的 静态结构以包图或设计类图的方式来描述
- 一般我们通过目录和源文件的命名来对应软件架构中的包、类等静态结构单元,这样典型的实现视图就可以由软件项目的源文件目录树来呈现。
部署视图 Deployment View
- 将执行实体和计算机资源建立映射关系。
- 执行实体的粒度要与所部署的计算机资源相匹配,比如以进程作为执行实体那么对应的计算机资源就是主机,这时应该描述进程对应主机所组成的网络拓扑结构,这样可以清晰地呈现进程间的网络通信和部署环境的网络结构特点。当然也可以用细粒度的执行实体对应处理器、存储器等。
- 部署视图有助于设计人员分析一个设计的质量属性
工作任务分配视图 Work assignment View
- 将系统分解成可独立完成的工作任务,以便分配给各项目团队和成员。
重要的软件质量属性
- 易于修改维护(Modifiability)高内聚低耦合
- 良好的性能表现(Performance)
- 安全性(Security)可靠性(Reliability)健壮性(Robustness)
- 易用性(Usability)
- 商业目标(Business goals)
软件危机与软件过程
软件危机
没有银弹
- 断言“在10 年内无法找到解决软件危机的杀手锏(银弹)。
- 认为软件工程专家们所找到的各种方法都是舍本逐末,它们解决不了软件中的根本困难,即软件概念结构的复杂性,无法达成软件概念的完整性和一致性,自然无法从根本上解决软件危机带来的困境。
软件过程
生命周期
分析、设计、实现、交付和维护五个阶段
- 分析阶段的任务是需求分析和定义,分析阶段一般会在深入理解业务的情况下,形成业务概念原型
- 设计阶段分为软件架构设计和软件详细设计,前者一般和分析阶段联系紧密,一般合称为“分析与设计”;后者一般和实现阶段联系紧密,一般合称为“设计与实现”。
- 实现阶段分为编码和测试,其中测试又涉及到单元测试、集成测试、系统测试等。
- 交付阶段主要是部署、交付测试和用户培训等。
- 维护阶段一般是软件生命周期中持续时间最长的一个阶段,而且在维护阶段很可能会形成单独的项目,从而经历分析、设计、实现、交付几个阶段,最终又合并进维护阶段。
软件过程
软件过程又分为描述性的(descriptive)过程和说明性的(prescriptive)过程。
- 描述性的过程试图客观陈述在软件开发过程中实际发生什么。
- 说明性的过程试图主观陈述在软件开发过程中应该会发生什么。
- 采用不同的过程模型时应该能反映出要达到的过程目标,比如构建高质量软件、早发现缺陷、满足预算和日程约束等。不同的模型适用于不同的情况,我们常见的过程模型,比如瀑布模型、 V模型 、原型化模型等都有它们所能达到的过程目标和适用的情况。
瀑布模型
- 瀑布模型中的主要阶段通过里程碑和交付产出来划分的。
- 瀑布模型是一个过程活动的顺序结构,没有任何迭代,而大多数软件开发过程都会包含大量迭代过程。瀑布模型不能为处理开发过程中的变更提供任何指导意义,因为瀑布模型假定需求不会发生任何变化。
带原型的瀑布模型
- 为了尽早暴露风险和控制风险,在瀑布模型的基础上增加一个原型化阶段,可以有效将风险前移,改善整个项目的技术和管理上的可控性。
- 原型就是根据需要完成的软件的一部分,完成哪一部分是根据开发原型的目标确定,比较常见的有用户接口原型和软件架构原型。
V模型
- 在瀑布模型基础上发展出来的
- 我们发现单元测试、集成测试和系统测试是为了在不同层面验证设计 ,而交付测试则是确认需求是否得到满足。也就是瀑布模型中前后两端的过程活动具有内在的紧密联系
- 如果将模块化设计的思想拿到软件开发过程活动的组织中来,可以发现通过将瀑布模型前后两端的过程活动结合起来,可以提高过程活动的内聚度,从 而改善软件开发效率。
- 生死相依原则:在开始一项工作之前,先去思考验证该工作完成的方法。
分阶段增量和迭代
- 每次可以交付系统的一小部分,从而缩短开发迭代的周期。
- 还可以让产品系统和开发系统并行进行。
- 交付策略分为两种,一是增量开发,二是迭代开发。
- 增量开发就是从一个功能子系统开始交付,每次交付会增加一些功能,这样逐步扩展功能最终完成整个系统功能的开发。
- 迭代开发是首先完成一个完整的系统或者完整系统的框架,然后每次交付会升级其中的某个功能子系统,这样反复迭代逐步细化最终完成系统开发。
- 显著的优点:
- 能够在系统没有开发完成之前,开始进行交付和用户培训;
- 频繁的软件发布可以让开发者敏捷地应对始料未及的问题;
- 开发团队可以在不同的版本聚焦于不同的功能领域,从而提升开发效率;
- 还有助于提前布局抢占市场。
螺旋模型
- 是一种演化软件开发过程模型,兼顾了快速原型的迭代的特征以及瀑布模型的系统化与严格监控。
- 螺旋模型最大的特点在于引入了其他模型不具备的风险管理,使软件在无法排除重大风险时有机会停止,以减小损失。同时,在每个迭代阶段构建原型是螺旋模型用以减小风险的基本策略。
- 每一次迭代过程分为四个主要阶段:
- 计划
- 决定目标、选项与约束
- 评估选项与风险
- 开发与测试
个人软件过程PSP和团队软件过程TSP
团队的基本要素
- 团队的规模
- 团队的凝聚力
- 团队协作的基本条件
CMM/CMMI,团队强度和项目特点的关系
- CMM/CMMI用于评价软件生产能力并帮助其改善软件质量的方法,成为了评估软件能力与成熟度的一套标准,它侧重于软件开发过程的管理及工程能力的提高与评估,是国际软件业的质量管理标准。 CMMI共有5个级别,代表软件团队能力成熟度的5个等级,数字越大,成熟度越高,高成熟度等级表示有比较强的软件综合开发能力。
- 一级,初始级,软件组织对项目的目标与要做的努力很清晰,项目的目标可以实现,但主要取决于实施人员。
- 二级,管理级,软件组织在项目实施上能够遵守既定的计划与流程,有资源准备,权责到人,对项目相关的实施人员进行了相应的培训,对整个流程进行监测与控制,并联合上级单位对项目与流程进行审查,这级能保证项目的成功率。
- 三级,已定义级,软件组织能够根据自身的特殊情况及自己的标准流程,将这套管理体系与流程予以制度化。科学管理成为软件组织的文化与财富。
- 四级,量化管理级,软件组织的项目管理实现了数字化,降低了项目实施在质量上的波动。
- 五级,持续优化级,软件组织能够充分利用信息资料,对软件组织在项目实施的过程中可能出现的问题予以预防。能够主动地改善流程,运用新技术,实现流程的优化。
敏捷方法
敏捷宣言
- 个体和互动 高于 流程和工具
- 可用的软件 高于 详尽的文档
- 客户合作 高于 合同谈判
- 响应变化 高于 遵循计划
总结
- 为了应对软件危机,首先想到的是通过简化和抽象的方法“就事论事”地处理软件本身的问题,从而诞生了结构化程序设计、面向对象分析和设计、模块化方法、设计模式 、软件架构等一系列技术。
- 这些技术确实在一定程度上缓解了软件危机的表现,这些技术本质上都是通过对软件本身的抽象来有效管控软件的复杂性。但在大型复杂软件系统中,这些技术依然力有不逮。(没有银弹)
- 行有不得,反求诸己。难以为复杂软件建立完整且一致的抽象概念模型,这一本质问题显现出来后,逐渐认识到相对于软件本身的管理这一局部问题,项目管理上的全局问题是更为主要的矛盾,于是开始反思软件开发过程本身,因此将软件过程改进纳入到应对软件危机的视野中,从而提出了各种软件生命周期模型及软件过程改进方法, 以PSP和TSP的基本方法为支撑的CMM/CMMI软件成熟度模型最具有代表性。
- 随着互联网、移动互联网以及虚拟化、云计算等技术的发展,软件要依赖的环境发生显著变化,当然这些变化本身也是软件塑造的结果。软件从复杂单体软件的以架构为中心向微服务架构的分布式软件转变 ,软件过程从CMM/CMMI向敏捷方法和DevOps转变。
- 重构作为编程的一种基本方法得到业界的普遍认同和采纳;微服务结构则有利于在更高的设计抽象层级上对软件进行重构;敏捷方法则进一步有利于在软件开发过程层面进行迭代和重构;DevOps则终极性地在业务、运维和效益层面进行快速迭代重构。
406
参考资料:代码中的软件工程 https://gitee.com/mengning997/se