程序流程图 —— 规范与画图

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 表示从左到右绘制。(就是上下左右首字母表示)。而 flowchartgraph 能够使用的样式不一样,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. 页面内引用

即接口,表示两个流程图间的接口。

用途在于:

  1. 连接到下一页;
  2. 避免流线交叉;
  3. 避免流线太长;

代码如下:

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. 循环结构

顾名思义,在一定的条件下,其会完成重复动作。

循环结构又有当型和直到型,可以看作 whiledo 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,为随机数。

  1. 输入数字n
  2. 判断大小,提示大或者小
  3. 猜对提示,是否继续
  4. 不继续退出,继续则重新生成 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 进行描述。

目前流程图可用于简单的单一功能描述中,其他需要后续再进行总结。一个完整的应用不会这么简单。

以上是关于程序流程图 —— 规范与画图的主要内容,如果未能解决你的问题,请参考以下文章

MarkDown画图(实例讲解) —— 流程图、序列图、饼图、甘特图

类图的作用与画图规范

类图的作用与画图规范

PlantUML画图软件简介

PlantUML画图软件简介

横空出世!IDEA画图神器来了,比Visio快10倍!