思考问题的本质--对软件设计中抽象层意义的一点思考

最近在作一个可视化的工具,之所以要开发这个工具,是因为我们正在开发的编译系统在处理源文件的过程

中,会生成内部的数据结构,很多时候,我们需要观测内部数据结构的状态。而这个数据结构从本质上来

说,就是一个Graph,对于Graph来说,仅仅观察它的单个结点或是某条边的状态的话,还是比较容易的,

直接dump出文本或是通过调试器trace就可以了。但是, 我们还经常会需要观察Graph中若干个结点以及

边之间的联系,这个时候,通过文本dump或是调试器trace的方式就有些费时费力了
因为本质上,

图结点,边之间的关系是一种空间拓扑关系,而文本dump更适合于提供一维的线性观察方式,通过调

试器来trace继而在大脑中直接建立起图的拓扑关系则会对脑力带来较多的消耗。而我们在开发过程

中,会经常需要对这个Graph的拓扑关系进行观察,分析,每次都消耗较多的脑力资源来完成拓扑关系

的建立不是很划算
。所以我们想自己开发一个可视化的工具,能够以这个内部Graph的数据结构表示作为输

入,输出则是一个以图形化方式建构出Graph拓扑关系的图片文件,这样我们在需要分析Graph拓扑关系的

时候,只需要把精力专注于分析拓扑关系本身,而不需要再花费太多精力去将抽象的数据拓扑关系映射到易

于分析的状态了。

在设计这个可视化工具的过程中,自己遇到了一个问题。

先简单交待一下跟这个问题相关的背景信息:

在Graph里,结点与结点之间的联结关系有两种:

1。通过Bus类型的边来联接;

2。通过Bit类型的边来联接。

一条Bus在本质上是由多条Bit边组成的。而一条Bit边既可以用于构成一条Bus边,也可以作为一条独立的直

接联结两个结点的边来使用。


自己遇到的问题就是, 在设计可视化工具的过程中,如何以准确并且宜读的方式完成Bus/Bit边的绘制

而Bus/Bit边的绘制工作的本质核心,就是能够将Bus/Bit边与它们各自的前驱,后继结点之间的关系以精确

,易读的方式表征出来。

Bus/Bit边的前驱,后继结点是有着多种可能性的,比如:Bus边的前驱可能是一个直接联结该Bus的前驱结

点(为便于描述,后面统称为 Bus-Level前驱结点), 构成一条Bus边的某条Bit边除了有Bus-Level前驱

结点以外还可能有自己独立的前驱结点(为便于描述,后面将这种直接联接Bit边的前驱结点统称为 Bit-

Level前驱结点
)。类似地,也有 Bus-Level后继结点Bit-Level后继结点

自己把Bus/Bit边与它们的前驱,后继结点可能的组合关系作了一番分析,大概有如下一些情况。

Bus/Bit边的前驱结点状况:

1。Bus边有一个Bus-Level前驱结点;

2。Bus边有N个Bus-Level前驱结点( N > 1,下同);

3。Bit边有一个Bit-Level前驱结点;

4。Bit边有N个Bit-Level前驱结点;

5。Bus边有一个Bus-Level前驱结点,这条Bus边中的某条Bit边还有一个Bit-Level前驱结点;

6。Bus边有N个Bus-Level前驱结点,并且这条Bus边中的某条Bit边还有N个Bit-Level前驱结点。

Bus/Bit边的后继结点状况类似也有6种可能。

可以看到,Bus/Bit边与前驱,后继的组合关系一共有 6 * 6 = 36种。自己需要作的事情其实就是针对所有

这些组合关系,分别进行适当的绘制。

自己一开始的设计策略比较直接,就是 直接就对这36种组合情况分别进行判断,根据判断结果再分别进行

绘制,结果发现自己的大脑里面同时要管理的状态数目有些多,大脑思考记忆的负担很重,勉勉强强实现完

了,跑完测试以后,根据测试结果再去调整代码的时候, 发现基于这种设计策略写出的code非常复杂,最

大的一个函数多达近300行的样子,里面还有一个夸张的7层的if条件嵌套,即便自己是刚写完代码没

多久,再读自己的code,还时不时在读到有些地方的时候不得不停下来,仔细地推敲一下才能确认自

己当初这样写的意图。


感觉这种设计策略给自己带来了较大的思考负担,实在是不利于以后的维护和扩展,于是开始琢磨是不是可

以有一个更简洁的策略。

琢磨了一两天,脑子里面闪出一个想法: 自己是不是可以引入一个抽象元素,这个抽象元素作为一个桥接

对象,分别跟Bus/Bit边的前驱,后继结点相联接。有了这个抽象元素以后,Bus/Bit边的前驱结点只

需要跟这个抽象元素打交道,后继结点也只需要跟这个抽象元素打交道。这样,自己需要考虑的问题就

变换为如何把前驱结点与抽象元素之间的关系,后继结点与抽象元素之间的关系精确的绘制出来
。在这

种设计思路里,自己需要管理的状态数变成了 6 + 6 = 12种了,复杂性一下子降了下来。于是自己兴冲冲

地沿着这个思路完成了设计方案,并且基于这个设计方案进行了重构。重构之后的代码的确变得简洁了许多

原始的那个300行的函数缩减为不到100行,以前的实现中,那个令人头晕目眩的7层if嵌套变为只

有4层if嵌套


除了表层的这些差异以外,自己在阅读重构之后的代码时,能够很清楚地理解每一段代码的意图,能够在大

脑中清晰地理出来代码的结构关系,这种对自己代码的清晰的把控感正是之前那个实现版本中所欠缺的。

剩下还有一个问题需要处理, 重构后的方案中,引入了一个抽象元素,而在真正的目标问题中,这个抽

象元素并不是真正需要存在的,自己为了简化对问题的处理,实际上也带入了副作用,增加了一个冗余

因素
。不过这个副作用比较容易消除,因为自己在完成Graph的绘制工作以后,可以通过一遍general的后处

理,把冗余的抽象元素清空,消除其对整体绘制工作的影响。因为在作后处理的过程中,自己只需要考虑如

何消除冗余的抽象元素,这个后处理算法的设计也是比较简便的。

至此,算是以一种自己还算满意的方式完成了Bus/Bit边的绘制工作了。

以这件工作为基础,自己也对抽象层乃至软件设计有了一些散乱的思考:

1。我现在的理解,设计软件,本质上就是在解决“ 如何管理复杂性”的问题,面对同等复杂规模程度的问题

,你能以更简洁,清晰有效的方式完成处理,你所设计的软件的品质就更能够得到保证。而对复杂性的管理

又可以分为几个不同的维度:

开发人员理解,维护,分析的复杂度。

时间复杂度。

空间复杂度。

一个软件的设计方案,如果对其的理解,维护,分析会带给开发人员非常大的脑力负担的话,这样的软件设

计出bug的概率相对来说会高于那些给开发人员带来较小脑力负担的设计。在软件设计过程中,我觉得,在可

选的范围内,我们应该选择那些清晰,简洁,易把握的设计方案,在设计层面尽可能减少开发人员的脑力负

担,能够让开发人员的脑力更多解放出来,用作别途,比如软件功能的升级,某个具体Bug的修复。

而很多时候,软件设计相对于开发人员是否易于理解掌控,跟软件实际运行过程中的时,空复杂度又有

着很密切的联系。有的时候,这种联系是正向关的,有的时候,这种联系则是负相关的(即软件的设计

易于开发人员理解掌控了,但是软件的时间,空间复杂度却上去了)。如何在这几种不同的复杂度之间

进行折衷,选择一个相对较优的平衡点,考量的就是软件设计人员的水准了。


2。很多时候,我们在软件设计过程中,所面对的问题会非常复杂,其复杂性甚至可能超出了自己脑力在同一

时间段内的处理能力的上限。在这种情境下,我觉得, 我们不应该一味地硬碰硬地去死磕这个问题,而应

该看看是不是有办法可以对问题进行分解或是在作了某种处理以后,把复杂度降到自己脑力处理能力的

范围内,这样便于自己在分析过程中,始终能够保证对问题的可控性
。比如说,引入一些抽象层次,对原

始问题进行隔离,在一段时间内只关注抽象层次这端的问题,对这一端的问题建立起深入的理解和认识以后

,再将注意力转移到抽象层次另一端的问题。通过类似于这样的手段,策略,我们可以在拥有同样的脑力处

理能力的前提下,更有效地解决复杂问题。

3。当然,有些时候,我们会发现很难对问题进行分解或是降阶难度的预处理,这种情形下,我们就只能死磕

下去了。在这一点上,我蛮认同我的老大的一句话:“ 很多时候,我们在具体工作中觉得一个问题很难,

无法逾越,其实往往这个问题并不是真得不可逾越,而是自己花费的心血不够。


真正用心去作一件事,经常会发现自己能够作到一些之前都不相信自己能作到的事情。而什么才叫用心,

不仅仅是可以用每天工作多少小时来简单衡量的了


在我看来,用心是一种状态, 在那种状态下,你的显意识也好,潜意识也罢,几乎全部都投入在某个问

题上,这种状态下,你的内外部行为高度统一
,所产生的处理能力是“非用心”状态所不能相比的。进入了

这种状态以后,很多之前以为不能解决的问题往往就离解决为时不远了。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值