学习收获
这个学期选修了孟宁老师的高级软件工程课程,主要讲授的内容是孟宁老师编写的《代码中的软件工程》。孟宁老师讲课的内容由浅入深,从码农的自我修养、必备工具和技能,到工程化编程,到需求分析和软件设计,再到设计模式和软件架构,最后到软件危机和软件过程,还结合了课后的编程实践以及学校的工程实践,让我对软件工程有了更深刻的理解和掌握, 毫不夸张地说,这门课让我受益匪浅。
在以前,我一直对软件存在一些误解,认为软件就是程序,软件的开发就是编写程序,只要编完了程序,一切也就ok了,而且我还片面的认为只要我掌握了时下最新的语言和工具,那么我就能写程序了。但是通过了高级软件工程这门课的学习,使我认识到了我以前的错误。软件其实不仅仅是程序,软件开发其实也不仅仅是编写程序,软件是思想在硬件上的载体和体现,处理的是逻辑和信息。唯有对软件和软件的开发过程,有充分的认识,才能更好的开发出,过程受控、质量受控的软件产品。软件工程与其说是一门课程,不如说是一门思想。是一个如何去分析和处理问题的过程,应该说其范畴已经远远不止局限于该门课程,成为了一个综合的一个能够解决问题的思想集合。
以下是对课程学习的内容的总结,以便在考试的时候更好的复习。
一、工欲善其事必先利其器
1.Vistual Studio Code
常用快捷键
打开文件夹 Ctrl/⌘+O
关闭文件夹工作区 Ctrl/⌘+K F
新建文件夹 Ctrl/⌘+N
关闭文件 Ctrl/⌘+W
编辑文件和保存文件 Ctrl/⌘+S
文件内搜索 Ctrl/⌘+F
关闭所有文件 Ctrl/⌘+K W
关闭以保存的文件 Ctrl/⌘+K U
Ctrl+/ 单行注释
Ctrl+shift+/ 块注释
Ctrl/⌘+Shift+E 文件资源管理器
Ctrl+Shift+G 源代码管理
Ctrl/⌘+Shift+F 跨文件搜索
Ctrl/⌘+Shift+D 启动和调试
Ctrl/⌘+Shift+P查找并运行所有命令
Ctrl/⌘+Shift+M查看错误和警告
Ctrl/⌘+Shift+X 管理扩展插件
Ctrl+`切换集成终端
用于代码理解和调试的第三插件与VS code主进程之间的桥梁:LSP和DAP
-
LSP(language server protocol)
-
DAP(debug adapter protocol)
2.Git
git本地常用命令
git init 在一个新建的目录下创建版本库
git status 查看当前工作区的状态
git add Files 把文件添加到暂存区
git commit -m “提交说明” 把暂存区里的文件提交到仓库
git log 查看当前HEAD之前的提交记录,便于回到过去
git reset --hard HEAD^^/HEAD-100/commit-id/commit-id的前几个字符 回退版本
git relog 可以查看当前HEAD之后的提交记录,便于回溯
git reset --hard commit-id/commit-id 的头几个字符 回退
git checkout 分支名 切换分支
git远程版本库常用命令
git clone 仓库地址 通过clone远端的版本库从而在本地创建一个版本库
git fetch 仓库地址 下载一个远程存储库数据对象等信息到本地存储库
git push 仓库地址 将本地存储库的相关数据对象更新到远程存储库
git merge 分支名 将目标分支合并到当前分支中,并自动进行新的提交,如果不想提交,可以加 --no-commit后缀
团队项目中的分叉合并
建议团队项目的每一个开发者都采用的工作流程大致如下:
1.克隆或同步最新的代码到本地存储库
2.为自己的工作创建一个分支,该分支应该只负责单一功能模块或代码模块的版本控制
3.在该分支上完成某单一功能模块或代码模块的开发工作
4.最后,将该分支合并到主分支
git默认的合并方式为:快进式合并,会降分支里commit合并到主分支里,合并成一条时间线,与我们期望的呈现为一段独立的分支线段不符,因此合并时需要使用no-ff参数关闭“快进式合并”
3.Vim
基本操作
vim三种模式:命令模式、输入模式、底线命令模式
常用命令:左下上有 hjkl
n n表示数字,表示向后移动n个字符
0或Home移动到当前行最前面的字符
$或End移动到当前行最后面的字符
H 光标移动到这个屏幕的最上方哪一行的第一个字符
M 光标移动到这个屏幕的中央哪一行的第一个字符
L 光标移动到这个屏幕的最下方哪一行的第一个字符
G 移动到这个档案的最后一行(常用)
nG n为数字。移动到这个档案的第n行
gg移动到整个档案的第一行
n n为数字,光标向下移动n行
删除操作
dd 删除游标所在的哪一行
ndd n为数字,删除光标所在的向下n行,例如20dd则是删除20行
复制粘贴
yy复制游标所在的哪一行
p 将已复制的数据在光标的下一行贴上
P 将已复制的数据在光标的上一行贴上
复原和重做
u 复原前一个动作
Ctrl+r 重做上一个动作
基本搜索
/word 想贯标之下寻找一个名称为word的字符串
?word向光标之上寻找一个字符串名为word的字符串
n下一个搜索结果 N上一个搜索结果
替换
:n1,n2s/word1/word2/g n1 与 n2 为数字。在第 n1 与 n2 行之间寻找 word1 这个字符串,并将该字符串取代为 word2 !举例来说,在 100 到 200 行之间搜寻 vbird 并取代为 VBIRD 则:『:100,200s/vbird/VBIRD/g』。(常用)
•:1,$s/word1/word2/g 或 :%s/word1/word2/g 从第一行到最后一行寻找 word1 字符串,并将该字符串取代为 word2 !(常用)
•:1,$s/word1/word2/gc 或 :%s/word1/word2/gc 从第一行到最后一行寻找 word1 字符串,并将该字符串取代为 word2 !且在取代前显示提示字符给用户确认 (confirm) 是否需要取代!(常用)
其他:set nu 显示行号 set nonu取消行号
4.正则表达式
贪婪匹配:找到符合正则表达式的字符串的最长可能部分。
懒惰匹配:找到符合正则表达式模式的字符串的最小可能部分。
比如使用.可以匹配任意字符,可以通过.?开启严格懒惰模式
-
/:vim中开启正则表达式
-
.、*、?、|
-
{3,5}
-
[a-z]、[abc]、[^]
-
/d /D /w
-
greedy贪婪匹配(默认):最长的
-
lazy懒惰匹配:最短的。加‘*?’
-
捕获组
二、工程化编程实战
代码的风格原则:简明、易读、无二意性
编写高质量代码的基本方法
1.通过控制结构简化代码
2.通过数据结构简化代码
3.一定要有错误处理
4.性能优先策略背后隐藏的代价
5.拒绝修修补补,要不断重构代码
模块化软件设计
模块化:是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立的进行设计和开发。这个做法背后的基本原理是关注点的分离。
耦合度:指软件模块之间的依赖程度,一般可以分为紧密耦合、松散耦合和无耦合
内聚度:是指一个软件模块内部各种元素之间互相依赖的紧密程度
理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性。
软件设计中的一些基本方法
KISS(keep it simple&stupid)原则:
-
一行代码只做一件事
-
一个代码块只做一件事
-
一个函数只做一件事
-
一个软件模块制作一件事
使用本地化外部接口来提高代码的适应能力。
先写伪代码的代码结构更好一些。
消费者和生产者重用
消费者重用是指软件开发者在项目中重用已有的一些软件模块代码,以加快项目工作进度。
接口的基本概念
接口就是互相练习的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。
接口规格包含的五个基本要素:
1.接口的目的
2.接口使用前所需要满足的条件,一般称为前置条件或假定条件
3.使用接口的双方遵守的协议规范
4.接口使用之后的效果,一般称为后置条件
5.接口所隐含的质量属性
RESTful API
GET
用来获取资源
POST
用来新建资源(也可以用来更新资源)
PUT
用来更新资源
DELETE
用来删除资源
接口与耦合度的关系
公共耦合(紧密):当软件模块之间共享数据区或变量名的软件模块之间即是公共沟壑,显然两个软件模块之间的接口定义不是通过显示的调用方式,而是隐式的共享了数据区域或变量名。
数据耦合(松散):在软件模块之间仅通过显式的调用传递基本数据类型即为数据耦合
标记耦合(松散):在软件模块之间仅通过显式的调用传递复杂的数据结构(结构化数据)即为标记耦合,这时数据的结构成为调用双方软件模块隐含的规格越多,因此耦合度比数据耦合高。但相比公共耦合没有经过显示的调用传递数据的方式耦合度要低。
通用接口定义的基本方法
参数化上下文、移除前置条件、简化后置条件
可重入的函数不一定是线程安全地,不可重入的函数一定不是线程安全的
微服务的概念
-
由一系列独立的微服务共同组成软件系统的一种架构模式;
-
每个微服务单独部署,跑在自己的进程中,也就是说每个微服务可以有一个自己独立的运行环境和软件堆栈;
-
每个微服务为独立的业务功能开发,一般每个微服务应分解到最小可变产品(MVP),达到功能内聚的理想状态。微服务一般通过RESTful API接口方式进行封装 ;
-
系统中的各微服务是分布式管理的,各微服务之间非常强调隔离性,互相之间无耦合或者极为松散的耦合,系统通过前端应用或API网关来聚合各微服务完成整体系统的业务功能。
-
微服务架构的基本概念可以简单概括为通过模块化的思想垂直划分业务功能,传统单体集中式架构和微服务架构如下图示意
三、从需求分析到软件设计
用例
用例的核心概念中首先它是一个业务过程,经过逻辑整理抽象出来一个业务过程,这是用例的实质。
用例的几个基本要素
1.一个用例应该由业务领域内的某个参与者所触发
2.用例必须能为特点的参与者完成一个特定的业务任务
3.一个用例必须终止于某个特定参与者,也就是特定参与者明确地或者隐含地得到了业务任务完成的结果
用例的三个抽象层级
1.抽象用例:只要用一个干什么、做什么或完成什么业务任务的动名词短语,就可以非常精简地指明一个用例。
2.高层用例:需要给用例的范围划定一个边界,也就是用例在什么时候什么地方开始,以及在什么时候什么地方结束。
3.扩展用例:需要将参与者和待开发软件系统为了完成用例所规定的业务任务的交互过程一步一步详细地描述出来,一般我们使用一个两列的表格将参与者和待开发软件系统之间从用例开始到用例结束的所有交互步骤都列举出来
用例建模的基本步骤
1.从需求表述中找出用例,往往是动名词短语表示的抽象用例
2.描述用例开始和结束的状态,用TUCBW和TUCEW表示的高层用例
3.对用例按照子系统或不同的方面进行分类,描述用例与用例、用例与参与者之间的上下文关系,并画出用例图。
4.进一步逐一分析用例与参与者的详细交互过程,完成一个两列的表格将参与者和待开发软件系统之间从用例开始到用例结束的所有交互步骤都列举出来扩展用例。
准确提取用例的基本方法
1.从需求中寻找业务领域相关的动名词和动词短语,比如做什么事、什么事情必须完成、或者执行某任务等。
2、验证这些业务领域相关的动名词和动名词短语到底是不是用例。验证业务领域相关的动名词或动名词短语是不是用例的标准是满足四个必要条件:
(1)它是不是一个业务过程
(2)它是不是由某个参与者触发开始
(3)它是不是显式地或隐式地终止于某个参与者
(4)它是不是为某个参与者完成了有用的业务工作
3.在需求中识别出参与者、系统或子系统
参与者会触发某个用例开始,用例也会显式地或隐式地终止于某个参与者,用例会属于系统或子系统。
面向对象分析涉及的基本概念
对象和属性之间有依附关系,属性用来描述对象或存储对象的状态信息。
由于对象能够独立存在,那么对象的创建必须是通过显式地或隐式地调用构造过程。
继承关系
表达着两个概念之间具有概括化/具体化的关系。一个概念比另一个概念更加概括/具体。一般用三角形箭头连线比偶是两个类之间的继承关系。
聚合关系
聚合关系表示一个对象是另一个对象的一部分的情况。比如发动机引擎是小汽车的一部分。也被成为“部分与整体”的关系。
聚合关系使用一个平行四边形的箭头表示:
关联关系
关联关系表示继承和聚合意外的一般关系,是业务领域内特定的两个概念之间的关系。
业务领域建模的基本步骤
第一步:收集应用业务领域的信息。聚焦在功能需求层面,也考虑其他类型的需求和资料。
第二步:头脑风暴,列出重要的应用业务领域概念,给出这些概念的属性,以及这些概念之间的关系。
第三步:给这些应用业务领域概念分类。分别列出哪些是类、哪些属性和属性值、以及列出类之间的继承关系、聚合关系和关联关系。
第四步:将结果用UML类图画出来。
反范式化总结
使用双向引用来优化你的数据库架构,前提是你能接受无法原子更新的代价
可以再引用关系中冗余数据到one端或N端
在决定是否采用反范式化时需要考虑下面的因素
你将无法对冗余的数据进行原子更新
只有读写比比较高的情况下才应该采取反范式化的设计
瀑布模型
最基本的过程模型,它把整个软件过程按顺序划分成了需求、设计、编码、测试和部署五个阶段
统一过程的核心要义是用例驱动、以架构为中心、增量且迭代的过程。用例驱动就是我们前文中用例建模得到的用例作为驱动软件开发的目标;以架构为中心的架构是后续软件设计的结果,就是保持软件架构相对稳定,减小软件架构层面的重构造成的混乱。
敏捷同一过程的四个关键步骤
1.确定需求
2.通过用例的方式来满足这些需求
3.分配这些用例到各增量阶段
4.具体完成各增量阶段所计划的任务
敏捷同一过程的增量阶段
在每一次增量阶段的迭代中,都要进行从需求分析到软件设计实现的过程,具体敏捷统一过程将增量阶段分为五个步骤:
用例建模
业务领域建模
对象交互建模
形成设计类图
软件的编码实现和软件应用部署
对象交互建模的基本步骤
找出关键步骤进行剧情描述
将剧情描述转换成剧情描述表
将剧情描述表转换成序列图
四、软件科学基础理论
软件的基本结构
顺序结构:最简单的程序结构,也是最常用的程序结构,只要按照解决问题的顺序写出相应的语句就行,它的执行顺序是自上而下,依次执行。
分支结构:是在顺序结构的基础上,利用影响标志寄存器上标志位的质量和跳转质量组合起来借助于标志寄存器或特定寄存器暂存条件状态实现分支结构。
循环结构:是顺序结构和分支结构的组合起来形成的更为复杂的程序结构,是指在程序中需要反复执行某个功能而设置的一种程序结构。
回调函数
回调函数是一个面向过程的概念,是代码执行过程的一种特殊流程。回调函数就是一个通过函数指针调用的函数。把函数的指针(地址)作为参数传递给另一个函数,当这个指针调用其所指向的函数时,就称这是回调函数。
设计模式的组成部分
该设计模式的名称
该设计模式的目的(即要解决什么问题)
该设计模式的解决方案
该设计模式的解决方案有哪些约束和限制条件
设计模式的分类
根据模式是主要用于类上还是主要用于对象上来划分的话,可以分为:
类模式:用于处理类与子类之间的关系,这些关系通过集成来建立,是静态的,在编译时刻便确定下来了。
对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。
由于组合关系或聚合关系比继承关系耦合度低,因此多数设计模式都是对象模式。
常用的设计模式
单例模式:某个类智能生产一个实例,该类提供了一个全局访问点供外部获取该实例,典型的应用如数据库实例。
原型模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新势力,原型模式的应用场景非常多,几乎所有通过复制的方式创建新实例的场景都有原型模式。
建造者模式:讲将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。主要应用于复杂对象中的各部分的建造顺序相对固定或者创建复杂对象的算法独立于各组成部分。
代理模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
适配器模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。继承和对象组合都可以实现适配器模式,但由于组合关系或聚合关系比继承关系耦合度低,所以对象组合方式的适配器模式比较常用
装饰(Decorator)模式:在不改变现有对象结构的情况下,动态地给对象增加一些职责,即增加其额外的功能
外观(Facade)模式:为复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用
策略模式:定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的改变不会影响使用算法的客户。
命令模式:讲一个请求封装为一个对象,使发出请求的责任和执行请求的责任分隔开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储\传递\调用\增加与管理
模板方法:定义一个操作者的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。模板方法是继承和重载机制的应用,属于类模式。
职责链:为了避免请求发送者与多个请求处理着偶合在一起,将所有请求的处理者通过前一对象记住旗下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,知道有对象处理它为止。通过这种方式将多个请求处理着串联为一个链表,去除请求发送者与它们之间的耦合。
中介者:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
设计模式背后的设计原则
1.开闭原则
2.Liskov替换原则
3.依赖倒置原则
4.单一职责原则
5.迪米特法则
6.合成复用原则
软件架构模型
软件架构模型是在高层抽象上对系统中关键要素的描述,而且表现出抽象的层次结构
构件软件架构模型的基本方法就是在不同层次上分解系统并抽象出其中的关键要素。
分解的常见方法
面向功能的分解方法,用例模型即是一种面向功能的分解方法
面向特征的分解方法,根据数量众多的某种系统显著特征在不同抽象层次上划分模块的方法。
面向数据的分解方法,在业务领域建模中形成概念业务数据模型即应用了面向数据的分解方法。
面向并发的分解方法,在一些系统中具有多种并发任务的特点,那么我们可以将系统分解到不同的并发任务中(进程或线程),并描述并发任务的时序交互过程。
面向事件的分解方法,当系统中需要处理大量的事件,而且往往事件会触发复杂的状态转换关系,这时系统就要考虑面向事件的分解方法,并内在状态转换关系进行清晰的描述
面向对象的分解方法,是一种通用的分析设计范式,是基于系统中抽象的对象元素在不同抽象层次上分解的系统的方法。
软件架构风格与策略
管道-过滤器风格:该软件架构是面向数据流的软件体系结构,最典型的应用是编译系统。
客户机-服务器结构(client/server):是指客户代码通过请求和硬打的方式访问或者调用服务代码。这里的请求和应答可以使函数调用和返回值,也可以是socket
中的send
和recv
,还可以是HTTP
中的get
请求和响应。
P2P结构:是客户-服务模式的一种特殊形式,P2P架构中每一个架构既是客户端,又是服务端,即每一个构件都有一种借口,该接口不禁定义了构件提供的服务,同时也指定了向同类构件发送的服务请求,这样众多构件一起形成了一种对等的网络结构
发布-订阅:在该类架构中,有两类构件:发布者和订阅者,如果订阅者订阅了某一事件,则该事件一旦发生,发布者就会发布通知给订阅者,观察者模式体现了发布-订阅架构的基本结构。
CRUD:是创建(create),读取(read),更新(update)和删除(delete)四种数据库持久化信息的基本操作的助记符,表示对于存储的信息可以进行这四种持久化操作。CRUD也代表了一种围绕中心化管理系统关键数据的软件架构风格。一般常见的各类信息系统,比如ERP,CRM,医院信息平台等可以用CRUD架构来概括
层次化:较为复杂的系统中的软件单元,仅仅从平面展开的角度进行模块化分解是不够的,还需要从垂直纵深的角度将软件单元按层次化组织,每一层为它的上一层提供服务,同时又作为下一层的客户。
MVC结构
- 许多应用系统的用途都是从数据库中检索数据,并将其显示给用户。
- 在用户更改数据之后,系统在将更新内容存储到数据存储中。
- 因为关键的信息流发生在数据存储和用户界面之间,所以一般倾向于将这两部分捆绑在一起,以减少编码量并提高应用程序性能。
- 模型-视图-控制器(MVC)结构将应用程序的数据模型、业务逻辑和用户界面分别放在独立构件中,这样对用户界面的修改不会对数据模型/业务逻辑造成太大影响。
- 模型(model):封装应用程序状态、响应状态查询、应用程序功能、通知视图改变;
- 视图(view):解释模型、模型更新请求、发送用户输入给控制器、允许控制器选择视图;
- 控制器(controller):定义应用程序行为、用户动作映射成模型更新、选择响应的视图;
软件架构的描述方法
分解视图:用软件模块勾画出系统结构,往往会通过不同抽象层级的软件模块形成层次的结构。
依赖视图:展现了软件模块之间的依赖关系。
泛化视图:展现了软件模块之间的一般化或具体化关系
执行视图:展示了系统运行时的时序结构特点,比如流程图、时序图等。
实现视图:是描述软件架构与源文件之间的映射关系。
部署视图:是将执行实体和计算机资源建立映射关系。
工作分配视图:将系统分解成可独立完成的工作任务,以便分配各项目团队和成员
高质量
外部质量:用户看到的产品质量,比如正确的功能、发生故障的数量等。
内部质量:开发者看到的产品质量,比如代码缺陷。
几种重要的软件质量属性:易于修改维护、良好的表现性能、安全性、可靠性、健壮性、易用性、商业目标
五、软件危机和软件过程
软件危机的根本问题:软件概念结构的复杂性,无法达成软件概念的完整性和一致性,自然无法从根本上解决软件危机带来的困境。
软件生命周期:分析、设计、实现、交付、维护。
团队的基本要素
-
团队规模
-
团队的凝聚力
-
团队协作的基本条件
敏捷宣言
-
个体和互动高于流程和工具
-
工作的软件高于详尽的文档
-
客户合作高于合同谈判
-
响应变化高于遵循计划
构件软件产品
驱动代码:是调用或处罚一个特定软件模块执行的测试程序,一般也称为测试驱动,它通过传递一些测试用例来模拟该软件模块的执行上下文。
桩代码:是一种模拟缺失的软件模块的特殊程序,它的主要目的是为了让依赖它的上层软件模块能正常测试执行,一般也称为测试桩