富内斯的思维模式

博尔赫斯(Jorge Luis Borges)有一篇奇文,曰《博闻强记的富内斯》,以纪实手法杜撰了一个具有非凡记忆能力的传奇人物富内斯。博尔赫斯自云:“(《博闻强记的富内斯》)是长夜失眠的隐喻。”博尔赫斯大约是文学大师中最擅长使用隐喻的人了。他在诺顿讲座的演讲中,专门做了一次题为“隐喻”的演讲。极限编程的创始人Kent Beck非常推崇“隐喻”,并将其作为Spike阶段理解架构的重要手段,也是XP敏捷实践之一。

我在读一些文学作品时,常产生对软件设计思想的遐想。这或许可以称之为“比较学”,但我认为可以说是“隐喻”。

回到这篇奇文。博尔赫斯用诗的语言向我们描述了抽象与具象。他写道:

我们能够充分直感的形象是黑板上的一个圆圈、一个直角三角形、一个菱形;伊雷內奥却能直感马匹飞扬的鬃毛、山岗上牲口的后腿直立、千变万化的火焰和无数的灰烬,以及长时间守灵时死者的种种面貌。

伊雷內奥即富内斯,他的超凡记忆力在19岁被马摔下地的时候被神奇的触发。他能记住他之所观、所听、所感的任何一刻纷繁复杂的细节,“非但记得每一座山林中每一株树的每一片叶子,而且还记得每次看到或回想到它时的形状。”然而,富内斯却不具备抽象的能力——“富内斯几乎不会进行一般的、纯理论的思维。他非但难以理解‘狗’这个共性符号包括不同大小、不同形状的许许多多、各色各样的个别的狗;麻烦的是,从侧面看的编号为3-14的狗,名称会和从正面看的编号为3-14的狗一样。”

所以博尔赫斯说:“思维是忘却差异,是归纳,是抽象化。在富内斯的满坑满谷的世界里有的只是伸手可及的细节。”

富内斯可以是一个好的记忆者,他可以用极短的时间学会世间任何一门语言,我想,程序语言自然也不例外,所以他若从事编码工作,一定是一个不错的高效程序员;但是,他却不一定是一个好的软件设计者。Dijkstra就指出:“抽象来自于对真实世界中特定对象、场景或处理的相似性的认知,并决定关注这些相似性而忽略不同之处。”这里所谓的“决定”就是设计时的一种判断,且需要具备抽象的能力去支撑。富内斯显然做不到这一点。

抽象是一种归纳的能力,是一种识别共同特征的能力。抹掉细节,只辨其共同之处,并将其归纳为一个抽象的名词,这是设计者需要具备的。不如此,则不能化繁为简;不如此,则不能应对变化。我们很难成为博闻强记的富内斯,这就意味着当我们面对太多如枝头纷繁堆叠的叶子一般的细节时,我们不可能都能记住它们的样子。从面向对象的角度来讲,远处山林中每一株树都是一个对象实例,但我们可以以抽象的Tree名之。树上生长的每一片叶子都是一个对象实例,但我们可以以抽象的Leaf名之。

在软件设计中,抽象除了是对共同特征的概括之外,还应该是对对象边界的一种控制与分离,即通过抽象将对象分离为“外部视图”与“内部视图”。Abelson与Sussman将抽象的这种能力称之为抽象壁垒。这是用更加强烈的语气强调了“边界的意义”。

在我曾经负责设计的SaaS产品中,需要根据元数据生成报表。报表的所有元素最终需要导出到Excel与Html网页上。我借用了“绘制”的隐喻来表示导出功能,因而Excel的Sheet与Html的Page都可以视为“画布”。基于这么一个隐喻,我提炼出与导出相关的报表元素的共同特征,即针对“画布”而言,它们都是一种“绘制元素”。于是,我得到了两个抽象概念:DrawingElementReportCanvas

1
2
3
4
5
6
7
8
public interface DrawingElement {
public void draw(ReportCanvas canvas);
public object getElement();
}

public interface ReportCanvas {
public void addElement(DrawingElement element);
}

DrawingElementdraw()方法负责将绘图元素绘制到ReportCanvas对象中。这种抽象提供了导出功能的外部视图,它隐藏了具体的实现细节,使得该功能是可扩展的。

抽象的方向有二:自上而下又或自下而上。

在进行自上而下的抽象时,我们往往需要遵循一些范式、模式乃至设计风格。例如针对业务模型,我们可以参考Martin Fowler的《分析模式》,直接套用该模式定义的模型对整个设计进行指导。又例如说针对一连串的数据流处理,我们可以参考Pipe-Filter模式,抽象出可以组合和扩展的filter。在自上而下抽象的过程中,偶尔需要设计者利用“隐喻”来给出一个形象而恰当的抽象概念。例如在Facebook的FBML架构中,就引入了“flavor(风味)”来形容不同上下文对输入的约束。

当面对未知领域时,又或者我们无法寻找到已有模式(范式)去follow时,我们就需要自下而上的抽象能力。这个过程就好像整理一堆打散了的扑克牌一样,我们要按照花色分类,然后再按照数值进行排序。扑克牌的花色就是抽象过程中要寻找的“共同特征”,扑克牌的数值以及排序过程则是需要封装的细节。例如在报表处理的上下文中,我们分辨出填充参数的多种实现方式,进而提炼出ParameterFilling这个抽象行为;在租赁系统上下文中,我们分辨出可以租赁的各式物品,进而提炼出Rentable这个抽象特征。

请注意,分类识别共同特征都属于一种抽象。前者获得的是is-a的关系,后者获得的是can-dohave的关系。无所谓优劣,抽象关系的选择取决于具体的场景。

博尔赫斯杜撰的富内斯能够过目不忘地背诵冗长晦涩的拉丁文《自然史》,超凡的记忆力让他可以拥有海量的知识,但他的思维方式仍然停留在留声机的初级阶段。他记住了知识,却不能有效地运用知识,因为他无法归纳、无法推演、无法演绎,换言之,他缺乏的是提炼知识的抽象能力。