工程 · 浅谈开发中的逻辑可视化,及 drawio 的自定义图形运用

3,216 阅读12分钟

事情是这样的,作为工程师,特别是全栈类的,经常要面对一人承担多角、照顾多项的现实。

任何一个项目都会随着时间发展而逐渐复杂、精细和庞大化。在凝结纯粹智力的编程开发过程中更是如此。

〇 先说说背景

最近圈内很流行一个词:“屎山”。虽然听起来有些不雅,但却十分形象地比喻了随着开发进度发展,而逐渐冗杂、晦涩、不条理等等拥有各种弊病的项目。

这种项目的代码,没有一个开发者愿意碰,或者继续修改。就跟其名一样,唯恐避之不及。

解决“屎山”唯一的办法,就是另起炉灶,再造一个新项目(即便新项目也有发展为新的“屎山”的可能)。

成熟的开发者通常拥有一定程度的代码洁癖,可以尽最大程度避免“屎山”的命运降临。但当项目体量增长后,再有洁癖的人,也会被各种逻辑搞到头晕。

不论是初学者还是资深开发,相信很多人都有这样的体验。第一天自己亲手写的代码,到了第二天再看的时候,就开始犯晕,到了第三天,感到理解困难。静置一周后,就彻底不知道自己当时是怎么写的了。如果放了超过半个月,可能连碰都不想碰了。

甚至常常会遇到,一顿饭的时间,脑海中的逻辑运作就被彻彻底底打乱。

这与程序员的工作方式有关。智力输出需要大脑进入深度思考,优质高效的产出更是需要极为强大的专注。这种状态下,对外界和时间的感知都将减弱,也就是所谓的“忘我投入”(可参考心流理论)。

日常生活中大多不需要这种对脑力近乎到底的深入,而程序员的工作则要求自己的大脑能够快速进入状态。

但,光进入状态还不够。深度思考的目的是将逻辑以代码的形式编写出来,这个时候面对的问题就是:“逻辑”从哪里来,以及如何快速将“逻辑”吸收到自己的大脑里。

① “逻辑”从哪里来?

首先,开发不是一气呵成的,少则数周,长则几十年。而人类是会遗忘的,今天写的程序,特别是复杂的算法,放置一小会儿就会懵逼。这就需要我们能够在文档或者代码中,重新载入/拾起之前的逻辑和状态。

在团队协作中,需要理解其他人写的代码是啥意思。个人开发中,则需要知道自己之前写的是个啥。

这些代码或者文档的背后,就是“逻辑”。

做到准确无误的理解是基本要求,而现实的生产开发是个工程,追求高效率就要求快速准确理解。

如果把大脑类比为电脑,那么想要快速进入深度状态,就要求你有一个优异的缓存,读写速度快,且容量大。

因为进入深度思考后,要同时顾及非常多的逻辑分支,大脑载荷会非常大。

而众所周知,计算机缓存的读写速度和容量大小,直接决定了性能好坏。人类大脑也是一样。

从“逻辑”衔接角度看,我们可以简单认为,编程工作需要将前序逻辑,加上自己的产出,输出给后续继续使用。就跟盖房子一样,了解前面的状态,添加新的状态,以供后来者继续添砖加瓦。

只不过这里所谓的砖瓦,具象为代码,本质是逻辑。也就是所谓的“搬砖”,搬自己或其他人大脑里生产的砖。

② 读写速度由什么决定?

读写速度,其实就是理解速度。

面对文档、代码,先理解字面意义,彻底消化吸收后,转化为抽象逻辑,进而对逻辑进行加工生产,然后转化为代码和文档,再输出给未来的自己或者其他团队协作者。

下图是个简易的示意,越往底走,跨层消耗的精力和脑力都越大。

image.png

这也是“屎山”为什么令人厌恶,就是因为要吸收的是一堆难以理解、冗杂晦涩的代码。不易阅读,且逻辑不通畅、不清晰、不条理,运行效率还低。对于追求颅内高潮的程序员来说非常不友好。

所以总有程序员之间会互骂对方写的代码跟“屎”一样,说的就是这个事。

优雅的代码总是给人一阵阵清风拂面的感觉。

提高理解速度有几个经过时间考验的经验方法,比如美观的代码书写风格,规整的接口设计等等,常见的方法论有驼峰命名法这些。

还有就是好好写文档和注释,解释某一行关键代码是干啥用的。

标准的格式,通用的规则,体系的言语,对于降低理解成本十分有益。

③ 关于开发者工作的客观环境

客观来讲,“理解”这个行为本身的状态,还与各种环境密切相关。

最近有一个明显的发现。工程体量大了以后,自己编程两个小时,有一个小时的时间都用来理解回忆之前写的代码。而等到真正进入核心的编码过程中,最多也不过半小时。随着代码数的递增,理解难度和需要的时间就会正相关增长。

更不用说,对于绝大多数开发者,还经常被外界噪音或者光线或者琐碎的杂事打乱。好不容易进入状态了,结果又被喊走处理其他事情,在进入深度思考的过程中反复横跳,消耗的是巨大的精力。

我突然理解了为什么之前会在一本专业书上看到作者推荐阅读《演员的自我修养》。程序员进入逻辑世界的过程,就像是演员进入情感世界。试试如果在一个演员刚刚进入角色时马上把他喊出来,想要再次进入角色就会异常困难。

同理于程序员,老是被打断,相当于从精神上毁灭。

这也是为啥几乎所有的程序员都在晚上夜深人静后工作效率暴涨,完完全全没有打扰、可以安静工作、彻底沉浸在编码过程中的大块完整的时间,在一天当中真的太少了。

而如果一周当中能够有一天完全沉下心来解决一些重要节点,可以说对于任何一个开发者都是莫大的幸福。

(所以关爱程序员,请不要突然吓他。)

既然环境不是那么的可控,那么我们从人类如何理解问题这个最本质的角度出发,回归逻辑本身。

④ “逻辑可视化”

虽然轮子都是自己造的,编码的时候也极尽可能地保持优美,但还是难免被一些复杂难点再次卡住。认知心理学告诉我们,人类对图像理解的速度远快于文本文字。所以用图像来描述逻辑,是一个非常有效的方法。

综上一大堆前置背景和现状条件,这就自然引申出一个概念:逻辑可视化

比如常见的流程图,就是其中一种。

幸运的是,经过工程学的长期发展,前人总结出了一些方法论和有效的逻辑工具,其中最为著名的,几乎所有专业人员都使用过的,就是 UML,全称:统一建模语言

需要先声明的是,建模,不是指单纯指物理层面的空间模型的构建,它可以指代构建任何设计或者概念中的实体,这些都可以叫做建模。比如数学模型、计算模型、软件模型等等等等,非常多。

UML 是一门语言,这就意味着它有系统的描述问题的方法论。而承载这门语言的具体载体形式,是图形。换一步讲,就是 icon。

这就在逻辑、可视化和生产需求,三者之间构建起了桥梁。

而 UML 在很多大学里,都是一项专门的必修课。

广义的 UML 可以泛指一切过程逻辑和流程。不过现在谈及,一定离不开它是一种面向对象设计方法下的产物。

这就不得不提到,它的弊端。也是我个人现在开发过程中遇到的鸡肋的地方。

⑤ UML 的一些束手无策

第一,UML 是文档驱动下的建模语言

相信说到文档驱动,第一反应一定是瀑布模型。而随着产品快速迭代的需求和敏捷开发的崛起,UML 这种规整老派的开发方式,无法响应不断变化的需求。甚至逐渐成为影响开发进度的绊脚石。

最传统的先建模,后根据模型来开发的工程思维,已经跟不上现在的节奏了。所以目前来说,它的驱动作用逐渐弱化,我个人更倾向于将其作为软件实现逻辑的归纳整理工具。

第二,UML 是面向对象的

虽然想要整理逻辑结构,但不论是用例图、时序图、泳道图、类图还是其他各种图形式,核心思想都是将操作环节看做对象的行为和交互。

面向对象的方法,很难将面向过程的部分也呈现出来。但纯粹的流程图描述下的面向过程思维又过于细颗粒度,对于想要在对象和过程之间找到一个合适的描述方法,UML 似乎又太顶层了。

比如说,我想描述一个函数实现的嵌套关系。UML 既没有“函数图”这种东西,又没有必要将嵌套关系以流程图的方式完完全全铺开来看。

现在的集成 IDE 有不少反向代码求图的工具,比如 pycharm 中的 show diagram、profile 里的 Call Graph,还有基于 Graphviz 的 PyCallGraph。

虽然可以一定程度上自动化生成类图或者函数图或者调用关系。但三者总有各自的短缺,比如没有接口io,或者没有参数说明,亦或者颗粒度不可控,以及功能十分有限。总之,每一款用起来都感觉缺斤短两。

如果把 CS 的一切具体都抽象为一种模型来描述,其实就是下图这种:

image.png

三大件:一个输入,一个输出,中间一个处理模块

任何函数或者算法或者模块,都是按照这种模型来构建的。

那其实,用这种方式来串联逻辑节点,描述功能结构,是行之有效甚至高效的。

⑥ 硬件开发的一些启示

如果你熟悉或者听说过虚幻引擎、Blender 或者达芬奇,他们的操作方式就是节点逻辑。一端输入,一端输出,各种迭代串联,连起来实现最终效果,甚至有了最近大热的低代码的影子。

但如果你仔细看这个图,就会发现这种形式好像在哪里见过。

是的,没错,为了更方便地可视化逻辑,我去找了画电路图的软件 Quartus ii。

意外发现,硬件开发方法,在软件开发中,竟然十分有用。

image.png

在硬件开发中,通常用引脚表示输入和输出,而且更普遍的是,为了方便理解和养成良好的习惯:左边表示输入,右边表示输出。

而图形代表的硬件配件内部是怎么实现的,则被封装起来。

此外,模块化的搭配使用,也完美满足代码复用,减少重复劳动。无论从哪个角度看,这种可视化的方式都完美契合我们的需求。

由此,找到了解决 UML 和流程图鸡肋的方法,也是一种高效的逻辑可视化的方式。

⑦ drawio 的高级使用方法

既然理论工具找到了,用什么来画呢?

  • IBM 的 Rational Rose 虽然是精于 UML 的,但年代过于久远,而且新系统上很多都无法运行。

  • Quartus ii 只专注于硬件设计,对于软件层面的类、函数、接口、流程这些,无从下手。

  • 纯绘图软件诸如 Adobe Photoshop 或者 illustrator 都太过于宽泛。

在试用过无数工具后,隆重推荐大名鼎鼎的 drawio,一款开源的流程绘制工具。

内置集成了非常多领域的图形元素,甚至还可以画产品界面、原型图、UI 等等。

它是基于 xml 的,跟 SVG 同源,这意味着可以运行在 web 端。也可以导出任意想要的格式。

基于 xml 是有一个原生优势的,那就是你可以自定义图形。

在实际操作中,有一些特殊图形或者符合我个人习惯的图形图标不存在,那么我就可以选择自定义图形,然后保存为自己的图形库。

它的语法格式也很通俗易懂,核心就三个部件:

  • <connections> 表示连接点

  • <background> 画图标背景层(方便做阴影样式等)

  • <foreground> 画前置层(比如文字说明、图形等)

* 举个栗子

设计了一个矩阵乘法的函数 icon,左边表示两个矩阵的输入,右边输出结果

样式如图:

image.png

背后的代码很简单:

<shape aspect="variable" h="60" name="Inverter 2" strokewidth="inherit" w="100">
  <connections>
    <constraint name="in" perimeter="0" x="0" y="0.33333" />
    <constraint name="in" perimeter="0" x="0" y="0.66666" />
    <constraint name="out" perimeter="0" x="1" y="0.5" />
  </connections>
  <background>
    <path>
      <move x="0" y="20" />
      <line x="20" y="20" />
      <move x="0" y="40" />
      <line x="20" y="40" />
    </path>
  </background>
  <foreground>
    <fillstroke />
    <path>
      <move x="77" y="30" />
      <line x="97" y="30" />
    </path>
    <fillstroke />
    <path>
      <move x="80" y="30" />
      <line x="20" y="60" />
      <line x="20" y="0" />
      <close />
    </path>
    <fillstroke />
    <fontcolor color="#FFFFFF" />
    <text align="left" str="m1:dict" valign="bottom" x="22" y="22" />
    <text align="left" str="m2:dict" valign="bottom" x="22" y="42" />
    <text align="left" str="m_result:dict" valign="bottom" x="80" y="28" />
  </foreground>
</shape>

具体自定义方法可以直接看官方文档: www.diagrams.net/doc/faq/sha…


把一个软件的结构像积木或者拼图一样展开,十分方便一目了然地掌握当前开发进度和逻辑节点。清晰看清逻辑脉络,如果真是一团乱麻,倒也很清楚地展现出什么叫做“屎山”(不是)。

综上作为个人的一种经验方法,跟大家分享交流,希望可以帮到有需要的人。

或者如果有什么更好的方法,也欢迎交流探讨。

祝大家新年快乐❤