高级软件工程课程总结

工欲善其事 必先利其器

Visual Studio Code

常用快捷键

  • 打开文件夹( Ctrl/⌘+O)和关闭文件夹工作区( Ctrl/⌘+K F)

  • 新建文件(Ctrl/⌘+N)、关闭文件(Ctrl/⌘+W)、编辑文件和保存文件(Ctrl/⌘+S)

  • 文件内搜索(Ctrl/⌘+F)

  • 关闭所有文件(Ctrl/⌘+K W)

  • 关闭已保存的文件(Ctrl/⌘+K U)

  • Ctrl+/用于单行代码注释和取消注释,Ctrl+Shift+A用于代码块注释和取消注释。

特性

  • 专注于开发者“最常用”的功能

  • 进程隔离的插件模型

  • UI渲染与业务逻辑隔离,一致的用户体验

  • 代码理解和调试——LSP和DAP两大协议

    (Language Server Protocol)

    1、节制的设计

    2、合理的抽象

    3、周全的细节

    (Debug Adapter Protocol)
  • 集大成的 Remote Development

    VSCRD VScode远程开发

Git

常用命令

  • git init 创建版本库

  • git clone 在本地克隆远端的版本库

  • git log 查看提交记录

  • git add 将文件添加到暂存区以便于commit

  • git commit 提交版本

  • git push 将本地仓库更新到远程仓库

  • git pull 获取远程仓库以更新本地仓库

  • git merge 合并两个以上的开发历史记录

  • git checkout 切换分支

  • git rebase 整理提交记录(还需要再看)

  • git status 查看当前工作区的状态

  • git reset – hard HEAD^^/HEAD~100/commit-id/commit-id的头几个字符 回退

  • git reflog 可以查看当前HEAD之后的提交记录,便于回到未来

  • git reset – hard commit-id/commit-id

  • git fetch 下载一个远程存储库数据对象等信息到本地存储库

Git

1、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 add和git commit -m提交版本,有了提交记录之后可以灵活地将当前工作区里的源代码回退到过去的某个版本,也就是回到过去。回到过去之后,也有可能发现之前撤销的某个版本是有价值的,希望找回来,这就需要回到未来。过去和未来之间的分界点就是HEAD,即当前工作区所依赖的版本。

2、git远程版本库的基本用法

  • git clone命令官方的解释是“Clone a repository into a new directory”,即克隆一个存储库到一个新的目录下。

  • git fetch命令官方的解释是“Download objects and refs from another repository”,即下载一个远程存储库数据对象等信息到本地存储库。

  • git push命令官方的解释是“Update remote refs along with associated objects”,即将本地存储库的相关数据对象更新到远程存储库。

  • git merge命令官方的解释是“Join two or more development histories together”,即合并两个或多个开发历史记录。

  • git pull命令官方的解释是“Fetch from and integrate with another repository or a local branch”,即从其他存储库或分支抓取并合并到当前存储库的当前分支。

git的设计理念和时间线

  • line diff是形成增量补丁的技术方法,即一个文件按行对比(line diff)将差异的部分制作成一个增量补丁。

  • commit是存储到仓库里的一个版本,是整个项目范围内的一个或多个文件的增量补丁合并起来,形成项目的增量补丁,是一次提交记录。每个提交(commit)都生成一个唯一的commit ID。

  • branch是按时间线依次排列的一组提交记录(commit),理论上可以通过当前branch上最初的提交(commit)依次打补丁直到HEAD得到当前工作区里的源代码。

  • tag标签就是某次提交(commit)的commit ID的别名

3、团队的分叉与合并

建议团队项目的每一个开发者都采用的工作流程大致如下: 1 克隆或同步最新的代码到本地存储库; 2 为自己的工作创建一个分支,该分支应该只负责单一功能模块或代码模块的版本控制; 3 在该分支上完成某单一功能模块或代码模块的开发工作; 4 最后,将该分支合并到主分支。

合并方法

默认的合并方式为"快进式合并"(fast-farward merge),会将分支里commit合并到主分支里,合并成一条时间线,与我们期望的呈现为一段独立的分支线段不符,因此合并时需要使用–no-ff参数关闭"快进式合并"(fast-farward merge)。

一、克隆或同步最新的代码到本地存储库
  1. git clone https://DOMAIN_NAME/YOUR_NAME/REPO_NAME.git

  2. git pull

二、为自己的工作创建一个分支,该分支应该只负责单一功能模块或代码模块的版本控制;
  1. git checkout -b mybranch

  2. git branch

三、在该分支上完成某单一功能模块或代码模块的开发工作;多次进行如下操作:
  1. git add FILES

  2. git commit -m “commit log”

四、最后,先切换回master分支,将远程origin/master同步最新到本地存储库,再合并mybranch到master分支,推送到远程origin/master之后即完成了一项开发工作。
  1. git checkout master

  2. git pull

  3. git merge --no-ff mybranch

  4. git push

4、Git Rebase

一般我们在软件开发的流程中,有一个朴素的版本管理哲学:开发者的提交要尽量干净、简单。开发者要把自己的代码修改按照功能拆分成一个个相对独立的提交,一个提交对应一个功能点,而且要在对应的 commit log message 里面描述清楚。因此在合并和 push 之前检查修改一下 commit 记录时常需要。

团队项目工作流程中增加一步Git Rebase,即在mybranch分支上完成自己的工作之后,为了让 log 记录将来更容易回顾参考,用 git rebase 重新整理一下提交记录。注意不要通过rebase对任何已经提交到远程仓库中的commit进行修改。

git rebase命令格式大致如下:
  • git rebase -i [startpoint] [endpoint] 其中-i的意思是–interactive,即弹出交互式的界面让用户编辑完成合并操作,[startpoint] [endpoint]则指定了一个编辑区间,如果不指定[endpoint],则该区间的终点默认是当前分支的HEAD。 一般只指定[startpoint] ,即指定从某一个commit节点开始,可以使用HEAD^^、HEAD~100、commit ID或者commit ID的头几个字符来指定一个commit节点,比如下面的代码指定重新整理HEAD之前的三个commit节点。

  • $ git rebase -i HEAD^^^

  • git rebase —abort

  • git rebase --continue

5、Fork + Pull request

为了解决开源社区松散团队的协作问题,Github提供了Fork+ Pull request的协作开发工作流程。

当你想更正别人仓库里的Bug或者向别人仓库里贡献代码时,要走Fork+ Pull request的协作开发工作流程: 1 先 fork(分叉) 别人的仓库,相当于拷贝一份; 2 做一些 bug fix或其他的代码贡献; 3 发起 Pull request 给原仓库; 4 原仓库的所有者 review Pull request,如果没有问题的话,就会 merge Pull request 到原仓库中。

Vim

三种模式

  • 命令模式

  • 输入模式

  • 底线命令模式 :q退出程序 :w保存文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nEQnzGJW-1657457016152)(image/image_iiLz0_23Ng.png)]

常用命令

  • hjkl 左下上右

  • 0或Home 移动到本行第一个字符

  • $或End 移动到本行最后一个字符

  • gg 移动到第一行

  • G 移动到最后一行 = 1G

  • n+Enter 向下移动n行

  • x del向后删除一个字符

  • X backspace向前删除一个字符

  • dd 删除一行

  • ndd 删除光标之后n行

  • yy 复制一行

  • nyy 复制光标之后n行

  • p 在当前行之后粘贴

  • P 在当前行之前粘贴

  • u 撤销

  • ctrl+r 重做

  • /word 在光标之后查找word字符串

  • n1,n2s/word1/word2/g 在第n1行与n2行之间将word1替换为word2

  • n1,$s/word1/word2/g 在第n1行之后将word1替换为word2

  • n1,$s/word1/word2/gc 在第n1行之后将word1替换为word2并且替换前需要确认

  • i 从光标处开始输入

  • I 从所在行的第一个非空格字符开始输入

  • a 从光标的下一个字符开始输入

  • A 从所在行的最后一个字符开始输入

  • o 从光标的下一行输入新的一行

  • O 从光标的上一行输入新的一行

  • r 取代模式,只取代光标处字符一次

  • R 一直取代字符(覆写)

  • :q 退出

  • :q! 强制退出

  • :w 将修改写入文件

  • :wq 保存退出

  • :wq! 强制保存退出

1、安装Vim

It is included as “vi” with most UNIX systems and with Apple OS X. 使用VSCode则可以在Linux、Windows和OS X上都能使用Vim 在VSCode中Ctrl/⌘+Shift+X管理扩展插件中搜索vim即可安装使用

  • 命令模式(Command mode),用户刚刚启动vi/vim,便进入了命令模式。此状态下敲击键盘动作会被vim识别为命令,而非输入字符。比如我们此时按下i,并不会输入一个字符,i被当作了一个命令。命令模式只有一些最基本的命令,因此仍要依靠底线命令模式输入更多命令

  • 输入模式(Insert mode),在命令模式下按下i就进入了输入模式,按ESC退出输入模式,切换到命令模式。

  • 底线命令模式(Last line mode),在命令模式下按下:(英文冒号)就进入了底线命令模式。底线命令模式可以输入单个或多个字符的命令,可用的命令非常多。基本的命令有q(退出程序)、w(保存文件)等。按ESC键可随时退出底线命令模式。

2、三种模式之间的切换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5eFvnSAb-1657457016153)(image/image_RwyBltzW_9.png)]

3、移动光标的基本方法

  • h 或 向左箭头键(←) 光标向左移动一个字符

  • j 或 向下箭头键(↓) 光标向下移动一个字符

  • k 或 向上箭头键(↑) 光标向上移动一个字符

  • l 或 向右箭头键(→) 光标向右移动一个字符

  • 如果你将右手放在键盘上的话,你会发现 hjkl 是排列在一起的,因此可以使用这四个按钮来移动光标。 如果想要进行多次移动的话,例如向下移动 30 行,可以使用 “30j” 或 “30↓” 的组合按键, 亦即加上想要进行的次数(数字)后,按下动作即可!

4、移动光标的更多方法

  • n 那个 n 表示『数字』,例如 20 。按下数字后再按空格键,光标会向右移动这一行的 n 个字符。例如 20 则光标会向后面移动 20 个字符距离。

  • 0 或功能键[Home] 这是数字『 0 』:移动到这一行的最前面字符处 (常用)

  • $ 或功能键[End] 移动到这一行的最后面字符处(常用)

  • H 光标移动到这个屏幕的最上方那一行的第一个字符

  • M 光标移动到这个屏幕的中央那一行的第一个字符

  • L 光标移动到这个屏幕的最下方那一行的第一个字符

  • G 移动到这个档案的最后一行(常用)

  • nG n为数字。移动到这个档案的第 n 行。例如 20G 则会移动到这个档案的第 20 行

  • gg 移动到这个档案的第一行,相当于 1G 啊! (常用)

  • n n 为数字。光标向下移动 n 行(常用)

5、删除

  • x, X 在一行字当中,x 为向后删除一个字符 (相当于 [del] 按键), X 为向前删除一个字符(相当于 [backspace] 亦即是退格键) (常用)

  • nx n 为数字,连续向后删除 n 个字符。举例来说,我要连续删除 10 个字符, 『10x』。

  • dd 删除游标所在的那一整行(常用)

  • ndd n 为数字。删除光标所在的向下 n 行,例如 20dd 则是删除 20 行 (常用)

  • d1G 删除光标所在到第一行的所有数据

  • dG 删除光标所在到最后一行的所有数据

  • d$ 删除游标所在处,到该行的最后一个字符

  • d0 那个是数字的0 ,删除游标所在处,到该行的最前面一个字符

6、复制与粘贴

  • yy 复制游标所在的那一行(常用)

  • p, P p为将已复制的数据在光标下一行贴上,P则为贴在游标上一行! 举例来说,我目前光标在第 20 行,且已经复制了 10 行数据。则按下 p 后, 那 10 行数据会贴在原本的 20 行之后,亦即由 21 行开始贴。但如果是按下 P 呢? 那么原本的第 20 行会被推到变成 30 行。 (常用)

  • nyy n 为数字。复制光标所在的向下 n 行,例如 20yy 则是复制 20 行(常用)

  • y1G 复制游标所在行到第一行的所有数据

  • yG 复制游标所在行到最后一行的所有数据

  • y0 复制光标所在的那个字符到该行行首的所有数据

  • y$ 复制光标所在的那个字符到该行行尾的所有数据

7、复原和重做

  • u 复原前一个动作。(常用)

  • [Ctrl]+r 重做上一个动作。(常用) 这个 u 与 [Ctrl]+r 是很常用的指令!一个是复原,另一个则是重做一次

8、自动化执行宏命令

  • J 将光标所在行与下一行的数据结合成同一行,2Jj则把下面的2行合并为一行并将光标下移一行

  • 在normal mode下q[a-z]开始录制宏命令,再次按q结束宏命令定义。

  • qa2Jjq,q-开始录制宏;a-宏的编号是a,最后一个q-结束宏定义

  • 3@a,执行三次a宏

9、基本搜索

  • /word 向光标之下寻找一个名称为 word 的字符串。例如要在档案内搜寻 vbird 这个字符串,就输入 /vbird 即可! (常用)

  • ?word 向光标之上寻找一个字符串名称为 word 的字符串。 n 这个 n 是英文按键。代表重复前一个搜寻的动作。举例来说, 如果刚刚我们执行 /vbird 去向下搜寻 vbird 这个字符串,则按下 n 后,会向下继续搜寻下一个名称为 vbird 的字符串。如果是执行 ?vbird 的话,那么按下 n 则会向上继续搜寻名称为 vbird 的字符串! N 这个 N 是英文按键。与 n 刚好相反,为『反向』进行前一个搜寻动作。 例如 /vbird 后,按下 N 则表示『向上』搜寻 vbird 。 使用 /word 配合 n 及 N 是非常有帮助的!可以让你重复的找到一些你搜寻的关键词!

10、基本搜索替换

  • :n1,n2s/word1/word2/g n1 与 n2 为数字。在第 n1 与 n2 行之间寻找 word1 这个字符串,并将该字符串取代为 word2 !举例来说,在 100 到 200 行之间搜寻 vbird 并取代为 VBIRD 则:『:100,200s/vbird/VBIRD/g』。(常用)

  • s是substitute的简写,表示执行替换字符串操作

  • g(global)表示全局替换;c(comfirm)表示操作时需要确认;i(ignorecase)表示不区分大小写

  • :1, s / w o r d 1 / w o r d 2 / g 或 : s/word1/word2/g 或 :%s/word1/word2/g 从第一行到最后一行寻找 word1 字符串,并将该字符串取代为 word2 !(常用) :1, s/word1/word2/g:s/word1/word2/gc 或 :%s/word1/word2/gc 从第一行到最后一行寻找 word1 字符串,并将该字符串取代为 word2 !且在取代前显示提示字符给用户确认 (confirm) 是否需要取代!(常用)

11、切换到编辑模式

  • i, I 进入输入模式(Insert mode): i 为『从目前光标所在处输入』, I 为『在目前所在行的第一个非空格符处开始输入』。 (常用)

  • a, A 进入输入模式(Insert mode): a 为『从目前光标所在的下一个字符处开始输入』, A 为『从光标所在行的最后一个字符处开始输入』。(常用)

  • o, O 进入输入模式(Insert mode): 这是英文字母 o 的大小写。o 为『在目前光标所在的下一行处输入新的一行』; O 为在目前光标所在处的上一行输入新的一行!(常用)

  • r, R 进入取代模式(Replace mode): r 只会取代光标所在的那一个字符一次;R会一直取代光标所在的文字,直到按下 ESC 为止;(常用) [Esc] 退出编辑模式,回到一般模式中(常用) 编辑模式在vi画面的左下角处会出现『–INSERT–』或『–REPLACE–』的字样

12、命令行模式

  • :w 将编辑的数据写入硬盘档案中(常用)

  • :w! 若文件属性为『只读』时,强制写入该档案。不过,到底能不能写入, 还是跟你对该档案的档案权限有关啊!

  • :w [filename] 将编辑的数据储存成另一个档案(类似另存新档)

  • :q 离开 vi (常用)

  • :q! 若曾修改过档案,又不想储存,使用 ! 为强制离开不储存档案。 注意一下啊,那个惊叹号 (!) 在 vi 当中,常常具有『强制』的意思~

  • :wq 储存后离开,若为 :wq! 则为强制储存后离开 (常用)

13、代码中批量添加注释

  • 批量注释:Ctrl + v 进入块选择模式,然后移动光标选中你要注释的行(VSCode可以鼠标选择代码块),再按大写的 I 进入行首插入模式输入注释符号如 // 或 #,输入完毕之后,按两下 ESC,Vim 会自动将你选中的所有行首都加上注释,保存退出完成注释。

  • 取消注释:Ctrl + v 进入块选择模式,选中你要删除的行首的注释符号,注意 // 要选中两个,选好之后按 d 即可删除注释,ESC 保存退出。

  • 批量注释:使用下面命令在指定的行首添加注释。使用命令格式: :起始行号,结束行号s//注释符/g(注意冒号),如:10,20s##//#g,:10,20s/^/#/g

  • 取消注释:使用名命令格式: :起始行号,结束行号s/注释符//g(注意冒号),如:10,20s#//##g,:10,20s/#//g

14、命令行环境下vim的配置

  • :set nu 显示行号,设定之后,会在每一行的前缀显示该行的行号

  • :set nonu 与 set nu 相反,为取消行号!

15、vi/vim键盘图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kanPTaAI-1657457016154)(image/image_zJojEJRUvD.png)]

正则表达式

通配符

  • . 任意字符

  • + 一次或多次

  • * 零次或多次

  • ? 可能存在前一个元素或使用最短匹配

  • [] 匹配组

  • - 匹配的字符范围

  • ^ 不相匹配的字符或字符串开头

  • $ 字符串结尾

  • \W [^A-Za-z0-9 _]

  • \w [A-Za-z0-9 _]

  • \D [^0-9]

  • \d [0-9]

  • () 捕获组

1、基本概念

正则表达式是对字符串操作的一种逻辑公式。

正则表达式的应用范围非常之广泛,最初是由Unix普及开来的,后来在广泛运用于Scala 、PHP、C# 、Java、C++ 、 Objective-c、Perl 、Swift、VBScript 、Javascript、Ruby 以及Python等等

使用正则表达式的原因

  • 测试字符串内的模式。例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。

  • 替换文本。可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。

  • 基于模式匹配从字符串中提取子字符串。可以查找文档内或输入域内特定的文本。

  • 例如,您可能需要搜索整个网站,删除过时的材料,以及替换某些 HTML 格式标记。在这种情况下,可以使用正则表达式来确定在每个文件中是否出现该材料或该 HTML 格式标记。此过程将受影响的文件列表缩小到包含需要删除或更改的材料的那些文件。然后可以使用正则表达式来删除过时的材料。最后,可以使用正则表达式来搜索和替换标记。

2、基本的字符串搜索方法

  • 在VS Code中跨文件搜索(Ctrl+Shift+F)和文件内搜索(Ctrl+F)的输入框输入字符串即可进行基本的字符串搜索。

  • 文件内搜索(Ctrl+F)可以使用Enter键代表继续搜索下一个,Shift+Enter键代表继续搜索上一个。这两个操作在文件内搜索面板上有对应的两个上下箭头,箭头右侧三条横线的小按钮是指在选定内容中查找(Alt+L)。

    跨文件搜索(Ctrl+Shift+F)和文件内搜索(Ctrl+F)的输入框内部右侧都有三个小按钮:

    • 第一个选中的话就是搜索时区分大小写(Alt+C);

    • 第二个选中的话表示搜索时全字匹配(Alt+W);

    • 第三个选中的话表示搜索时采用正则表达式(Alt+R),如果输入框中使用了正则表达式的语法规则,则需要选中第三个按钮或使用Alt+R快捷键。显然上图中还没有使用正则表达式。

  • 在 Vim 一般命令模式(Normal Mode)下输入/word 向光标之下寻找一个名称为 word 的字符串。例如要在档案内搜寻 vbird 这个字符串,就输入 /vbird 即可! (常用)

  • ?word 向光标之上寻找一个字符串名称为 word 的字符串。

  • n 这个 n 是英文按键。代表重复前一个搜寻的动作。举例来说, 如果刚刚我们执行 /vbird 去向下搜寻 vbird 这个字符串,则按下 n 后,会向下继续搜寻下一个名称为 vbird 的字符串。如果是执行 ?vbird 的话,那么按下 n 则会向上继续搜寻名称为 vbird 的字符串!

  • N 这个 N 是英文按键。与 n 刚好相反,为『反向』进行前一个搜寻动作。 例如 /vbird 后,按下 N 则表示『向上』搜寻 vbird 。

  • 使用 /word 配合 n 及 N 是非常有帮助的!可以让你重复的找到一些你搜寻的关键词

3、同时搜索多个字符串的方法

  • 在VS Code跨文件搜索(Ctrl+Shift+F)或文件内搜索(Ctrl+F)时,只要将多个字符串之间增加或运算符“|”,比如"main|int" ,同时选中输入框最右侧使用正则表达式(Alt+R)的小图标,即可同时搜索多个字符串。

  • 在 Vim 中搜索多个字符串的用法基本一致。如果你想匹配"yes"或"no",你需要的正则表达式是/yes|no

  • 你也可以搜索超过两种模式,通过添加更多的模式来添加更多的或运算符来分隔它们,如/yes|no|maybe

4、在匹配字符串时的大小写问题

  • 在VS Code跨文件搜索(Ctrl+Shift+F)和文件内搜索(Ctrl+F)中,默认是忽略大小写的,只有通过选中搜索输入框中区分大小写(Alt+C)小按钮,才会按照字符串的大小写严格匹配。

  • 在 Vim 中通过底线命令方式:set ignorecase 设置为忽略大小写;通过:set noignorecase 恢复到大小写敏感的状态,Vim 环境下默认是大小写敏感的。

  • Unix类的系统默认都是大小写敏感的,而Windows系统下默认是大小写不敏感的。这大概是VS Code和Vim在大小写的默认设置上不同的原因吧。

  • 在 Vim 中也可以通过快捷方式\c 表示大小写不敏感,\C 表示大小写敏感,比如/ignorecase\c,这个正则表达式可以匹配”ignorecase”,“igNoreCase"和"IgnoreCase”。

  • 在VS Code跨文件搜索(Ctrl+Shift+F)和文件内搜索(Ctrl+F)中不支持在正则表达式中使用\c这种快捷方式

5、通配符的基本用法

  • 通配符“.”将匹配任意一个字符。通配符也可称为 dot 和 period。你可以像正则表达式中的任何其他字符一样使用通配符。例如,如果你想匹配“hug”,“huh”,“hut”和“hum”,可以使用正则表达式hu.来匹配这所有四个字符串。

  • 通配符“+”用来查找出现一次或多次的字符,例如hahhhhh,可以使用正则表达式hah+来匹配。

  • 通配符“* ”匹配零次或多次出现的字符,使用正则表达式hah*来匹配,还可以匹配ha字符串。

  • 通配符“?”指定可能存在的元素,也就是检查前一个元素存在与否,如正则表达式colou?r、favou?rite中通配符“?”前面的u字符存在和不存在两种情况的字符串都会匹配。

简要总结一下通配符“.”表示任意一个字符;“?”表示前一个字符是否存在,也就是存在 0 次或 1 次;“+”表示前一个字符出现一次或多次;“ *”表示前一个字符出现 0 次、1 次或多次。

如果是指定只查找某个字符出现3次到5次的情况怎么办呢?可以使用 quantity specifiers 数量说明符指定模式的下限和上限数。数量说明符使用大括号{and}。你将两个数字放在大括号之间用逗号“,”隔开表示上限和下限数。

  • 要匹配字符串"aaah"中出现 3 到 5 次的 a,你的正则表达式将是a{3,5}h;

  • 仅匹配字符串"haaah"与至少出现 3 次的字母 a,正则表达式将是/ha{3,}h;

  • 为了仅匹配"hah"中出现 3 次的字母 a,你的正则表达式将是/ha{3}h。

6、匹配具有多种可能性的字符集

方括号[and]中来定义一组你希望匹配的字符。character sets 字符集允许你通过将其放在方括号[and]中来定义一组你希望匹配的字符。例如,你要匹配"bag",“big"和"bug”,而不是"bog"。你可以创建正则表达式/b[aiu]g 来执行此操作。[aiu]是只匹配字符"a","i"或"u"的 character sets 字符集。

在 character sets 字符集中,你可以使用连字符“-”定义要匹配的字符范围。例如,要匹配小写字母 a 到 e,你将使用[a-e]。使用连字符“-”匹配一系列字符并不只限于字母,它也可以匹配一系列数字。例如character sets 字符集[0-5]匹配 0 和 5 之间的所有数字,包括 0 和 5。

字符“^”定义不想要匹配的字符,称为negated character sets 否定字符集。要创建一个 否定字符集,你可以在方括号的开括号之后放置一个插入字符“”。例如[aeiou]排除元音的所有字符。

如果匹配的可能的字符太多,写起来不是很方便,因而字符集还提供快捷方式的写法。

  • 快捷方式\w 匹配字母数字[A-Za-z0-9_ ]。这个character sets 字符集匹配大小写字母加数字。注意,这个character sets 字符集还包括下划线字符“_”。

  • 快捷方式\W 搜索\w 的相反方向。需要注意相反的模式使用大写字母。此快捷方式与[^A-Za-z0-9 _]相同。

  • 快捷方式\d 搜索数字字符集[0-9]。

  • 快捷方式\D查找非数字字符,等于字符集[^0-9]。

7、贪婪匹配 vs. 懒惰匹配

在正则表达式中,greedy 贪婪匹配找到符合正则表达式模式的字符串的最长可能部分,并将其作为匹配返回。相反还有 lazy 懒惰匹配,是找到符合正则表达式模式的字符串的最小可能部分。

你可以将正则表达式t[a-z]*i应用于字符串"titanic"。这个正则表达式基本上是以 t 开始的模式,以 i 结尾,并且之间有0个、1个或多个字母。

正则表达式是默认的是 greedy 贪婪匹配,所以匹配将返回"titani"。它可以找到最大的子字符串,以符合该模式。

但是可以使用?字符将其更改为 lazy 懒惰匹配。“titanic”匹配调整后的t[a-z] *?i正则表达式会返回[“ti”]。注意这时字符“?”表示 lazy 懒惰匹配,字符“?”还可以作为通配符表示检查前一个元素存在与否。

8、一些特殊位置和特殊字符

  • 插入字符“^”用于表示字符串的开头。

  • 美元字符“$”表示字符串的末尾。

    如在"Ricky is first and can be found”查找开头的 Ricky 则为^Ricky,查找结尾的 found 则为/found$。

    可以使用\s 搜索空格,这是一个小写的 s 即 space 之意。此模式不仅匹配空格,还包括回车、制表符、换页和新行字符。你可以将其看作与字符集[\r\t\f\n\v]类似。

    使用\S 搜索非空格,这是一个大写的 S。此模式将不匹配空格、回车符、制表符、换页和新行字符。你可以想象它类似于字符类[^\r\t\f\n\v]。

    • \n:换行(光标到下行行首);

    • \r:回车(光标到本行行首);

    • \f:换页;

    • \t:水平跳格(水平制表);

    • \v:垂直跳格(垂直制表)。

9、使用捕获组复用模式

可能搜索的某些模式在字符串中多次出现,手动重复这些正则表达式是浪费时间的。有一个更好的方法可在你的字符串中有多个重复子串时进行指定,那就是capture groups 捕获组。

用括号(and)可以定义capture groups 捕获组,用于查找重复的子串,即把会重复的模式的正则表达式放在括号内。

要指定重复字符串的出现位置,可以使用反斜杠“\”,然后使用数字。该数字从 1 开始,并随着用括号定义的捕获组数量而增加。比如\1 来匹配前面通过括号定义的第一个捕获组。

使用 capture groups 捕获组来匹配字符串中连续出现三次的数字,每个数字由空格分隔,如(\d+)\s\1\s\1。42 42 42

10、基本的字符串搜索替换方法

  • 在VS Code中基本的字符串搜索替换方法比较简单,只要点击查找输入框左侧的“>”小按钮就可以打开替换输入框,如下图所示。也可以使用快捷键跨文件替换(Ctrl+Shift+H)和文件内替换(Ctrl+H),与跨文件搜索(Ctrl+Shift+F)和文件内搜索(Ctrl+F)相对应。

  • 在 Vim 中基本的字符串搜索替换方法为:n1,n2s/word1/word2/g,以:开头,n1 与 n2 为数字,即在第 n1 与 n2 行之间寻找 word1 这个字符串,并将该字符串取代为 word2 字符串。举例来说,在 100 到 200 行之间搜寻 regex 并取代为 RegEx 则为:100,200s/regex/RegEx/g。

  • 其中 s 是 substitute 的简写,表示执行替换字符串操作;最后的/g 是 global 的简写,表示全局替换。另外与/g 的用法相似,/c 是 comfirm 的简写,表示操作时需要确认;/i 是 ignorecase 的简写,表示不区分大小写。

  • :1,$s/word1/word2/g 或 :%s/word1/word2/g 从第一行到最后一行寻找 word1 字符串,并将该字符串取代为 word2 字符串。

  • :1,$s/word1/word2/gc 或 :%s/word1/word2/gc 从第一行到最后一行寻找 word1 字符串,并将该字符串取代为 word2 字符串,且在取代前显示提示信息给用户确认 (confirm) 是否需要取代。

11、复用捕获组的方式进行替换

  • 如果我们在搜索替换中希望保留搜索字符串中的某些字符串作为替换字符串的一部分,可以使用美元符号$访问替换字符串中的捕获组。

  • 比如在搜索正则表达式中的捕获组为(capture groups),则替换的正则表达式中可以直接使用$1复用搜索正则表达式中的捕获组为(capture groups)。

  • 在VS Code中,如果想将项目中所有的HTML标题标签中h改为大写H,搜索正则表达式可以查找出所有标题标签,如

    等,其中还定义了捕获组(\d)。

  • 替换的正则表达式使用$1复用了搜索正则表达式中定义的捕获组(\d)。

  • 在Vim中,复用捕获组的方式进行替换的用法为:1,$s/(capture groups)/$1/g

  • 如果想在当前文件中将所有的HTML标题标签中h改为大写H则正则表达式为:

    1,$s/<h(\d)>//g
    

代码中的软件工程

代码风格的基本原则

简明、易读、无二义性

代码的基本结构

顺序执行、条件分支、循环结构、递归结构(部分语言支持)

编写高质量代码的基本方法

  1. 通过控制结构优化代码

  2. 通过数据结构简化代码

  3. 一定要有错误处理 debug

模块化

模块化(Modularity)是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发

关注点的分离在软件工程领域是最重要的原则,我们习惯上称为模块化,翻 译成我们中文的表述其实就是“分而治之”的方法。

耦合度

耦合度是指软件模块之间的依赖程度一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BS4xBbVe-1657457016154)(image/image_Id8Prp2Coy.png)]

  • 公共耦合:软件模块之间共享数据区或变量名的软件模块之间

  • 数据耦合:软件模块之间仅通过显式的调用传递基本数据类型

  • 标记耦合:软件模块之间仅通过显式的调用传递复杂的数据结构(结构化数据),耦合度高于数据耦合低于公共耦合

内聚度

内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度

理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性(Feather)。

KISS(Keep It Simple & Stupid)原则

  • 一行代码只做一件事

  • 一个块代码只做一件事

  • 一个函数只做一件事

  • 一个软件模块只做一件事

“不要和陌生人说话” 原则

含义:

一个对象实例的一个方法内,应该只有以下变量成员

  • 对象实例本身(就是 This)

  • 方法的入参

  • 在方法内创建的实例变量

  • 对象内的其他成员变量和方法

  • 对象实例具有访问权限的全局变量

而禁止有其他成员,比如和运行环境耦合的东西,如界面元素、或者是第三方的网络接口。

可重用软件设计

消费者重用和生产者重用

消费者重用是指软件开发者在项目中重用已有的一些软件模块代码,以加快项目工作进度。软件开发者在重用已有的软件模块代码时一般会重点考虑如下四个关键因素 :

  • 该软件模块是否能满足项目所要求的功能;

  • 采用该软件模块代码是否比从头构建一个需要更少的工作量,包括构建软件模块和集成软件模块等相关的工作;

  • 该软件模块是否有完善的文档说明;

  • 该软件模块是否有完整的测试及修订记录;

生产者重用即生产者进行可重用软件设计

接口

接口就是互相联系的双方共同遵守的一种协议规范。在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。

5个基本要素
  • 目的/目标

  • 前置条件或假定条件

  • 协议规范

  • 后置条件

  • 质量属性

tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);

• 该接口的目标是从链表中取出链表的头节点,函数名GetLinkTableHead清晰明确地表明了接口的目标

• 该接口的前置条件是链表必须存在使用该接口才有意义,也就是链表pLinkTable != NULL;

• 使用该接口的双方遵守的协议规范是通过数据结构tLinkTableNode和tLinkTable定义的;

• 使用该接口之后的效果是找到了链表的头节点,这里是通过tLinkTableNode类型的指针作为返回值来作为后置条件, C语言中也可以使用指针类型的参数作为后置条件

• 该接口没有特别要求接口的质量属性,如果搜索一个节点可能需要在可以接受的延时时间范围内完成搜索;

2种方式
  • Call-in

  • Callback

基本方法
  1. 参数化上下文

  2. 移除前置条件

  3. 简化后置条件

微服务的概念

微服务是由一系列独立的微服务共同组成软件系统的一种架构模式;

每个微服务单独部署,跑在自己的进程中,也就是说每个微服务可以有一个自己独立的运行环境和软件堆栈;

微服务接口

微服务接口一般使用RESTful API来定义接口。REST即REpresentational State Transfer的缩写,可以翻译为“表现层状态转化”。有表现层就有背后的信息实体信息实体就是URI代表的资源,也可以是一种服务,状态转化就是通过HTTP协议里定义的四个表示操作方式的动词: GET、POST、 PUT、 DELETE,分别对应四种基本操作:

• GET用来获取资源;

• POST用来新建资源(也可以用于更新资源);

• PUT用来更新资源;

• DELETE用来删除资源。

微服务架构的基本概念可以简单概括为通过模块化的思想垂直划分业务功能,传统单体集中式架构和微服务架构如下图示意:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QNLSdv29-1657457016154)(image/image_DH9EaaxSAa.png)]

微服务接口示例:
POST /service/ocr-handwriting?code=auth_code HTTP/1.1
Content-Type: image/png
[content of handwriting.png]

• 该微服务接口的目标是手写识别服务,通过微服务命名ocr-handwriting来表明接口的目的;

• 该微服务接口的前置条件包括取得调用该微服务接口的授权auth_code,以及已经有一张手写图片handwriting.png

• 调用该微服务接口的双方遵守的协议规范除HTTP协议外还包括PNG图片格式和识别结果 JSON数据格式定义;

• 调用该微服务接口的效果即后置条件为以JSON数据的方式得到了识别的结果;

• 从以上示意代码中该微服务接口的质量属性没有具体指定,但因为底层使用了TCP协议,因此该接口隐含的响应时间质量属性应小于TCP连接超时定时器。

质量属性:反映软件产品某一方面质量的特征或特性。如可靠性、安全性、易用性等。

CALLBACK,即回调函数

CALLBACK,即回调函数,是一个通过函数指针调用的函数。

如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。

可重入函数与线程安全

线程(thread)是操作系统能够进行运算调度的最小单位。

  • 可重入:可由多个任务并发使用不会产生数据错误,可随时中断并继续运行

  • 不可重入:不能由超过一个任务共享,除非保证互斥

可重入函数基本要求

  • 不为连续的调用持有静态数据;

  • 不返回指向静态数据的指针;

  • 所有数据都由函数的调用者提供;

  • 使用局部变量,或者通过制作全局数据的局部变量拷贝来保护全局数据;

  • 使用静态数据或全局变量时做周密的并行时序分析,通过临界区互斥避免临界区冲突;

  • 绝不调用任何不可重入函数。

线程安全

多个线程同时运行且结果总与预期相同

线程安全问题都是由全局变量及静态变量引起的

Makefile

  • $@ 目标文件

  • $^ 所有依赖文件

  • $< 第一个依赖文件

看待软件质量的角度

产品、用户、商业

从需求分析到软件设计

需求类型

  • 功能性需求:根据需要的活动描述需要的行为

  • 质量需求或非功能需求:描述软件必须具备的一些质量特征

  • 设计约束: 设计决策,如平台或接口组件的选择

  • 过程约束: 对可用于构建系统的技术或资源的限制

高质量需求

  1. 可测试性

  2. 处理冲突

  3. 特点:准确的,一致的、无二义性的、完整的、可行的、无与主要目标不相关的需求、可测试的、可追踪的

需求分析的两种基本方法

原型化方法

原型化方法可以很好地整理出用户接口方式(UI,User Interface),比如界面布局和交互操作 过程。

建模的方法

建模的方法可以快速给出有关事件发生顺序或活动同步约束的问题,能够在逻辑上形成模型来 整顿繁杂的需求细节。

用例

用例(Use Case)的核心概念中首先它是一个业务过程,经过逻辑整理抽象 出来的一个业务过程,这是用例的实质。什么是业务过程?在待开发软件所处的业务领域内完成特 定业务任务的一系列活动就是业务过程。

用例的必要条件

  • 是一个业务过程

  • 由某个参与者触发开始

  • 显式或隐式地终止于某个参与者

  • 为某个参与者完成了有用的业务工作

三个抽象层级

  1. 抽象用例(Abstract use case)。只要用一个干什么、做什么或完成什么业务任务的动名词短语,就可以非常精简地指明一个用例。

  2. 高层用例(High level use case)。需要给用例的范围划定一个边界,也就是用例在什么时候什么地方开始,以及在什么时候什么地方结束

  3. 扩展用例(Expanded use case)。需要将参与者和待开发软件系统为了完成用例所规定的业务任务的交互过程一步一步详细地描述出来,一般我们使用一个两列的表格将参与者和待开发软件系统之间从用例开始到用例结束的所有交互步骤都列举出来。
    扩展用例最后可以用两列表格描述。

用例建模的步骤

• 第一步,从需求表述中找出用例,往往是动名词短语表示的抽象用例

• 第二步,描述用例开始和结束的状态,用TUCBWTUCEW表示的高层用例;

• 第三步,对用例按照子系统或不同的方面进行分类,描述用例与用例、用例与参与者之间的上下文关系,并画出用例图

• 第四步,进一步逐一分析用例与参与者的详细交互过程,完成一个两列的表格将参与者和待开发软件系统之间从用例开始到用例结束的所有交互步骤都列举出来扩展用例

• 其中第一步到第三步是计划阶段,第四步是增量实现阶段。

准确提取用例的基本方法

• 第一步,从需求中寻找业务领域相关的动名词和动名词短语,比如做什么事、什么事情必须被完成,或者执行某任务等;

• 第二步,验证这些业务领域相关的动名词和动名词短语到底是不是用例。验证业务领域相关的动名词或动名词短语是不是用例的标准

  • 是满足四个必要条件:

    • 必要条件一:它是不是一个业务过程?

    • 必要条件二:它是不是由某个参与者触发开始?

    • 必要条件三:它是不是显式地或隐式地终止于某个参与者?

    • 必要条件四:它是不是为某个参与者完成了有用的业务工作?

• 如果以上四个必要条件都满足的话,那么该业务领域相关的动名词或动名词短语就是一个用例。

  • • 第三步:在需求中识别出参与者、系统或子系统。

    • • 参与者会触发某个用例开始,用例也会显式地或隐式地终止于某个参与者;

    • • 用例会属于系统或子系统。

用例图的基本画法

P36

面向对象分析

对象和属性(类和对象的UML图)

一个对象作为某个类的实例,在业务领域内是能够独立存在的,属性往往不能独立存在。

类和对象的UML图:

类名 对象名:类名
属性 属性
方法 方法
继承关系(空心箭头指向父类)

继承关系表达着两个概念之间具有概括化/具体化的关系。

一般用三角形箭头连线表示两个类之间的继承关系。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GEn8N8G0-1657457016155)(image/image_R17gHTpIyA.png)]

聚合关系(菱形箭头指向整体类)

聚合关系表示一个对象是另一个对象的一部分的情况。

聚合关系使用一个平行四边形的箭头表示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IV4zFSzr-1657457016155)(image/image_uf4yW9CPxb.png)]

关联关系(实线)

关联关系表示继承和聚合以外的一般关系,是业务领域内特定的两个概念之间的关系,既不是继承关系也不是聚合关系。

业务领域建模

  1. 第一步,收集应用业务领域的信息。聚焦在功能需求层面,也考虑其他类型的需求和资料;

  2. 第二步,头脑风暴。列出重要的应用业务领域概念,给出这些概念的属性,以及这些概念之间的关系;

  3. 第三步,给这些应用业务领域概念分类。分别列出哪些是类、哪些属性和属性值、以及列出类之间的继承关系、聚合关系和关联关系

  4. 第四步,将结果用 UML 类图画出来

统一过程

统一过程的核心要义是用例驱动(以用例为开发目标)、以架构为中心(保持架构稳定,减少重构)、增量且迭代的过程。

用例驱动就是我们前文中用例建模得到的用例作为驱动软件开发的目标;以架构为中心的架构是后续软件设计的结果,就是保持软件架构相对稳定,减小软件架构层面的重构造成的混乱;增量且迭代体现在下图中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rjn90eny-1657457016155)(image/image_5s8U1XHuWN.png)]

瀑布模型

把整个软件过程 按顺序划分成了需求、设计、编码、测试和部署五个阶段。

敏捷统一过程

敏捷统一过程进一步将软件过程中每一次迭代过程划分为计划阶段增量阶段

4个关键步
  1. 确定需求

  2. 通过用例满足需求

  3. 将用例分配到各增量阶段

  4. 完成各增量阶段的任务

增量阶段的5个步骤
  1. 用例建模

  2. 业务领域建模

  3. 对象交互建模

  4. 形成设计类图

  5. 软件的编码实现和软件应用部署

对象交互建模的基本步骤

  • 找出关键步骤进行剧情描述(scenario)

  • 将剧情描述(scenario)转换成剧情描述表(scenario table)

  • 将剧情描述表转换成序列图的基本方法

  • 从分析序列图到设计序列图

  • 一个完整用例的对象交互建模

•第一步,在扩展用例中右侧一列中找出关键步骤(nontrivial steps)。关键步骤是那些需要在背后进行业务过程处理的步骤,而不是仅仅在表现层(presentation layer, i.e., the Graphical User Interface or GUI)与参与者进行用户接口层面交互的琐碎步骤。

•第二步,对于每一个关键步骤,从关键步骤在扩展用例两列表格中的左侧作为开始,完成剧情描述(scenario),描述一步一步的对象交互过程,直到执行完该关键步骤。

•第三步,如果需要的话,将剧情描述(scenario)进一步转换成剧情描述表(scenario table)。

•第四步,将剧情描述(scenario)或剧情描述表(scenario table)转换成序列图。

•对象交互建模的四个基本步骤以某个用例的扩展用例为输入,中间借助业务领域知识及业务领域建模中的相关对象、属性等,最终产出结果为序列图。

将剧情描述表转换成序列图的基本方法

  • 情形一:主体(Subject)是一个参与者(Actor)

  • 情形二:主体(Subject)是一个对象(Object)

  • 情形三:主体(Subject)需要接收返回值的情形

  • 情形四:主体(Subject)和主体行为的作用对象(object acted upon)是同一个对象的情形

形成软件设计方案的基本方法

•软件产品庞大复杂,前面的形成的设计类图只是其中一个用例得到的设计结果,我们需要对每一个用例进行分析和设计,最终再将各用例得到的设计结果综合成一个软件产品的整体设计方案。其中涉及两个基本的方法:分析(analysis)和综合(synthesis)。

分析是分解大问题变成易于理解的小问题。比如用例建模是将错综复杂的需求分解成一个一个的用例。在分析的过程中除了“分而治之”的切分分解的方法外,抽象方法的运用是一个关键

综合是将一个个小问题的解决方案组合起来构建软件的整体解决方案。我们对每一个用例的关键步骤进行对象交互建模逐步形成了用例对应的解决方案,如何将多个用例的小解决方案组合起来构建软件整体设计方案?这在软件设计中是一个非常有挑战性的问题,一般我们通过参考已有的软件设计模式提供一个思路从而综合出一个软件整体解决方案

关系数据模型的MongoDB设计与实现

MongoDB简介

MongoDB是一个通用的、基于文档的、分布式的数据库,为云计算时代的现代应用程序开发者而生,没有数据库比MongoDB在应用开发效率上更加高效。

MongoDB是一种文档数据库,也就是说MongoDB用类似JSON格式的文档来存储数据。目前普遍认为JSON格式是理解和存储数据最自然的方式,JSON格式比传统的关系数据模型有更强大的数据表达能力。

MongoDB中存储的JSON格式文档范例如下所示。每一个JSON文档对应一个ID即下图中“id"的值,除“ id"外的数据按照“key:value”的方式可以任意定义数据的结构

在一个MongoDB数据库中,可以创建多个集合(Collection),集合的概念类似于关系数据库中的表(Table),只是比表更加灵活。下图是在users集合中检索邮政编码为90404的数据,可见其检索方式比传统的SQL语言更加强大灵活

内嵌,子引用,父引用

三种基本的设计方案:内嵌,子引用,父引用

在选择方案时需要考虑的两个关键因素:

  1. 一对多中的多是否需要一个单独的实体;

  2. 这个关系中集合的规模是一对很少,很多,还是非常多。

一对很少且不需要单独访问内嵌内容的情况下可以使用内嵌多的一方。

一对很多且很多的一端内容因为各种理由需要单独存在的情况下可以通过数组的方式引用多的一方的。

一对非常多的情况下,请将一的那端引用嵌入进多的一端对象中

反范式化

•反范式化在节省你读的代价的同时会带来更新的代价:如果你将零件的名字冗余到产品的文档对象中,那么你想更改某个零件的名字你就必须同时更新所有包含这个零件的产品对象。

•在一个读比写频率高的多的系统里,反范式是有使用的意义的。如果你很经常的需要高效的读取冗余的数据,但是几乎不去变更他的话,那么付出更新上的代价还是值得的。更新的频率越高,这种设计方案的带来的好处越少。

•例如:假设零件的名字变化的频率很低,但是零件的库存变化很频繁,那么你可以冗余零件的名字到产品对象中,但是别冗余零件的库存。

•需要注意的是,一旦你冗余了一个字段,那么对于这个字段的更新将不再是原子的。和上面双向引用的例子一样,如果你在零件对象中更新了零件的名字,那么更新产品对象中保存的名字字段前将会存在短时间的不一致。

使用双向引用来优化你的数据库架构,前提是你能接受无法原子更新的代价。

可以在引用关系中冗余数据到one端或者N端。

在决定是否采用反范式化时需要考虑下面的因素:

你将无法对冗余的数据进行原子更新。

只有读写比比较高的情况下才应该采取反范式化的设计

关系数据模型的MongoDB设计与实现总结

  1. 优先考虑内嵌,除非有什么迫不得已的原因。

  2. 需要单独访问一个对象,那这个对象就不适合被内嵌到其他对象中。

  3. 数组不应该无限制增长。如果many端有数百个文档对象就不要去内嵌他们可以采用引用ObjectID的方案;如果有数千个文档对象,那么就不要内嵌ObjectID的数组。该采取哪些方案取决于数组的大小。

  4. 不要害怕应用层级别的join:如果索引建的正确并且通过投影条件限制返回的结果,那么应用层级别的join并不会比关系数据库中join开销大多少。

  5. 在进行反范式设计时请先确认读写比。一个几乎不更改只是读取的字段才适合冗余到其他对象中。

  6. 在mongodb中如何对你的数据建模,取决于你的应用程序如何去访问它们。数据的结构要去适应你的程序的读写场景。

涉及UML图

用例图、序列图(顺序图)、类图

软件科学基础概论

回调函数

回调函数是一个面向过程的概念,是代码执行过程的一种特殊流程。回调函数就是一个通过函数指针调用的函数。 把函数的指针(地址)作为参数传递给另一个函数,当这个指针调用其所指向的函数时,就称这是回调函数。回调函数不是该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

闭包

闭包是变量作用域的一种特殊情形,一般用在将函数作为返回值时,该函数执行所需的上下文环境也作为返回的函数对象的一部分,这样该函数对象就是一个闭包。

更严谨的定义是,函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript中,每当函数被创建,就会在函数生成时生成闭包。

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

异步调用

Promise对象可以将异步调用以同步调用的流程表达出来,避免了通过嵌套回调函数实现异步调用。

Promise对象的基本用法

Promise对象实际上是对回调函数机制的封装,也就是通过then方法定义的函数与resolve/reject函数绑定,简化了回调函数传入的接口实现,在逻辑上也更加通顺,看起来像是个同步接口。

var promise = new Promise(function(resolve, reject) {
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value) { // resolve(value)
  // success
}, function(value) { // reject(error)
  // failure
});

三种系统类型

S系统:

有规范定义,可从规范派生

·矩阵操纵矩阵运算

P系统:

需求基于问题的近似解,但现实世界保持稳定

·象棋程序

E系统:

嵌入现实世界并随着世界的变化而变化(大多数软件都属于这个类型)

·预测经济运行方式的软件(但经济尚未被完全理解)

软件具有复杂性和易变性,从而难以达成概念的完整性与一致性。(需求的达成永远赶不上需求的变化)

设计模式

设计模式的本质是面向对象设计原则的实际运用总结出的经验模型。对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解的基础上才能准确理解设计模式。

设计模式的优点

正确使用设计模式具有以下优点。

  • 可以提高程序员的思维能力、编程能力和设计能力。

  • 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。

  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

用模块化来包容变化,使用模块化封装的方法,按照模块化追求的高内聚低耦合目标,借助于抽象思维对模块内部信息的隐藏并使用封装接口对外只暴露必要的可见信息,利用多态、闭包、lamda函数、回调函数等特殊的机制方法,将变化的部分和不变的部分进行适当隔离。这些都是设计模式的拿手好戏

设计模式的分类

根据模式是主要用于类上还是主要用于对象上来划分的话,可分为类模式和对象模式两种类型的设计模式:

  • 类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。比如模板方法模式等属于类模式。

  • 对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。由于组合关系或聚合关系比继承关系耦合度低,因此多数设计模式都是对象模式。

根据设计模式可以完成的任务类型来划分的话,可以分为创建型模式、结构型模式和行为型模式 3 种类型的设计模式:

  • 创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。比如单例模式、原型模式、建造者模式等属于创建型模式。

  • 结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,比如代理模式、适配器模式、桥接模式、装饰模式、外观模式、享元模式、组合模式等属于结构型模式。结构型模式分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,所以对象结构型模式比类结构型模式具有更大的灵活性。

  • 行为型模式:用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。 比如模板方法模式、策略模式、命令模式、职责链模式、观察者模式等属于行为型模式。行为型模式分为类行为模式和对象行为模式,前者采用继承在类间分配行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,所以对象行为模式比类行为模式具有更大的灵活性

常用的设计模式

  • 单例(Singleton)模式某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,典型的应用如数据库实例。

  • 原型(Prototype)模式将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例,原型模式的应用场景非常多,几乎所有通过复制的方式创建新实例的场景都有原型模式。

  • 建造者(Builder)模式将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。主要应用于复杂对象中的各部分的建造顺序相对固定或者创建复杂对象的算法独立于各组成部分

  • 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。代理模式是不要和陌生人说话原则的体现,典型的应用如外部接口本地化将外部的输入和输出封装成本地接口,有效降低模块与外部的耦合度。

  • 适配器(Adapter)模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。继承和对象组合都可以实现适配器模式,但由于组合关系或聚合关系比继承关系耦合度低,所以对象组合方式的适配器模式比较常用。

  • 装饰(Decorator)模式在不改变现有对象结构的情况下,动态地给对象增加一些职责,即增加其额外的功能。装饰模式实质上是用对象组合的方式扩展功能,因为比继承的方式扩展功能耦合度低。装饰模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。

  • 外观(Facade)模式为复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

  • 享元(Flyweight)模式运用共享技术来有效地支持大量细粒度对象的复用。比如线程池、固定分配存储空间的消息队列等往往都是该模式的应用场景。

  • 策略(Strategy)模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。策略模式是多态和对象组合的综合应用。

  • 命令(Command)模式将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

模板方法(TemplateMethod)

模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。模版方法是继承和重载机制的应用,属于类模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J4BeyWMY-1657457016156)(image/image_0OnaCAjQ-T.png)]

职责链(Chain of Responsibility)

职责链(Chain of Responsibility)模式:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。通过这种方式将多个请求处理者串联为一个链表,去除请求发送者与它们之间的耦合。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NZaQAfbh-1657457016156)(image/image_Up-LaeKgiq.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ISG51xPi-1657457016156)(image/image_2CfFkE5Kf8.png)]

中介者(Mediator)

中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者,采用“中介者模式”大大降低了对象之间的耦合性,提高系统的灵活性。

设计模式背后的设计原则

  • 开闭原则(Open Closed Principle,OCP)

  • Liskov替换原则(Liskov Substitution Principle,LSP)

  • 依赖倒置原则(Dependence Inversion Principle,DIP)

  • 单一职责原则(Single Responsibility Principle,SRP)

  • 迪米特法则(Law of Demeter,LoD)

  • 合成复用原则(Composite Reuse Principle,CRP)

开闭原则(Open Closed Principle,OCP

软件应当对扩展开放,对修改关闭。

遵守开闭原则使软件拥有一定的适应性和灵活性的同时具备稳定性和延续性。统一过程以架构为中心增量且迭代的过程和开闭原则具有内在的一致性,它们都追求软件结构上的稳定性。当我们理解了软件结构模型本质上具有不稳定性的时候,一个小小的需求变更便很可能会触动软件结构的灵魂,瞬间让软件结构崩塌,即便通过破坏软件结构的内在逻辑模型打上丑陋的补丁程序,也会使得软件内在结构恶化加速软件的死亡。因此开闭原则在基本需求稳定且被充分理解的前提下才具有一定的价值。

Liskov替换原则(Liskov Substitution Principle,LSP

Liskov替换原则主要阐述了继承用法的原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说儿子和父母要在DNA基因上一脉相承,尽管程序员是自己的代码的上帝,但也不能胡来,要做一个遵守自然规律的上帝

Liskov替换原则如今看来其价值也大大折扣,因为为了降低耦合度我们往往使用对象组合来替代继承关系。反而是Liskov替换原则不推荐的多态成为诸多设计模式的基础。

依赖倒置原则(Dependence Inversion Principle,DIP

依赖倒置原则的原始定义为:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。其核心思想是:要面向接口编程,不要面向实现编程。

单一职责原则(Single Responsibility Principle,SRP

单一职责原则(Single Responsibility Principle,SRP)又称单一功能原则, 单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。

单一职责原则的核心就是控制类的粒度大小、提高其内聚度。如果遵循单一职责原则可以降低类的复杂度,因为一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多;同时可以提高类的内聚度,符合模块化设计的高内聚低耦合的设计原则

迪米特法则(Law of Demeter,LoD

迪米特法则的定义是:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

合成复用原则(Composite Reuse Principle,CRP/

合成复用原则(Composite Reuse Principle,CRP)又叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)。它要求在软件复用时,要尽量先使用组合或者聚合关系来实现,其次才考虑使用继承关系来实现。如果要使用继承关系,则必须严格遵循Liskov替换原则。

通常类的复用分为继承复用和对象组合复用两种。

继承复用的缺点
  • 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。

  • 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。

  • 继承复用限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

合成复用的优点

采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点。

  • 组合或聚合复用维持了类的封装性。 因为属性对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。

  • 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取属性对象的唯一方法是通过属性对象的接口。

  • 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与属性对象类型相同的对象。

观察者(Observer)

观察者(Observer)模式指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为,这样所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SStArcWK-1657457016157)(image/image_lgFe2PESfT.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4nU5i7TQ-1657457016157)(image/image_pH-cZEvqnP.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rvW2FIR1-1657457016157)(image/image_oryhhJkVlZ.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UVAZMIkY-1657457016157)(image/image_ILDwtPC8PK.png)]

常见的软件架构举例

三层架构

层次化架构是利用面向接口编程的原则将层次化的结构型设计模式作为软件的主体结构。比如三层架构是层次化架构中比较典型的代表,如下图所示我们以层次化架构为例来介绍

MVC架构

MVC即为Model-View-Controller(模型-视图-控制器),MVC是一种设计模式,以MVC设计模式为主体结构实现的基础代码框架一般称为MVC框架,如果MVC设计模式决定了整个软件的架构,不管是直接实现了MVC模式还是以某一种MVC框架为基础,只要软件的整体结构主要表现为MVC模式,我们就称为该软件的架构为MVC架构。

MVC中M、V和C所代表的含义如下:

Model(模型)代表一个存取数据的对象及其数据模型。

View(视图)代表模型包含的数据的表达方式,一般表达为可视化的界面接口。

Controller(控制器)作用于模型和视图上,控制数据流向模型对象,并在数据变化时更新视图。控制器可以使视图与模型分离开解耦合。

MVC模式通常用开发具有人机交互界面的软件,这类软件的最大特点就是用户界面容易随着需求变更而发生改变,例如,当你要扩展一个应用程序的功能时,通常需要修改菜单和添加页面来反映这种变化。如果用户界面和核心功能逻辑紧密耦合在一起,要扩展功能通常是非常困难的,因为任何改动很容易在其他功能上产生错误。

为了包容需求上的变化而导致的用户界面的修改不会影响软件的核心功能代码,可以采用将模型(Model)、视图(View)和控制器(Controller)相分离的思想。采用MVC设计模式的话往往决定了整个软件的主体结构,因此我们称该软件为MVC架构。

MVC架构 vs. 三层架构

模型和视图有着业务层面的业务数据紧密耦合关系,控制器的核心工作就是业务逻辑处理,显然MVC架构和三层架构有着某种对应关系,但又不是层次架构的抽象接口依赖关系,因此为了体现它们的区别和联系,我们在MVC的结构示意图中将模型和视图上下垂直对齐表示它们内在的业务层次及业务数据的对应关系,而将控制器放在左侧表示控制器处于优先重要位置,放在模型和视图的中间位置是为了与三层架构对应与业务逻辑层处于相似的层次

MVC例子

Model Student class

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GLHuBsGe-1657457016158)(image/image_2Cxh7btP2o.png)]

View StudentView class

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-soqc6zYN-1657457016158)(image/image_vwfSy8tVN_.png)]

Controller StudentController class

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U9IlgaIF-1657457016158)(image/image_wgWy62yjkC.png)]

MVCPatternDemo 岩石MVC模式的用法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ARMWShcU-1657457016158)(image/image_cOmDzVlWS2.png)]

应用MVC架构的软件其基本的实现过程

  • 控制器创建模型;

  • 控制器创建一个或多个视图,并将它们与模型相关联;

  • 控制器负责改变模型的状态;

  • 当模型的状态发生改变时,模型会通知与之相关的视图进行更新。

  • 可以看到这与抽象简化的MVC设计模式已经有了明显的区别,变得更为复杂,但这与实际软件结构相比还是极其简化的模型,实际情况可能会有更多合理的和不合理的复杂联系,要保持软件结构在概念上的完整性极为困难。

MVC架构中的模型、视图与控制器

  • 在MVC架构下,模型用来封装核心数据和功能,它独立于特定的输出表示和输入行为,是执行某些任务的代码,至于这些任务以什么形式显示给用户,并不是模型所关注的问题。模型只有纯粹的功能性接口,也就是一系列的公开方法,这些方法有的是取值方法,让系统其它部分可以得到模型的内部状态,有的则是写入更新数据的方法,允许系统的其它部分修改模型的内部状态。

  • 在MVC架构下,视图用来向用户显示信息,它获得来自模型的数据,决定模型以什么样的方式展示给用户。同一个模型可以对应于多个视图,这样对于视图而言,模型就是可重用的代码。一般来说,模型内部必须保留所有对应视图的相关信息,以便在模型的状态发生改变时,可以通知所有的视图进行更新。

  • 在MVC架构下,控制器是和视图联合使用的,它捕捉鼠标移动、鼠标点击和键盘输入等事件,将其转化成服务请求,然后再传给模型或者视图。软件的用户是通过控制器来与系统交互的,他通过控制器来操纵模型,从而向模型传递数据,改变模型的状态,并最后导致视图的更新。

什么是MVVM?

Vue.js框架的MVVM架构

•在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。以比较流行的Vue.js框架为例,MVVM架构示意图如下:

MVVM的优点

MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点:

  • 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。

  • 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。

  • 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。

  • 可测试。界面素来是比较难于测试的,测试可以针对ViewModel来写。

两种不同层级的软件架构复用方法

  • 克隆(Cloning),完整地借鉴相似项目的设计方案,甚至代码,只需要完成一些细枝末节处的修改适配工作。

  • 重构(Refactoring),构建软件架构模型的基本方法,通过指引我们如何进行系统分解,并在参考已有的软件架构模型的基础上逐步形成系统软件架构的一种基本建模方法。

分解的常见方法

  • 面向功能的分解方法,用例建模即是一种面向功能的分解方法;

  • 面向特征的分解方法,根据数量众多的某种系统显著特征在不同抽象层次上划分模块的方法;

  • 面向数据的分解方法,在业务领域建模中形成概念业务数据模型即应用了面向数据的分解方法;

  • 面向并发的分解方法,在一些系统中具有多种并发任务的特点,那么我们可以将系统分解到不同的并发任务中(进程或线程),并描述并发任务的时序交互过程;

  • 面向事件的分解方法,当系统中需要处理大量的事件,而且往往事件会触发复杂的状态转换关系,这时系统就要考虑面向事件的分解方法,并内在状态转换关系进行清晰的描述;

  • 面向对象的分解方法,是一种通用的分析设计范式,是基于系统中抽象的对象元素在不同抽象层次上分解的系统的方法。

管道-过滤器

管道-过滤器风格的软件架构是面向数据流的软件体系结构,最典型的应用是编译系统。 一个普通的编译系统包括词法分析器、语法分析器、语义分析与中间代码生成器、目标代码生成器等一系列对源代码进行处理的过程。就像如图:管道-过滤器风格示意图,对源代码处理的过滤器通过管道连接起来,实现了端到端的从源代码到编译目标的完整系统

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YTSDxOnk-1657457016159)(image/image_BsW_lWmJXC.png “执行视图”)]

客户-服务

  • Client/Server(C/S)和Browser/Server(B/S)是我们常用的对软件的网络结构特点的表述方式,但它们背后蕴含着一种普遍存在的软件架构风格,即客户-服务模式的架构风格。

  • 客户-服务模式的架构风格是指客户代码通过请求和应答的方式访问或者调用服务代码。这里的请求和应答可以是函数调用和返回值,也可以是TCP Socket中的send和recv,还可以是HTTP协议中的GET请求和响应。

  • 在客户-服务模式中,客户是主动的,服务是被动的。客户知道它向哪个服务发出请求,而服务却不知道它正在为哪个客户提供服务,甚至不知道正在为多少客户提供服务。

  • 客户-服务模式的架构风格具有典型的模块化特征,降低了系统中客户和服务构件之间耦合度,提高了服务构件的可重用性。

P2P

P2P(peer-to-peer)架构是客户-服务模式的一种特殊情形,P2P架构中每一个构件既是客户端又是服务端,即每一个构件都有一种接口,该接口不仅定义了构件提供的服务,同时也指定了向同类构件发送的服务请求。这样众多构件一起形成了一种对等的网络结构,如图:P2P网络结构示意图。

P2P架构典型的应用有文件共享网络、比特币网络等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8C4dekim-1657457016159)(image/image_weHHuEkIqO.png “部署视图”)]

发布-订阅

在发布-订阅架构中,有两类构件:发布者和订阅者。如果订阅者订阅了某一事件,则该事件一旦发生,发布者就会发布通知给该订阅者。观察者模式体现了发布-订阅架构的基本结构。

在实际应用中往往会需要不同的订阅组,以及不同的发布者。由于订阅者数量庞大往往在消息推送时采用消息队列的方式延时推送。如图:包含消息队列的发布-订阅架构示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oixdbbSW-1657457016159)(image/image_rveujSYOC-.png “分解视图”)]

CRUD

CRUD 是创建(Create)、 读取(Read)、更新(Update)和删除(Delete)四种数据库持久化信息的基本操作的助记符,表示对于存储的信息可以进行这四种持久化操作。CRUD也代表了一种围绕中心化管理系统关键数据的软件架构风格。一般常见的各类信息系统,比如ERP、CRM、医院信息化平台等都可以用CRUD架构来概括。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VdLsWGyD-1657457016159)(image/image_dxszLX0L2I.png “分解视图”)]

层次化

•较为复杂的系统中的软件单元,仅仅从平面展开的角度进行模块化分解是不够的,还需要从垂直纵深的角度将软件单元按层次化组织,每一层为它的上一层提供服务,同时又作为下一层的客户。

•通信网络中的OSI(Open System Interconnection)参考模型是典型的层次化架构风格,如图:OSI参考模型示意图。在OSI参考模型中每一层都将相邻底层提供的通信服务抽象化,隐藏它的实现细节,同时为相邻的上一层提供服务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gq3HkjUb-1657457016160)(image/image_g7O2Hcakg6.png “分解视图”)]

软件架构的描述方法

软件架构模型是通过一组关键视图来描述的,同一个软件架构,由于选取的视角(Perspective)和抽象层次不同可以得到不同的视图,这样一组关键视图搭配起来可以完整地描述一个逻辑自洽的软件架构模型。一般来说,我们常用的几种视图有分解视图、依赖视图、泛化视图、执行视图、实现视图、部署视图和工作任务分配视图。

分解视图

分解是构建软件架构模型的关键步骤,分解视图也是描述软件架构模型的关键视图,一般分解视图呈现为较为明晰的分解结构(breakdown structure)特点分解视图用软件模块勾划出系统结构,往往会通过不同抽象层级的软件模块形成层次化的结构。由于前述分解方法中已经明确呈现出了分解视图的特征,我们这里简要了解一下分解视图中常见的软件模块术语。

依赖视图

依赖视图展现了软件模块之间的依赖关系。比如一个软件模块A调用了另一个软件模块B,那么我们说软件模块A直接依赖软件模块B。如果一个软件模块依赖另一个软件模块产生的数据,那么这两个软件模块也具有一定的依赖关系。

泛化视图

泛化视图展现了软件模块之间的一般化或具体化的关系,典型的例子就是面向对象分析和设计方法中类之间的继承关系。值得注意的是,采用对象组合替代继承关系,并不会改变类之间的泛化特征。因此泛化是指软件模块之间的一般化或具体化的关系,不能局限于继承概念的应用。

执行视图

执行视图展示了系统运行时的时序结构特点,比如流程图、时序图等。 执行视图中的每一个执行实体,一般称为组件(Component),都是不同于其他组件的执行实体。如果有相同或相似的执行实体那么就把它们合并成一个。

实现视图

实现视图是描述软件架构与源文件之间的映射关系。

涉及到源代码都是实现

部署视图

部署视图是将执行实体和计算机资源建立映射关系。

工作分配视图

工作分配视图将系统分解成可独立完成的工作任务,以便分配给各项目团队和成员.

几种重要的软件质量属性

  • 可修改性:高内聚低耦合

  • 性能:响应时间、吞吐量、负载

    • 提高资源的利用率

    • 更有效地管理资源分配

    • 先到先得:按收到请求的顺序处理

    • 显式优先级:按其分配优先级的顺序处理请求

    • 最早截止时间优先:按收到请求的截止时间的顺序处理请求

    • 减少对资源的需求

  • 安全性:2个关键特征:系统免疫力、自我恢复能力

  • 可靠性:指定条件下正确执行要求的功能,与缺陷有关

  • 健壮性:不正确条件能正确执行,与容忍程度有关,“防人之心不可无,害人之心不可有”

  • 易用性:用户操作的难易度,多与交互有关

  • 商业目标:客户希望的质量属性

软件的基本构成元素

  • 对象:类的实例,属性和方法的集合

  • 函数和变量/常量

  • 指令和操作数:CPU加载和执行的基本单元

  • 0和1:基本信息元素

软件的基本结构

顺序结构、分支结构、循环结构、函数调用框架、继承和对象组合

软件中的一些特殊机制

  • 回调函数:函数作为参数

  • 多态:代码相同不同上下文行为不同

  • 闭包:返回的函数附带函数外部上下文

  • 异步调用

  • 匿名函数

设计模式

优点:

  1. 可以提高程序员的思维能力、编程能力和设计能力。

  2. 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。

  3. 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强

分类:

  • 类模式、对象模式

  • 创建型模式、结构型模式、行为型模式

常用的设计模式

  • 单例模式:只有一个实例

  • 原型模式:新实例均为某实例的拷贝

  • 建造者模式:根据需求添加功能模块最终形成复杂对象

  • 代理模式:使某对象对外提供指定/统一的接口

  • 适配器模式:连接两个不兼容接口

  • 装饰模式:不改变对象结构,使用对象组合方式扩展其功能

  • 外观模式(门面模式):为系统各功能提供统一的对外接口集合

  • 享元模式:提取相同的部分以复用,节省空间

  • 策略模式:某些函数可替换

  • 命令模式:将命令封装为对象并调用

  • 模板方法模式:继承重载重定义某些函数

  • 责任链模式:请求沿处理对象链传递直到被处理

  • 中介者模式:使用中介者对象协调其他所有对象间的交互

  • 观察者模式:某个观察者发生变化时通知其他观察者

设计原则

  • 开闭原则:对扩展开放,对修改关闭

  • 里氏(Liskov)替换原则:子类可扩展不可重写父类

  • 依赖倒置原则:面向接口编程,调用使用抽象或接口而非具体类

  • 单一职责原则:一个类只负责一个职责

  • 迪米特法则:最少知道原则,对其他类了解越少越好

  • 合成复用原则:复用时优先考虑组合、聚合,其次才是遵循里氏替换原则的继承

常见软件架构

  • 三层架构

  • MVC(模型-视图-控制器)架构:包容变化,三部分分离提高灵活性

  • MVVM(C->ViewModel)架构:通过拦截绑定监听器自动更新视图

软件架构模型

2种不同层级的软件架构复用方法

克隆、重构

作用

  1. 有助于整体理解

  2. 为复用提供高层视图

  3. 为项目构建提供蓝图

  4. 有利于理清系统演化的内在逻辑,跟踪分析架构的依赖关系,有助于项目管理决策和项目风险管理

常见分解方法

  • 面向功能

  • 面向特征

  • 面向数据

架构风格

  • 管道-过滤器:流水线处理

  • C/S客户-服务:客户主动,服务被动

  • P2P: C/S的特殊形式,每个节点既是C又是S

  • 发布-订阅:类似于观察者模式

  • CRUD:Create创建、Read读取、Update更新、Delete删除(类似于数据库增删改查)

  • 层次化:层次结构,如操作系统

视图

  • 分解视图:模块划分

  • 依赖视图:模块之间的依赖关系

  • 泛化视图:模块之间的泛化关系(一般化或具体化,如继承)

  • 执行视图:流程图、时序图(顺序图)等

  • 实现视图:类似于目录树,用于查找具体实现

  • 部署视图:用实际硬件建立联系

  • 工作任务分配视图:任务划分

软件的质量属性

IEEE将软件质量定义为,一个系统、组件或过程符合指定要求的程度,或者满足客户或用户期望的程度。

  • 可修改性:高内聚低耦合

  • 性能:响应时间、吞吐量、负载

    • 提高资源的利用率

    • 更有效地管理资源分配

    • 先到先得:按收到请求的顺序处理

    • 显式优先级:按其分配优先级的顺序处理请求

    • 最早截止时间优先:按收到请求的截止时间的顺序处理请求

    • 减少对资源的需求

  • 安全性:2个关键特征:系统免疫力、自我恢复能力

  • 可靠性:指定条件下正确执行要求的功能,与缺陷有关

  • 健壮性:不正确条件能正确执行,与容忍程度有关,“防人之心不可无,害人之心不可有”

  • 易用性:用户操作的难易度,多与交互有关

  • 商业目标:客户希望的质量属性

软件危机和软件过程

没有银弹

软件工程中不存在银弹——没有任何一项技术或方法可使软件工程的生产力在十年内提高十倍。软件工程之所以不存在银弹是因为,软件工程本身存在复杂性,一致性,不可预见性,不可视化性。

软件危机的根本问题

软件概念结构的复杂性,无法达成软件概念的完整性和一致性,自然无法从根本上解决软件危机带来的困境。

软件的生命周期

  • 分析:需求分析和定义

  • 设计:软件架构设计、软件详细设计

  • 实现:编码和测试(单元、集成、系统测试)

  • 交付:部署、交付、培训

  • 维护

软件的故障率曲线

出现故障:垂直上升,修复故障:类1/x曲线回落,总体呈上升趋势

软件过程模型

软件过程

  • 描述性(实际发生)

  • 说明性(应该发生)

瀑布模型

几乎不会发生需求变化/变更,无任何迭代

原型化瀑布模型:瀑布模型增加原型化阶段,即先做基本雏形,将风险前移,增加可控性

原型分为用户接口原型和软件架构原型

V模型

将瀑布模型中的测试与开发结合,改善开发效率

分阶段的交付开发策略

  • 增量开发:部分->整体,扩展功能

  • 迭代开发:整体->整体,升级功能

分阶段开发优点

  • 在开发完成之前即可进行交付和用户培训

  • 可以让开发者及时处理意外情况

  • 开发团队在不同版本关注不同功能,提高效率

  • 提前抢占市场

生死相依原则:特定过程活动和评估该过程的过程活动成对出现

螺旋模型

基本策略:在每个迭代阶段构建原型以减小风险

每个循环重复计划,确定目标、替代方案和约束条件,评估替代方案和风险,开发和测试

个人软件过程PSP

  1. 计划阶段 (PSP0)

    • 项目评估 (PSP1)
  2. 开发阶段 (PSP0)

    • 分析 (PSP2.1)

    • 设计规格 (PSP2.1)

    • 编码标准规范 (PSP0.1)

    • 设计 (PSP0)

    • 设计评审 (PSP2)

    • 编码 (PSP0)

    • 代码评审 (PSP2)

    • 测试 (PSP0)

  3. 统计记录各项工作用了多少时间 (PSP1.1)

  4. 项目测试报告 (PSP1)

  5. 程序规模度量 (PSP0.1)

  6. 开发完成后进行总结分析 (PSP0)

  7. 过程改进计划 (PSP0.1)

  • PSP0、PSP0.1重点是个体度量过程,量化

  • PSP1、PSP1.1重点是个体计划过程,评估

  • PSP2、PSP2.1重点是个体质量管理,开发早期能够发现缺陷

  • PSP3重点是个体循环过程,螺旋上升将模块增量和迭代为大型软件

团队软件过程TSP

团队的基本要素

  • 团队规模

  • 团队的凝聚力

  • 团队协作的基本条件

建设高效团队

  • 建设具有凝聚力的团队

  • 设定有挑战性的目标

  • 反馈

  • 共同遵守的工作流程和框架

基本策略

  1. 计划先行,也就是做出承诺之前先计划

  2. 完成概念设计

  3. 选择开发策略

  4. 完成初步规模估算

  5. 完成初步时间估算

  6. 评估风险

  7. 建立策略文档

  8. 制定配置管理计划

CMM/CMMI 能力成熟度模型

用于评估软件开发商的能力

2种通用的评估方法

  • 软件过程评估

  • 软件能力评估

评估的三个阶段

  1. 准备

  2. 评估

  3. 报告

CMM/CMMI的5个等级

  • 一级-初始级:目标清晰,但同类目标不保证也能完成,成功与否主要取决于实施人员

  • 二级-管理级:在一级基础上,对项目有管理程序,避免偶然性

  • 三级-已定义级:在二级基础上,制度化管理体系和流程,可用于同类项目和其他项目

  • 四级-量化管理级:在三级基础上,实现数字化,提高精度和稳定性,降低质量波动

  • 五级-持续优化级:在四级基础上,预防可能出现的问题,主动优化流程

敏捷方法

敏捷宣言

  • 个体和互动 高于 流程和工具

  • 工作的软件 高于 详尽的文档

  • 客户合作 高于 合同谈判

  • 响应变化 高于 遵循计划

核心思想:尽管右项有其价值,我们更重视左项的价值

Scrum敏捷开发

每一轮迭代称为一个冲刺,冲刺包括:

  1. 冲刺规划会议

  2. 每日站立会议

  3. 冲刺评审会议

  4. 冲刺回顾会议

基本流程

  1. 找出要做的事

  2. 决定当前冲刺要做的事

  3. 冲刺

  4. 得到增量版本,冲刺评审并发给用户

总结

  • 全员规划,分块并行

  • 文档为纲,当面交流

  • 迭代开发,分块检查,持续交付

  • 优先开发,讲究实效

DevOps

可以将DevOps看成是敏捷方法从技术开发领域扩展到业务运维领域

精益原则

在需要的时候、按需要的量、生产所需的产品,特点

  • 零库存

  • 快速反应

  • 企业内部活动和外部市场(顾客)需求统一

  • 强调人力资源的重要性

611

参考资料:代码中的软件工程 https://gitee.com/mengning997/se

你可能感兴趣的:(软件工程,git,github)