随谈:软件架构师与数学的关系
记得上小学五年级时,我的数学老师问班上每个同学,为什么学习数学?记不清我的答案是什么了,大概是记账和测量之类的很肤浅的说法吧。其实,隐隐约约地藏在内心深处的还有一个模糊的想法,数学比较好玩,有意思。可能就是所谓的好奇心对未知的渴望吧。因为在我的那个刚刚可以吃饱饭的童年,好玩的有意思的东西实在不多,所以,只能把数学拿来凑数了,小学的数学其实应该叫做算术。
数学和软件架构师到底有没有关系呢?从功利的观点来看,数学知识可以直接应用于许多领域,作为和数学有紧密关系的计算机软件更不例外。比如,概率论与数理统计是语言模型的基础,而语言模型是自然语言理解相关应用的必要模块,如搜索引擎、机器翻译和语音识别等。毫无疑问,不懂概率论与数理统计,就谈不到去理解、设计、开发这样的软件程序。当然,不是所有人从事的软件领域都直接和数学相关,所以,也有论者认为,我做了很多年的软件架构师,参与了很多项目,除了一般的加减乘除,从来没有应用过稍稍高深一点儿的数学知识。结论就是,不懂数学,并不影响软件架构师的职业发展。
这种幼稚的看法和我回答我的小学老师的问题的说辞听起来一样微不足道。数学对于软件架构师的意义绝不仅限于数学知识的直接应用。有道是,功夫在诗外。陆游认为,学习写诗,不能“但欲工藻绘”,只在辞藻、技巧、形式上下功夫。诗的高下更着重于诗人的人生阅历、精神境界和学问修养,这些形而上的东西就是所谓的诗外的“功夫”。王国维在《人间词话》中讲到:“词以境界为上。有境界则自成高格,自有名句。”也是同样的道理。如果架构可以比做一篇诗词,那么它的韵脚往往会落在数学的概念上。数学也就是软件架构师的诗外功夫。
那么,数学和软件架构师会有怎样的关系呢?我们有必要先来探讨一下软件架构师究竟是怎样一种角色,问题似乎太简单了,顾名思义,软件架构师不就是设计和架构软件系统或应用的吗?岂有他哉?在这里,我们不妨反问一下,果真如此吗?事实上,只要我们稍稍地质疑一下,世上很多事情都经不起仔细地推敲。一个软件组织真的有那么多的架构的工作要做,以至于必须要有专门的软件架构师的角色来承担不可吗?我观察到的情况大多并非如此,譬如以软件产品为主导的公司,根据用户或市场的需求,不断地开发新的功能,发布新的版本。在此过程中,产品架构早已注定,还架构什么呢?即使有些重构的工作,但大多时候,恐怕都不会做剧烈的变动。以项目为主导的公司又怎样呢?每个项目都不相同,是不是都要一一架构呢?项目虽然不同,但开发这些项目的环境如数据库、编程工具、集成框架并无分别,因此架构这些项目的系统时,基本上是举一反三,并没有多少出奇之处。互联网公司的那些五花八门的应用(如O2O)更是如此,无论是前端还是后端,都有越来越成熟的技术框架,这些框架本身就决定了应用的结构,没有太多需要架构的东西和技巧。简单地讲,架构其实就是一个分解和连接的技艺。一个大的应用系统被分解成不同层次的功能子模块,然后定义子模块之间数据交换的接口,把这些子模块按照一定的逻辑连接起来,当然还要照顾到功能的一致性和可扩展性。不仅仅是架构师,任何程序员在写程序时,这些不都是要考虑的问题吗?
由此可见,实际的软件实践中,真正需要架构的东西并不多。而且,任何程序员都会或多或少地涉及到架构的问题,所以软件架构师的角色并没有那么的神圣,甚至于都没有清晰的界定。那么,软件架构师的作用和价值又在哪里呢?我认为,软件架构师更应该是一个解决问题的专家,其作用和价值也正在于此。在软件产品或项目开发的过程中,随时会遇到性能方面的或功能方面的不确定的问题。比如,如何用时间换空间和空间换时间的艺术实现性能瓶颈的突破。面对问题,软件架构师要有办法,知道从哪里着手,设定目标和方向;也要有能力,组织人马,攻艰克难。 一个软件架构师在其职业生涯中,就某个领域积累了大量的知识和经验,形成了自己独到的解决问题的方法论。而软件架构师的数学修养决定了TA理解问题和解决问题的水平和高度。
数学的主要方法是思想(Idea)和技巧(Technique)交织在一起的逻辑推理的艺术。给定一个问题,首先,数学思想擘画出大致的方向,然后,数学技巧完成具体的步骤,最终,达至预期的目标。比如,我们欲穷千里目,那么,临高眺望是为思想,而上楼或登山构成技巧。来看一个经典的数学的例子,Gödel不完全性定理(Incompleteness Theorem)的证明包涵了匪夷所思的数学思想和数学技巧。在Gödel之前,对 Hilbert计划(Hilbert's program)中所谓公理化的算术(Arithmatics)系统的一致性和完全性的讨论都局限在元数学(meta-mathematics)中,没有人对元数学命题的证明是什么有清晰的认识,更遑论如何证明了。而Gödel把关于算术系统的一致性和完全性的元数学的命题映射到算术系统自身即元数学的算术化的思想使得这一问题豁然开朗;为完成这一映射,Gödel证明的技巧性体现在用到的46个预备性的定义和重要的引理,如著名的Gödel数和对应引理。这些创造性的思想和技巧仿佛天授,非人力所及。再看一个数学的例子,在证明实数集合不可数时,德国数学家Cantor建立了判断两个集合大小是否相等的思想,即如果能够找到一个映射,使得两个集合的所有元素一一对应,则两个集合相等。而在证明的过程中,Cantor发明了著名的对角线法,真是巧妙之极。尽管Cantor的工作在数学界引起了巨大的争论,但这一证明结果使我们认识到,无穷不仅是有意义的,并且无穷和无穷是不一样的,即有多种不同的无穷,不能等闲视之。
需要指出的是,思想和技巧只有大致的区分,没有严格的界定,当然,也完全没有必要给思想和技巧做精确的定义。但是,可以确定的是,许多科学技术领域的方法论都不会超出思想和技巧的范畴,而不同领域思想和技巧的比重可能会有所不同。作为解决问题的一个模式,我们断不可轻视思想和技巧的重要作用。比如说,虽然在很多程序库中都实现了各种排序算法,但是我们还是学习排序算法的细节,那是因为排序算法中包含了很多解决问题的思想和技巧,如合并排序和快速排序包含的分而治之的算法设计思想,以及快速排序用到的partition的技巧。思想和技巧作为硬币的正反两面,必须是以问题方案专家自许的软件架构师念兹在兹的不二法门。
众所周知,计算机科学是从数学尤其是从作为数学基础的数理逻辑的发展中衍生出来的。计算机科学包含的思想和技巧往往与数学保持高度的契合。例如,数学归纳法是使用递归来完成一个数学证明,而递归又广泛地用于计算理论和算法中。递归不仅仅是程序和算法的一种机制,递归的思想也是思考和表示某些问题的一种简洁的优雅的方式,如树和图的遍历。还有,算法时间复杂度和空间复杂度的分析思想来源于数学的极限的概念,表示的是算法时间和空间的花销随着输入规模增长的快慢程度。 极限是数学分析微积分的基础,今天广泛使用的ε-δ形式的精确的极限的定义是数学家们经过了两百多年的痛苦挣扎才最终由德国数学家Karl Weierstrass给出的。他山之石可以攻玉,软件架构师完全可以借鉴这些和计算机科学相通的数学思想和数学技巧,提升和拓宽自身的视野,激发创造的灵感。对于计算机软件而言,难题之所以是难题,常常是没有理解问题中隐含的数学概念和数学关系,一旦这些最本质的东西把握住了,就可以举重若轻地实现巧妙的突破。而其他所谓代码方面的困难的问题,如查找内存泄漏,需要的只是耐心地、仔细地以机械的方法搜寻问题相关的琐碎的细节,尽量借助工具提供的线索,找到问题的根源。这是一个体力活,功夫到了,自然可以迎刃而解。
软件架构师的一项重要工作是为真实的应用空间建立抽象的模型。有了模型之后,接下来要在计算机内部表示模型,即用数据结构来表示模型的对象或实体,用算法来表示模型的过程。最后的代码实现水到渠成。这个过程并非一帆风顺,一层不变的,回环往复常常不可避免。有可能在写程序的时候才发现模型有严重的缺陷,当然,这是极糟糕的事儿。构建模型的重要性正如Steven S.Skiena在其著作The Algorithm Design Manual所讲的那样:
“Perhaps the single most important design technique is modeling, the art of abstracting a messy real-world application into a clean problem suitable for algorithm attack.”
抽象的模型要求简单、清晰和无歧义。这正是数学语言特有的优点,所以数学语言是最适合建模的语言,很多时候,模型常常等同于数学模型。十七世纪的德国的哲学家和数学家Leibniz试图架构一个史无前例的超级系统,这一系统将囊括所有的人类知识和推理。为此,Leibniz给出了三个阶段的宏大计划(grand program):第一,将人类的知识分门别类的整理出来,就像百科全书做的那样;第二,用符号代替这些知识的关键概念;第三,将知识的推理规约到对这些符号的操作。Leibniz为他的系统建立的模型的核心思想就是用一个普适的数学符号系统来表示人类知识,发明一个神奇的计算工具来操作这些符号来代替推理演绎。这样,当两个人在争论一个问题时,就不必徒逞口舌之利了。而只要按照Leibniz的系统定义的方法算算,孰对孰错就清清楚楚了。这可能是最早的人工智能系统的雏形吧。由此得到的启发是:建模的方法是以数学抽象的方法找出系统或应用中的关键元素,用适当的数学符号表示他们,并定义他们之间的关系,当然,这种关系可以用数学公式表示。这里的关键是抽象,德国数学家Hermann Weyl更是毫不保留地说:“数学抽象中最关键的一步让我们忘记这些符号所表示的对象,有许多操作可以应用于这些符号,而根本不必考虑他们到底代表着什么。”真是无招胜有招,与Leibniz英雄所见略同。数学离不开抽象,架构的建模过程同样离不开抽象。道理常常是相通的,这种抽象方法在艺术领域也有广泛的应用。比如在拍摄风景题材的照片时,有经验的摄影师都知道:一幅好的作品真正打动人心的是光线、颜色和形状(如优雅的线条),因为这才是审美的最基本的要素。所以,通过调节曝光、光圈、焦距等参数,通过精心构图,表达出来的是摄影师心目中升华了的理想的的光线、颜色和形状。至于拍摄对象是山川、江河、沙漠、草原、树木、花草亦或是别的什么东西,还是忘了它们吧。咦,我们似乎也忘了,架构去哪儿了?
软件架构师的另一项重要工作是确定系统的子模块以及他们之间的关系。数学是特别讲究关系的,实际上,一个数学对象是通过与其他数学对象的关系来定义的,这也是一个数学对象存在的原因和意义的总和。数学家本着内心审美的直觉,常常会发现不同寻常的关系,Euler公式将三角函数、指数函数和复数这些貌似不相干的概念联系起来。它之所以看起来那么美,那是因为揭示了纷繁芜杂的大千世界背后的秩序、和谐与统一。在数学中,这样的例子并不鲜见,比如 Gauss发现的素数分布定理、以及π的Leibniz公式都是对数学之美的很好的诠释。再把目光投向计算机软件,目前十分看好的大数据不就是应用算法对大规模数据进行分析来挖掘不同事物的相关关系吗?有些事物之间的因果链太长,以至于很难靠人工的推理发现它们之间的相关性,但大数据技术可以做到。是否可以利用大数据技术帮助数学家发现更多人类智能不容易捕捉到的微妙关系呢?不知道。不过在《大数据时代》这本畅销书中,作者认为可以用大数据分析发现的相关关系代替因果关系。只问相关性,不管因果性,我想,这恐怕不过是作者为吸引读者眼球吹勒个牛吧。不管怎样,多了解一点数学,软件架构师在寻找、定义系统模块关系时,想必也是极好的。
最后,我们再次回到摄影的例子。一个技术高明的婚纱摄影师在职业生涯中为客户拍过数不清漂亮的照片,但却可能没有一件伟大的作品让人看上一眼就能够联想到爱情,因为他也许根本就不懂爱情。同样的道理,不经过数学浸润的架构师无论做过多少项目,也很难超越那个婚纱摄影师所能达到的平凡的水准。