程序流程图 —— 规范与画图
Posted Gemo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序流程图 —— 规范与画图相关的知识,希望对你有一定的参考价值。
Typora
支持使用 mermaid
进行绘图,其中就包括流程图。
日常生活中应用流程图的场景并不算太多,在写代码的过程中也更偏向使用伪代码。近期读《计算机程序设计艺术》卷一,上来就是一张流程图,不知怎的,突然觉得流程图不可或缺。或许流程图搭配伪代码更好。
以上是我强行扯出的写本文的理由。
关于 mermaid
mermaid
众多代码绘图工具之一。最先接触的是 PlantUML
,当时为了画类图交软件文档,但又不想手动画,于是搜索到了这个工具。PlantUML
如其名,主要是 UML
图,支持的图较少,且依赖较多,配置的步骤较多。
mermaid
虽然没有 PlantUML
那样完整支持 UML
,但基本的图形都能绘制,且感谢大前端的发展,其只是一个“简单”的 .js
文件,导入浏览器导出使用,在 Markdown
这种兼容 html
的标记语言下,真心流程。
它支持简单的程序流程图,之所以说简单,是因为完整的流程实在太过于复杂。如果用不上,复杂的就搁置吧,mermaid
已经足够使用了。
安装 mermaid
参考 安装教程。
接触 mermaid
主要是 Typora
本身支持使用 mermaid
,我的方案是在 Typora
完成编辑,随后导出 HTML
,再到某个支持 Markdown
的平台进行发布。部分网站自身支持 mermaid
。
绘制到简单流程图如下:
graph LR
A([开始]) --> B[/编辑文章/]
B --> C{内容完成?}
C -->|是| D{再编辑?}
C -->|否| B
D -->|否| Save[保存]
Save --> E{导出?}
D -->|是| B
E -->|否| End([结束])
E -->|是| ExportHTML[/导出HTML/]
ExportHTML --> Publish[发布]
Publish --> End
Markdown 中使用 mermaid
在 Typora
使用 mermaid
很简单,直接引入代码即可:
```mermaid
```
而我们需要使用流程图,可以参考文档,引入流程图直接使用 flowchart
或者 graph
关键字,同时需要明确指定流程图的绘制方向,TD
或者 TB
表示从上到下绘制,LR
表示从左到右绘制。(就是上下左右首字母表示)。而 flowchart
和 graph
能够使用的样式不一样,flowchart
相对箭头比较平滑,能用的箭头样式多样,graph
的样式相对古板。我们可以尝试一个简单的流程:
```mermaid
graph LR
START([开始])
END([结束])
START --> A[测试]
A --> isRight{正确?}
isRight -->|是| END
isRight -->|否| A
```
上述代码绘制如图:
graph LR
START([开始])
END([结束])
START --> A[测试]
A --> isRight{正确?}
isRight -->|是| END
isRight -->|否| A
我们将 graph
替换成 flowchart
如图:
flowchart LR
START([开始])
END([结束])
START --> A[测试]
A --> isRight{正确?}
isRight -->|是| END
isRight -->|否| A
目前开来仅仅是样式存在区别,而文档中标注 flowchart
属于 Beta
状态,文档中只罗列出了更多的箭头支持和子流程图支持。这并非我们应该关注的重点,因而使用那个关键字,完全在于个人喜好。flowchart
虽在文字上具有较为强烈的识别性,但 graph
绘制出了图像更清晰,在 Typora
中也有代码高亮,且 flowchat
提供的功能也用不上,因此后续使用 graph
作为关键字。
流程图基础
流程图的主要功用在于相对文字能更直观描述某件事情的过程。
而使用代码完成流程图的构建,更像是伪代码去即时展现逻辑,当然也方便不熟悉代码的人直接理解代码的基本思路(不知道这有啥用)。绘制流程图,理清自己的逻辑是最主要的。
而人与人交流都需要一个规范,流程图本身有一个规范。
流程图符号
此处将罗列常规符号和 mermaid
对应的代码。
mermaid
并没有规定那个符号表明哪个意思,只是表述了该符号的形状。
1. 流程
表示执行或者处理某些工作。
在 mermaid
中流程的符号很简单,就是中括号 []
,示例如下:
graph LR
A[流程1]
B[流程2]
A --> B
其效果如下:
graph LR
A[流程1]
B[流程2]
A --> B
mermaid
中,流程是一个基本的 node
,它的形状是矩形,还有圆角矩形(使用 ()
表述),两者区别不大,没有特定的要求,但 []
更容易输入,因而使用 ()
的必要不大。
2. 开始或结束
在 文档 中,所有开始或结符号都是使用流程直接代替的,并没有给出具体的要求。但流程图规范当中,开始和结束应当有别与流程的形状。在 mermaid
中使用 ([])
可以绘制一个区别于流程的图形,如下:
graph LR
START([开始])
END([结束])
START --> END
结果如下:
graph LR
START([开始])
END([结束])
START --> END
文档 中,称之为 A stadium-shaped node
,其形状类似于体育场。而开始和结束只有一个,在初始时将其定义好,后续可以直接使用。
特别注意,定义结束的时候,切勿使用小写 end
,否则可能倒置流程图绘制中断
3. 判定
判定是统一的,mermaid
使用 {}
表示。如下:
graph LR
START([开始])
END([结束])
Flow[流程]
isTrue{正确?}
isTrue -->|是| END
isTrue -->|否| Flow
START --> Flow --> isTrue
效果如下:
graph LR
START([开始])
END([结束])
Flow[流程]
isTrue{正确?}
isTrue -->|是| END
isTrue -->|否| Flow
START --> Flow --> isTrue
如代码所示,我们定义 isTrue
作为一个判定框,其内容是 “正确?”,而正确与否的之后的流程如何处理很简单,直接在下一个前流程使用 |是/否|
即可。
4. 子流程
如其名,是一部分流程的集合,主要功用在于简化整个流程图。而其内部的流程可以以附件方式展示到另一个流程图中。类似于子程序,mermaid
中为 A node in a subroutine shape。
代码如下:
graph LR
id[[子流程]]
效果如下:
graph LR
id[[子流程]]
即使用两个中括号表示,[[]]
。
5. 数据库
表示存储位置,属于扩展。
代码如下:
graph LR
DB[(数据库)]
效果如下:
graph LR
DB[(数据库)]
符号是 [()]
。
6. 页面内引用
即接口,表示两个流程图间的接口。
用途在于:
- 连接到下一页;
- 避免流线交叉;
- 避免流线太长;
代码如下:
graph LR
OUT((接口))
效果如下:
graph LR
OUT((接口))
符号是 (())
。
7. 输入/输出
出现输入或者输出的时候,应该区别于流程。
代码如下:
graph LR
input[/输入/]
效果如下:
graph LR
input[/输入/]
在 mermaid
还有反向的平行四边形,其符号与上述相反,[\\\\]
,效果如:
graph LR
input[\\输入\\]
但我们一般使用第一个。
8. (附加)箭头样式
流程图似乎只有实线箭头,其他箭头很少见到,但 mermaid
提供了不少样式的箭头。
代码如下:
graph LR
A --- |直线| B
A ----- |直线长度是 - 符号定义 - 长度3-5| B0
A --> |直线箭头| B1
A ---> |长直线箭头| B2
A === |粗线| B3
A ==> |粗线箭头| B4
A -.- |虚线| B5
A -.-> |虚线箭头| B6
效果如下:
graph LR
A --- |直线| B
A ----- |直线长度是 - 符号定义 - 长度3-5| B0
A --> |直线箭头| B1
A ---> |长直线箭头| B2
A === |粗线| B3
A ==> |粗线箭头| B4
A -.- |虚线| B5
A -.-> |虚线箭头| B6
可以根据自己的需求使用。
当然,mermaid
还提供许多其他图形,包括梯形等等。这些请参考文档。
接下来我们绘制下流程图的基础结构。
流程图的基本结构
流程图有三大基本结构,分别是:顺序结构、选择结构和循环结构,程序的构成也基本如此。在流程图绘制规范中,有规定这些结构的绘制方法,而利用 mermaid
绘制出来的图形似乎并不那么规范。
1. 顺序结构
顺序结构即按程序先后顺序执行,从上往下/从左往右,一个一个执行。最基本的结构。
代码如下:
graph LR
START([开始])
END([结束])
START --> A
A --> B
B --> C
C --> END
效果如下:
graph LR
START([开始])
END([结束])
START --> A
A --> B
B --> C
C --> END
2. 选择结构
选择,即分支,即 if-else
结构:
graph TD
isTrueA{条件A}
isTrueB{条件B}
isTrueC{条件C}
A([开始]) --> isTrueA
isTrueA --> |是| A1[步骤1]
isTrueA --> |否| A2[步骤2]
A1 --> AE([结束])
A2 --> AE
B([开始]) --> isTrueB
isTrueB --> |是| BE([结束])
isTrueB --> |否| B1[步骤3]
B1 --> BE
C([开始]) --> isTrueC
isTrueC --> |是| C1[步骤4]
isTrueC --> |否| CE([结束])
C1 --> CE
效果如下:
graph TD
isTrueA{条件A}
isTrueB{条件B}
isTrueC{条件C}
A([开始]) --> isTrueA
isTrueA --> |是| A1[步骤1]
isTrueA --> |否| A2[步骤2]
A1 --> AE([结束])
A2 --> AE
B([开始]) --> isTrueB
isTrueB --> |是| BE([结束])
isTrueB --> |否| B1[步骤3]
B1 --> BE
C([开始]) --> isTrueC
isTrueC --> |是| C1[步骤4]
isTrueC --> |否| CE([结束])
C1 --> CE
可以看到,虽然我们按照顺序编写代码,但 条件B
和 条件C
的绘制中可以看出有步骤的情况在右,若两边都包含步骤,则按顺序绘制。因为“是否”是按照 |文字|
嵌入的,则按照程序缺省行为执行绘制。
3. 循环结构
顾名思义,在一定的条件下,其会完成重复动作。
循环结构又有当型和直到型,可以看作 while
和 do while(!条件)
。
代码:
graph TD
isTrue1{当型判断?}
isTrue2{直到型判断?}
A([开始]) --> isTrue1
isTrue1 --> |是| A1[步骤1] --> isTrue1
isTrue1 --> |否| AE([结束])
B([开始]) --> B1[步骤2]
B1 --> isTrue2
isTrue2 --> |是| BE([结束])
isTrue2 --> |否| B1
效果:
graph TD
isTrue1{当型判断?}
isTrue2{直到型判断?}
A([开始]) --> isTrue1
isTrue1 --> |是| A1[步骤1] --> isTrue1
isTrue1 --> |否| AE([结束])
B([开始]) --> B1[步骤2]
B1 --> isTrue2
isTrue2 --> |是| BE([结束])
isTrue2 --> |否| B1
都到这了,不考虑什么画图规范了……
案例
来几个经典的案例。
1. 猜数字
猜数字 number
,为随机数。
- 输入数字n
- 判断大小,提示大或者小
- 猜对提示,是否继续
- 不继续退出,继续则重新生成
number
, 重复步骤1-4
graph TD
START([开始])
END([结束])
%% 该数字非输入,由系统生成
gen_number[生成随机数字 number--系统生成]
input_n[/输入数字n/]
%% 定义判断条件
n_eq_number?{ n == number? }
n_lt_number?{ n < number? }
continue?{ 继续? }
%% 提示
ALERT_gt[/提示数字大了/]
ALERT_eq[/提示数字正确/]
ALERT_lt[/提示数字小了/]
START --> gen_number --> input_n
input_n --> n_eq_number?
n_eq_number? --> |是| ALERT_eq --> continue?
n_eq_number? --> |否| n_lt_number?
n_lt_number? --> |是| ALERT_lt
n_lt_number? --> |否| ALERT_gt
n_lt_number? --> input_n
continue? --> |是| gen_number
continue? --> |否| END
graph TD
START([开始])
END([结束])
%% 该数字非输入,由系统生成
gen_number[生成随机数字 number--系统生成]
input_n[/输入数字n/]
%% 定义判断条件
n_eq_number?{ n == number? }
n_lt_number?{ n < number? }
continue?{ 继续? }
%% 提示
ALERT_gt[/提示数字大了/]
ALERT_eq[/提示数字正确/]
ALERT_lt[/提示数字小了/]
START --> gen_number --> input_n
input_n --> n_eq_number?
n_eq_number? --> |是| ALERT_eq --> continue?
n_eq_number? --> |否| n_lt_number?
n_lt_number? --> |是| ALERT_lt
n_lt_number? --> |否| ALERT_gt
n_lt_number? --> input_n
continue? --> |是| gen_number
continue? --> |否| END
逻辑有待优化。
2. 注册登录逻辑
1. 注册
graph TD
START([开始])
END([结束])
input[/输入帐号密码/]
START --> input
input --> pw_pair?{密码是否符合格式?}
pw_pair? --> |否| input
pw_pair? --> |是| user_pair?{帐号是否符合格式?}
user_pair? --> |是| get_code[获取验证码]
user_pair? --> |否| input
get_code --> input_code[/输入验证码/]
input_code --> code_pair?{验证码是否正确?}
code_pair? --> |是| success[注册成功]
success -.- |存储| database[(用户数据库)]
code_pair? --> |否| 记录验证码错误次数 --> try_more?{尝试次数过多?}
try_more? --> |是| will_END[限制获取验证码次数,禁止注册] --> END
try_more? --> |否| en_time?{是否间隔60s获取验证码?}
en_time? --> |是| get_code
en_time? --> |否| wait_time[等待60s] --> get_code
success --> END
graph TD
START([开始])
END([结束])
input[/输入帐号密码/]
START --> input
input --> pw_pair?{密码是否符合格式?}
pw_pair? --> |否| input
pw_pair? --> |是| user_pair?{帐号是否符合格式?}
user_pair? --> |是| get_code[获取验证码]
user_pair? --> |否| input
get_code --> input_code[/输入验证码/]
input_code --> code_pair?{验证码是否正确?}
code_pair? --> |是| success[注册成功]
success -.- |存储| database[(用户数据库)]
code_pair? --> |否| 记录验证码错误次数 --> try_more?{尝试次数过多?}
try_more? --> |是| will_END[限制获取验证码次数,禁止注册] --> END
try_more? --> |否| en_time?{是否间隔60s获取验证码?}
en_time? --> |是| get_code
en_time? --> |否| wait_time[等待60s] --> get_code
success --> END
2. 登录
graph TD
subgraph db [数据库]
db_user[(用户数据库)]
db_login[(已登录用户数据库)]
end
START([开始])
END([结束])
input[/输入帐号密码/]
START --> input
input --> get_code[获取验证码]
get_code --> input_code[/输入验证码/]
input_code --> code_pair?{验证码是否正确?}
code_pair? --> |是| pw_pair[从数据库匹配帐号密码]
pw_pair -.- |查找| db_user
pw_pair --> pw_pair?{帐号密码匹配?}
pw_pair? --> |是| success[登录成功]
success -.- |存储| db_login
pw_pair? --> |否| input
code_pair? --> |否| 记录验证码错误次数 --> try_more?{尝试次数过多?}
try_more? --> |是| will_END[限制获取验证码次数,禁止登录] --> END
try_more? --> |否| en_time?{是否间隔60s获取验证码?}
en_time? --> |是| get_code
en_time? --> |否| wait_time[等待60s] --> get_code
success --> END
graph TD
subgraph db [数据库]
db_user[(用户数据库)]
db_login[(已登录用户数据库)]
end
START([开始])
END([结束])
input[/输入帐号密码/]
START --> input
input --> get_code[获取验证码]
get_code --> input_code[/输入验证码/]
input_code --> code_pair?{验证码是否正确?}
code_pair? --> |是| pw_pair[从数据库匹配帐号密码]
pw_pair -.- |查找| db_user
pw_pair --> pw_pair?{帐号密码匹配?}
pw_pair? --> |是| success[登录成功]
success -.- |存储| db_login
pw_pair? --> |否| input
code_pair? --> |否| 记录验证码错误次数 --> try_more?{尝试次数过多?}
try_more? --> |是| will_END[限制获取验证码次数,禁止登录] --> END
try_more? --> |否| en_time?{是否间隔60s获取验证码?}
en_time? --> |是| get_code
en_time? --> |否| wait_time[等待60s] --> get_code
success --> END
在相对复杂的逻辑下,整体绘图有些杂乱。
小结
流程图不适用于过于复杂的逻辑,对于复杂的逻辑会有些表现力不足。前一章的登录界面我绘制了多次,包括一张图表现登录注册登出等功能(使用 subgraph
)当绘制完成之后,尽管伪代码很容易理解,图像却错综复杂。
流程图更适用于表现逻辑关系,对于程序中一个功能进行描述(面向过程),而状态变化却不好表达。这又得引出一个状态流图。
而面向对象的类图又需要 UML
进行描述。
目前流程图可用于简单的单一功能描述中,其他需要后续再进行总结。一个完整的应用不会这么简单。
以上是关于程序流程图 —— 规范与画图的主要内容,如果未能解决你的问题,请参考以下文章