写在前面

2012 年从编写第一个网站开始,有幸进入程序员的行列已经接近 10 年了。十分惭愧的是,在这期间并没有什么特别的成就和产出。

还好喜欢思考以及写写东西,将工作、生活中收集到的一些关于软件工程中的认知心得从不同地方整理出来,构成了这本个人风格非常强烈的电子书。我给这本小小的电子书起名为认知心得,你可能会好奇,为什么我把程序员和认知这两种看似毫无关联的内容联系到一起呢?因为在工作中,我们需要解决大量的问题,而解决问题则需要对事物有充分的认识,这就是认知。

认知,是一个非常"虚"的概念。

在心理学上,有两类智力 [1]

一种被叫做流体智力,类似于计算机的内存、CPU,它的效率取决于个体差异,表现为反应速度、工作记忆 [2]等特征。我们常常认为反应速度快、记忆力好的人更聪明,以至于忽略了另外一种智力。

另外一种智力被叫做晶体智力,表现为最终解决问题的能力。虽然有些人从流体智力看起来更为聪明,但是解决问题并不仅仅靠聪明,还需要用到知识、经验、工作策略来配合才能最终解决问题。

知识、经验、工作策略增强我们解决问题能力的,同时也伴随认知能力的提升。在同样的流体智力下,通过对事物不同角度的认识,获取更好的工作策略,晶体智力会被强化。

我曾在认知中获得甜头,想通一个问题背后的逻辑,可以抵消大量的努力。

比如,我曾短期参与过一些咨询工作。咨询工作中有大量的隐喻,需要分析客户的预期和动机,但是有些动机是不能在明面上表达的,如果我们对动机的理解每多一分,工作就会越轻松。

同样的,我们每时每刻都在做一些判断,认识到问题背后的逻辑,判断越准确,返工折腾就越少。

在另外一方面,我曾掉入另外的陷阱:迷信认知就能解决实际问题,就变成坐而论道缺乏实践了。自以为知道却实际上不知道的人,比知道自己不知道的人更危险。因为后者保持对问题的敬畏,而能把每一件小事做好。

也不得不承认我并非是一个对软件工程认识足够深刻的人,理解软件和做好软件还需要很长一段路要走,而且不能停留到认知本身,还需要落地实践。但是无论如何,有一些网友被我发过的博客吸引而来,有必要整理出来抛砖引玉。

为什么起名为心得是因为这些内容需要在不同的上下文中甄别,并且某种程度上来说没有太多用处,因为"做到"比"知道"更重要,也是我在努力的方向。

这本电子书包含了程序员能用上的一些认知心得:

  1. 一些哲学内容,建立接收、判断信息的基本原则。

  2. 一些逻辑学内容,将逻辑学的内容和软件工程结合起来。

  3. 一些模型思维、软件业务理解、架构相关的心得。

  4. 将团队和计算分布式系统连接起来的认知心得。

如果需要联系我,可以通过以下途径:

  1. 微信公众号:TechLead 少个分号

  2. 网站:http://shaogefenhao.com/

  3. 微信:shaogefenhao

公众号二维码:

wechat oa

认知变化的来源

在 IT 行业谋生并不轻松,无数混乱的事让人应接不暇。

作为一名开发者,你是否感到过疲惫,产品经理的需求变化无穷?作为一名架构师,你是否感到过挫折,怎么摆弄架构都不合适?作为一名项目经理,你是否感到沟通的对方非常固执,明明简单随手的事情却遭到对方拒绝?即使作为老板,你是否对团队失望过,自己一个绝妙的点子换来的是响应者寥寥,得到的仅仅是应付?

在无数的编程思想书籍上都说过,代码需要被设计的足够灵活,只有足够的灵活就才能从容的应对需求的变化。关于应对需求变化的问题,在一次架构师研讨会上,我听到了令人耳目一新的答案:

"有些需求变化的根源是软件背后生意的变化,使用过于灵活的设计不仅不能满足所有的情况,还会给团队带来隐藏的成本。架构的灵活性需要克制的考虑,过于灵活的设计意味着更多的维护成本和认知负担。”

这名架构师已经跳出从技术角度看待需求的变化局限,通过更高的认知水平看待问题。

著名电影《教父》里有这样一句话:“花半秒钟就看透事物本质的人,和花一辈子都看不清事物本质的人,注定是截然不同的命运"。在军事行动中,决策者采取合适决策的前提是建立在对战场局势有充分的理解之上的,负责侦查的部队也会在不暴露自己的前提下,找到视野足够好的高地。深刻理解事物的本质,可以让自己时刻处于优势地位,无论对个人还是企业都至关重要,而这一切都需要站在更高的认知之上。

在软件行业,不同角色的人对待事物的认知差异极大。如果工作上的问题,有时候我们以为是一个技术问题,其实可能是一个经济问题;当我们以为是一个经济问题,其实是一个管理问题(甚至政治问题)

认知差异会带来冲突,如果我们没有意识到它的存在,会带来非常多的麻烦。

这种认知差异带来的冲突无时不刻在发生。普通开发者追求能在限定的时间内完成分配到的任务,技术经理或架构师关注采用的技术方案是否能持续维护,项目经理更为关注完成这些工作需要的人力成本,而产品经理或者公司老板更关心这些方案能不能创造业务价值。

如果只是站在特定的角度看待问题,就像盲人难以感知到大象的全貌。认知不足的人难以成为一个组织的领袖,只有“感官”完整、看的既宽又深且头脑清醒的人才有资格坐在企业、团队的"驾驶舱"中驱动发动机让汽车在高速公路上飞驰。

我很佩服高认知的人可以看到事物背后的逻辑和规则,这些逻辑和规则往往是深刻的、直接的,但是又是残酷的、血淋淋的,不以个人意志而转移。事物表面的规则就像精美的电器外壳,精美的代价是留给你操作的接口很少,提高认知的过程就像打开电器的外壳,直面复杂、诡谲的线路板。打开电器外壳充满了风险,意味着你有可能损坏它,也可能被电流伤害,但是得到了改变一些事情的机会。如果你想成为一名 GEEK(科技爱好者),想要从容的定制电器的行为,那么打开电器外壳就是第一步。

很多软件说承载的业务背后是企业对他们“生意”的探索,普通的产品经理关注原型、交互,把用户交互奉为圭臬,而优秀的产品经理看到的是经济、利润和成本;普通的架构师关注模式、最佳实践,优秀的架构师关注权衡、取舍和每个组件的职责;普通的项目经理看到的是事,优秀的项目经理看到的是人。

认知层次不同的结果是,低认知的人今天信这套方法明天相信那套方法,相信这套方法的时候攻击其他方法,信仰改变的时候嘲笑过去的自己;高认知的人深刻的认识到方法论背后的逻辑可能并没有差异,能掌握这套自然也能掌握另外一套。

以哲学为师

认知并不是一个新的话题。2021 年知识付费如火如荼,刘润、罗振宇等人通过比普通人高的认知能力获得了他们想要的东西;各个行业咨询公司也站在更高的维度把握行业方向;管理大师通过高明的认知能力让团队勤勤恳恳为他工作。

在不好的一面,一些成功学大师也通过独特的认知能力收割大众,导致参与人高价参加课程换来的是如镜花水月一般的“成功梦”。

对于普通人来说,这些内容并非没有道理,刘润有道理、罗振宇有道理、产品经理梁宁有道理甚至成功学大师陈安之也有道理,但是,合到一起没有道理。或者,今天有道理,明天又没有道理了,后天继续想想还是觉得有道理。

这些道理在生活中无处不在,比如,有人说做人要宁死不屈,也有人说要屈能伸。在建立自己的世界观和认知体系之前,太多的道理反而成了认知负担。

我们如何建立自己的认知体系?

我们并不是首个回答这个问题的人,建立在空想和畅销书之上的认知是非常脆弱的,而且很难形成体系。每一个人都有自己的想法,建立认知体系的过程只能靠自己完成,外界的知识只能提供养分。

古希腊、中国的先秦时期已经诞生了大量的思想家,先秦的思想家即使在科学技术落后的时代,认知能力也比现代的普通人要高。在西方,我们可以学习苏格拉底、柏拉图、亚里士多德等哲学大师的著作和思想;在东方,我们可以学习诸子百家的思想。

随后的几千年时间里,黑格尔、叔本华、笛卡尔、康德、尼采等哲学家为我们建立了浩瀚的思想宇宙,通过哲学我们可以建立强大的认知体系。哲学并非缥缈虚无,建立在哲学之上的认知无比坚韧。

这和软件工程有什么关系,如何利用哲学的认知体系解决软件工程中的问题呢?哲学包含了认识论、逻辑学、方法论等不同的分类,这些内容可以指导我们如何辨别过度的信息、如何找到合适的方法、如何理解业务、如何建模和架构,以及如何处理团队的问题。

亚里士多德的逻辑学可以让我们在沟通过程中分辨什么是诡辩,什么样的描述是符合逻辑的。例如,订单中的商品是商品吗?要回答这个问题,需要深入理解概念,通过概念的内涵和外延来识别软件建模中的陷阱。基于逻辑学,可以让我们更深入的理解概念,不被概念的名字迷惑。

方法论这个词汇起源于笛卡尔的《谈谈方法》[3],那么软件工程中的各种方法论说的又是什么呢?例如,笛卡尔在《谈谈方法》中告诉我们如何分解问题,合理地分解问题和任务是测试驱动开发(TDD)的前提。

哲学分支认识论 [4]用主体、客体描述行为,将操作者看做主体,将被操作者看做客体。通过主客体方法可以把代码写的如自然语言一样流畅。定义清楚谁操作谁,使用拟人、拟物的方式来定义代码中的职责,这样的代码更加符合普通人的理解方式。

用哲学理解团队协作也别具一格。《社会契约论》[5]中,组织是个体权利让渡后的结果。个体让渡了部分权力,以失去部分自由的代价实现团队协作,以换取个体的利益,当个体利益受到损害时,团队就会崩溃。这种认知要比道德、情怀的诠释深刻得多,也有用的多。

以生活为师

哲学思辨无法代替客观世界,哲学离开生活就变得空洞。软件工程产出的成果需要服务大众生活,软件的需求也来源于生活。罗翔老师讲过一个故事,他使用同样的司法案例在学校的课堂上和校外的普法活动中讲,发现普法活动的人民群众反而比课堂上的学生更能理解案例背后的法律原则,因为人民群众的生活经验比大学生多得多,而法律也来源于生活。

软件开发也可以从生活学习。在设计软件模型时,很多模型模棱两可,我们既不知道如何起名字也不知道如何设计。实际上,大部分业务软件的模型都可以在生活中找到影子。对软件架构理解深刻的架构师,往往对生活的理解也深刻。

例如,几乎所有的系统都需要登录,而登录过程中会话的概念对初级开发者不是很好理解,对于架构师来说也不是很好建模。这种情况,我们一般建议在生活中去寻找一些启示,通过比喻来提高认知。对会话的理解可以想象为开车上收费的高速公路,进入收费入口时,工作人员会给驾驶员发一张卡片,这张卡片就是这辆车行驶在这条高速公路的凭证。

还有一个例子,有一个需求是“当群组没有成员时,这个群组不出现在群组列表中。”如果按照编程的惯性思维,会很容易写出性能低下的代码:群组列表的每一次展示都需要过滤没有成员的群组。

回到生活,这个例子可以理解为最后一个离开办公室的人需要关灯,群组就像办公室,列表中元素的可见性类似于办公室的灯光。作为上帝,让每一个观察办公楼的人自动忽略无人的办公室太强人所难,而制定一个规则让最后一个离开办公室的人关灯则非常好理解。通过生活中的例子,有时候非常容易理解技术方案。

不以生活为师,很多需求就是伪需求,很多架构设计也是伪架构。软件产品设计的很多交互和环节是现实生活中确实存在的,如果产品经理 "创新"的发明一些特色的流程,用户可能反而不买单。

比如,现在的智能电视需要搭配机顶盒一起使用,机顶盒和智能电视系统中有非常多的功能还彼此重叠,但是很少人使用这些功能。智能电视不仅不智能,还变得极其复杂,让家里的老人打开电视都非常困难。从软件工程上来说,它是失败的。

但是反过来看,从商业上来说它却算非常成功,这些功能即使消费者不去使用也必须对它付费。所以,不能仅仅使用软件工程中的思维来理解,应该使用更高的认知尝试理解它。

黑格尔在《法哲学原理》[6]中有一句名言,“凡是存在的就是合理的”,不过这句话容易被误解成“存在的就是正确的”。这句话合理的理解为,“凡是存在的东西,都是现实的”。我们不能理解一些事物说明还有一些隐藏的规则没有被发现或认识到,当这些隐藏的规则不存在后,这句话就变成“灭亡也是合理的”了。

在认识事物时,需要认识到事物的反面,然后认识到反面中值得肯定的东西。在哲学上,我们把这种认知方式叫做“否定之否定”,在生活中充满了无数的“否定之否定”。

以旁观者为师

为什么我们陷入技术问题很长时间,当开口向同事求助时,刚起身就想到解决办法?

同样的行为也发生在心理学案例中,为什么有一些电信诈骗的受害者在完成汇款之后立马就能意识到被骗了,而在之前很长时间都无法意识到,这种现象在心理学中被叫做思维定势。

思维定势无处不在,即使划时代的思想家也无法幸免,当我们获得一种思维方式,就会被这种思维方式困住。特别是这种思维方式在曾经带来过辉煌和成功,它就像长期佩戴的骑士勋章,慢慢的就变成了枷锁。

思维定势可以看做是大脑的一种性能优化,在一个场景中,我们得到了各种结论,大脑就会缓存这些结论,默认这些结论是正确的,而不再质疑它的正确性和留意其他解决方法,这样会加快思维的速度,但是代价是我们很难意识到需要去重新验证看起来明显不可能的结论,即便这些结论是潜在可行的。

每一次打破思维定势都是一种认知升级,踏上下一个阶梯的方法是离开上一个阶梯。可以引入旁观者来消除思维定势带来的认知局限,这就是为什么大型企业需要引入咨询师的原因之一,这些咨询师甚至没有行业背景,没有客户本身那么了解问题上下文。相反来看,没有背景反而可以跳出思维定势,提出一些富有建设性的意见。

这给扮演旁观者的人提出了挑战,旁观者需要足够高和不受限的认知能力,否则提出的意见也是局限的。如果咨询师能扮演足够灵活的旁观者,将不设限制的选项摆在桌子上,就能将咨询师的价值最大化。

在中国的古代,决策者意识到自己的认知局限性,他们的谋士(相当于咨询师)发明了一种有趣的思维方法叫做奇门遁甲。奇门遁甲往往被当做占卜、军事工具,其实它更像是一个形势分析工具。

奇门遁甲中的甲代表首领,遁甲的意思是需要把自己从当前的形势中隐藏起来,奇门的意思是事物发展的方向。奇门遁甲的局势相当于当前处于的环境,通过旁观者的形式观察自己的处境和周边事物来寻求突破。

在低谷中苦思

认知提升需要特别的条件:认知提升的动力、合适的环境以及收集足够的信息。哲学类书籍没有任何门槛人人都能获得,五彩斑斓的生活提供了充足的锻炼环境。在三者之中,缺乏认知提升的动力是认知提升最大的障碍。

在自然环境中,生存为生物提供了进化的选择压力;在社会中,所面临的困难提供了认知的进化压力。《侏罗纪公园》系列电影中马尔科姆博士说"生命总会找到出路”,这句话深刻而残酷的诠释了进化的逻辑,可能他没有说出的下一句台词是“没有找到出路的生命会被终结”。

正常情况下,现实中大部分人并没有认知升级的动力,只有在工作和生活陷入挣扎之后才会在无人的黑夜中苦苦思索:是哪里错了,为什么我的策略不起作用?

这些认知的进化压力,可能来自每一次失败、挫折、困惑、嘲讽。一名成为团队 Leader 不久的开发者告诉我,他感到非常的痛苦,这个团队好像一台汽车,但是我却是背着它在马路上艰难前行。不具备理解团队和组织运作逻辑的认知水平,就无法轻松的驾驭团队,好像什么都无法推动,团队越大负荷反而越大。

我并没有帮助到他什么,在后来他理解了 "创造让他人行动的条件,而不是恳求他人行动" 的逻辑后,兴奋的告诉我他知道如何安排接下来的工作了,并把这个认知分享给了我。合适的做法是:他应该分析每个人的动机,驱动团队前进,而不是推着团队前进。

我自己也有过类似的体验,某件事情将原有的认知通通打破。在某个项目上,开始相信自己是对的,后来意识到自己的判断和能力不足以完成这样的工作(后来反应过来前面的认识、选择和判断都不对),遭受到了巨大的心理打击。

但是,请不要忘记那些曾经令我们痛苦的事,那是点燃认知进化之火的完美燃料。

第 1 章 软件工程中的哲学

充分的利用哲学、逻辑和科学可以提高认知能力,避免凌乱、迷信的思维方式。在这个层面上和软件工程关系并不大。哲学是构建一个人基本认知体系的重要手段,像一根线把学到的经验、规律和知识作为珠子串联起来,逻辑学可以认为是串联这些珠子的规则,按照特定花色、大小进行串联。

虽然每个人都应该学习逻辑学和哲学,但遗憾的是我国教育并没有非常重视哲学和逻辑学。另外,中国古代哲学、古希腊哲学和西方近现代之间的哲学差异很大,黑格尔说中国文明是人类文明的幼年时期,这个观点争议非常大。但是,现实是现代社会的基础是建立在西方哲学、逻辑学以及数学之上的,如果能了解一些简单的哲学、逻辑学,就能让我们的基本认知获得一定的提升。

1.1 软件行业中的对错观

在这个社会中并不缺乏观点。软件开发方法学更是层出不穷,花样繁多,大家为了这些观点提出了各种各样的理由和见解。但有时候在讨论中互相指出对方观点是错的,自己是对的,往往会让沟通陷入困境。

提高认知的第一步是清醒地看待各种各样的观点,选择自己需要的,得到有用的。非常重要的一点是关于对错的讨论,什么的对的,什么是错的。

我相信对错的概念已经植入大部分人的潜意识,我们很自然地使用对错的概念去描述事物,就好像使用高和低,美和丑这些概念来描述事物一样。我们好像不曾学习过它们,而是天生就会。

如果这样去想,就陷入了认知局限。这种认知局限,让我们陷入无休止的无效辩论中。在软件工程中,选择计算机编程语言对技术栈、人员招聘有重大影响,因此计算机编程语言的辩论是一个热点话题。

最经典的问题是:“PHP 是不是最好的语言?”。不可否认,PHP 是一门优秀的语言,虽然这几年略显颓势,但是也不妨碍依然有大量的 IT 从业者使用这门语言。

你觉得这个问题的答案是对的吗?如果你是一名通过 PHP 拿到高薪并乐此不疲的开发者,可能会说对。但如果体验过 PHP 在大团队中因为类型系统缺失而带来协作上的不便,就会说不对。 有过生活经验的人都会慢慢理解到,对错是相对的,所以才有那句名言“小孩子才谈对错,成年人只谈利弊”。

在《老子》中有一句话诠释了这个观点:“天下皆知美之为美,斯恶已,皆知善之为善,斯不善已。 故有无相生,难易相成,长短相形,高下相倾,音声相和,前后相随”。老子认为这些是非的观点是因为比较才产生的,如果没有比较也就没有了对错。在后人的哲学评价中,这是一种朴素地辩证思想,至于为什么是朴素的,我们在以后会聊到。

关于对错的思想,道家的代表人物列子也在《列子·周穆王》中讲述了一个故事。

春秋战国时,秦国人逢氏有个儿子,小时候非常聪明,但是长大后却得了一种迷糊的怪病。他听到唱歌却以为是哭泣,看到白色却说这是黑色,闻到香味却以为是臭的,尝到甜昧以为是苦味,做了错事却以为是正确的。他所想到的事物,无论是天地、方向、水火、寒暑,和常人相比都是颠倒错乱的。一个姓杨的人对他父亲说:“鲁国的君子学问多,也许能治好,你为么不去拜访呢?”

于是这位父亲准备去鲁国,在路过陈国时,碰到了老子,于是对老子说了孩子的情况。老子说,你怎么知道是你儿子糊涂还是其他人糊涂呢?现在天下的人(春秋战国时,礼崩乐坏)对什么是对的,什么是错的已经搞不明白了。也许不是你儿子迷糊,而是天下的人迷糊,甚至是鲁国的君子们糊涂。不要浪费你的粮食,赶紧回家去吧。

要理解这个故事,需要了解一下周朝末期春秋战国时代的历史背景。如果说商朝的社会规则建立在鬼神宗教之上的话,周朝是建立在一套叫做“礼”的规则之上的。“礼”不是礼物,也不是礼貌的意思,而是当时的一种社会规则,简单来说就是“君君臣臣,父父子子”,类似印度的种姓制度之上,每个人都需要恪守社会规则不能逾越。在社会规则之上才有了对错,不符合“礼”就是错的。由于社会生产力的发展,这套规则在当时崩溃了,而新的社会秩序还没有建立起来,这就是百家争鸣的历史背景。

关于对错的认识我们可以更进一步。思考这样一个问题:对错的概念起源于哪里呢?人们从什么时候开始就有了对错的概念呢?

很遗憾,没有在中国古代的资料中找到定义对错的内容,不过在亚里士多德的逻辑学中找到了一些线索。古希腊哲学家亚里士多德,将能判断对错的句子叫做命题 [7],用命题的真假来表达事物的对错。为了得出命题的对错,亚里士多德认为需要将命题放到特定的条件下,通过这些条件才能得到命题的真假。

实际上,现实中人们的对错观并没有这么严谨,大多数情况下的对错并不是一个命题。比如当我们看到一项新的技术,你可能会说"我认为这项技术会流行起来",并说了一大堆理由,但是这句话并不是一个命题,在短期内无法验证正确性,只是一个人的主观意愿。

《中共四川省委党校学报》上有一篇文章《现代思维模式真假、好坏、对错》[8] 深刻讨论了人们的对错观。作者提出了一种新颖的观点。作者将真假看做认识事物方面的判断,好与坏是价值形态的判断,对与错就是对人们行为的判断。其实,采用不同的分类方法,就可以获得不同的认知角度,这也是人们讨论问题时难以达成共识的原因之一,这里我们不必评价作者这样分类的利弊(不然又陷入对错的局限中了)。

按照作者的理解:真假是在讨论客观事物是否符合某种规律,叫做合规律性;好坏是从一个人的立场出发讨论事物是否符合他的期望,叫做合目的性;对错一般是对人们行为的讨论,可以叫做合规范性。我们平时使用的对错混合了多种情况,按北京土话来讲就是,我说城门楼子,你说胯骨肘子。

如果用这种认知方法来分析得到的信息会看得更深刻。在某一段时间,一些明星和社交媒体的大 V 号召国人吃素,他们认为吃素是一种更“对”的生活方式,因为可以更健康、减少碳排放。

那么,吃素“对”吗?

这里的对错观混淆了合乎规律性,吃素可以更健康吗?吃素可以有效地减少碳排放吗?这里也混淆了合目的性,大家想要吃素吗?明星说喜欢吃素的目的和我们一样吗?这里也混淆了合规范性,不吃素违法犯罪吗?吃肉会违反一般道德约束吗?

从规律性上来说,吃素并不是很健康,因为人体需要均衡的营养,有一些必要的氨基酸需要肉类的蛋白质才能补充。仅仅中国人吃素也不能有效地减少碳排放,因为中国人喜欢吃的猪肉相对西方人偏爱的牛羊肉来说碳排放更少。

从合目的性上来说,大部分国人并不是素食爱好者,我们在主观意愿上并没有享受吃素带来的好处。根据某些人士曝光,明星宣传吃素是受到了境外势力的影响,花钱让他们宣传中国人应该吃素,达到打击畜牧渔业,弱化国民健康的目的。

从合规范性上来说,只要不吃野生保护动物,吃饲养的肉类以及合法捕捞的渔获并不违法,也不违反世俗的道德。

所以,我们的对错观极其脆弱,如果有人可以混淆这些问题,就可能被人牵着鼻子走,有时候甚至是无意的。在规律性上用伪科学、经验混淆科学,在合目的性上用个体混淆群体,在合规范性上用小圈子的道德混淆大众的道德甚至法律。

我们容易被这样的话术影响:

  • 混淆合乎规律性:“我用 PHP 10 年了,写的好好的啥都能做,PHP 是最好的语言。”

  • 混淆圈子道德:“喜欢汉服的人都不会穿山(一定程度上的仿制品)”,“狗是人类的朋友,你不应该吃狗肉”。

  • 混淆合目的性:“996 是福报,没有 996 哪有你翻身的机会呢?”。

在社会生活中,他们三者又是联系在一起的。比如:因为根据规律,打疫苗可以降低感染疾病的几率(真),所以对我的身体健康是有益的(好),所以我应该去打疫苗(对)。

所以在和其他沟通时,当一个人说出对错的时候,应该从几个方面分析问题:

  • 他认为的客观规律是什么?

  • 他的目的和期望是什么?

  • 观点是否符合世俗的道德和法律?

比如你对同事说,这份工作太辛苦,我想要辞职。他说对,同意你的说法。一般情况下辞职符合道德和法律,他说“对”的原因是:

  • 工作真的很辛苦。

  • 真心希望你不用这么辛苦。

如果你们关系比较好,这是有可能的,如果你们处于竞争关系,他说“对”的原因可能是希望你离职,然后他就能获得升职的机会。

因此,在本书中,我们也会谨慎地使用对错这组词汇。因为对错的使用,需要在一定条件下才能有效。如果团队的目的是一致的话,尽量使用合适、不适合来代替对错讨论一个技术方案,用真、假来描述一个规律,如果真、假太过于学术,可以训练自己使用成立、不成立来描述规律。

当然,我们也不必这么极端,在大家都能默契的认同某些条件的情况下,使用世俗的对错也没有什么不对。

阅读本书需要记住的是,就像在序中所说的,不能因为得到了一些新的认知方法,就被新的方法所困住,也尽量避免使用通常意义上的对错来衡量本书的内容。

1.2 打破局限:柏拉图的洞穴寓言

即使重新建立了对错观念,学会了不那么急于下结论,为了对错和人争辩,但是无法避免的是认知总会受限。这是因为认识事物的过程包括了收集信息、加工信息两个部分,任何一个阶段不够充分都会让结果造成偏差。

收集信息是通过我们身体的“传感器”完成的,也就是身体的感知器官,按照佛家的说法就是眼耳鼻舌身意。收集信息的过程中,充斥了大量的诱导、错觉,让加工信息无从谈起。比如,无糖可乐就是欺骗了味觉感受器,用特殊的甜味剂代替糖类,让人依然感受到甜味。

加工信息是根据我们的经验、思维方法、价值观等复杂机制下对收集的信息做出判断,大脑存在一些隐藏的“程序”,在我们无意识的情况下就能影响人的思维,正是这种隐藏的程序成了我们很多决策的基础。比如,程序员去一家公司面试,如果这家公司的装修非常 GEEK,类似于 Google 等著名企业,面试者就会对这家公司充满好感,虽然装修和雇主质量并没有决定性影响,但是非常影响面试者是否加入一家公司。

这两方面的因素让人的认知有所不同。

历史上的哲学家对我们如何真实地感知世界提出了很多理论,非常令人印象深刻的就是柏拉图在《理想国》[9] 中记录的洞穴寓言。

洞穴寓言是这样描述的:假设有些人住在地下的洞穴中,他们是一群囚徒,生来就在地下,坐在地上背对洞口,不能转头看到洞口,只能面向洞壁。在他们身后有一矮墙,墙后面有些人形的生物走过,手中举着各种不同形状的人偶。人偶高过墙头,被一把火炬照着,在洞穴后壁上投下明明灭灭的影子。这些人终生都只能看到这些影子,会认为这些影子就是具体的事物。

洞穴寓言告诉了我们每天争论的概念,都是每个人工作、生活背景投射的影子。概念会随着人们对事物认识的加深而变化,尊重逻辑的人不会强行要求某个概念必须按照自己见解来解释(类比中世纪教会对经书的解释权)。

人们对信息的加工在一定程度上取决于有多少可靠的信息被输入进来。在软件工程中,如果 CTO 和架构师从来没有去团队中实地考察,去看看一线的工程师是如何编写代码的,那么就像洞穴中的人一样,缺少足够的信息输入。

比如,CTO 在听取质量部门的汇报后,Bug 太多,认为团队的代码质量太差,要求所有的开发人员使用 TDD,并将单元测试覆盖率要求设定到 100%。实际上可能是因为需求输入就不合理,没有逻辑清晰的需求,即使使用 TDD 也无济于事。

按常理来说,CTO 应该是公司获得信息最多的人之一了,缺乏有效信息的 CTO 就像站在高处瞭望,但是却高度近视。

另一方面,即使有足够多的信息输入进来,处理这些信息的“程序”过于低效或者过时,也无法建立高的认知能力。认知高的人可以通过尽可能充分地利用收集到的信息来分析问题,做到“不出户,知天下”的程度。这也很好理解,如果找到了一些足够好的模型来推理问题,就可以利用有限信息推理出更多信息。科学家也从没亲自去地球外看看地球,是通过一些观测方法和数学来进行推理的。

在软件工程中,每个人大脑中处理问题的“程序”不同,这决定了是否能更深刻的认识事物,并推动解决问题。所以一些高认知的人通过模型来建立处理信息的“程序”,这些“程序”要比本能反应成熟的多。

例如,团队 Leader 在处理团队问题时,如果团队出现摩擦,经验不多的 Leader 会被动地安抚团队成员帮助解决问题。如果换种思路,把团队问题使用一个模型进行抽象,通过“动力-阻力”模型,将这些摩擦看做团队的阻力,将激励团队前进的因素看做团队的动力。

团队起火往往是“动力-阻力”不再平衡,通过两个方面着手,可以向团队注入一些激励的因素,也可以优化团队运作方式,减少阻力,增加润滑。激励的因素不仅仅是金钱,还可以是情感关怀、营造成就感、为工作建立荣誉感等非常多的方式。

如果意识到局限的存在,并打破局限,就可以获得源源不断的方法去解决问题。

那么如何在一定程度上打破认知局限呢?洞穴寓言告诉我们可以从两个方面入手。其一是挣脱锁链走出洞穴,接收更多的信息。其次是通过有限的信息,进行推理,慢慢刻画事物的本来面目(有一些哲学家认为事物没有本来面目,取决于我们观测的方式,一些哲学家反对,整个哲学史都潜在的讨论这个话题)。

第一个方面就是我们通常说的多学习、多听、多看,其实质上是在拓展感知渠道,通过丰富信息输入的方式提高认知能力,所谓行万里路,读万卷书就是这个道理。除了多听多看之外,通过用不同感官获得的信息进行校对也是增加有效信息输入的方法。在洞穴寓言中,如果囚徒能够走近并触摸墙上的阴影,那么能得出不同的结论,认为阴影和通常的实体并非同样的东西。有机玻璃和普通玻璃用肉眼看可能没有区别,但是触摸一下就能发现本质是不同的。

如果仅仅是增加信息接受的渠道,还远远不够。还依赖大量的经验、知识、教训、反馈作为输入。从某种程度上看,年长的人必然会比年轻的人认知能力高,所谓“老年人吃过的盐比年轻人吃过的饭还多,老年人走过的桥比年轻人走过的路还远”。自然地认为学历高的人比学历低的人输入的知识多、信息多,认知相对也较高。

实际上是这样的吗?在一定程度上认知能力和年龄确实存在正相关性,但是也不完全相关;学历高的人在商业上的认知有时候并没有比贩夫走卒强太多。

所以我们也需要关注第二个方面,信息是如何高效、可靠的被大脑处理,建立更好的“程序”。柏拉图的学生亚里士多德说 “吾爱吾师,吾更爱真理”,否定了尚古思维。他认为不应该用世俗的观念干扰对真理的追求,而是需要找到一种真正可靠分析事物、处理信息的方法。这些方法被写到了《形而上学》《逻辑学》《政治学》等诸多著作中,成为了现代诸多学科的种子。

这些学科、方法可以用简化、稳定的模型来理解和叙述,模型是人为刻画的,并不是真理,否则又进入了另外一个洞穴之中。寻找更多、更有用的模型来描述事物的本质,这就是哲学家、科学家一直在做的事。

著名的投资大师、巴菲特之友、伯克希尔·哈撒韦创始人查理·芒格就是应用多模型思维的高手,他总能(模型并不是 100%起作用,只是提高了几率)在有限的信息中寻找到有用的结论。

在叙述前面的内容中,我使用了一个隐藏的模型来描述人的认知问题,简化人的认知为“接收-程序处理”,用这个简单的模型来解释认知差异,姑且比各种“大道理”有用吧。

1.3 精英中的宗教:货物崇拜

你的技术决策是理性的吗?

大多数技术领导者、架构师在做出技术决策时都会认为自己是理性的。这无可厚非,软件行业作为现代商业的先锋,基本上代表着先进、科学、理性,好像和宗教、愚昧、迷信毫无关系。

但是有意思的是,如果回头看,软件行业其实充满了狂热、迷信和很多不理智的行为(有部分看似不理智是利益造成的)。从互联网产品兴起开始,几乎每年都会有热点词汇和技术(Buzz Word)。TDD、DDD、低代码、中台、VR、GraphQL 等新的模式和技术层出不穷,这些技术和模式有用,但是在布道者的推动下,大量狂热的追捧者不加选择的采信,因此有人戏称很多软件项目都是面向 Buzz Word 编程。

关于 TDD 有这样一段对话:

开发者:我们的项目质量不高,出现了很多的 Bug,这些 Bug 修复起来需要花费很多的时间。
TDD 布道者:你这是没有使用 TDD。
开发者:但是 XXX 的项目也在使用 TDD,却并没有改善项目的质量问题呢,怎么看待这类问题呢?
TDD 布道者:这是 TDD 没用对,TDD 用好了不会这样。

如果熟悉哲学史的朋友可能会想到,在经院哲学时期,人们会像下面这样论述上帝是否有用:

信众:我过得很痛苦,生活经历了太多的不顺利,我该如何改变呢?
教主:信上帝,只要信上帝就能改变。
信众:我隔壁的约翰是一名虔诚的教徒,每周都会去教堂忏悔,但是他疾病缠身,上周去世了。
教主:这一定是信的方法不对,他被魔鬼诱导了,没有来我这里,并用正确的方法。

如果架构师用这种叙述方式讨论技术选择合理性的话,那么他们对事物的认知还停留在中世纪,还没有跨越文艺复兴的阶段。

不否定 TDD 在一些知名企业获得过成功,但是这不应该是其他团队也以此作为采纳 TDD 的依据。由于知名企业的某种技术实践导致大量的狂热崇拜,在软件工程中,人们称之为货物崇拜编程(Cargo Cult Programming)。

货物崇拜编程是指开发者不明就里、仪式性的使用代码、架构和团队实践,通常是因为开发者被这些实践所带了的收益所诱惑,而没有看到背后的代价,并冷静地思考和权衡,以至于最后付出惨痛的教训。

货物崇拜来源于一个有趣的故事。在太平洋战争期间,美军在太平洋的美拉尼西亚岛建立了军事基地,当时岛上的土著文明还处于原始部落阶段。当时的土著看见美军从“大铁船”(军舰)出来感到很惊讶,同时也看到了美军用“大铁鸟”(飞机)运送物资。由于美军也给了土著人一些现代化的物品,这些物品对土著来说非常有用,于是把美军当做神。

美军离开美拉尼西亚岛后只留下一些军服、低价值的物品,土著遍认为这些物品具有神奇的力量,相信美军还会回来带给他们更多的物品。虽然美军一直没有再回来,但是这些土著发展出了一套宗教仪式,以木刻的飞机为图腾,以美军的军服为法器进行崇拜。

在软件行业,一些技术和实践变成了图腾,一些人将软件成功的因素归结到一个单一实践上。例如,认为使用 TDD 或者 DDD 就能让项目获得成功。

货物崇拜的原因是混淆了因果性和相关性。相关性不等于因果性是科学研究中非常重要的一个原则。举个简单的例子,很多父母信佛,然后小孩高考的时候天天去求神问佛。如果小孩考上了大学,那么就是拜佛的功劳,如果没有考上,那就是小孩不努力。同样的,星座也是一种典型的相关性和因果性混淆,一个人的生日和这个人的性格目前没有证据能说明它们之间的因果性,但是因为选择性认知偏差让人将性格和生日联系到一起。

同样的桥段在各种电视、电影中存在。某一伙人闯入了一个村子,然后这个村子发生了瘟疫,群众认为是这些人带来了不详。软件行业也会有类似的讨论,因为大公司都用的 Java 而不是 PHP,所以 PHP 是一个垃圾语言,我们要成为大公司,所以要把 PHP 换成 Java。所以很多公司明明可以苟一下,却因为折腾死掉了。

另外一种破除货物崇拜的方法是使用逆向思维,当我们采纳一项技术或者实践时,不仅需要分析它所带来的收益,最为关键的是思考它同时带来的成本。

2020-2021 年间,软件行业最狂热的货物崇拜行为就是中台了。阿里巴巴在 2016 年提出了 “大中台,小前台” 的战略,传说中台的概念来源公司高层的一次外部访问(SuperCell 公司)。

热门游戏愤怒的小鸟就是 SuperCell 的产品。这家公司位于芬兰,2016 年以 86 亿美元的价格出售了 84.3% 的股份给腾讯,成为游戏行业史上价格最高地收购案 [10]

这家公司独特之处还不止于此,SuperCell 被收购时还不到 300 人,并且由非常多的小团队组成。这些小团队独立完成游戏的设计、开发、运维,在这些小团队背后有一些支持团队,用来整合公司的资源,让小团队能在短时间内复用公司内部的基础设施快速地让游戏上线。

面向终端的小团队就是中台概念中的前台团队,在公司内服务终端小团队的支撑团队就是中台团队。如果了解过领域驱动设计(DDD)的朋友可能会联想到,这不就是架构中应用服务和领域服务的区别么。

应用服务用于提供具体业务场景下的应用,在阿里巴巴的产品生态下,闲鱼、飞猪就是类似的应用。 领域服务用于提供复用的能力,同样在阿里巴巴的技术生态下,订单服务、用户服务就是类似的服务。

这也是为什么我们在闲鱼上卖出去的东西可以在淘宝网的卖家后台看到的原因,因为他们复用了共同的能力(交易能力)。

因为中台建设给阿里带来了共享技术服务体系,让他们可以一个半月上线自己的团购平台,也就是聚划算。这种架构策略极其具有诱惑性,导致做互联网产品的企业前赴后继,甚至让传统企业在数字化转型的初期就直接进入中台建设。

随着越来越多的企业发现中台战略并不适合他们后,开始放弃这个策略,并发现好像阿里巴巴在开始“拆除”中台。

实际上,大型企业关注平台化建设、能力的复用并非开始于阿里的中台战略之后。例如,网易的课程服务平台,可以将课程能力提供给不同的产品,和国家精品课程中心合作的中国大学 MOOC 就是一个非常好的例子。

另外一方面,阿里巴巴并非否定了中台战略,而是根据环境进行了多元化改造。中台建设的好处是提供了共享能力,但是这种能力过于强大,制约了阿里巴巴的灵活性,让阿里巴巴应对外部环境的变化变得非常困难。中台在阿里巴巴持续存在,只是中台变得更薄。通过牺牲部分复用能力,换取更强的机动性,这是一种妥协,或者说是一种取舍。我相信,他们甚至能构建动态中台策略,中台的厚薄能更加自如调整。

货物崇拜带来一个非常糟糕的效应:一开始绝对肯定的人,在后面变成绝对反对的人,而最初的提出者却很清醒。阿里巴巴董事长兼 CEO 张勇在湖畔大学分享时也说:如果第一天奔着平台来创业,基本上都是死 [11]。同样,如果一个企业内部奔着中台做中台,也是死。中台并非没有价值,它描述了一种构建了自己生态体系的企业,如何利用复用的能力去实现更大的价值。问题在于,往中台战略前进时,它早已暗中标好了价格。

1.4 软件行业中的奥卡姆剃刀

奥卡姆剃刀是由 14 世纪方济会修士奥卡姆的威廉提出的一种逻辑学原则,这个原则是这样表述的:

“切勿浪费多余功夫去做本可以较少功夫完成之事”。

按照流行的话来说就是“如无必要,勿增实体”(这句翻译的出处已经不可考)。

要理解奥卡姆剃刀,需要理解当时的历史背景。 14 世纪是经院哲学如日中天的时期,经院哲学为了证明上帝存在性发明了大量的哲学概念和要素,始终讨论无休无止的“本质”。哲学家提出了一个概念,为了解释这个概念需要更多的概念来修补,陷入大量无意义的讨论。

这些讨论非常类似于我国的魏晋时期,魏晋时期人们热衷于讨论玄学,讨论世界的本源。在西方这类讨论被我国的翻译家叫做形而上学,形而上学在亚里士多德的《形而上学》中实际上是“如何做学问的学问”,仍然属于哲学范畴,并非完全一样。

奥卡姆的剃刀原则并非减少了形而上学的讨论,而是将逻辑学、哲学、自然科学、神学分开,他主张不能将神学的讨论纳入逻辑学中。逻辑学是关于概念、推理和语言上的学问,哲学是关于如何思考的学问,自然科学是关于具体事物的学问,上帝的问题留给神学来讨论。

因此,富有逻辑的人应该使用尽可能少的要素来解释更多的问题,而不是无休无止的引入学说,打上大量的补丁。

但是需要警惕的是,奥卡姆剃刀容易被伪科学人士当做工具用来攻击专业且复杂的学术理论。奥卡姆剃刀背后的本质是,对认知理解越深入,需要解释的就越少。甚至有人认为,奥卡姆剃刀的思想触发了随后的宗教改革和文艺复兴。

也就是说,如果我们能找到解释繁杂问题的根本原因,就不需要太多的概念和文字描述。有时候有人洋洋洒洒写了好几千字的文章,但是富有洞见的人却能用一句话表述出来。反过来看,如果暂时不能找到更简洁的理论,就依然得使用现存的理论,寻找终极简洁的理论是目前物理学在始终追求的目标。

举个例子,看似繁复的领导力问题实际上在一些“粗鄙”的江湖人士中,会这样表述:

"别人愿意和你一起干,作为领导者,你能保护他们,且让他们吃到肉。"

这里的肉是一个绝妙的比喻,含义是处于组织中的个体,是以自由的让渡作为代价,换取组织的保护,以获取比个体更多的价值。

保护不仅仅是安全,譬如合理的工作方式、良好的环境都算作保护。在企业工作的员工,并非喜欢打工,而是相比单独讨生活,在公司工作可以获得更合理的工作流程、更舒适的工作环境和更高的报酬。当一名员工可以独立企业之外,获得想要的一切时,他没有动机继续留在企业中。我们并不能使用道德来评价这些现象。

奥卡姆剃刀的启发性是它最重要的价值。在自然科学、社会学的中有大量的案例和应用场景,最著名的例子是爱因斯坦用它大胆的否定了以太学说。

19 世纪的物理学家们,为了找到电磁波和光的传输介质,提出了以太这种假象中的物质。以太最初来自亚里士多德,虽然它的概念随着历史发展不断变化,但是它是非常重要的一种假想物,用来解释是什么填充无穷的宇宙。

但是非常麻烦的是,引入以太这个概念,不仅没有解决物理学的问题,反而让很多问题变得更加复杂。1905 年爱因斯坦大胆抛弃了以太说,认为光速不变是基本原理,并以此为出发点之一创立了狭义相对论。

爱因斯坦因此说:

"Make everything as simple as possible, but not simple。让一切尽可能简单,而不仅仅是简单。"是数学中一种形式

思维经济原则可以看做奥卡姆剃刀的发展,用更清晰的方式诠释了奥卡姆剃刀的本质(注意很多文章将这两个理论混同了)。马赫认为“思维经济是科学的主要任务”,也就是说,如果科学成果不能让复杂的事物简化,那么就没有起到应有的作用。

他认为科学研究是科学模型和待解决问题之间的模写(提取模型)。提取模型就好像做 HASH 算法,将复杂的现实抽象成简单的公式。模写是简单化和抽象化思维,这种思维有经济性(不费力理解的一类更受欢迎)的倾向。思维经济性原则对世界起到巨大的影响,大量的理论出现然后按照经济性被选择下来,人类因此获得更优的理论。

奥卡姆剃刀被应用到最有趣的地方是应用到刑侦技术中,因为往往众多的假设中,对犯罪嫌疑人来说思维成本更低的最有可能,大部分场景下不太可能出现高明、精巧的作案方式。

奥卡姆剃刀也可以应用到软件工程中来,用一些模型简化业务问题。甚至能用模型简化软件开发过程中的模型,这种模型更加抽象叫做元模型,我会在后面专门聊模型思维的时候详细阐述。

作为架构师,需要对技术选型,找到合适的技术组件来完成业务目标。如果使用奥卡姆剃刀原则,就可以剔除思维过程中的杂质,直接对问题寻找更简洁和经济的方案。例如,一个客户找到我们需要建立一个数据湖,实际上当我们分析问题后发现他们仅仅需要一个简单的数据同步机制,而通常意义上的数据湖也具备这样的能力,给客户带来了认知干扰。

有一些违反奥卡姆剃刀原则的原因可能是人们在工作中逐渐忘记了目标和手段。

比如在一个电商程序中,如果一个发货单有 “拣货中”、“打包完成”、“运送中”、“妥投” 等状态,我们需要确保状态的并发操作正确。两个业务动作(打包和物流揽收)会导致系统产生两个请求:

  • A 请求要把状态更新为 “打包完成” 。

  • B 请求要把状态更新为 “运送中” 。

由于种种原因 B 请求先被响应了,A 请求后被响应,如果不做控制,单据的状态就会变成了“打包完成”,而不是我们预期的“运送中”。

有朋友希望引入一套分布式锁机制来完成这个业务。通过奥卡姆剃刀原则来指导分析,我们会发现如果处理 “运送中” 的状态,必须检查已经是 “打包完成” 才会继续,如果条件不满足就让 B 请求报错即可。

在这个场景中,它的目的是避免异常数据的产生,分布式锁并不是目的,可能仅仅是因为习惯引入了分布式锁。这个场景下,其实可以用更简单的方法清晰的解决这个业务需求,节省大量的工作量。

总之,奥卡姆剃刀原则、思维经济原则给了我们一个很好的启示,用来评价什么是一个好的模型。不过,我们总是应该反过来思考,奥卡姆剃刀原则的局限性是什么?

"奥卡姆剃刀并没有说简单的假设就一定更好。[12] " 有人开始将奥卡姆剃刀当做一种真理,这显然是违背科学原则的。当我们在横向对比诸多模型时,我们可能偏好选择简单的那个,但是需要建立在这些模型都能良好的描述研究对象才行。

在没有更简洁的模型出现之前,也不得不承认我们的认知停留在这里,只能先使用复杂的模型。

1.5 使用主客体理解软件开发

面向对象是应用软件设计比较好的方式,可以指导用计算机解决现实中的业务问题,因此是软件开发中的一种主流方式。

不过,用好面向对象则比较困难,即使有数年经验的软件工程师也难说能很好驾驭。大多数人往往是照猫画虎,没有理解软件开发的 “骨相”。

背后的原因在于面向对象是对现实业务的抽象,需要使用者对现实有深入的理解,于是面向对象带有一定的哲学认识论的色彩。

实际上,现代英语、现代法律、面向对象编程都和近代哲学有关,而近代哲学被称为“主体性哲学”,“主体” 概念和主客体关系是非常重要的内容。

现代英语、现代法律、面向对象编程看似三个无关的领域,背后的逻辑却惊人的一致。

在现代英语(古代英语除外)的主要语法是:主语 + 谓语 + 宾语 + 修饰语。想象一下你在一家餐厅吃饭,你点了一份三明治,用一般现在时就是:I order a large sandwich。在这套逻辑关系中,主体就是你自己,客体是三明治,行为是点餐,其他的内容都是修饰成分。

假定你和餐厅出现了纠纷,餐厅忘记给你上了餐,却说已经上了。你发起了诉讼,让餐厅赔给你三明治。在诉讼的逻辑关系中,这是一起民事纠纷,你是民事主体,民事客体就是三明治,诉讼内容是赔偿行为。

而如果软件工程师需要编写一个软件用来处理订单,实现一个收银机功能。可能他会写一个 OrderService 来实现。伪代码如:

class OrderService {
    public Order createOrder(User user, Product product) {
        Order order = new Order();
      	……
        return order;
    }
}

这段代码可能会被认为不符合面向对象,因为某些书中,Order 是需要自己来完成业务的。实际上,在这段代码中,当我们认识到主客体关系时,一切豁然开朗。OrderService 是我们的业务主体,Order、User、Product 不过都是业务客体。和民事行为一样,业务逻辑也应该发生在业务主体中,这样就容易理解了。

既然主体、客体思维可以让面向对象更容易理解,我们来严肃的说下这些概念。哲学可能会有一些无聊,不过值得去了解它们。

主体、客体在哲学中的定义是什么呢?按照主流的哲学教科书,可以看到类似下面的描述:

  • 主体是有认识能力和实践能力的人,或者,是在社会实践中认识世界、改造世界的人。

  • 客体是实践和认识活动所指向的对象,是存在于主体之外的客观事物。

这里需要修正下,随着科学技术的发展,主体可以不只是普通的 “人” 了,可以是一个具有集体意识的团体、网络虚拟世界的一个形象,比如 xxx 公司、初音未来也可以是主体。与之相对的普通人是 “自然人”。

主体、客体思维从笛卡尔时期就开始出现,在康德时期又进一步发展。在主体、客体英文分别是 Subject、Object,它们都是实体,主体是具有行为、感知和思想的一类。

需要注意的是,主体、客体是相对的、动态的。比如用户,在做出一些操作的时候它是主体,当被管理员或者系统操作的时候,它又是客体,这点尤为重要。

关于更详细的主体、客体的知识,我们会在后面大量被用到,在具体使用的场景中再深入探讨,比如业务分析、领域建模、架构设计和团队管理等方面。

当我们理解到主体和客体的思维后,就可以用它分析和指导我们的实践,也就是面向对象的编程了。

1.6 学习和解决问题的方法论

我们经常听到很多公司使用方法论这个词,那么方法论是什么呢?

广义的方法论指所有的思维方式,通常包括哲学、逻辑学等。狭义的方法论指解决特定领域问题的方法,比如面向对象算一种方法论。

方法论的起源可以追溯到笛卡尔的《谈谈方法》一书,在这本书中谈到了一些理解复杂事物和解决问题的一般方法,对世界影响深远,是众多方法论的基础。

笛卡尔认为,复杂的东西是简单的东西组成的,理解复杂事物就要先要理解简单事物,然后组合它们。如果复杂的事物理解不了,就回到上一步理解简单的东西,直到彻底被理解后再尝试理解复杂的事物。

分析一个问题,设计一段程序,策划一场活动,甚至只是学习一门技术,都可以按照这种思维进行。分解任务,然后一步一步完成,看起来是最慢的路线,实际上是最快的路线。“大道甚夷,而民好径”也说的是这个意思。

分解可以用作两个地方:学习新的知识和解决困难问题。

对于学习新的知识来说,学校教育最大问题是学习的阶段是固定的,不同认知能力的学生根据年龄分班,导致的后果是学习速度快的学生没有及时的投喂新的知识,学习慢的学生每个阶段都会欠债。

欠债是学习最大的杀手,俗话说就是基础不牢,但是有时候什么是基础这件事情不太好定义。总而言之学习的过程需要逐步铺垫,缺少背景知识会造成了很多学生无法理解新的知识以至于形成厌学的心理。

符合认知的学习方法是:在彻底理解一个知识点之前不要开始下一个知识点的学习,通过大量的练习彻底习惯使用前面的知识点解决问题(做题),然后再用前面的知识解释后面的问题。就像不理解函数的人难以理解导数,更不用说微积分了。每一次新的知识的引入都是在拓展一个人的认知边界,超出认知边界越多学习就越困难,当然学到的新东西也越多,学习曲线也越陡峭。反之,引入新知识越少也就越容易吸收,但是也会显得无聊。

总之,我们只能通过已经存在的认知范围和新的知识重叠的部分来学习,完全超过认知边界的内容我们是无法理解的。这就好比,一个已经消亡的文明留下完全未知的语言我们难以破解,就是因为找不到和现有认知之间的联系。

学习阶段的固定这个是社会资源客观决定的,不可能所有人都有条件通过私教定制课程和教学计划,所以在自学的时候就需要认识到这一点。

笛卡尔在《谈谈方法》中介绍了几个简单的过程:

  1. 尝试分解复杂的知识,从最容易的部分学习。

  2. 基于容易的部分向复杂的内容拓展。

  3. 当复杂的内容无法理解时,回头看容易的部分,确保彻底理解前置内容。

  4. 重新尝试理解复杂知识。

对于我们学习编程或者新的计算机语言来说,找人推荐一本靠谱的 “Step by Step” 的书,一般这类书都叫做 《xxx Cook Book》之类,一步一步练习每一个阶段的例子,如果无法理解时,回去看前面的知识。

当然,这种方法需要两个条件:1. 这本书靠谱 2. 大量的练习。

对于解决问题方面也是类似的,如果我们没有大量的背景知识和经验,解决起来非常困难。尝试去请教一个熟悉此领域的人对问题进行分解(或者自己完成),将问题分解到显而易见的时候,再尝试解决这些问题。

操作的过程中,将这些分解的子问题写到纸上(一定要写下来或者使用笔记软件)。一步一步尝试解决这些子问题,如果问题还是无法解决,就继续尝试分解。当解决完子问题后,将这些问题合并起来。

如果一些场景下问题难以被分解,就进行假设。不要预设解决方案,将问题或者假设统统地写下来,无论它看起来多么不现实。最后在逐步的对这些问题进行一一验证、排除,直到找到答案。

这就是: “Put everything on the table” 的思想,有时候会发现合适地解决方案往往意想不到,或者被思考的早期阶段否定了。

1.7 用否定之否定来看待技术选择

否定之否定起源于古老的朴素唯物辩证法,被黑格尔首次系统性的阐述 [13]。我们不使用哲学相关的“大词”,用通俗的话来说就是通过不断看到问题的反面并来回倒腾加深对事物的看法。

其实敏捷就是否定之否定的结果。举个例子来说,没有任何管理的软件开发是无序的,有任何变化都会被工程师立即响应。大家认识到这种开发模式的局限性后,提出需要约束开发过程,像“瀑布”一样经历分析、设计、开发、测试等多个阶段,让软件开发具有被工程管理的可能性。

这就是第一次否定

但是随着大家对瀑布开发的进一步认识,逐渐发现其存在实践上的局限性。瀑布开发的局限性是反馈周期太晚,过于依赖原始设计的可靠性。但是实际上,由于人们很难从一开始就做出完美的详细设计,并且也不满足响应变化的要求。

所以人们对瀑布进行了否定,产生了敏捷的思想,这就是否定的否定。可以说敏捷就是克制的在迭代内装了一个瀑布。

那么,敏捷的局限性是什么呢?这值得我们思考,这并非是在否定敏捷的价值。实际上,任何具体的方法论都有其局限性,没有局限性的事物会变成形而上学。

我们否定掉又重新肯定的东西很多,这些东西还经常影响技术选型和对问题的判断。拿非常火的领域驱动设计(DDD)来说,它的局限性是什么?怎么通过否定之否定来分析呢?

领域驱动设计中的充血模型是对 Smart UI 和事务脚本的否定,由于直接操作数据库往往会忘记考虑业务一致性约束,非常经典的例子就是订单的总价需要订单项来整理计算出来,单独修改任何一项的数据都会带来业务一致性问题。

领域驱动中使用聚合来处理一致性问题,在很多人理解的领域驱动设计中,是通过将业务逻辑“充血”到实体(聚合根+实体)中。并对聚合“整存整取”的内存操作来完成业务一致性封装的。领域驱动设计的局限性也在于此——充满理想化的思想让我们掉入下一个更美好的陷阱,“充血 + 整存整取”总是会带来各种各样的问题,因为现实世界没有一个足够大且永不断电的内存。

理想化的方案总是和局限性共存。它俩就像黑白两面,让工程师被梦魇笼罩。被否定之否定的规律支配的事物还很多,当我们得到了一个方案的好处,常常需要在看不见的地方付出代价。中台是另外一个经典例子,中台建设获得了可复用的支撑能力,但是它的局限性是失去了灵活性,因为中台一旦被使用,改起来影响就大了。

那么我们怎么通过否定之否定来对新事物进行分析呢?

否定之否定规律能发挥作用因为事物矛盾的存在,正是因为旧的矛盾就会出现新的方案,当新的方案出现后解决了原来的矛盾,新的矛盾又会出现。分析新的技术或做出选择时,可以从下面几个方面着手:

  1. 该技术解决旧的矛盾是什么,旧的矛盾是否是我们需要解决的主要矛盾?

  2. 使用该技术是否会带来新的矛盾,或者是否会将原来的次要矛盾变成新的主要矛盾?

  3. 那些矛盾是我们能接受或者容忍的?

从这三个方面应该能帮助我们做出一些技术、软件工程上的选择。

  1. 如果引进一个新的技术或者实践,但是它解决的旧矛盾并不存在或者不是主要矛盾,那么这项选择可能不太合适。

  2. 如果引进一个新的技术或者实践,但是它带来的新矛盾我们无法接受,那么也不合适。

以一个真实项目案例来说,某项目是 Node.js 作为主要的开发语言,但是出现了很多 Bug,不够稳定。新的技术领导认为,这是 Node.js 语言的原因,需要换成 Java。结果花费巨大的代价,但效果并不好,实际上是因为软件不够稳定而不是 Node.js 语言的原因,那么这次的“否定”无效。

而有效的否定之后否定就像瀑布否定无序开发,敏捷否定瀑布;网站开发中,模板开发否定了对 CGI 输出字符串的模式,前后端分离否定了模板开发,而后端渲染(SSR)又部分否定了前后端分离。

那么,我们每前进一次,为新技术而满怀激动的时候,带来的新矛盾是什么呢?

第 2 章 软件工程中的逻辑学

破除诡辩的方法是系统的学习逻辑学,比较可惜的是学校教育中系统性的逻辑学课程已经很少见了。另外在软件工程中,很多人也并不重视逻辑学。

大多数有经验的开发者、系统分析师都具备一定的辩证思维和方法,要说谁没有逻辑,这件事情很难说得过去。如果每个人都是用自己的思维方式和 “逻辑”,这会这会让沟通过程变得非常困难。我疑惑的是每个人都相信逻辑是很重要的,但几乎没有文章讨论过在软件设计和开发过程中如何使用现代逻辑学,以及解决诡辩的问题。

这里讨论一些能在软件工程中使用的逻辑学基础知识,尤其是概念相关的内容在业务分析、领域建模和架构设计中都可能会被用到。

公认的逻辑学之父是亚里士多德,但我们现在使用的逻辑学基础来源于弗雷格、黑格尔以及莱布尼茨等人的工作。

2.1 理解概念

我曾经参与一个物联网系统的设计,这其中大家经常会提到一个词“设备”,但是“设备” 这个词在不同的开发者眼里有不同的概念,为此,讨论 “设备” 这个词花费了不少的功夫,最终依然没有定义清楚。

有些开发者认为“设备”是现实中看得见摸得着的物品,另外的开发者将服务器上用于映射物理设备的实例也叫做“设备”。于是,他们在沟通时经常会出现对于“设备”的理解不一致导致的混乱。

另外一个例子是“用户”这个概念。在不同的场景(上下文)下,“用户”这个概念可以是使用软件的大活人,也可以是数据库中的一条记录,也可以是服务中的用户对象,有时候也将用户服务类叫做用户。

这样就非常混乱,不仅无法沟通,而且还导致开发者对系统的认知也变得困难,很多东西处于混沌状态。

在咨询的工作中,我发现非常有意思的是,将软件中的概念一一定义清楚,整个系统的设计工作差不多就完成了。所以设计软件的过程和现实中人们相互交流非常类似。英国哲学家维特根斯坦把人们交流的过程叫做“语言的游戏”,当我们描述事物的时候实际上就是将有清晰边界的元素贴上标签,这个标签就是我们说的概念。

朴素的概念是来源于个人背景和理解,因此概念难以统一。正是于此不同语言之间准确的翻译也不太可能,不同文化背景难以找到合适的概念互相映射。后来哲学家认识到人们认识的概念是由一些更为基础的属性构成的,那可以认为概念就是由属性组成的。比如 “人” 这个概念,有四肢、可直立行走、皮肤光滑等属性。

这些基本的属性又是一些更基本的概念,如果我们对这些基本的概念达成共识,那么我们就有机会对概念进行统一。类似于面向对象语言中的类,类有各种属性,这些属性最终都可以通过 8 种基本的数据结构描述。

因此属性是认识概念非常重要的一方面。属性包含了事物自身的性质、行为,比如黑白、高矮、是否能飞行、是否独立行走。事物除了自身的性质外,还与其他事物发生一定的关系,比如大于、相等、对称、属于等。事物的性质、行为以及和其他事物的关系,统称为事物的属性。

通过属性就能找到概念的边界。具有相同属性的概念是同一个概念,即使是叫法不同也不应该分为不同的概念。比如土豆、马铃薯,一旦属性的增加和移除都算作不同的概念,比如小土豆是土豆吗?通过属性就能发现生活中的命名谬误,比如小熊猫并不是小的熊猫,而是单独的一种动物。

2.2 形式语言和非形式语言

概念只是我们对所认识的事物起的一个名字,词语是概念的自然语言形式,概念是词语的思想内容。

一个概念可以具有多种表达方法,对于软件设计来说,我们可以用自然语言描述概念。也可以通过定义一个类来描述,并在程序运行时实例化这个概念。通过数学或者数理逻辑,我们可以使用集合来描述一个概念。

比如 “商品” 这个概念,可以通过不同的方法表达。

自然语言中,商品是指可以通过货币或者其他物品交易的物品,可以是自然实体,也可以是虚拟物品。而社会经济中对商品的描述为,商品具有一个核心属性就是价格,有价格意味着可以交易。

自然语言中,概念和词语之间并不是一一对应的,这是需要日常特别注意的。

  1. 自然语言中,任何概念都必须通过词语来表达,但不是所有的词语都表达概念。在语言中,基本上都会将词分为虚词和实词两大类,只有部分实词(注意不是名词)可以表达概念。

  2. 同一个概念可以由不同的词语表达,比如前面提到的土豆、马铃薯。

  3. 一个词在不同的的情况下(上下文),可以用来表达几个不同的概念,多义词就是这样,同一个词表达不同的概念,叫做这个词的词项。

自然语言(Natural Language)就是人类讲的语言,它是自然人类发展中自然形成的,比如汉语、英语。

这类语言不是经过特别设计的,而是通过自然进化的。它的特点是语法规则只是一种规律,并非需要严格遵守的规则,这种语言含有大量的推测,以及对话者本身的认知背景(比如东西方不同的文化背景形成了大量的哩语)。认知背景赋予了词汇、概念的不同含义,比如,豆腐脑这个词,不说东西方差异,就是国内南北都会有争议。

著名的白马非马争论在于自然语言的不确定性:

  • 从概念上说,白马这个概念不是马这个概念,所以白马非马。

  • 从谓词(“是” 这个谓词)逻辑来说,白马这个概念代表的事物集合属于马这个概念代表的事物集合。所以白马是马(白马属于马,但是白马这个概念不是马这个概念)。

正因为如此,才会产生大量的诡辩,让交流效率降低。

逻辑学中的形式语言开始发挥作用。形式语言(Formal Language)是用精确的数学或机器可处理的公式定义的语言 [14]。例如数学家用的数字和运算符号、化学家用的分子式等,以及编程语言中的一些符号(Token)。计算机编程也是一种形式语言,是专门用来表达计算过程的形式语言。

形式语言需要严格遵守语法规则,例如 1+1=2 是数学中一种形式语言。

形式语言来源于形式逻辑,特别是形式逻辑中的符号逻辑。亚里士多德的《工具论》为形式逻辑奠定了基础,中国古代的周易的卦爻可以看做一种朴素的形式逻辑,后来很多人牵强附会将计算机的起源和二进制关联到周易上。虽然中国古代有了形式逻辑的朴素思想,但是遗憾的是没有脱离朴素的辩证法。中国古代哲学不缺乏更有价值的内容,但是周易的形式逻辑和计算机并无关系。

形式逻辑的含义用一句话表述为:用一套特别的(形式的含义)、显性的规则来约束逻辑推理规则。

由于形式逻辑也在不断演化,现代形式逻辑和古希腊也不太一样。19 世纪以前的形式逻辑主要是传统逻辑,19 世纪中叶以后发展起来的现代形式逻辑,通常称为数理逻辑,也称为符号逻辑。

莱布尼兹倡导建立“普遍的符号语言”,通过推理演算和机械化的思想一步一步严谨的完成逻辑推理。如果需要建立符号推理系统,就需要定义符号、推理规则作为系统的要素。

弗雷格完成了《概念文字》[15]一书,建立了一个初步的逻辑体系(一阶逻辑体系),通过概念系统来消除二义性,避免偷换概念,并通过推理结构来完成严格的推理。

这个时候的概念依然是通过定义完成的,直到康托尔创立了集合论,通过集合来描述概念,集合中的元素就是概念的内涵,集合能被用到的地方就是外延。集合论的建立,标志现代形式逻辑的建立,也就是符号逻辑。

实际上,弗雷格建立的一阶逻辑体系可以看做一套逻辑体系模板,用它可以拓展出更多的逻辑体系。例如,在人们认识到概率前,认为逻辑推理是确定的,当引入概率后就不行了,因此哲学家们又发展出来概率归纳逻辑。

总之,知道形式语言和自然语言之间的区别,可以避免无意义的争论。软件工程师就是一个对现实业务形式化的工作岗位,将需求这种自然语言转变为代码这种形式语言。

正因为如此,需求和沟通的矛盾不可能避免,除非提出需求的人也是用形式语言,那么软件工程师的价值也就没有了。

使用形式语言可以精确的定义一个概念,并使用精确的语义规则和计算机沟通,这就是软件工程师编写软件的过程。通过计算机语言来描述一个概念,其实就是面向对象中的一个类,这里定义商品有两个属性名称和价格:

public class Item {
  private String name;
  private BigDecimal price;
}

如果用集合的枚举法来表述商品就是:

Item{name,price}

计算机语言和数学语言是一种形式化的语言,可以精确地描述一个概念,但是自然语言只能给出模糊的概念描述。自然语言翻译成计算机语言的不确定性,带来了无休无止的争吵,但这也是软件设计者的主要工作。

2.3 概念的内涵和外延

正是因为自然语言的这种模糊性,为了更加具体地描述一个概念。哲学上概念的共识是概念有两个基本的逻辑特征,即内涵和外延。概念反应对象的特有属性或者本质属性,同时也反映了具有这种特有属性或者本质属性的对象,因而概念有表达属性的范围。

概念的内涵是指反映在概念中的对象特有属性或本质属性。概念的外延是指具有这些属性的所有对象,即囊括对象的范围。

例如商品这个概念的内涵是 “能进行交换的商品”,本质属性是能进行交换,从本质上区别产品。它的外延就是投入市场能卖的所有事物。

对概念外延的清晰描述对我们设计软件产品的定位非常有帮助,我们购买软件服务无非两种情况,生活娱乐使用,或者工作使用。马克思社会经济学精妙的描述为生产资料、生活资料。这其中的逻辑完全不同,按照生活资料的逻辑设计一款生产资料的产品注定要走弯路。

概念的内涵和外延是在一定条件下或者上下文中被确定的,这取决于参与人的共识。概念的内涵和外延是一个此消彼长的兄弟。当内涵扩大时,外延就会缩小,概念就会变得越具体。当内涵缩小时,外延就会扩大,反映的事物就会越多。通过操控内涵和外延,也可以在语言中构造诡辩,这一点在后面会讨论。

这在面向对象软件建模中的影响非常明显。对象特有属性或者本质属性越少,那么这个对象能被复用的场景越多,也就是内涵越小。反之,特有属性越多,能被复用的情况就越少了。软件建模过程中随意修改概念往往意识不到,但是每一次属性的添加和移除都带来概念的内涵和外延发生变化。

非常典型的一个例子发生在订单模型中。一般来说,我们会把支付单和订单分开设计,订单的概念中没有支付这个行为,但有时候觉得支付单的存在过于复杂,会将支付单的属性放到订单中,这个时候订单的内涵和外延变了。

内涵和外延发生变化但是设计人员没有意识到,会使用同一个词语。一旦使用同一个词语就会产生二义性,二义性的存在对软件建模是致命性打击。比如用户维护的地址、地址库中的地址、订单中的地址,这三个 “地址” 虽然名字相同,但是内涵和外延不同。

意识不到概念的内涵和外延,是无法设计出逻辑良好的软件模型的。

2.4 定义一个概念

变量命名和缓存失效是编程中最让人头疼的两件事。

变量命名其实就是在给一个概念下定义。定义是揭示概念的内涵和外延的逻辑方法,一个准确的定义需要反映出对象的本质属性或特有属性。下定义困难普遍有两个痛点:

  1. 不懂好的下定义的逻辑方法。

  2. 对业务概念或者领域不熟悉。

对于第一个痛点,根据概念的属性、内涵和外延,逻辑学中有一些很好地下定义方法。

属加种差定义法。 这种下定义的方法通俗来说就是先把某一个概念放到另一个更广泛的概念中,逻辑学中将这个大的概念叫做 “属概念”,小的概念叫做 “种概念”。从这个属概念中找到一个相邻的种概念,进行比较,找出差异化本质属性,就是“种差”。比如,对数学的定义,数学首先是一门学科,和物理学处于同类,它的本质属性是研究空间形式和数量关系。于是可以得到数学这个概念定义:

数学是一种研究现实世界的空间形式和数量关系的学科。

用这种方法给订单、支付单、物流单下一个定义:

订单是一种反映用户对商品购买行为的凭据。属概念是“凭据”,种差是“反映用户对商品购买行为”。

支付单是一种反映用户完成某一次支付行为的凭据。属概念是“凭据”,种差是“用户完成某一次支付行为”。

物流单是一种反映管理员完成某一次发货行为的凭据。属概念是“凭据”,种差是“管理员完成某一次发货行为”。

在逻辑中可以参考下面的公式:

被定义的概念 = 种差 + 属概念

对于第二个痛点,这不是软件建模能解决的问题,需要充分和领域专家讨论,获取足够的业务知识。人们对概念的定义或者认识是随着对事物的认识不断加深而变化的。一个完全对某个领域没有基本认识的软件工程师很难做出合理的软件建模,例如银行、交易所、财会等领域的软件需要大量的行业知识。

我们做消费者业务的互联网开发时,往往因为和我们的生活相关,所以这种感受并不明显。当做行业软件时,领域知识对软件模型的影响甚至是决定性的。

2.5 同一律、矛盾律、排中律

概念只是语言的元素,如果需要建立逻辑思维,还需要一些逻辑规律。逻辑学的三个基本规律可以让沟通更加准确,避免无意义的争论,减少逻辑矛盾,让讨论有所产出。这三个重要的规律是:同一律、矛盾律、排中律。

同一律

在同一段论述(命题和推理)中使用的概念含义不变,这个规律就是同一律。形式化的表述是 A → A。同一律描述的是在一段论述中,需要保持概念的稳定,否则会带来谬误。在辩论赛中可以利用这个规律,赢取辩论。

比如论题是“网络会让人的生活更美好吗?”,两个论点主要的论点是:

  • 网络让人们的生活更方便。

  • 网络让人们沉溺虚拟世界。

假如我们选择的论点是 “网络让人们的生活更方便”。在辩论赛中,我们陈述了“没有网络非常不方便”,反方被诱导描述了“打电话、写信也可以让人生活很美好,不一定需要网络,且不会像网络一样容易沉溺在虚拟世界中”。这刚好落入我们的逻辑陷阱。我们指出,邮政、电话网络也是网络的一种,对方的逻辑不攻自破。

这属于典型的 “偷换概念”,我们偷换了“计算机网络”和“网络”这两个概念。

矛盾律

矛盾律应用的更为普遍,几乎所有人都能认识到矛盾律。它的含义是,在一段论述中,互相否定的思想不能同时为真。形式化的描述是: “A 不能是非 A”。

矛盾律这个词的来源就是很有名的 “矛和盾” 的典故,出自《韩非子·难势》中。说有一个楚人卖矛和盾,牛吹的过大,说自己的盾在天底下没有矛能刺破,然后又说自己的矛,天底下的盾没有不能穿透的。前后矛盾是一个众所周知的逻辑规律,但是并不是一开始马上就能看出来,需要多推理几步才能看出来。即使如此,在同一个上下文中,出现了矛盾的逻辑论述也被认为是不可信的。

具有矛盾的论述有时候又被称为悖论。尤其是宗教领域充满了大量的悖论,例如,是否存在一个万能的神,做一件自己不能完成的事情。

矛盾律的用处可以驳斥不合理的论断,也可以用于反证法。在软件开发过程中,我们时常遇到这种情况,开发过程中才发现矛盾。这个很难避免,除非有充足经验的工程师。

需要注意的是逻辑学中的矛盾律和毛泽东思想中的矛盾论不是一回事,前者是逻辑学规律,后者是辨证唯物论的一种方法。

排中律

排中律是逻辑规律中最难理解的一个规律。它的表述是:同一个思维过程中,两个互相否定的思想必然有一个是真的。用形式化的表述就是:“A 或者非 A”。

排中律的意义在于,明确分析问题的时候不能含糊其辞,从中骑墙。比如有人讨论:人是不是动物。不能最终得到一个人既是动物又不是动物,这种讨论是没有意义的。

比如在一次技术会议中,需要选择使用的数据库,只能使用一种数据库。如果采用了 MySQL 就不能说没有采用 MySQL。

排中律看起来好像没有意义,却是一项重要的逻辑原则,让讨论最终有结论,而不是处于似是而非的中间状态。

2.6 诡辩

在沟通中,人们会下意识的引入谬误,从而主动或者被动的诡辩。诡辩的方法非常多,下面是常见的几种诡辩方法,认识到诡辩的存在,让讨论的输出更可信。

偷换概念

偷换概念是一种利用同一律的诡辩方法。往往是利用一个词语的多义性来制造诡辩,这种例子相当常见,在一次日常对话中:

朋友:为了让自己的判断和认知更为客观,我们应该同时学习多个学科的东西。
我(故意抬杠):人不能同时学习多个学科的东西。
朋友:为什么,学生不都是同时学习数学、语文、英语么。
我:你现在正在看手上这本书,能同时看我手上这本么。
朋友:。。。(感觉被套路)

我偷换了概念,把 “同时” 这个词的时间精度调低了,导致这次对话变了味。

偷换概念在生活中无处不在。《武林外传》中的秀才利用 “我”这个概念的偷换,让姬无命莫名其妙并自杀了。

相关性不等于因果性

最经典的例子是,很多父母信佛,然后娃高考的时候天天去求神问佛。如果小孩考上了大学,那么就是拜佛的功劳,如果没有考上,那就是小孩不努力。多么完美的逻辑闭环,完全无懈可击。

同样的桥段在各种电视、电影中存在。一家人在村里常做好事,乐善好施,后来子弟里有人做官,就有人说这是行善积德的福报 其实通常这样的人家都有很好的家教和一定的经济基础,所以子女能接受良好的教育,成才概率自然就更高,做好事和做官都是这个原因的共同结果,但是它们之间可能不一定有因果关系。

程序员圈子也会有类似的议论:因为大公司都用的 Java 而不是 PHP,所以 PHP 是一门糟糕的语言,我们要成为大公司,所以要把 PHP 换成 Java。所以很多公司明明可以苟一下,然后因为折腾死掉了。

我们需要时刻记住,相关性不等于因果性,才能认识到一些微妙的逻辑关系。

因果倒置

“可怜之人必有可恨之处。” 这是很多人挂到嘴边的话,支持者甚多。

我小的时候对这句话记忆深刻。小学的时候被年长的同学欺负,后来因为打架被老师知道了,其他同学都说我是个被欺负的可怜鬼,可老师还是对我们都做出同样的处罚。说出了一句举世名言:“为什么欺负你,不欺负别人”。

为什么只欺负你,不欺负别人,所以你也不对,同样要受到惩罚。这是典型的强盗逻辑,从结果推导出原因,但是这个原因并不成立,因为我们知道原命题为真,逆命题不一定为真。

归纳法的局限

逻辑学上把个别的知识推广到一般的知识规律叫做归纳推理。归纳推理是一种朴素的认识方法,在逻辑学中,归纳推理有其意义,但是需要注意的是逻辑学从来没有把归纳法得出的结论当做真理。

归纳法的问题和类比谬误类似。古人认识的到了一个规律,鸡叫三遍天会亮,但是后来出去旅游发现其他地方的鸡不是这样的,真的是应了那句,“东方不亮西方亮,黑了南方有北方。”

中国太大了,甚至二十四节气的规律都不能适用于每一个地方。归纳法只能有限的反应某种规律,不能广泛、绝对地得到真理,也不能从个体推出一般。

算命先生希望从四柱八字、面相分析、掌纹、笔迹这些中归纳真理,如果认识到归纳法的局限性,就不会平白无故交这些智商税了。

责任转移

证明神存不存在,保健品有没有功效,壮阳药有没有作用是科学界三大难题。

从逻辑上证明有其实很容易,只需要找出一个例子即可,比如证明天鹅是白色的,只需要找出一个白色的天鹅即可。但是证明黑色的天鹅不存在,是非常困难的,除非穷举世界上所有的天鹅,才能得出这个结论。

人们的思维中,天生偷懒,所以人们才会有 “宁可信其有,不可信其无”。

所以有一种诡辩,我姑且称之为责任转移,就是在辩论中把举证的责任推给别人,然后再来挑对方的毛病。这是一种非常高级且隐晦的诡辩手段。

比如有神论要求无神论者给出证据,证明神不存在,但是证明无非常困难。对方只能举出一些例子,但是这些例子非常脆弱,如果再结合偷换概念就更无懈可击了。

大师:神会保佑你的。
无神论者:神不存在。
大师:你怎么证明神不存在呢。
无神论者:我从来没看到过神。
大师:没看到过神,不代表神不存在。
无神论者:看都没看见,怎么能说神存在呢。
大师:神是一种信念,它无处不在,慢慢体悟吧。
无神论者:。。。

责任转移大法是不断把举证的责任推给对方,然后在挑错,让对方自顾不暇。

2.7 逻辑工具:概念图

在实际使用逻辑学技巧的过程中,我们可以借助一些思维工具。

概念图是指用图表的方法梳理概念之间的关系,不仅可以加深对概念的理解,还能避免因为概念混乱带来的诡辩。尤其是偷换概念是诡辩中最容易出现的一种,

学计算机的人大多有被人要求修电脑的经历。“你一个学计算机的修个电脑都不会”这种“大妈式”的暴力辩论逻辑实际上是一种典型的偷换概念,计算机维修和学计算机完全是不同的概念。

所以通过梳理概念之间的关系,可以清晰得到一个概念,可以通过使用概念图来描述概念的层次关系,上面使用的图即是概念图。

美国著名教育学家诺瓦克遇到了同样的问题,他需要一种清晰地方式给学生解释一些课程中需要用到的概念。同时,也需要了解学生在理解概念上的变化。他们的研究小组从拓扑分类学和语义学方面得到灵感,创造出“概念图”这一思维工具来表达概念。诺瓦克在他的著作《学习、创造与使用知识——概念图促进企业和学校的学习变革》一书中详细阐明了概念图相关的内容。

例如一个常见的逻辑问题是:联合国是否是一个国家?

因为“联合国”这个词的结尾有一个国字,而且中国是联合国成员国,所以有人想当然的认为联合国也是 “国”。这种谬误不仅发生在对世界不理解的少年儿童身上,甚至会出现在成年人的对话中。联合国是一个由国家构成的组织,而中国才是一个国家,为了避免谬误,合适的表述是“中国是联合国成员国”。

下面这张图为使用联合国是概念出发绘制的概念图,用来描述概念和概念之间的关系。

解释联合国的概念图
图 1. 解释联合国的概念图

当然,你会发现“成员”的概念下面只有中国和美国,这是因为全球 200 多个国家和地区也画不下。到目前为止,概念图也没有标准,那么也就不存在概念图的“对错”。

概念图的绘制工具

上面关于"联合国"的概念图就是一个典型示例。绘制概念图的工具非常多,也并不受限于何种工具绘制。 IHMC(Human & Machine Cognition)提供了一套专业的概念图绘制工具,也是诺瓦克书中推荐的绘制工具,上面风格的图片就是通过该软件绘制。 在实际使用中,IHMC 提供的工具并不好用(难以对齐和美化),为了便于管理可以参考其他工具:

  1. PPT。

  2. Keynote。

  3. 在线绘图工具,例如:https://www.draw.io/。

后续的示例采用其他工具绘制。

“概念”的概念图

使用概念图的目的是为了将复杂的概念清晰的表述出来,因此甚至可以使用概念图表达“概念”这个概念本身。

在前面我们已经讨论过“概念”本身的内容了,如果通过概念图来表达就更清晰了。概念图的发明者罗瓦克对概念的定义非常朴素:从事件或对象中感知到的规律或模式,可以打上一个标签,这个标签就是概念。

那么为了描述这个标签就可以用概念的“内涵”、“外延”来描述,这个概念就可以在日常对话中阐述被描述对象的特征。当一个人无法清晰的描述概念时,可以通过概念图可视化表达出来。

关于概念本身的概念图
图 2. 关于概念本身的概念图

常见的概念图

为了说明概念图的意义,下面给出了几个编程中常用的概念图,来梳理日常并不是那么容易理解的概念。

编程

有一天我回到办公室,有两个同事在讨论编程语言相关的内容。

同事 A:“我是做 Python 的,我现在想转 Java。”

同事 B:“编程思想都是一样的,什么语言都一样。”

同事 A 陷入迷惑中。同事 A 想从 Python 转到 Java ,可能是市场对 Java 的接受程度更高,但有时候会有人说“编程语言都一样”。看起来同事 B 说的很有道理,但他们说的是同一回事吗?

实际上同事 A 是想表达对编程职业生涯的担忧,“Java” 在他的意识里是指的相关整套技术体系,同事 B 想说的只是用来完成编码的计算机语言。我们用概念图看下“编程”这个概念,再来看他们讨论的是不是同一个东西:

关于编程的概念图
图 3. 关于编程的概念图

我们真的只是缺乏编程思想吗?

我们要完成编程这个活动,需要了解编程语言、框架、库以及阅读相关的文档、书籍和开源代码。切换技术栈的成本是巨大的,不只是切换编程思想这么简单。

往往我们在谈论 Java 时,谈的不仅仅是一个编程语言的 Java,我们还在谈 JDK、JVM、Spring 等内容。甚至我们在谈论 Spring 的时候我们在谈论 Spring IOC、Spring Mvc 以及 Spring Boot。

谈论数据库的时候也会谈论 DBMS、SQL、JDBC、Driver、ORM 等概念,甚至包含了数据库连接的客户端工具例如 DBeaver,有时候也会谈数据库的具体实现:MySQL、Oracle 或者其他 NoSQL 数据库。

甚至谈论数据库这个概念本身往往都包含了多个含义:数据库管理系统(DBMS)、一个数据库实例(DB)。

Java 服务器编程

我在刚刚学习 Java 技术栈做 Web 服务器开发时对很多概念非常困惑,Java 的生态非常完善,带来的概念也非常多包括:JPA、Servlet 容器、Tomcat 等,它们的关系对于初学者来说相当的微妙。在使用 PHP 开发网站时,往往只需要查阅 PHP 的文档和一个框架的文档,而 Java 生态圈充斥着大量陌生概念。

我整理了一份侧重于 Java、Servlet、Spring 家族一系列概念的概念图,这里主要关注几个比较难以分清的概念,真实的 Java 服务器开发领域所涉及的概念还是非常多的。

关于 Java 服务器编程概念图
图 4. 关于 Java 服务器编程概念图

作为语言的 Java 衍生出来的概念是相关的运行环境、库和框架。Java 字节码运行在由 JRE 运行环境提供的 JVM 虚拟机之上,Tomcat 是一个 Java 应用程序,并提供了 Servlet 容器负责处理 HTTP 的请求和响应,而我们做的应用程序(WAR)只是一个寄生兽,挂靠在 Servlet 容器上负责处理业务逻辑。

库最具有代表性的是 Spring。Spring 这个词最初只代表一个 IOC 库,后来不断发展,Spring 实际上衍生成 Spring IOC、Spring Mvc、Spring Data 等库的一个集合。最终由 Spring Boot 整合成一个完整的框架。

而对数据库的操作又是一堆概念。Java 程序使用 JDBC 的驱动(数据库具体的 Driver)连接数据库,人们又希望使用 ORM 技术让对象和数据库记录同步,这一实现主要有 Hibernate、TopLink,Java 社区做了规范称为 JPA。Spring data JPA 又对 JPA 做了封装,使之在 Spring 环境下更易用。

通过梳理这些概念可以给学习 Java 编程的新人推荐一个合适的学习路线:计算机基础 → 计算机网络 → Java 基础 → Servlet → Spring IOC → Spring MVC → Spring Boot。没有前置概念的铺垫,直接学习 Spring Boot 是相当痛苦的。

前端开发

最近很火的 Vue 是一个框架还是一个库,亦或者是一个开发体系?

随着前端开发工程化的发展,现代前端开发体系爆炸性的增长,每天都在出现新的概念,那么学习前端到底该学些什么呢。下面我整理了一个前端常见概念点的概念图:

关于前端开发的概念图
图 5. 关于前端开发的概念图

前端开发在 Node.js 出现之前还是非常简单和容易理解的,在浏览器中运行的页面无非是 HTML、CSS、JavaScript。Node.js 把 Chromium 的 JavaScript 引擎单独拿出来运行 JavaScript 脚本,并提供了很多操作系统的 API,形成独立的运行平台。JavaScript 的应用场景从浏览器中脱离出来,变得无比开阔。

Node.js 提供了文件相关的 API,JavaScript 便能够具备文件生成、JavaScript 的压缩、Less 到 CSS 的转换等前端工程构建的相关能力。于是 JavaScript 可以反过来对 JavaScript 代码进行文本处理,构建 JavaScript 项目(无论前端还是后端)。从最开始利用 Grunt 对 JavaScript 代码进行简单的压缩、混淆、模板替换等,到后面的 Gulp 更灵活的构建工程,以及现在的 Webpack 对前端资源彻底的整合。

Node.js 平台上也可以运行包管理程序来对各种依赖管理,这就是 npm 和 yarn,这就是 Node.js、npm、JavaScript 的概念之间的关系。

同理,对于前端各种库来说,它们的关系通过概念图也能表达的更为清晰。React 和 Vue 都只是发布在 npm 中的一个库,前端项目需要这些库作为原料,并通过构建工具来做成蛋糕,并放到浏览器中呈现给用户。

以上就是前端开发生态发展的基本逻辑。

构建概念图的过程

你可以很容易的构建出自己的概念图。 一个典型的概念图主要有节点、连接线两种元素构成,分别对应了概念、概念的联系,两个相连的概念之间可以构成逻辑命题,命题应该能通过节点和连接线读出。

绘制概念的方法非常简单,你只需要在纸上或者绘图软件上罗列出相关概念然后使用连接点标记出概念的关系即可。诺瓦克给出了一个非常详细的构建概念图的流程,这非常适用于教育专家来处理日常遇到的大量复杂的信息和概念,但对于我们来说稍显冗长。我做了一点简化和改进,归纳如下:

  1. 确定概念图需要解决的焦点问题。 例如我需要解决“鱼香肉丝里面有没有鱼”的问题,或介绍 “鱼香肉丝” ,围绕着鱼、动物、鱼香、调料、烹饪、口味、肉丝、鱼香肉丝、川菜等概念来构建概念图,然后得到命题 “鱼香是一种口味”,“鱼香的调料起源是用来烹鱼”,从图中我们得不到 “鱼香有鱼” 这样的命题。在解决这个问题的过程中,鱼生活在池塘中,池塘、水草等概念就没有意义了。

  2. 罗列关键概念。 围绕着焦点为题来寻找概念,但是概念不宜多,在罗列概念时,尝试对概念进行定义,使用更准确的词替换模糊的词。例如讨论编程时大家喜欢用“语言”这个词,尽量使用“编程语言”这类准确的词。

  3. 寻找概念的冲突和二义性,分化概念。 《公孙龙子》在三脚鸡的辩论中,“鸡有脚,数数时,鸡有两只脚,加起来有三只脚”。这里的鸡的概念有集合和个体两个内涵。可以分为“鸡”和“一只鸡”两个概念。

  4. 构建联系,得到命题。 将分化后的概念,通过连接线连接起来,连接过程中给出一个合理的连接词,概念+连接词+概念成为一个完整的命题。例如“鱼香是一种口味”。

关于鱼香肉丝的辨析
图 6. 关于鱼香肉丝的辨析

上面是从操作流程上归纳创建概念图的方法,另外在构建的逻辑上,概念的关系一般有下面两条线索:

  1. 概念的抽象程度。 这种思考方式画出来的图往往是一个树状,从上到下是概念抽象层次的逐渐收敛的过程。例如计算机科学→计算机硬件 → CPU → Inter CPU → I5。概念从抽象逐渐到具体,这是一种理想的概念图构建方法,读者能从上到下找到清晰地逻辑关系和明确的命题。

  2. 概念的联系紧密程度。 画出来的图往往是一个网状,从上到下是概念联系逐渐从紧密到疏远的过程。Java → 编程语言 → 编译型语言,可能两个概念没有直接关联了。这是现实中很正常的情况,一术语往往具有多个概念,概念之间又不断延伸和交叉。

概念图和思维导图的最大区别就在这两条线索上。概念图是用来表达概念的关系,节点之间应该具有逻辑关系,可以说是收敛的;思维导图是用来促进创造性思维的,条目之间具有引导的关系,可以说是发散的。

使用概念图的常见问题

主题范围失控,概念图没有焦点

做出取舍,解决该解决的问题,解决不了的问题收敛主题,并再画一张图。例如我想要辨析的主要内容是:Java、JVM、Spring 等几个概念的逻辑关系,我开始想绘制一个非常大的主题“服务器编程”,这样的话我就必须把 PHP、Go 等其他语言纳入了,但这些内容和我想要辨析的主题关系并不大。于是我最终选择收敛主题到 “Java服务器编程”,把焦点聚焦到 Java 和 Spring 上。

如果需要表达 Java、PHP 概念之间的关系,我需要发散主题 “服务器编程” 然后进行绘制,但是不会加入 Spring 相关的内容,概念图的深度也可能不会到达 Spring Mvc、Zend PHP 这样层次的深度。

概念图不必追求大而全。

概念层次不清晰

把概念图绘制成流程图是最容易犯的错误。概念图是表达概念的抽象层次关系,用概念图表达多个时间关系不同但抽象层次相同的概念没有意义,你应该使用一个流程图来表达。

在电商领域中,购物车、订单、支付记录,下单流程中的几个关键概念。这几个概念在抽象上是类似的。上图的左边部分是一个不好的示例,虽然表达了概念之间的生成关系,但是这些概念的内涵和外延无法在图中表达。

诺瓦克在《概念图》一书中给出评价概念图的方法之一是利用拓扑分类学,主题应该体现出 “渐进分化”的特点。

语义描述不当

概念图节点是概念,概念是认知世界的元素,按照诺瓦克定义来说,就是给印象中的事物打一个标签。概念应该有名词(包括抽象名词)、动名词、形容词,而概念之间的关系可以为动词、介词、副词。

好的概念图还需要对读者友好,阅读者能组合概念和概念的联系变成一条有意义的命题,例如 “马分为白马“。虽然不一定具有语法上的完整性,但是逻辑关系非常重要。

第 3 章 编程中的模型思维

我们经常会看到某些人工智能产品的介绍中,关于 XXX 模型的介绍,听起来非常高端。我们不禁会问,到底什么是模型?有一次,在培训中,有人问到了这个问题,大家对模型的阐述非常复杂。我想到了一个简洁的比喻,于是让一位朋友使用手指算出 3+4=7,于是我说在这个场景下手指就是模型。

模型,是人类重要的思维工具,在历史长河中不亚于火的使用。历法是一种认识时间周期和气象变化的模型,通过 24 节气可以描述四季并指导农业生产;太极是阴阳家和道家认识世界的思维模型,通过阴阳描述了事物的两面,建立了朴素唯物主义的辩证体系;“君君臣臣父父子子” 的儒家社会模型,建立了相对稳定的封建社会。

认识世界和改造世界是我们每个人的日常活动,但是世界太复杂了。股神巴菲特的合伙人查理芒格说,我们必须在头脑中拥有一些思维模型,通过模型来认识世界。现代社会中经济学、社会学模型就更多了,波特五力模型、金字塔、四象限、2-8 定律等模型被各种培训和咨询活动广泛使用。

我相信几乎每一个程序员都听说过 “编程思维”,但是又没人敢说自己已经掌握了 “编程思维”,甚至说不清 “编程思维” 是什么。我们编写应用程序时,每个人都有自己一套方法来设计软件,但是随着需求的不断变化,程序的逻辑会慢慢变得混乱,不再简洁。

计算机科学和软件设计慢慢变成了哲学化、玄学化,并衍生出很多形而上的概念,这是因为计算机科学体系变得过于复杂造而成的。计算机科学是一门实践科学,计算机无非就是一台由半导体组成的电器而已,那么也可以通过模型理解计算机科学并指导编程。

从影响计算机科学建立的基本模型来看,有图灵模型、冯诺依曼体系、TCP/IP 网络模型等;从应用程序设计上来看有 RBAC、MVC 模型等;从业务设计上来看有具体业务领域的模型,比如电商、SNS 等。

业务设计上往往没有建立起特定的领域模型,这是我们架构腐化和软件开发困难的关键原因。从业务领域建立好模型,并指导代码实践,这就是 “编程思维”。

3.1 模型思维

模型这个词常常会听到,通常出现在某个 PPT 或者一篇商业评论中,社会和经济学中的模型往往比较朴素,金字塔、V 型图、四象限会以各种形式出现在不同场合中;软件工程师的模型会更加形式化,UML、E-R 图等,能用较为精确的形式语言描述;数学模型就更加精确,马尔可夫、蒙特卡洛等模型可以用数学语言描述。

广义来说这些都叫模型,甚至你随手在白板上画的一个用来解释当前程序结构的图形,这也能叫做模型。哲学家库恩将这种思维框架叫做范式,也就是模型。维基百科将广义的模型定义为:

“用一个较为简单的东西来代表另一个东西,这个简单的东西被叫做模型。”

我们天生就有用简单的东西代表另外一个东西的能力,比如幼儿园数数用的竹签,学习物理时的刚体、真空中的球形鸡,都是模型。通俗来说模型就是经验的抽象集合,平时听到的谚语、公式、定理本质上都是一种模型。

为了理解模型,斯科特·佩奇在 《模型思维》一书中给出了模型的几个特征:

  1. 模型是简化的。 正是因为我们要认识的事物非常复杂,因此需要通过简化找出最一般的规律,才能一语中的。“天圆地方”学说就是最简单的古人认识世界的模型之一;毛主席的”阶级划分论“ 简单、直接的指出旧中国的社会状态。

  2. 模型是逻辑的。 例如用金字塔原理描述社会阶层,每层的定义是明确的而非模糊的,数学模型能用数学符号系统和公式描述,模型中的元素能用一种逻辑关系做到自洽。

  3. 模型是错误的。 因为模型是一种抽象,所有的模型都是错误的,只能在一个方面反映事物的特征。场景变了,模型就需要修正,连牛顿、爱因斯坦的定律都没能逃脱这个规律。好的模型能在尽可能简单的情况下较好的拟合事物,完全匹配现实的模型就不再满足简化特征了。

为了建立和利用模型,模型思考有几个层次:

  1. 数据: 我们能直接观察到的现实情况,比如下雨了,并且雨下的很大。

  2. 信息: 信息需要从观察到的情况中采样,转换成具体的的数字,比如某个地区某年的降雨量。

  3. 知识: 知识是面对信息的处理方式,比如我们利用信息,将信息中的一般规律找出来,建立模型。比如某地区降雨量和年度呈现一定相关性,建立一个周期性降雨模型。

  4. 智慧: 面对不同情况需要使用不同的模型和修正模型的能力,并能用它指导实践,比如根据周期性降雨模型修建水利设施。

在斯科特·佩奇在《模型思维》[16]一书中,使用了一张形象的图例,如下所示:

信息的层次
图 7. 信息的层次

我们可以尝试用这种方式来看待原本很困难的知识,比如去简化复杂问题,并理解它。通过模型思维来看待软件开发,我们会发现,软件从设计到开发的过程就是各种模型的转换。

我整理了一个图表,说明了一款软件从商业探索开始到编译成可交付的软件整个过程中可能会用到的模型。

软件工程中的模型
图 8. 软件工程中的模型

我将模型分为形式化和非形式化两种。形式化的模型是精确描述的模型,例如表达领域模型的 UML、ER 图,而非形式化的模型是一些非精确描述的模型,主要用来做商业、业务探索。

对于应用开发的软件工程师来说,核心的问题并非如何编写代码,而是如何将非形式化的业务输入(模型)进行合理抽象、设计,并转换为形式化模型的过程。

某种程度上来说,通过高级语言编写的代码也是一种模型。在多年以前,计算机科学家们认为编写 Java 代码的人不算程序员,可以由业务人员直接编写业务软件。软件工程中非形式化和形式化之间存在一个巨大的鸿沟,编程就是模型的形式化过程,从这个角度看能深刻分析业务并获得良好抽象结果的程序员具有竞争力,并不会被 AI 编程所代替。

在非形式化模型这一步,实际上又存在两种模型。一种是描述软件背后的生意,即使不使用计算机系统参与到业务中,该如何完成交易,并让企业获得利润,我把它叫做商业模型。另一种是描述软件的操作和交互的模型,关注参与的用户、流程和业务规则,我把它叫做软件业务模型。

我们可以分别将其定义一下:

商业模型定义:商业模型是指描述企业盈利方式的模型,关注企业如何实现赚钱。
业务模型定义:业务模型是指描述企业如何为用户提供服务,如何参与到社会分工中的模型。

那么弄明白商业模型和业务模型后,再来看软件设计,软件设计就是关注软件如何为业务、商业提供服务,提高业务和商业能力。虽然商业模型和业务模型有所不同,但是从商业模型体现的生意出发就能快速地理解一个应用软件(和日常生活相关的软件,区别于工具类软件,例如电商、ERP 等软件)。

优秀的产品经理往往能深刻地理解商业模型,然后才设计出合适的软件业务模型,避免了空想的业务规则,以及铺天盖地无用的功能。优秀的架构师应该也能理解商业模型,并从软件业务模型中提取合适的概念,构建软件的骨架,而不是让软件构建在没有地基的空地上,然后修修补补。

在软件实施过程中,需要思考项目管理、团队的问题,项目经理或者 Tech Lead 也可以有自己的模型理解和认识项目、团队管理、项目管理的模型,就是我们熟悉的瀑布、敏捷。团队管理的模型比较少,在后面会讨论一种系统化的团队模型,通过将团队中的个体分为 Dispatcher、Worker 来认识团队。

除此之外,还有一些模型并不需要过多深入了解,这就是和计算机工作原理相关的模型,这是计算机科学家的工作,对于普通开发者来说可以加深对计算机的理解。例如,符合人类的认知的编程语言(面向对象、函数式)背后的模型,面向对象可以看做一种模型,还有一些计算机科学基础的模型:冯诺依曼结构、图灵模型、布尔逻辑(数理模型)。

将这些模型串起来,能够提高对软件工程的理解,以及每个部分背后的逻辑,明白这些模型背后的目标后可以更加从容地应对各种问题。

3.2 计算机科学中的模型

图灵和冯诺依曼模型

从算盘到计算机,人类走过了漫长的历史。计算机发展的转折点往往都是一些大师提出关键模型的时期,了解这些模型可以帮我们更好理解计算机世界。

计算机是数学的延伸和应用,图灵机模型是一个分水岭,图灵机和可计算性理论让自动计算具有了理论基础。虽然在此之前的模型也很重要,但是还停留在数学上,比如数理逻辑中最重要的一部分——布尔代数。

新一代的软件工程师已经不再关注计算机是如何工作的了,他们把计算机当做一种可以通过编程语言对话的“生物”来看待了。我曾被问到过,我们日常使用的“电脑”为何被称作计算机,它和计算看似毫无关系。

要回答这个问题需要将图灵和冯诺依曼模型两个计算机科学基础模型清晰地分开。

计算机能够发展出这么多的功能,其实这只是一个偶然,现代计算机的各种高级应用是计算机的研究者们没有想到的。布鲁斯·斯特林创作了一本小说,名字叫做《差分机》。这本小说是为了致敬查尔斯•巴贝奇,巴贝奇设计了一种机械计算机,这种计算机需要通过蒸汽驱动,这就是差分机。在某个平行宇宙中,人类走向了由差分机带动的新一轮技术革命,不过这种技术革命还是蒸汽时代的延续。

理论上讲,全自动的机械计算机是能够被制造出来的,因为“程序”在图灵模型中被表述为“有限执行的操作序列”,所以很多东西都可以看做计算机。

算盘会被经常和计算机一起提到,算盘是人力驱动的一种计算机,算珠的状态可以看做寄存器。对中国人来说理解图灵机非常简单,我们可以使用算盘来类比。当算盘归零后,算盘的状态为初始状态,每一次拨动算珠就是一个指令,当所有的指令下发完成,算盘上最终状态就是计算结果。指令序列就是算法,算盘就是一个状态机。

在算盘之后的时代,还有计算尺,甚至手摇计算机。手摇式计算机算一种半自动的计算机,我国科研人员曾使用它进行原子弹的计算工作。

计算机带有计算两个词的功劳得归到图灵。图灵在 1937 发表了论文阐述可计算性这个概念,并给出了计算机的抽象模型。图灵在论文《论可计算数及其在判定问题中的应用》中,提出了著名的理论计算机的抽象模型——“图灵机”。

它描述了这样一种机器:一个虚拟的机器,由一条无线长的纸带和读写头组成。纸带上分布有连续的格子,并能被移动,读写。机器能读取一个指令序列,指令能对格子纸带进行移动和读写。和算盘的逻辑一样,机器每执行一个指令,纸带的状态就发生了变化,最终完成计算。

在电子计算机中,图灵模型是由门电路完成的,门电路就是开关电路。记录状态的门电路可以想象为算盘上算珠的拨动位置。门电路有开关两种状态,因此能通过简单的方法实现加法器,进而实现各种运算。

通过开关就能做出计算机?听起来在开玩笑,用机械来实现当然无比复杂,但是用电气来实现就非常简单。所有的运算都可以通过加法完成,这个不难理解。加法如果用电器开关来表达,只需要做到下面几种条件:

  • 0 + 0 = 0

  • 1 + 0 和 0 + 1 = 1

  • 1 + 1 = 10

如果把每个数字想象为两个灯泡的话,怎么设计一个电路满足上面三种情况让相应的灯泡亮起、熄灭。因此要通过电气实现图灵模型就需要实现指令的基本元素:加法器,以及一个存储结构:锁存器。

理解原始计算机的基本原理只需要理解加法器和锁存器是如何制作出来的,这个不是玄学,只需要初中物理学就能搞定,可以参考书籍《编码——隐匿在计算机软硬件背后的语言》,这本书讲述了计算机从简单的电气结构到复杂结构的完整演化过程。

图灵模型只是描述了如何一步一步的完成计算任务,这种机器称不上“电脑”。让一堆“沙子”具备通灵般能力的人是冯·诺依曼。现代的计算机实际上是一个死循环,可以类比为冲程发动机,才让计算机看起来有了生命。

ENIAC 是公认第一个满足图灵模型的电子计算机,ENIAC 通过纸带编写程序,并拨动开关执行和获得结果。冯诺依曼在比 ENIAC 更先进的计算机项目 EDVAC 中描述了另外一种模型,他认为程序本质上也是一种数据,将指令和数据共同存放到内存中,这些指令中存在特殊的跳转指令,让程序周而复始的运行。

存储程序模型构建了一个能自我运行的计算模型,构成了一个系统。处理器和内存之间使用总线连接,用来给这个系统提供输入的设备叫做外设,每一次指令循环可以访问一次外设传入的信号,这就是中断。

想象一台由继电器组成的计算机,如果每一次执行指令计算机会发出 “嘚” 的声音,图灵模型就是程序开始运行后线性的 “嘚嘚嘚……嘚嘚停”。冯·诺依曼的模型就是上电后 ”嘚嘚嘚嘚嘚……中断……嘚嘚嘚嘚嘚”,并反复循环。冯·诺依曼让计算机永不停息,并产生交互效果。

冯诺依曼简化模型
图 9. 冯诺依曼简化模型

我将计算机科学基础模型展开,每种模型都能作为计算机科学的原料:

  1. 布尔数学逻辑模型:为开关电路组成复杂的逻辑规则提供了数学工具。

  2. 加法器的电气模型:实现全加器,为图灵模型提供基础指令。

  3. 图灵模型:算法是有序的操作序列,数据是状态,计算的过程就是有序修改状态。

  4. 冯·诺依曼模型:算法也是数据,算法可以控制指令序列的跳转,然后无限循环下去,进而可以响应外部的信号输入。

在我朴素的认知里:冲程发动机、计算机、生命是一类事物,启动后便不再停下,直到能量耗尽或受到外界的干预。

自动推理模型(理解编程语言)

各种各样的编程语言层出不穷,由于工作的需要会接触不同的编程语言。如何能理解编程语言的本质是什么呢?我尝试找一些模型简化对编程语言的理解。先用矛盾论分析一下编程语言解决的是什么矛盾:

计算机只能识别机器指令和人类难以使用机器指令解决具体问题之间的矛盾。

所以人类设计出来各种各样符合人类习惯(各不相同)的方式编写程序,这些编写程序的模型就是高级语言。要使用自己定义的语法规则来写程序,就需要一个转换器,能将符合人类习惯的语法进行转换,这就是编译器。

一门新的语言需要满足几个条件:

  1. 新定义的语法必须是形式化的。

  2. 新定义的语法能方便的被转换。

  3. 人们能接受这种语法编写程序。

所以编译器是一个自动推理机,只要能被推理的形式化语言都可以作为输入。除了自然语言无法实现之外,无论用中文、表情包、符号、图形都能作为一种编程语言的形式。

编译的过程有:语法分析、词法分析、语义分析、中间代码和优化、目标代码。大师通过编译过程学习如何实现编译器,普通工程师可以反过来用这个过程理解一门新的语言。

我尝试为编译过程中的环节找到一个现实中的类比来理解编译器,将其类比为人类阅读法律文书(法律文件是最贴近形式化的自然文本)。

阶段 编译器 类比

词法分析

扫描,识别代码 Token,将关键字、变量、操作符提取出来

处理调查材料,案件人员、行为等要素

语法解析

将 Token 组织为一棵树(AST) 用于推理

将人员和行为映射成图谱,形式逻辑推理

语义分析

处理上下文相关的信息

识别行为发生的动机、背景,提取上下文信息

中间代码

上面三步是前端,中间代码是为了多平台代码生成用

整理为卷宗

目标代码

根据不同的平台进行代码生成

输出到报纸、网站等媒体

尝试找到一些通俗的模型理解编译过程,在 https://craftinginterpreters.com/a-map-of-the-territory.html 这个网站下介绍了一个清晰的编译过程。

理解编译器后再学编程语言就清晰很多,比如语法(Grammar)有三个层次:

  1. 词法(Lexical):决定哪些表达式、关键字是否合法。

  2. 句法(Syntax):决定一个句子是否合法,比如流程语句。

  3. 语义(Grammar):决定一段代码的组织结构是否合法,函数、类、闭包等规则。

Lexical 和 Syntax 往往可以看成一体,Grammar 不太一样,在一些编译器中 Syntax 和 Grammar 的错误提示都不太一样。所以可以这样看一门语言:Syntax 是类 C 的还是非类 C 的,Grammar 上是面向对象的还是面向过程的,是否支持闭包这类上下文追溯的能力。

理解推理模型可以用来帮助学习编程语言,比如 TypeScript 可以编译成 JavaScript,很多时候我们不需要特别学习 TypeScript,将小段 TypeScript 代码编译一下,看看生成的 JavaScript 是什么就行了。

面向对象模型

有了自动推理机,可以将人们自己定义的语法转换成机器代码的语法规则。让我们有了方法、变量、条件、循环等这些概念,可以大大简化编程的心智负担。

面向过程的语言依然还是图灵模型解决问题的思路:有限的有序指令序列。只不过这里的指令从机器语言、汇编代码换成了容易理解的表达式而已,面向过程的编程语言和机器代码在认知上没有本质区别。

组织面向过程的程序,这部分工作的心智负担需要高水平的程序员来承受,将现实中的业务分解成有限的有序指令序列。分解任务成为指令序列的过程就是编程,它要求程序员既要像人一样思考现实又要像机器一样思考。像机器一样思考需要最聪明的人来完成才行,好的程序员可不好找。

能不能想办法利用推理机,再进一步,让程序员按照人类一样思考事物,写出符合人类语义的代码,然后再翻译成目标代码呢?回答这个问题就需要先回答另外一个问题,符合人类认知的思考方式的语言是怎么回事。

人类需要通过概念来进行交流,给一撮物质一个标签,这个标签就是概念。将一堆标签夹起来再打上标签,就是抽象概念。不同的语言、不同文化背景的人无法交流就是因为使用了不同的标签系统,甚至也有可能贴错了标签的情况,导致认知无法对齐。

理解面向对象需要到生活中去,观察玩泥巴的小孩。他们用泥巴创造出一个城堡前,泥土就好像计算机世界中的数据,将泥土组织成有清晰边界的物品就是对象。我们为了描述这类对象,就需要给它起个名字才能交流。类可以对应现实中的一个概念,很多面向对象的书籍并没有点破这一点。

可以把现实和面向对象中的元素对比一下,建立一个理解面向对象的模型。

现实 人类语言 比喻 面向对象

一类物质

概念

标签

不存在的实物

抽象概念

一组标签的标签

抽象类

一个有清晰边界物体

实体

用陶土制作了一个杯子

对象的实例化

一个有行动的的人

拿起了这个杯子

调用了人这个对象拿起杯子

符合条件的人

契约

有手就能拿起杯子

接口

所以面向对象编程是建立在非常好的心智模型上的,只不过这个模型对于不熟悉西方哲学的人来说过于抽象。对象、实体、类、行为,这些面向对象中的内容和概念早已经被哲学家讨论过数千年,但是在中文的语境中并不新鲜。

人是通过语言思考的,我们不遗余力的使用自然语言描述事物,面向对象是计算机语言和自然语言的一座桥梁,这座桥梁由哲学链接。对象这个词在不同的领域都被用到,这并不是巧合:

  1. 哲学中的对象概念。

  2. 数学(范畴学)中的对象概念。

  3. 语言中的宾语。

维特根斯坦的《逻辑哲学论》中对对象、类的阐述和面向对象极为相似,不过这本书非常晦涩。通俗来说:

对象是人认识世界的基本单位,对象由实体和正在发生的事构成。

也就是说对象不是一成不变的,可以由“造物主”自由的设计和组合。当我们在开发一款 XXX 管理系统时,被管理的“物品”被模拟为一个静态的物品,就能看做一个对象。假设我们正在开发仓储管理系统,极端的面向对象者会告诉你将行为放到“货物”这类实体中,这样看起来更加像面向对象的风格,但是他们背离了面向对象的初衷。

虚拟的世界里,静态的对象需要由动态的对象处理,这构成了一组主客体关系。而对于“上帝”来说,它们都是对象。熟悉 Java 的程序员可以这样理解,Spring 中的 Bean 是一种对象,在应用启动时就被初始化了,就像上帝造出亚当开始干活儿。而从数据库中提取出来的实体,就像是从仓库中提取出来的“物品”。

如果开发一款游戏,对象貌似都是有生命的。但是对于普通的管理系统来说,真正需要设计的是“货物管理者”,“收银员”这类对象,而“货物”这类实体就应该让它们安安静静的躺在那里。

使用面向对象越久,越会下意识的使用面向对象思考现实,面向对象是程序员进入哲学世界的启蒙课。

3.3 应用开发中的模型

使用模型思维开发软件并不是计算机科学家的专利,对于应用开发来说我们也会想尽办法找到合适的模型。应用程序设计中有很多套路,一些书叫做范式、模式或者其他词汇,如果按照模型思维的逻辑,我们可以叫它们模型。根据场景找到合适的模型就能把应用程序设计的很好。

做应用程序设计,除了特定领域外,大部分应用都有一些通用的的内在逻辑,我们可以尝试把这些内在的逻辑找出来,通过模型可以帮助分析业务问题。

通俗来说,系统分析的关键是怎么找到一根线把系统的大部分元素串起来,达到逻辑自洽的目的。串的东西越多,能分析的系统就越复杂。现代商业软件系统的类型往往由商业价值决定的,一般有这几类:

  • 电商类。业务的关键逻辑是电商,即使看起来和电商无关。像 Keep、抹茶美妆这类垂领域的 APP 看似是生活类 APP,实际上也是电商应用。对于电商类,订单就是贯穿整个用户操作逻辑,我们可以围绕订单串整个系统。

  • 协作工具类。一些项目管理系统,比如禅道、JIRA、Worktitle 等,都属于协作工具类。这些工具类应用中最核心的是工作流,任务的状态和流转是贯穿整个系统的主线。

  • 社交类。校内网、微博这类应用,属于典型的社交应用,其实也应该把像知乎这类 UGC 应用算进去。社交类以用户关系和内容串联整个系统。

当然从分类上来说不可能做到尽善尽美,只能说常见的产品属于上面三类,还有一些难以划分在这几类之中。

订单模型

在互联网产品中我们会发现大部分产品都是电商平台,即使是类似文化、阅读的产品也会有产商品的概念贯穿其中。我工作早期做的餐饮系统,也发现无论怎么变化关键的部分都是围绕订单和订单状态设计的。

订单的状态是分析此类系统很好的着手点,从已下单、已支付、已收货、已完成等状态,串联整个系统的其他元素。在处理业务逻辑的时候,考虑订单的状态是否能保持一致,基本能保证系统的逻辑大方向一致。

分析订单模型可以侧重使用 UML 中的状态图,以及 E-R 图建立对象模型。为了降低局部复杂性尝试使用 DDD 的思想进行领域划分、上下文划分。

工作流模型

我们做的内部 ERP 系统大多数都可以抽象成工作流模型,工作流模型的关键元素是任务、参与者、角色。

  • 任务。一个工作流的客体,任务的状态变化体现业务逻辑的推进。

  • 参与者。一个工作流的主体,参与者的活动体现工作流过程中关键的方法。

  • 角色。参与者的分类,用于管理参与者的组织架构和权限。

工作流模型业务分析的关键是参与者角色的识别,往往这类系统角色、关键活动非常多。通过对角色+关键活动组成的用例进行识别,大量系统逻辑都能被分析的清晰并容易理解。

分析工作流模型可以借鉴一些开源工作流产品,除了直接使用这些工作流框架(例如 Apache activiti)之外,可以直接借用它们的定义的概念来自己设计模型。

信息流 (Feed) 模型

设计社交类应用时,无法绕开的模型就是 Feed 模型。信息流模型一般包含信息、信息生产者、信息消费者、推送平台等元素。

  • 信息。用户产生的内容,比如文章、心情、图片或者视频。

  • 信息生产者。产生信息的角色,比如发帖、评论、转发代表的角色。

  • 信息消费者。阅读信息的角色,比如拉取个性化 Feed 流、读取热榜列表时代表的角色。

  • 推送平台。负责将信息从生产者推/拉发送到信息消费者的视图中。

社交类应用往往信息生产者和信息消费者是同一个人,但是在设计时需要分开看待,否则会混乱。通过信息流模型可以让技术实现更有方向感,比如将精力放在推送平台的建设和性能优化上,否则普通的技术选型无法支撑信息流模型。

租户模型

除了上面的几个模型方式之外,还有一种模型需要考虑,就是租户模型。租户模型与前面讨论的三类应用无关,所有应用都有可能存在多租户的情况。多租户指的是客户希望复制一套属于他自己内容的软件产品,例如多用户建站系统可以开通后复制一套自己的 CMS 系统,通过修改域名和模板就能建站。 互联网产品或多或少都有一些多租户的要求,常见的就是一些 SaaS 平台,比如建站系统、企业微信、用友 ERP、收银系统等。通过租户隔离可以实现双赢的局面。

  • 对软件提供者来说,可以低成本实现倍增收益。

  • 对于软件使用者来说,相比于自行研发,可以享受到基础设施共享带来的低成本。

但是多租户带来的最大的问题是:每个租户潜在的个性化需求和软件提供者希望打造通用解决方案之间的矛盾。认识这个矛盾后,租户一般会使用服务级别协议模型。服务级别协议(SLA)将使用者分为几个级别,一般互联网产品付费策略都会一定程度类似如下划分:

  • 基础版本,共享数据库等所有资源,数据、应用程序不隔离,通过数据字段区分数据集合,后期考虑通过租户。

  • 数据隔离,共享同样的应用程序,开通专用的数据空间。

  • 应用隔离,私有化部署,数据和应用租户完全物理网络隔离。

  • 定制开发,除了私有化部署外,提供额外的定制开发。

在产品设计初期,多租户模型容易陷入的误区是把个别租户的个性化需求当做通用需求来做,导致基础版本的业务逻辑混乱,体验复杂。 根据 2-8 定律,大部分租户基础版本已经能满足需要,定制需求往往只是小部分租户需要。使用 SLA 模型可以较好地控制定制需求,当租户确实需要个性化功能,并能接受定制开发成本时,开发定制化需求并进行私有化部署,但不应该污染基础版本。如果产品经理认为这些个性化需求能满足大多数租户的需求时,优化并合入基础版本即可。

另外应用租户模型成本非常高,尤其是多租户下用户打通时带来的复杂性会导致程序难以维护,需要谨慎考虑。

3.4 模型有效性评估

当有人提出一个模型,然后一群人喋喋不休,争吵得你死我活的时候。让我想起了 George Box 的一句话:“All models are wrong; some are useful. ”这句话深刻的揭示了我们日常讨论心智模型时的一个原则,那就是:

模型当不得真,但是再看起来不可思议、违背认知的模型或许能找到有用的地方。

所以我们能看到一些奇奇怪怪的思维模型,但是居然能找到用处。数学家布尔发现可以通过真值表来做一些逻辑判定,然后通过基本的对错进行复杂的逻辑运算,在布尔活着的时候数学界并不承认布尔逻辑是数学。

在那个年代,布尔逻辑只不过是一种分析问题的小把戏。这种观点一直持续到了布尔去世 200 年后,人们发现了门电路,当组合门电路就可以创造出复杂的逻辑装置。比如现代家庭卧室都安装了的双向开关,就是一种门电路的应用。

那么既然模型当不了真,如果我获得了一个思维模型或者理论,甚至仅仅只是软件中的领域模型,怎么知道它是否有用或者合适呢?简单来说就是两个方向:拟合现状和预测未来。

一般来说,我们不会平白无故的获得什么模型,比如有很多讲思维模型的书籍,介绍了上百种模型。其实这类书籍用处不大,因为模型存在的价值首先在于解决问题。

找到合适的问题模型就有用了,不管是先有模型还是从模型中找到能解决的问题。这就是拟合现状,找到的模型能满足当前的状态。比如在项目上,有一大堆的技术债需要解决。

我们可以使用一个四象限模型对这些技术债归类,这个四象限有两个维度,分别是重要性和紧急性。就能分出处理问题的优先级:

  1. 重要且紧急。

  2. 紧急不重要、重要不紧急。

  3. 不重要也不紧急。

按照这种方法就可以清晰识别手上的一堆问题,这就是拟合;同时也为我们采取行动指明了方向,这就是预测未来。我们发现,拟合 + 预测,可以作为模型评估的“模型”,要做到拟合、预测就要满足一些条件,将其展开就能得到一些模型评估的清单。

尝试问几个模型拟合性相关的问题:

  1. 是否足够简单?

  2. 是否符合逻辑自洽?

  3. 是否能描述问题的本质?

  4. 是否有清晰的应用边界和局限性?

尝试问几个模型预测性相关的问题:

  1. 是否能解决提出这个模型背后的问题?

  2. 是否能对行动做出指导?

  3. 是否能用来规划未来的发展?

  4. 是否能推广到其他领域?

3.5 将一堆模型组织到一起

软件工程中太多的模型了,有时候多到不知道用哪个。有属于 UML 规范中的时序图、类图、ER 图,有现代更潮流的用户旅程、业务蓝图、应用蓝图等,也有随意画画的 Freestyle。

我以前也整理过一个项目上需要用到的模型清单,比如用 C4 架构图 + 时序图+领域模型图(UML)+ ER 图基本上能满足大部分业务需要,能充分的描述软件现状。当我慢慢把虚拟的软件世界和现实世界映射时,发现可以用一个思维框架来组织哪些模型是我们需要的。

我们用“宇宙”这个词来描述现实世界,在中文的词源中“宇宙”是两个词组成的,宇指代空间,宙指代时间。更有意思的是,“宇宙”这个词汇出自庄子的《庄子·齐物论》:“旁日月,挟宇宙,为其吻合。”。

那么我们可以从时间和空间两个方面来寻找一些模型描述软件,并且做到正交,就能清晰的认识到相关模型在软件建模体系中的位置,这样也能让模型保持干净。

先从空间上来看。领域模型图是在描述软件概念骨架,描述业务概念或数据在软件中是如何组织的。设计领域模型就好比我们将手机和电池分开设计,需要清晰地定义手机、电池,以及手机与电池的关系。

所以在建立领域模型图时,需要保持干净,体现实体、实体关系就行了,用 DDD 的风格,表达出聚合、上下文就完全满足开发需要。进一步来说,领域模型图、数据库实体关系图、架构图、部署图都是在空间上描述软件,只不过这是不同的形态。从空间上来说,可以类比建筑行业的图纸,但是软件是动态的,需要响应用户交互,于是又需要从时间(流程)上来看,这点和建筑完全不同。

从时间上来看,依然可以用不同的态来看待这些模型。时间维护上的模型体现的是流程、事件。从最朴素的流程图到用户旅程、服务蓝图,再到事件风暴、时序图、状态图都算描述时间过程上的模型。流程图由于粒度不确定的问题,可以用于草稿,实际上用的不多。

用户旅程、服务蓝图,描述现实中业务主体为了达成业务目标中间所经历的过程,可以用在商业探索早期。将用户旅程带入用例的视角,就可以解决颗粒度的问题。以用例为单位,用例的流动就是参与业务主体的契约在不断变化。

事件风暴则通过系统核心状态变化为线索寻找背后的实体,有点像电影的关键帧,通过状态的变化可以提取出描述软件空间结构的领域模型。

时序图则用于软件编写中,描述操作实体的服务(主体)之间的交互,时序图也可以有不同的层次关系,比如子系统(微服务)之间,服务之间(Controller、Service)。

那么通过时间和空间的思维,可以帮团队裁剪一套软件设计过程中的模型,例如:

软件模型的两个维度
图 10. 软件模型的两个维度

3.6 总结

模型思维不算是一项硬技能,它相当的软,以至于不少人在使用这种思维但是意识不到。显性化强调模型思维这个概念的用处是可以训练自己有意识的使用模型思维。为模型寻找一个表达方式,这样更容易理解复杂事物,载体可以是一个比喻,也可以是一页 PPT,或者简单的在白板上画出来。

把一些有规律的一组信息的首字母提取出来拼凑为一个有意义的单词来记忆也是一种设计模型的方法。比如说 PEST 分析法、FIRST 原则、SOLID 原则等。

如果想自创一些模型并让人信服不见得是一件容易的事情,由于晕轮效应的存在,人们对于大众流行的模型更能接受。所以如果我们想在汇报、说服的场景讲模型,可以将自己的理念嫁接到已经广泛流行的模型上,可以起到意想不到的作用。比如在项目的管理上,可以对开发过程模型 RUP、Scrum 模型进行裁剪,设计基于主流模型的定制化方案,相比基于自己经验完全制定的方案更加容易令人接受。

第 4 章 理解软件背后的生意

程序员总是困在代码中,一厢情愿的以为实现产品经理提出的各种崎岖的需求就是技术高明地体现。如果我们跳出技术的思维来看待软件,将会是别有一番风景。

根据第一性原理,如果我们能找到问题的起源和最根本的地方,就能直奔主题,快速命中目标。 商业软件的逻辑是什么?是技术卓越吗?是代码整洁吗?是设计优美吗?其实都不是,商业软件这个词的前缀是商业,也就意味者赚钱,不赚钱的软件不是商业软件。

企业需要软件,是为了帮助他们节约成本以及赚更多的钱。计算机是一个伟大的发明,它提供了一个通用的平台,只需要简单的编写指令就可以实现一个“机器人”。

老板为了赚钱,它会思考商业模式,思考赚钱的逻辑;进而找到一些人来帮他干活,一起赚钱,这是业务架构。举例来说,靠卖菜赚钱是老板的商业模式,老板组织员工做蔬菜配送是业务模式。我们可以用商业画布来表达商业模式,用业务架构中的服务蓝图、用户故事地图来表达业务模式。

而计算机以及软件工程出现后,一切都变了,好的软件可以代替一大群人的工作,只需要一点点人开发和维护即可,更神奇的是,软件还可以复制分发,简直是一本万利。老板找到了产品经理,让他设计软件来帮助业务人员一起更高效、低成本的工作。描述产品设计的方法有电梯演讲、将 IT 系统纳入服务蓝图、原型图等。

完成产品设计后,需要找到真正理解软件开发的工程师或者架构师来设计领域模型、数据库以及系统结构。 这套链路如此长,以至于过了多年的人们尤其是工程师,只看到了要做一个软件,忘记了为什么要做这个软件。如果工程师理解软件背后的商业目标,就可以理解业务的工作模式,那么也就可以对产品经理提出的需求进行补充、质疑和评审。 有时候当产品经理的设计不那么合理时,我们可以换一种简单的交互方式,就可以避免系统的巨大成本。因为对老板和业务人员来说,软件也只是手段而非目的,但是对于产品经理和软件工程师来说,这就是他们的目的。

软件的逻辑和动机
图 11. 软件的逻辑和动机

但是回头来看,如果说业务就是生意,那么就意味着业务等同于商业,但是如果就这样思考,会存在一个误区,很多软件的业务不是直接和钱相关,而是间接和钱相关。

举个例子来说,卡巴斯基和 360 同样都是杀毒软件,可以认为他们的业务模型类似。但是卡巴斯基是通过用户付费实现盈利,360 通过广告盈利,则可以认为他们的商业模型有所区别。但是无论如何,商业软件做出来都是为了赚钱,业务模型会紧贴商业模型变化。同样的,分析商业模型和业务模型的方法也不同,关注的人也不同,所以有必要分开讨论。

所以本章讨论两个问题,软件背后的生意,以及需要设计的软件如何支持生意的完成。要讨论这两个部分,就需要拿出在上一章模型思维中的商业模型和软件业务模型。通过这些模型展开讨论如何快速找到软件背后的生意,以及软件到底创造了什么价值。

4.1 热身:快速了解一个全新的应用软件

这里以一款售后的 CRM 系统为例,如果想要快速地理解售后 CRM,我们可以先从必不可少的业务概念开始,聚焦生意本身,其他的业务概念都是为生意服务的。

售后 CRM 的生意是当用户需要售后服务时,销售方需要创建一个服务工单来管理相关的售后问题。一个极端简陋的 CRM 就是一个服务工单,服务工单的两端构成了消费者和售后服务方之间的契约。例如,消费者买了一部手机,在保修期内需要维修,消费者发起了保修的请求,售后服务方通过服务工单作为契约接过了手机,开始维修。

售后服务方的接待员需要在系统中有一个身份,那么就需要引入一个用户的概念。紧接着,如果维修工作需要材料,CRM 就需要增加对材料的管理功能,否则就只能用纸、Excel 来管理消耗的物料。对于大型企业来说,这些物料需要在网点、总部之间流转,那么就需要建立一套调拨关系。网点和总部之间的物料来往需要出入库相关的流程来支持多地的库存管理。为了 进一步扩大服务体系,会将一些地区的网点承包给当地商户,这样物料的往返、工费就发生了结算,以及财务相关的逻辑就必不可少了。

虽然复杂的 CRM 系统会极其复杂,但是识别核心的业务关系并不会过于复杂。当识别到这些关键业务概念后,进一步展开看到具体的业务逻辑和流程就不会晕头转向。

快速理解业务
图 12. 快速理解业务

当然,并不是说通过这种方法就能真的完整的理解业务,而是作为了解业务的第一步。

另外一个方法是“向钱看”。这是一种逆向思维,通过结算和财务往来快速找到系统之间的关键业务流转。它的逻辑很简单,一般商业软件都直接或间接和钱有关系,如果不涉及业务结算的软件一般是一些工具软件、非商业软件,不在我们讨论的范围。

正是因为商业软件具有和钱绑定的特点,那么我们可以首先去看结算的相关业务,也就是“谁在和谁做生意”,分析谁在和谁做生意必然会发生财务往来。通过财务线索看财务双方的身份,就找到了业务的核心主体,再看收费的项目就找到了业务的客体。

在前面售后 CRM 例子中,大型公司往往将其售后服务外包给一些地区代理商,这些代理商会和公司签约共同进行网点建设,人员培训,提供售后服务。如果不从结算出发,我们的视线会被淹没在一些无关紧要的场景中,忽略了真正重要的业务主体是代理商和公司之间。

再来看结算的项目中使用了什么载体做的结算处理。在售后体系下,既有物料的概念也有产品的概念。但是有意思的是,产品只是被维修的设备,用于登记用户维修的信息、判断被维修设备是否属于保修期内。如果用户在保修期外,需要付费维修,维修的过程中需要消耗物料,那么代理商就需要对服务费、配件的消耗进行结算。如果去查看结算相关的资料,我们会发现结算的客体是物料而不是产品。我们就能找到整个系统中最为关键的对象,也就能快速地理解整个系统了。

4.2 需求变化的原因:软件价值金字塔

需求变化是软件工程师最难以容忍的一件事,为了做好软件设计,不得不猜测未来需求的变化方向。猜中了就是 “正交分解”,猜不中就是冗余设计。

那么需求变化背后的逻辑是什么呢?

首先我们不得不承认,从客观上讲软件它是区别于硬件的,为什么叫软件,因为它本身就是能改的,并且修改的成本低于硬件。硬件涉及电路设计、制版、开模等流程,在开发的过程当中,需求变化会带来巨大的成本。这是为什么软件能够提高效率的原因,因为通过搭建在通用计算机平台上的软件,能够很快做出业务应用。通用软件的出现,软件开发和硬件开发分离是信息社会的一个关键节点,所以软件被发明出来就是为了容易改。

但是事物总是矛盾的,容易改的软件相比硬件降低了成本,但软件容易修改的能力被滥用后,给软件工程也带来负担。

对于软件来说,修改并不是没有成本的,只是相对硬件而言小了许多。对软件工程师来说,业务的变化往往会带来困扰,因为它会让软件的架构设计和模型的建立变得非常复杂。

但并不是所有的软件需求变化,我们都不能接受。对于一些软件的交互和界面 UI 样式等这些细节上面的修改不影响主体的业务变化,这种修改是没有任何问题的。我们说的软件需求变化带来的困扰是指的在软件开发过程中随意变更软件的逻辑,让软件的整体性和逻辑性受到了破坏,这是我们不喜欢,不能接受的软件修改的方式之一。

对于专业的产品经理来说,软件的修改是非常谨慎的,因为修改一个地方,可能会影响其他地方。

那么在软件开发和迭代的过程当中,我们可能难以意识到一个小小的修改会影响整个开发、测试、上线等不同的环节,造成项目的延期,这是开发团队人员不喜欢软件被修改的根本原因。

那么怎么应对软件需求的变化呢?

软件价值模型

如果我们对软件的需求进行分层,我们可以把软件所存在的价值分为 4 层。

最底层是软件所存在的业务价值,或者是通俗来说它是前面提到的“软件的生意”。我们在构建一个点餐软件、构建一个电商软件、构建一个物流软件,那么软件帮我做的事情就是取代原来传统商业活动中人需要做的事情。提高这些行为的效率,为社会创造更多的价值,这些软件背后的需求就是我们的生意。业务价值,可以看做软件的灵魂。

那么在这层软件需求之上的,是我们软件的架构。软件的架构承载了生意或者商业模式,可以看做软件的骨骼。比如说电商里面就有订单等这些关键的模型,或者一些惯用模式。类比起来就相当于我们人体的一个骨架或者建筑物的一些承重结构。

还有一些是软件的一些具体的逻辑细节,比如说约束电商系统确认收货时间是多少天。软件这些业务规则,就像人体的血肉一样,丰满了软件。业务规则填充了软件的一些交互逻辑细节,让软件工程师在不修改主体结构的情况下去,改这些逻辑细节,有时候并不是非常困难的,软件工程师对于这类需求的变化也是乐于接受的。

还有一种软件的需求,就是软件的交互方式和 UI 样式,这些就好像动物的皮肤。不具备特别的功能性,而是负责软件的美观性。这些需求的变化,修改成本也是非常低的。

所以我们总结一下的软件的价值可以分为 4 层:

软件价值模型
图 13. 软件价值模型

当我们软件的业务架构和业务价值发生翻天覆地的变化时,修改这个软件的难度,会呈指数上升,不亚于重新设计一个软件。

我曾经听过一个故事,有一家公司构建了一个财经的软件,但后来希望这个财经软件具有社交的功能,能够有直播,有聊天,有打赏。对于这个软件来说,已经侵害了它原有的逻辑。社交作为业务流程中天然不具有的一个能力,如果强行加上,软件整体的逻辑性和完整性就被破坏了。这种软件的业务价值没有被确定,那么它的业务架构就很难确定,需求也会翻天覆地变化。

对于创业公司来说,他们的业务架构和生意,或者说它的商业模式还不确定,还在探索当中。对于这样的业务来说,他们的需求几乎每天都会发生变化,因为他们的生意会变,一旦生意会变,“上层建筑就会变化”。

对于成熟的公司来说,软件是这个公司业务流程的沉淀,业务流程可能不会发生特别大的变化,比如说银行、保险或者财会,这些特定的业务流程基本上已经形成了行业的规范或者标准。他们的变化情况不会特别大,那么软件的架构也就不容易受到破坏,重大的业务需求变化就会非常少。

竞争力和适应性

对于一些传统的公司来说,他们过去的业务价值或者是商业模式被冲击,他们会认为应该去探索新环境下的业务模式,于是他们对业务的定位进行了改变。这个时候,已经在赚钱的业务模式可能不是他们的重点,他们探索新的业务价值,在很多方向就变得和创业公司一样,都想去尝试,这些尝试的方向都是对软件的未来重新定位。麻烦在于尝试的方向很多,软件的定位就会变得混乱,甚至开始伤害原有正常在运行的业务流程。

但是对于这些传统公司来说,他们又不得不去转型,这就陷入了一个逻辑的悖论。还没有确定的新的业务,去侵害了既有业务的定位和方向,让整个转型过程当中充满了风险和不确定性。有一些数字化转型的企业认识到这一点,他们通过构建一个新的公司或者新的软件来重新开始,并代替原来的业务流程和软件。如果失败了,对原来的业务流程和商业模式并没有任何的影响,这是一种转型思路。

总之,软件需求的变化,需要客观的看待。如果是上层的变化,比如说简单的一个规则和 UI 界面,这可能来自用户的一些反馈或者主动优化,对软件背后存在的商业模式和业务价值没有破坏。反之,如果我们的商业模式发生了变化,也就是软件背后所存在的业务价值改变,我们就很难保证我们的软件架构不会被推翻。这时就需要去权衡或讨论,是构建一个新的软件,还是将原有的软件重构成我们目标的样子,而不是简单的说软件需求变化了。

软件在市场中存活和生物适应生态环境非常相似,如果一个物种对生态的适应性非常强,或者自身的改造性非常强,它一定程度上在某个特定环境下的竞争力就会被削弱,如果他在某一个特定环境下具有强烈的竞争能力,那么他就会牺牲适应其他环境的能力。

特定环境的竞争力和对广泛环境的适应性存在矛盾。

架构的背后就是权衡的艺术,适者生存。软件也是这样,因此我们软件需要有清晰的定位和适应市场的领域。如果我们需要重生,重新构建一个新的软件,繁衍下去,还是改造原来的软件,这是一个值得思考的话题。

对软件工程师的启示

软件价值模型给了软件工程师 2 点启示。

首先,我们可以通过这种方式来快速理解一个软件的架构和需求。一个能够在市场上存活的软件,一定有它背后的业务逻辑和业务价值。那么我们从底层出发,找到了一个软件的业务价值,也就是它的生意,我们就可以快速地理解软件的架构。

其次,我们可以真正地挖掘出业务分析师或产品经理希望的业务。基于软件价值模型,软件背后的逻辑和生意总是存在的,但是产品经理不一定能够用自己的语言或合适的方式讲给软件工程师。

对于软件工程师来说,只有两个选择。要么给自己的软件的架构设计提供足够的灵活性,这也是很多软件设计思想提倡的。但它背后的代价很明显,我们需要留出 “冗余设计”,在特定的环境下,软件的竞争力被削弱。一个有灵活或者弹性的软件架构,背后是付出一定的代价,但往往我们没有意识到这一点。

另外一个选择就是真正地理解软件背后的生意,通过软件价值模型的启示从变化中找到不变。因此我们不得不将视野从软件本身返回到软件承载的业务上,为了理解这些业务我们又需要追溯回到这些业务服务的商业目标中。

那么软件工程师理解软件的路径为:理解商业→理解业务→理解软件产品和信息系统。

4.2 理解商业

如果我们理解了软件背后的生意,可以更加从容地设计软件。更为重要的是,和需求提出者的交流更加容易,除非需求提出者也并不熟悉正在设计的软件背后承载的商业目标。

分析一个企业的商业模型方法非常多,下面介绍比较常见也比较简单的方法——商业模式画布。

顾名思义,商业模式画布是一种描述企业商业模式的模型,最早来源于亚历山大·奥斯特瓦德的《商业模式新生代》 [17] 一书。其主要的思想是,商业模式不应该由几百页的商业策划书来描述,而是应该由一页纸就能清晰地呈现。根据思维经济性原则,无法清晰表述的商业模式其价值也值得怀疑。

商业模式画布,包含 9 个模块,可以呈现在一张画布上。如下图所示:

商业模式画布
图 14. 商业模式画布

编写商业模式画布实际上需要回答 9 个问题,弄明白至少这 9 个问题,才能知道对未来相关的商业设想是否靠谱。如果投资人看这份商业模式画布,才能快速知道这笔生意是否能赚钱。

这里将商业模式画布 9 个模块的含义通过问题给出来。

含义

1. 客户细分(CS,Customer Segments)

企业的产品或者服务是为哪类人群提供的?客户是愿意为你的产品或服务付费的人,在未来会给企业带来收入。

这里容易混淆将客户同用户混淆,大多数情况下客户和用户是等同的,但是有时候用户不是客户。例如,搜索引擎一般是免费的,他们的客户一般是广告商,而用户是日常使用搜索引擎的人。当我们将用户和客户分开后,有时候用户只是企业的一种资源,并没有构成商业合作关系,这也为什么互联网公司都会出具不同形式的免责声明,因为用户并不是企业的客户。

2. 价值主张(VP,Value Propositions)

客户为什么愿意花钱购买我们的产品和服务?企业提供的价值是什么?

仍然以搜索引擎为例,搜索引擎需要给客户提供足够的广告曝光,为了提供广告曝光,于是需要给用户提供信息索引的服务,以便获得足够的流量。

3. 渠道通路(CH,Channels)

客户怎么知道企业能满足他们的需求?塑造企业的品牌,以及构建完整的渠道体系。如果把企业比喻成一台能赚钱的机器的话,渠道是这机器中的油路。

营销渠道可以将企业拓展的足够远,但又不至于把自己的摊子铺的过大。

4. 客户关系(CR,Customer Relationships)

如何将企业的服务和产品嵌入到客户的生产体系?

客户关系应该理解为彼此需要,不仅仅是如何同客户如何相处。商业社会是一个复杂的系统,每个企业是社会化大分工中的一环。客户关系关注的是,如何补全客户的商业体系,组成更为完整的生态。

搜索引擎公司补充的是广告商的互联网平台,而不是取代广告商。和客户建立良好关系的唯一做法是利益方向一致,所以很多公司避免将自己的商业版图拓展的太宽,将手伸到别人的碗里可不见得是件好事。

5. 收入来源(RS,Revenue Streams)

提供的所有产品和服务中,客户愿意花钱的核心点是什么?

这是整个商业模式画布中最难回答的问题。对于软件产品来说,往往愿意使用产品和服务的用户很多,但是愿意付费变成客户的极少。

6. 核心资源(KR,Key Resources)

企业拥有那些资源(资产),能击败同类竞争对手?

这些核心资源往往决定商业模式是否真正有用,因为商业模式画布本身并不值钱,很容易被复制,真正有价值的是背后的资源。比如专利、商标、政商关系、市场形象,甚至域名。

7. 关键业务(KA,Key Activities)

提供的核心产品和服务是什么?

一般来说,收入来源就是核心产品和服务,但是在某些情况下并非完全匹配。

8. 重要合作(KP,Key Partnership)

在嵌入的生态体系下,除了客户之外,还有那些商业主体?

如果是生产类企业,一般是下游的供应商。对于互联网平台型企业来说,包括达成合作的商业主体。比如,直播类平台,重要合作就是知名主播以及内容产生者。

会计是一种商业的语言,在财务会计中,客户和供应商往往分开处理,这是因为他们分别代表着不同的交易往来。

9. 成本结构(CR,Cost Structure)

为了提供这些产品或服务,需要消耗什么成本以及代价?

这些成本需要包含显性成本以及隐性成本,需要对成本保持极高的警惕性。当产品和服务不具备垄断性的优势之后,成本结构就成了企业竞争的主要战场。

案例

使用商业模式画布来研究商业模式的案例非常多,这些案例的研究材料容易找,我以拼多多为例并结合 IT 视角来看商业模式对信息系统的影响。

很多人可能和我一样对拼多多有一些疑惑,为什么在电商格局已经充分竞争后依然还有崛起的机会?我们不妨用商业画布来的分析一下。

1. 客户细分(CS,Customer Segments)

拼多多的客户是什么?

如果不加以区分客户和用户,我们很容易得到拼多多的客户是普通的消费者。实际上从财务的角度,如果消费者的每一笔消费都算在拼多多的收入中,那么拼多多需要支付巨额的增值税,消费者不是拼多多的客户。

拼多多的业务为帮助小微企业、农户、个人快速开设店铺,并从中获得佣金。因此在客户这侧和发展初期的淘宝网差别并不大,在某种程度上说,由于天猫的存在和战略因素,阿里电商在这块领域相当薄弱。

2. 价值主张(VP,Value Propositions)

关于价值主张这部分我一直比较疑惑,拼多多到底能提供什么新的价值?

在一份名为《“电商黑马”拼多多的商业模式探析》[18].国际商务财会,2021(11):34-37+41. ] 的报告中,提到了拼多多价值主张为“免去诸多中间环节,实现 C2M 模式,提供物有所值的商品和互动式购物体验的 “新电子商务”平台”。C2M 为(Customer-to-Manufacturer,用户直连制造)但是这个模式并不新鲜,戴尔、玫琳凯等直销公司都是这种模式。

一些分析者将拼多多的模式总结为物找人。通过拼单的方式,先定义物品,再通过社交媒体找到需要的目标群体。让“社会化消费”发挥作用。

从价值主张上说,拼多多的价值和其他主流、非主流电商的差异并不大。

3. 渠道通路(CH,Channels)

在价值主张上,各种电商平台差距非常小,无非都是“消除中间商,降低流通成本”。但是在细分领域,渠道通路的竞争非常明显,甚至有些电商平台将自己的电商属性隐藏了起来。

例如,以抹茶美妆、小红书、Keep这些产品的电商属性非常弱,实际上是通过社交渠道强化了电商的渠道能力。拼多多的渠道是建立在一种病毒营销的模式上的,俗称“人传人”。

4. 客户关系(CR,Customer Relationships)

拼多多的店铺分为了几类 [19],不过最终还是可以分为专业类(企业或个体工商户店)和普通类。专业类的客户为具有一定资本的经销商,需要缴纳保证金以及登记工商材料,普通类的无需保证金和工商材料即可开店,而正是普通类占据了主要的店铺类型。

5. 收入来源(RS,Revenue Streams)

根据财报显示(2021 年数据),拼多多的收入来源为在线市场服务和少量的自营商品销售,财务来源并没有特殊的地方,主要还是来源于店铺佣金。

6. 核心资源(KR,Key Resources)

在商业模式和收入来源都没有特殊的情况下,拼多多的核心资源是什么呢?在一些商业分析中,将拼多多的核心资源归结为用户流量。截至 2021 年第一个季度结束,拼多多年活跃买家数达 8.238 亿 [20],那么这些买家是哪里来的呢?

除了前面说的“人传人”的基础上,拼多多借助了微信渠道,而微信的宣传渠道屏蔽了其他电商,可以说这是拼多多的核心资源。

7. 关键业务(KA,Key Activities)

拼多多的关键业务是市场活动和供应链管理。

8. 重要合作(KP,Key Partnership)

拼多多的合作伙伴有:腾讯微信、物流企业、电视媒体。将商家排除在外的原因是,商家已经作为了客户存在。

换句话说,商家是赚消费者的钱,拼多多是赚得商家的钱。由腾讯微信提供渠道,通过特有的病毒营销获得用户流量,并将流量转化为商家的客源,可以看做是微信的用户群体资源在电商领域的变现。

9. 成本结构(CR,Cost Structure)

拼多多的成本结构主要是市场推广费用,其次是管理费用和研发费用。

根据商业模式画布分析,拼多多的商业模式主要是以独特的营销推广为基础,帮助小微企业和个体农商户促成交易。在交易渠道上借助了微信腾出的渠道真空(微信渠道对淘宝不开放,拼多多和京东无竞争关系,腾讯为拼多多的第二大股东)。从其营收结构主要为在线市场佣金收入反映了这一点,成本结构上以营销费用为主也进一步佐证。

商业模式画布小结

很多公司宣传的商业模式和真实的商业模式有很大的差别,这里面的原因很复杂,有一部分原因是公司处于转型期,当前的商业模式和公司未来的模式并不相同;另外有一部分原因是照顾主流舆论的主持,将公司的商业模式进行美化,以便在资本市场更加有利。

对于一线开发人员来说,公司的商业模式并不重要。但是,对于架构师来说来说,正如前面提到的,商业模式是理解信息系统需求变化的关键线索,架构的调整也需要适应公司的转型需要。

4.3 理解业务

通过商业模式画布可以理解企业的商业模式,弄明白在企业的业务中谁是客户,收入从哪里来,合作伙伴是谁等。不过,商业模式画布没有将企业的内部运转结构打开,一个企业需要运转起来,需要各个部分之间的通力合作,并和用户产生交互。

业务服务蓝图

要明白的表达企业内部各方的合作情况,业务服务蓝图可以帮上忙。不过请注意在使用服务蓝图时,存在一些争议。例如,是否应该将 IT 系统参与到服务蓝图中表达?这里存在两种流派和方法:一种是使用两张图来表达,这样能看清楚企业引入 IT 系统前后的变化,区分为业务服务蓝图和应用服务蓝图;另外一种流派是将其绘制到一张图上,统称为服务蓝图。

由于在这里区分了商业模型和业务模型以及加入 IT 系统之后的形态,应用服务蓝图更多的是关注待分析的 IT 信息系统。

业务服务蓝图本质上是一种流程图,表达商业中各个参与的主体(参与业务的各个部门可以看做业务主体)之间的往来,通过多个泳道来表达参与的业务主体。服务蓝图在“服务设计”这个概念下可以看做是用户旅程的延伸。在服务蓝图中,不仅包含水平方向的客户服务过程,还包括垂直方向各个业务主体之间的合作关系,描述服务前、中、后台构成的全景图。

我找到了一份不错的服务蓝图定义和绘图模板(主要是好看),[21] 的一篇文章。

服务蓝图模板
图 15. 服务蓝图模板

在这份模板中,服务蓝图包含 5 个主要元素:

  1. Evidence。业务凭证或者接触点,比如在保险服务中,投保单、保单都是接触点和凭证。

  2. Customer Journey /actions。用户的旅程或者行为。

  3. Frontstage。服务提供方(企业)的对客部门或者单位。

  4. Backstage。服务提供方的后台部门或者单位。

  5. Support processes。其他支持单位,比如财会、法务等。

这里面还有三条关键的交互线:

  1. Line of interaction 交互线 。用户服务提供方交互的边界,可以将交付线的上下分别看成独立的业务主体,他们通过业务凭证作为客体完成业务往来,在合法的经营活动中,业务凭证会作为契约以及法律凭证。

  2. Line of visibility 可见线。用户直接接触的范围,以及可视范围。例如,用户通过某保险公司的经理人购买某保险,对用户来说用户只能看到保险经理人以及相关活动,当用户提交投保单信息后,后续的投保流程将由保险公司的具体部门审核通过,并生成正式的保单。

  3. Line of internal interaction 内部交互线。内部交付线为企业内部单位作为业务主体之间的往来,这些往来关系对用户不可见,其权责本质上也属于企业对其的让渡。

通过这 5 个元素和 3 条交互线我们能梳理一个企业实现其商业目标时需要参与的业务细节,并在一张图表上表达。

通过业务服务蓝图还可以发现机会点,有时候也会被体现到服务蓝图中。机会点为现有的业务蓝图中可以被改进的地方,机会点往往意味着商业机会、用户体验优化的方向。

业务服务蓝图示例

以蔬菜配送为案例,我们来看下服务蓝图的应用。我还原了一个真实的小本买卖——某批发市场的食材配送公司的业务形态。

餐厅老板往往(或者他的员工)需要自己整理一些食材清单,然后通过电话下订单给某家配送公司的客户经理,客户经理生成食材订单后,构成简易合同,随即让仓配部进行配送。好在我虚拟的这家配送公司自建了物流,出库和配送是一个部门,否则还需要新的契约来满足配货出库和物流之间的关系。

当餐厅收到货物后,食材配送公司一般会出具一张清单,餐厅清点完成后需要签字盖章。这张单据往往会被用来作为处理纠纷的关键单据,而纠纷的发生会比想象中多非常多,可以说商业就是处理纠纷的艺术。

在具有固定合作关系的商业主体之间,往往都不会结算现款,都有一定的结算周期,通过结算单来完成结算,随之进行支付,某些情况下还会出现抵扣。

业务服务蓝图模板
图 16. 业务服务蓝图模板

和用户旅程的关系

在服务设计和设计思维中,和服务蓝图类似的思维工具还有用户旅程。不过他们之间有一点区别。用户旅程也是一种非常好的思维工具,它更加关注于用户体验,以及用户的心情曲线。

过度关注用户旅程的陷阱就是将用户和客户混淆,容易产生不计成本和盲目的用户体验优化。换句话说,用户旅程描述了某个业务主体的行为和职责,在这些行为下面我们可以绘制出心情曲线,根据心情曲线可以寻找服务或者软件产品的机会点。而服务蓝图描述的是多个业务主体之间的行为,以及职责转移,不体现心情曲线。

它们两者各有所长。在体验设计上,可以更关注用户旅程;从业务理解上,服务蓝图更加有用。有时候它们又可以相互补充,我们可以在合适的时候使用它们。

服务蓝图和用户旅程的关系
图 17. 服务蓝图和用户旅程的关系

图片来源:https://blog.practicalservicedesign.com/the-difference-between-a-journey-map-and-a-service-blueprint-31a6e24c4a6c

4.4 理解软件产品和信息系统

如果理解了一个企业的商业模式,以及支撑商业模式的业务,再来看构建在两者之上的信息系统或者软件就容易很多。

我们可以做一个思维实验,一家主营食材配送的企业,它的客户是餐厅老板,公司的主要业务为每日清晨为各个餐厅配送食材。毫无疑问在现代化的社会,信息系统必然是存在的。这家公司使用了微信作为渠道,建立了小程序、H5 应用建立了来支持食材的订购,同时又为承担配送工作的员工开发了具有送货、打单功能的安卓原生 APP,以及财务核算的 Web 应用。

假定在某天系统故障了,但是配送的工作不能停下来,这是事关商誉的事情。如果因为一次无理由的断供,导致相关的餐厅无法营业,营业中断带来的损失远远超过当天的货物的价值。于是,公司领导无论如何都需要想办法将食材送到客户手中。在信息系统无法使用时,它们可能的做法是,从数据库导出备份的数据,打印出来,人工的通知到客户。我们会发现,对于这类软件,完全可以使用纸和笔延续之前的业务。

这种思维实验,也是在软件设计时常用的方法。当业务复杂,产品经理或者业务人员无法描述清楚,我们可以将“电”断掉,思考如何通过纸和笔来完成软件设计。

断电法,可以将系统中晦涩难懂的概念在现实中找到可以被理解的物品。比如,用户这个概念比较抽象。餐厅老板或者经理可以作为用户在配送平台上下单,如果断电了,那么用户在现实中是什么呢?可能是食材配送老板大脑中的一段记忆,也可能是写在笔记本上的一段记录。

可能会发生这样的场景:

烤鸭店张老板老板需要预定 100 斤大葱,打电话给食材配送的王老板。
张老板:老王啊(电话接通后)。
王老板:原来是老张啊,今天需要什么货呢。(根据电话号码、声音定位到这个用户)
张老板:我需要 100 斤大葱,上午帮我送过来。(下单、填写配送时间)
王老板:收到等下我就去装(确认订单,备货)
……

如果用现实中的行为扮演 IT 系统的逻辑,可以降低认知难度,更加容易理解业务。前面的业务服务蓝图,可以看做断电后的纸笔推演,而接下来我们需要将 IT 系统引入整个商业体系。

对于行业软件来说,软件技术本身并不复杂,它更像是一个“机器人”(软件),难在如何教会这个机器人像专业人士一样工作(领域知识)。

为了描述我们引入新来的小帮手,可以使用两个思维工具:电梯演讲和应用服务蓝图。

电梯演讲

电梯演讲几乎是每一个咨询师都知道的一种思维工具。它来源于麦肯锡,麦肯锡要求他的每一个业务人员,都必须有能力在 30 秒内给客户讲明白方案的能力。30 秒是一个虚数,是指咨询师应该能精确的提炼方案,能在极短的时间内完成方案介绍,甚至能在和客户搭乘电梯的契机介绍清楚方案。

电梯演讲可能会被用到很多地方,而用电梯演讲描述需要开发的软件产品再合适不过了,因为我们在开始设计软件前需要清晰的找到软件的地位。

这里以一款 API 文档生成工具为例,提供一种广泛使用的模板,来对某种产品进行定位。

对于:基于 RESTful 前后端协作的开发者
它们想:自动化生成文档,便于前端使用
这个:文档生成器
是一个:通过对 Java 代码注解分析,导出文档的工具
它可以:在编译部署时自动生成一个在线的 API 文档
不同于:常用的文档编辑工具
我们的工具:可以自动生成和更新 API 文档

这套模板中,只需要按照从上到下的顺序读出来就能完成演讲,在极短的时间内说明待开发软件的定位。

针对前面的业务场景,我虚构了一个互联网产品“微食材”。该产品可以融入企业的 IT 系统,便捷的解决食材采购、记账、财务分析、成本分析等问题,为餐饮企业降本增效。

如果这个产品和各个餐饮系统能整合互补的话是不是能获得非常好的竞争力呢?实际情况也如此,市面上目前类似的产品已经占据这块市场,因为它商业模式简单明了,对我们理解软件在商业体系中的意义很有帮助。

对于:想要采购食材的餐厅老板
它们想:高质量、高效的采购食材
这个:微食材
是一个:在线食材配送平台
它可以:快捷下单,企业 IT 打通,解决食材采购、记账、财务分析、成本分析等问题
不同于:传统食材配送小商贩
我们的工具:高效便捷,融入企业信息系统

很多时候我们使用电梯演讲用来表达产品定位。但是不乏有时候真的需要“推销”方案,这时候该怎么做呢?虽然电梯演讲有很多模板,无论选择使用哪种模板,都应该理解它的内核。即将叙事分解为 3 个核心部分:吸引(Hook)、给利(Mutual Benefit)和收网(Call to Action )。

3 是一个非常独特的数字,甚至有人将其整理为“ 3 法则”。从苏格拉底的三段论开始,就不断有人通过将一些模型总结为 3 要素,来分析他们之间的关系。电梯演讲也可以这样理解,通过亮点吸引和打开听众,然后介绍其优势说明亮点的来源和原理,最后再说明获得这些优势需要付出什么。

通过 3 个支点足够对某项产品进行定位,并逻辑自洽。

应用服务蓝图

对一款软件定位清晰后,就可以加入到服务蓝图中来了。有软件参与的服务蓝图,我们可以叫它应用服务蓝图。意味着,我们找到了“电子帮手”,电子帮手会参与到业务体系中。

在应用服务蓝图中,我们可以将 IT 系统看做新的主体,这个主体可以帮助业务更高效的完成工作。在前面食材配送的例子中,微食材会参与到业务中,作为虚拟的主体存在。

为什么说 IT 系统可以作为一个业务主体存在?这就回到了权责利关系。IT 系统背后的团队通过开发、维护软件系统获利,拥有访问 IT 资源的权利,自然需要承担因为 IT 系统异常带来的损失的责任。

将 IT 系统加入服务蓝图中后,它们的变化非常容易理解,但是还有一条暗线是业务各个部门和 IT 相关部门之间的博弈。在软件开发中,大部分困难往往也在于此。

一些现象可以佐证这一点。一些传统公司在数字化转型时,往往会单独拎出来一家公司(法人主体)承担软件开发职责。这是一个矛盾,老板既希望业务和 IT 走得更近便于协作,同时 IT 能深刻地理解业务和商业背景。但是鉴于管理上的原因和权责利的平衡,又不希望他们走得更近。

应用服务蓝图
图 18. 应用服务蓝图

4.5 产品设计经济性原则

当我们把 IT 系统引入到业务体系中后,会面临一个新的问题。无论是产品设计还是软件设计,无论作为产品经理还是架构师,有一个难以回避却引人思考的问题是:哪种产品设计是好的?

不像艺术设计类工作岗位,产品设计、领域模型或者架构岗位往往不好通过某种考核来评价人员的能力,于是更多的是过往的工作经历来证明其设计水平。

另外一方面,大家评价一个方案的好坏有多种偏好。有人喜欢用是否足够前沿和创新来评价方案,有人会用同行的竞品来评价方案,甚至有人通过个人偏好来评价方案。但是归根结底要从方案的收益和成本上来看,否则为什么要做这个软件呢?这就是产品设计的经济性考量。潘家宇老师的《软件方法》[22]一书中给出了一个公式:

利润 = 需求 - 设计

这是一个非常反常识的观点。首先他将产品设计同利润联系起来,因为我们做软件产品是为了提高生产力,而不是花费更多的人力建造软件,但是带来的收益还无法抵消构建软件花费的人力成本(虽然现实中这种例子非常多)。那么利润就是省下来的钱,这也是投资该软件的目的和动机,这是一切的起源。

如果这个公式用于软件产品设计,可以这样表述:一家公司有各种各样的业务场景,支撑业务场景的就是需求,因为产品经理或者业务分析师(BA)经过抽象、归纳和设计,使用较少的软件功能就能支撑这些场景,就说明能获得更高的利润。

如果代入到领域模型中,用足够少的领域模型能支持多种多样的功能,并具有一定的拓展性,那么就能说明领域模型的抽象和设计是能减少重复开发,因而能带来收益。

既然设计是如此重要,可不可以将设计做到极致,让开发成本极其低呢?理论上是可以的,但是这样会牺牲用户体验,以及软件的可靠性。如果将所有页面都设计成简单的增删改查,开发人员能极快的完成开发,复制粘贴就可以做完所有的工作。但是,这样的软件能满足需求吗?

所以产品设计中有一个矛盾,我们需要在这个矛盾中找到平衡,这个矛盾是:

尽可能短的业务流程和契约完整性之间的矛盾。

通俗来说,就是能用 3 个关键步骤就能满足业务需要的设计,没有必要设计为 5 步。精兵简政能提高用户体验,降低成本,在当下的互联网软件中是一个趋势。在设计良好的情况下,这个成本减少带来的收益是双方的,用户的操作成本和企业的运营成本都会减少。

但是如果产品经理不加以设计,直接简化关键步骤,一拍脑门说要学习“极简风格”的乔布斯。后果是软件没有完整阐述线下的商业逻辑,本来应该达成的契约没有完成,剩下的就是运营人员需要面对无休止的纠纷。

举个例子,某会员系统具有一个会员充值功能,但是在业务上操作员在为会员充值时可能出错。一般此类系统,都会提供一个“冲正”的功能来修正之前的错误操作,但是两条记录都会存在。

某产品经理在新的版本中创新的简化了逻辑,提供了一个“撤销”的功能,可以对原来的充值单做出修改,在用户侧也“简洁”的看不到两条记录。在上线运行后,偶尔接到恶意投诉,用户提供了某时刻的账户余额截图,以及充值记录,说自己明明有多少钱(装作不知道撤销这回事)但是后面又少了。

所以用通俗的话来说,靠谱的产品设计是在不产生“扯皮”的情况下,用最低的成本和流程把“生意”做了。使用经济学作为基本原理,选择最具有经济性的产品方案。

所谓极致的用户体验和简洁性,其实也是从用户的角度发挥产品设计的经济性原则。让用户做的少了,代价是软件的开发、运营成本变高了,公司需要做的更多。为了让用户感受到丝滑般的体验,需要付出巨大的成本,从公司的角度也不是最经济的。

换句话说,先用低成本把生意做成了,再付出更多成本让用户体验更好,吸收更多的用户。生意没做好,和系统好不好用没有太大关系。虽然信息系统对企业运转至关重要,但是也没有到生死攸关的地步。

第 5 章 领域建模,设计软件的骨相

通过对软件业务模型的分析,对业务领域进行建模就能获得软件的逻辑结构。获得了软件的逻辑结构就能进一步指导服务划分、数据库设计、API 设计等技术实践。没有领域模型设计的软件,工程师往往会过多的关注到技术问题上,而忽视了产品设计和业务的目标。

这类软件特点是技术听起来都很高大上,但是就是不好用,或者没用。所以领域建模对于商业软件来说是非常重要的一环,也是工程师消化行业领域知识的重要方法。

5.1 认识领域驱动设计

领域驱动设计(DDD)[23] 是 Eric Evans 提出的一种软件设计方法和思想,主要解决业务系统的设计和建模。DDD 有大量难以理解的概念,尤其是翻译的原因,某些词汇非常生涩,例如:模型、限界上下文、聚合、实体、值对象等。

实际上 DDD 的概念和逻辑本身并不复杂,很多概念和名词是为了解决一些特定的问题才引入的,并和面向对象思想兼容,可以说 DDD 也是面向对象思想中的一个子集。如果遵从奥卡姆剃刀的原则,“如无必要,勿增实体”,我们先把 DDD 这些概念丢开,从一个案例出发,在必要的时候再将这些概念引入。

从纸和笔思考 IT 系统的工作逻辑

让我真正对计算机软件和建模有了更深入的认识是在一家餐厅吃饭的时候。数年以前,我还在一家创业公司负责餐饮软件的服务器端的开发工作,因为工作的原因,外出就餐时常都会对餐厅的点餐系统仔细观察,以便于改进我们自己产品的设计。

一次偶然的情况,我们就餐的餐厅停电了,所幸是在白天,对我们的就餐并没有什么影响。我突然很好奇这家店,在收银系统无法工作的情况下怎么让业务继续运转,因此我饶有趣味的等待服务员来接受我们的点单。

故事的发展并没有超出预期,服务员拿了纸和笔,顺利的完成了点餐,并将复写纸复写的底单麻溜的撕下来交给了后厨。

我这时候才回过神来,软件工程师并没有创造新的东西,只不过是数字世界的砖瓦工,计算机系统中合乎逻辑的过程,停电后人肉使用纸和笔一样合乎逻辑。

合乎现实世界的逻辑和和规则,使用鼠标和键盘代替纸和笔,就是软件设计的基本逻辑。如果我们只是关注于对数据库的增、删、改、查(CRUD),实际上没有对业务进行正确地识别,这是导致代码组织混乱的根本原因。

会计、餐饮、购物、人员管理、仓储,这些都是各个领域实实在在发生的事情,分析业务逻辑,从中找出固定的模式,抽象成计算机系统中对象并存储。这就是 DDD 和面向对象思想中软件开发的一般过程。

你可能会想,我们平时不就是这样做的吗?

现实是,我们往往马上关注到数据库的设计上,想当然的设计出一些数据库表,然后着手于界面、网络请求、如何操作数据库上,业务逻辑被封装到一个叫做 Service 对象上或散落在其他地方,然后基于对数据的操作来完成对业务的模拟。

面向数据库的编程方法
图 19. 面向数据库的编程方法

一般来说这种方法也没有大的问题,甚至工作的很好,Martin Fowler 将这种方法称作为事务脚本(Transaction Script)。还有其他的设计模式,将用户界面、业务逻辑、数据存储作为一个“模块”,可以实现用户拖拽就可以实现简单的编程,.net、VF曾经提供过这种设计模式,这种设计模式叫做 SMART UI。

这种模式有一些好处。

  • 非常直观,开发人员学习完编程基础知识和数据库 CRUD 操作之后就可以开发。

  • 效率高,能短时间完成应用开发。

  • 模块之间非常独立。

麻烦在于,当业务复杂后,这种模式会带来一些问题。

虽然最终都是对数据库的修改,但是中间存在大量的业务逻辑,并没有得到良好的封装。客人退菜,并不是将订单中的菜品移除这么简单。需要将订单的总额重新计算,以及需要通知后厨尝试撤回在做的菜。

不长眼的新手程序员擅自修改数据片段,整体业务逻辑被破坏。这是因为并没有真正的对一个 “订单” 的对象负责执行相关的业务逻辑,Service 上的一个方法直接就对数据库修改了。保持业务逻辑的完整,完全凭程序员对系统的了解。

我们在各个餐厅交流的时候,发现这并不是一个 IT 系统的问题。某些餐厅,所有的服务员都可以收银,即使用纸和笔,收银员划掉菜品没有更新小计,另外的服务员结账时会发生错误。于是餐厅,约定修改菜品必须更新订单总价。

一致性问题
图 20. 一致性问题

我们吸收到这个业务逻辑到 IT 系统中来,并意识到系统中这里有一些隐藏的模型:

  • 订单。

  • 菜品。

我们决定,抽象出订单、菜品的对象,菜品不应该被直接修改,而是通过订单才能修改,无论任何情况,菜品的状态变化都通过订单来完成。

复杂系统的状态被清晰的定义出来了, Service 承担处理各个应用场景的差异,模型对象处理一致的业务逻辑。

在接触 Eric Evans 的 DDD 概念之前,我们没有找到这种开发模式的名字,暂时称作为 朴素模型驱动开发

朴素模型驱动开发
图 21. 朴素模型驱动开发

模型和领域模型

从上面的例子中,模型是能够表达系统业务逻辑和状态的对象。

我们知道要想做好一个可持续维护的 IT 系统,实际上需要对业务进行充分的抽象,找出这些隐藏的模型,并搬到系统中来。如果发生在餐厅的所有事物,都能在系统中找到对应的对象,那么这个系统的业务逻辑就非常完备。

现实世界中的业务逻辑,在 IT 系统业务分析时,是和某个行业和领域相关的,因此又被叫做领域。

领域,指的特定行业或者场景下的业务逻辑

DDD 中的模型是指反映 IT 系统的业务逻辑和状态的对象,是从具体业务(领域)中提取出来的,因此又叫做领域模型

通过对实际业务出发,而非马上关注数据库、程序设计。通过识别出固定的模式,并将这些业务逻辑的承载者抽象到一个模型上。这个模型负责处理业务逻辑,并表达当前的系统状态。这个过程就是领域驱动设计。

我从这里面学到了什么呢?

我们做的计算机系统实际上,是替代了现实世界中的一些操作。按照面向对象思想来说的话:我们的系统是一个电子餐厅。现实餐厅中的实体,应该对应到我们的系统中去,用于承载业务,例如收银员、顾客、厨师、餐桌、菜品,这些虚拟的实体表达了系统的状态,在某种程度上就能指代系统,这就是模型,如果找到了这些元素,就很容易设计出软件。

后来,如果我什么业务逻辑想不清楚,我就会把电断掉,假装自己是服务员,用纸和笔走一遍业务流程。

分析业务,设计领域模型,编写代码。这就是领域驱动设计的基本过程。领域模型设计好后(通俗来说,领域模型就是一些 UML 类图),可以指导后续的工作:

  • 指导数据库设计。

  • 指导模块分包和代码设计。

  • 指导 RESTful API 设计。

  • 指导事务策略

  • 指导权限设计。

  • 指导微服务划分(有必要的情况)。

软件设计过程
图 22. 软件设计过程

在我们之前的例子中,收银员需要负责处理收银的操作,同时表达这个餐厅有收银员这样的一个状态。收银员收到钱并记录到账本中,账本负责处理记录钱的业务逻辑,同时表达系统中有多少钱的状态。

技术和业务复杂度

我们进行业务系统开发时,大多数人都会认同一个观点:将业务和模型设计清楚之后,开发起来会容易很多。

但是实际开发过程中,我们既要分析业务,也要处理一些技术细节,例如:如何响应表单提交、如何存储到数据库、事务该怎么处理等。

使用领域驱动设计还有一个好处,我们可以通过隔离这些技术细节,先进行业务逻辑建模,然后再完成技术实现,因为业务模型已经建立,技术细节无非就是响应用户操作和持久化模型。

我们可以把系统复杂的问题分为两类:

  • 技术复杂度。软件设计中和技术实现相关的问题,例如处理用户输入,持久化模型,处理网络通信等。

  • 业务复杂度。软件设计中和业务逻辑相关的问题,例如为订单添加商品,需要计算订单总价,应用折扣规则等。

技术复杂度和业务复杂度
图 23. 技术复杂度和业务复杂度

当我们分析业务并建模时,不要关注技术实现,会带来极大地干扰。和上一章聊到的断电法理解业务一样,就是在这个过程把“电”断掉,技术复杂度中的用户交互想象成人工交谈,持久化想象成用纸和笔记录。

DDD 还强调,业务建模应该充分的和业务专家在一起,不应该只是实现软件的工程师自嗨。业务专家是一个虚拟的角色,有可能是一线业务人员、项目经理、或者软件工程师。

由于和业务专家一起完成建模,因此尽量不要选用非常专业的绘图的工具和使用技术语言。可以看出 DDD 只是一种建模思想,并没有规定使用的具体工具。我这里使用 PPT 的线条和形状,用 E-R 的方式表达领域模型,如果大家都很熟悉 UML 也是可以的。甚至实际工作中,我们大量使用便利贴和白板完成建模工作,好处是一屋子的人方便参与到共创的工作坊中来。

这个建模过程可以是技术人员和业务专家一起讨论,也可以使用 “事件风暴” 这类工作坊的方式完成。这个过程非常重要,DDD 把这个过程称作 协作设计。通过协作设计,我们得到了领域模型(这里以简单的图表表示,也可以用 UML)。

领域模型 v1
图 24. 领域模型 v1

上图是我们通过业务分析得到的一个非常基本的领域模型,我们的点餐系统中,会有座位、订单、菜品、评价几个模型。一个座位可以关联多个订单,每个订单可以有多个菜品和评价。同时,菜品也会被不同的订单使用。

上下文、二义性、统一语言

我们用领域模型驱动的方式开发软件系统,相对于事务脚本的方式,已经容易和清晰很多了,但还是有一些问题。

有一天,市场告诉我们,这个系统会有一个逻辑问题。就是系统中菜品被删除,订单也不能查看。在我们之前的认知里面,订单和菜品是一个多对多的关系,菜品都不存在了,这个订单还有什么用。

菜品,在这里存在了致命的二义性!!!这里的菜品实际上有两个含义:

  • 在订单中,表达这个消费项的记录,也就是订单项。例如,5号桌消费的鱼香肉丝一份。

  • 在菜品管理中,价格为30元的鱼香肉丝,包含菜单图片、文字描述,以及折扣信息。

菜品管理中的菜品下架后,不应该产生新的订单,同时也不应该对订单中的菜品造成任何影响。这些问题是因为,技术专家和业务专家的语言没有统一, DDD 一书提到了这个问题,统一语言是实现良好领域模型的前提,因此应该 “大声地建模”。我在参与这个过程目睹过大量有意义的争吵,正是这些争吵让领域模型变得原来越清晰。

这个过程叫做 统一语言

领域模型 v2
图 25. 领域模型 v2

和现实生活中一样,产生二义性的原因是我们的对话发生在不同的上下文中,我们在谈一个概念必须在确定的上下文中才有意义。在不同的场景下,即使使用的词汇相同,但是业务逻辑本质都是不同的。想象一下,发生在《武林外传》中同福客栈的几段对话。

关于上下文的对话
图 26. 关于上下文的对话

这段对话中实际上有三个上下文,这里的 “菜” 这个词出现了三次,但是实际上业务含义完全不同。

  • 大嘴说去买菜,这里的菜应该被理解为食材,如果掌柜对这个菜进行管理,应该具有采购者、名称、采购商家、采购价等属性。

  • 秀才说实习生把账单中的菜算错了价格,秀才需要对账单进行管理,这里的菜应该指的账单科目,现实中一般是会计科目。

  • 老白说的客人点了一个酱鸭,这里老白关注的是订单下面的订单项,订单项包含的属性有价格、数量、小计、折扣等信息。

实际上,还有一个隐藏的模型——上架中商品。掌柜需要添加菜品到菜单中,客人才能点,这个商品就是我们平时一般概念上的商品。

我们把语言再次统一,得到新的模型。

领域模型 v3
图 27. 领域模型 v3

4个被虚线框起来的区域中,我们都可以使用 “菜品” 这个词汇(尽量不要这么做),但大家都明确 ”菜品“ 具有不同的含义。这个区域被叫做 上下文。当然上下文不只是由二义性决定的,还有可能是完全不相干的概念产生,例如订单和座位实际概念上并没有强烈的关联关系,我们在谈座位的时候完全在谈别的东西,所以座位也应该是单独的上下文。

识别上下文的边界是 DDD 中最难得一部分,同时上下文边界是由业务变化动态变化的,我们把识别出边界的上下文叫做限界上下文(Bounded Context)。限界上下文是一个非常有用的工具,限界上下文可以帮助我们识别出模型的边界,并做适当的拆分。

限界上下文的识别难以有一个明确的准则,上下文的边界非常模糊,需要有经验的工程师并充分讨论才能得到一个好的设计。同时需要注意,限界上下文的划分没有对错,只有是否合适。跨限界上下文之间模型的关联有本质的不同,我们用虚线标出,后面会聊到这种区别。

领域模型 v4
图 28. 领域模型 v4

使用上下文之后,带来另外一个收获。模型之间本质上没有多对多关系,如果有,说明存在一个隐含的成员关系,这个关系没有被充分的分析出来,对后期的开发会造成非常大的困扰。

聚合根、实体、值对象

上面的模型,尤其是解决二义性这个问题之后,已经能在实际开发中很好地使用了。不过还是会有一些问题没有解决,实际开发中,每种模型的身份可能不太一样,订单项必须依赖订单的存在而存在,如果能在领域模型图中体现出来就更好了。

举个例子来说,当我们删除订单时候,订单项应该一起删除,订单项的存在必须依赖于订单的存在。这样业务逻辑是一致的和完整的,游离的订单项对我们来说没有意义,除非有特殊的业务需求存在。

为了解决这个问题,对待模型就不再一视同仁了。我们将相关性极强的领域模型放到一起考虑,数据的一致性必须解决,同时生命周期也需要保持同步,我们把这个集合叫做聚合

聚合中需要选择一个代表负责和全局通信,类似于一个部门的接口人,这样就能确保数据保持一致。我们把这个模型叫做聚合根,聚合根充当一组领域模型领航员的角色。当一个聚合业务足够简单时,聚合有可能只有一个模型组成,这个模型就是聚合根,常见的就是配置、日志相关的。

在聚合中,无论是否是聚合根,对于有自己的身份(ID)的模型,我们都可以叫做实体

领域模型 v5
图 29. 领域模型 v5

我们把这个图完善一下,聚合之间也是用虚线链接,为聚合根标上更深一点的颜色。识别聚合根需要一些技巧。

  • 聚合根本质上也是实体,同属于领域模型,用于承载业务逻辑和系统状态。

  • 实体的生命周期依附于聚合根,聚合根删除实体应该也需要被删除,保持系统一致性,避免游离的脏数据。

  • 聚合根负责和其他聚合通信,因此聚合根往往具有一个全局唯一标识。例如,订单有订单 ID 和订单号,订单号为全局业务标识,订单 ID 为聚合内关联使用。聚合外使用订单号进行关联应用。

还有一类特殊的模型,这类模型只负责承载一组字段值的表达,没有自己的身份。在我们饭店的例子中,如果需要对账单支持多国货币,我们将纯数字的 price 字段修为 Price 类型。

public class Price {
    private String unit;
    private BigDecimal value;

    public Price(String unit,BigDecimal value){
        this.unit = unit;
        this.value = value;
    }
}

价格这个模型,没有自己的生命周期,一旦被创建出来就无须修改,因为修改就改变了这个值本身。所以我们会给这类的对象一个构造方法,然后去除掉所有的 setter 方法。

我们把没有自己生命周期的模型,仅用来呈现多个字段的值的模型和对象,称作为值对象

值对象一开始不是很容易理解,但是理解之后会让系统设计非常清晰。“地址” 是一个显著的值对象。当订单发货后,地址中的某一个属性不应该被单独修改,因为被修改之后这个“地址”就不再是刚刚那个“地址”,判断地址是否相同我们会使用它的具体值:省、市、地、街道等。

最简单的理解,值对象就是“属性包”,就是一些自己定义的通用拓展类型,持久化时展开到数据库表或者存为 JSON 字符串。

值对象是相对于实体而言的,对比如下。

实体 值对象

有 ID 标识

无 ID 标识

有自己的生命周期

一经创建就不要修改

可以对实体进行管理

使用新的值对象替换

使用 ID 进行相等性比较

使用属性进行相等性比较

另外值得一提的是,一个模型被作为值对象还是实体看待不是一成不变的,某些情况下需要作为实体设计,但是在另外的条件下却最好作为值对象设计。

地址,在一个大型系统充满了二义性。

  • 作为订单中的收货地址时,无需进行管理,只需要表达街道、门牌号等信息,应该作为值对象设计。为了避免歧义,可以重新命名为收货地址。

  • 作为系统地理位置信息管理中的地址具有自己的生命周期,应该作为实体设计,并重命名为系统地址。

  • 作为用户添加的自定义地址,用户可以根据 ID 进行管理,应该作为实体,并重命名为用户地址。

我们使用浅色表达值对象以便区别于聚合根和实体,更新后的模型图如下:

领域模型 v6
图 30. 领域模型 v6

虽然我们使用 E-R 的方式描述模型和模型之间的关系,但是这个 E-R 图使用了颜色(如果是黑白印刷的纸质版可能看不到具体的颜色,可以自行体会即可)、虚线,已经和传统的 E-R 图大不相同,把这种图暂时叫做 CE-R 图(Classified Entity Relationship)。DDD 没有规定如何画图,你可以使用其他任何画图的方法表达领域模型,如果需要严谨一点可以采用 UML 的类图绘制(推荐使用 UML 绘制领域模型)。

5.2 建模方法元模型

Eric 在 DDD 一书中阐述了领域驱动设计的重要意义和一些基本实践,但是并没有给出一套具体的建模过程方法。这给架构师巨大发挥空间,各种建模方法就都可以拿来使用,比如事件风暴、 四色原型等建模过程方法。

于是有一些朋友会产生疑惑,这些建模方法背后的逻辑是什么呢,它们有没有什么共通之处?这里和大家一起探讨软件建模过程方法的基本逻辑,以及如何设计一套简单的建模过程。

目前进行领域建模方法使用的最多的是事件风暴。事件风暴 [24] 的发明人是 Alberto Brandolini ,它来源于 Gamestorming,通过工作坊的方式将领域专家和技术专家拉到一起,进行建模。事件风暴非常有意思的地方在于,它先从事件开始分析,捕获事件。然后分析引发事件的行为和执行者,从这些信息中寻找领域模型,最终进一步展开。

Event Storming 的逻辑是什么?为什么需要先从事件开始分析?这是事件风暴工作坊中遇到过最多的问题。

我带着这些问题请教了很多专家,甚至发送了邮件给 Alberto Brandolini,有幸得到回复。根据 Alberto Brandolini 理解,他认为系统中事件是一种容易寻找到的元素,通过头脑风暴,容易打开局面,仅此而已。

带着同样的问题,分析了几种建模方法(为了减少争议避开了公司同事发明的建模方法)。

系统词汇法(OOA)

系统词汇法就是面向对象分析方法。这种面向对象建模的方法比较原始和直接,直接通过经验提取领域模型,就是简单的面向对象分析方法。其操作过程简化如下:

  1. 首先,从需求陈述中找出所有的名词,将它们作为 “类—对象” 的初步候选者。去掉不正确和不必要的对象(不相关的、外部的和模糊的对象),做出合理地抽象。

  2. 为上一步的模型做出定义,构建数据字典,描述对象的范围、成员和使用场景。

  3. 聚合,把业务一致性强的对象聚合到一起。

  4. 使用合适的关联方式设计对象之间的关系。

系统词汇法建模的优点和缺点都比较明显。优点是没有过多的建模过程,对于简单的系统有经验的架构师马上就能观察出合适模型。相应的,缺点也很明确,没有对业务充分分析,直接得到模型,容易错误理解业务和过度设计模型。

用例分析法

用例模型是一种需求分析模型,是需求分析后的一种输出物,通过对用例再加工可以得到我们的领域模型。1992 年, Jacobson 中提出了用例的概念和可视化的表示方法用例图。

用例(UseCase)是对一个活动者使用系统的一项功能时所进行的交互过程的一个文字描述。

用例由参与者、关系、用例三个基本元素构成,用例图的基本表示方法如下:

用例图
图 31. 用例图

通过用例图来提取领域模型的方法如下:

  1. 梳理用例的用词,统一用例中所有的概念,避免混淆。

  2. 从用例中提取出名词,作为备选模型,这个时候不区分对象或者属性。

  3. 找动词,通过动词和用例关系分析模型之间的关联关系,比如:用户结账用例,会触发累积积分的用例,说明用户账户和积分有关联。

  4. 对名词进行抽象、展开,把用例中作为属性的名词归纳到对象中,补充为完整模型。

因为用例图从不同的参与者出发,非常适合表达业务行为,可以避免错误的复用。在很长一段时间里,很多软件架构师对的模型的建立都依赖用例图。用例分析法的特点是不容易漏,缺点是由于名词的二义性,往往会设计出一些过度复用的模型。

四色建模法

四色建模法的思路和用例略有不同,它的理念是:

“任何业务事件都会以某种数据的形式留下足迹”。

四色建模法其实是以数据驱动,通过挑选一些关键数据(类似于办事过程中的存根),来还原整个业务流程。然后基于这个线索,找出时标性对象(moment-interval)、实体(party/place/thing)、角色(role)、描述对象(description)。

  1. 以满足业务运营的需要为原则,寻找需要追溯的业务事件。

  2. 基于这些业务事件发生的的存根,建立时标性对象,比如订单 → 发货单 → 提货单等。

  3. 基于时标性对象反推相应的实体,比如订单 → 商品,发货单 → 货物和发货员。

  4. 最后把描述的信息放入描述对象,附着在需要补充的对象上。

  5. 梳理为最终的模型。

四色建模法由 Peter Coad 提出 [25],其实并不是一种非常主流的建模方式,其原因为存根和时标性对象在很多业务系统中并不容易找到。

事件风暴

事件风暴相对其他的建模方法非常独特,所以放到最后来说,但是简单来说,它的思路是:

“事件是系统状态变化的关键帧”。

事件是比较容易找到的,它的建模过程有点逆向思维。

  1. 寻找事件。事件(Event)是系统状态发生的某种客观现象,事件格式参考 “XXX 已 YYY”,比如 “订单已创建”。

  2. 寻找命令和执行者。命令可以类比于 UML 分析中的业务用例,是某个场景中领域事件的触发动作,执行者是命令的发生者。

  3. 寻找模型。为了在这个阶段保持和业务专家的良好沟通,寻找 “领域名词” 。

  4. 设计聚合。对领域名词进行建模,获得模型的组合、关系等信息。

  5. 划分限界上下文。对模型进行划分,在战略上将模型分为多个上下文。

事件风暴在获得模型的深刻性上具有优势,但是在操作上更为困难。另外由于它不从用例出发,和四色建模一样,可能有一些遗漏,所以对工作坊的主持人要求较高。

元模型

元模型是关于模型的模型,我们可以为建模方法建立一个模型。在计算机领域中,研究元模型的资料和书籍较少,因为涉及到更高的抽象层次,理解起来比较困难。在有限能查到的资料中,《本体元建模理论与方法及其应用》一书介绍了如何建立软件建模的元模型。

通过对这些建模方法进行分析,发现他们有一些共同特点。都是围绕着参与者、行为、事件、名词这几个元素展开的,通过对这些方法的总结,我们可以尝试建立一个简单的建模方法元模型,为建模方法的改进提供依据。

其实,面向对象中的模型是现实世界在计算机系统中的一种比喻,类似的比喻还有函数式等其他编程范式。对于现实世界的分析,我们可以使用认识论建立一个非常简单的模型。

主体 + 行为 + 客体 = 现象

主体:主体是有认识能力和实践能力的人,或者是在社会实践中认识世界、改造世界的人。
客体:客体是实践和认识活动所指向的对象,是存在于主体之外的客观事物。

在认识论中,每一个客观现象的出现,都可以使用主体、客体来分析。找到导致这个客观现象的行为背后的主体、客体,就能清晰地描述事件,也更容易看到问题的本质。从认识论的角度出发,建模的过程就是找到确定的客体作为模型的过程。

基于元模型把 4 种建模方法实例化一下:

系统词汇法(OOA)

用例分析法

四色建模法

事件风暴

主体

-

参与者

角色

执行者

行为

-

用例关系

-

命令

客体

名词,模型

名词,模型

时标性对象、实体、描述对象

领域名词、模型

现象

-

-

业务事件

事件

从这个图我们可以看出,系统词汇法的建模线索不够清晰,直接获得模型,没有从业务行为中抽取的过程。而事件风暴可以这样理解:

执行者作为业务主体,在系统中发出了一个命令作为业务行为,对模型的状态发生了改变,最终导致了事件的发生。 事件风暴是从事件、命令和执行者为线索推导出模型,整个过程更加完整。

为特定领域调整建模过程

在识别模型的过程中,模型这个词太过于宽泛,因此不适用于业务专家找到这些模型。于是有咨询师认为不应过早强调模型,建议先使用 “领域名词”、“业务概念” 等和业务相关的概念,甚至可以直接使用 “合约”、“单据” 这类和行业相关的词汇。

因此,在和业务专家的交流时候,我们可以换成和当前业务相关的词汇系统。不仅可以让建模方法发挥更好的作用,还可以为客户定制一套建模方案。

我们以事件风暴为蓝本,针对餐饮行业设计一个特有的建模法,姑且我把它叫做 Cake Flow。餐饮行业的过程中,围绕大量的单据展开,这些单据的本质是业务凭证。业务凭证意味着业务中各个参与者的责任转移,所以我们可以寻找模型的阶段调整为 “寻找业务凭证”。

我们依然可以使用事件风暴的结构:

  1. 寻找事件。这些事件的线索是业务凭据被改变或者转移。

  2. 寻找命令。找出那些业务参与者发生了什么行为修改业务凭证、生成了新的凭证。

  3. 寻找业务凭证。比如:菜单,是餐厅能提供产品的凭证;桌位,是接待客人的凭证;订单,是一次产品供应的凭证;出餐小票,是后厨生产的凭证;发票,是交税的凭证。

在建模的过程中,先不引入计算机中的技术概念,通过走访餐厅、收集它们的单据、调研优秀餐饮公司的工作流。避免需求叙述过程中制造的新概念、重新命名的业务名词,根据奥卡姆剃刀的原则,减少 “伪需求”的产生。

同样的,架构师需要意识到为特定领域调整建模方法的局限性,只有在特定的范围内才能发挥作用,如果把 “合约”、“业务凭证” 这类词汇系统带入其他行业,会让业务专家更加迷惑。

设计自己的建模方法

根据元模型,选取一个建模视角(从主体、行为、客体和现象选择),可以轻松的设计一个适合自己的建模方法。Cake Flow 的结构还是先从事件出发,那么我们这次选择另外一个视角出发会有什么好玩的事情发生呢?

比较少的建模方法从主体出发,这次我们选择从主体出发,先找出业务的参与者,通过角色扮演的方式建模,我把这个方法叫做 “Play 建模法”。 [26] 这次的建模方法的流程完全不同于 Event Storming 的结构,而且更为有趣。

  1. 寻找业务参与者。将业务的参与者全部找出来,在工作坊中找到熟悉该角色工作内容的人扮演。如果让工作坊更为有趣,可以用 A4 纸叠一个帽子,写上该角色的名字。

  2. 每个业务参与者需要有两个人来扮演,一个人扮演按照正常操作者,另外一个人扮演异常操作者。

  3. 选择一个场景开始,正常操作者在墙上用便利贴逐步写上该角色工作过程中的行为,这些行为需要产生业务凭证。异常操作者需要寻找任何可以退出、停止的行为触发异常流程。

  4. 扮演足够多的场景,从这些行为中提取业务凭证。如果异常操作者发现流程漏洞,需要梳理合适的分支流程。

  5. 对业务凭证进行细化、展开得到领域模型。

  6. 回顾扮演者的职责转移,业务凭证的转移往往意味着上下文的切换。比如,订单生成后,需要分解为不同后厨的出餐单,凉菜、中餐、甜品在后厨由不同的厨师完成,订单和出餐单发生了业务凭证的转移。

Play 建模法有几个特点。有明确的职责转移,容易找出上下文;角色扮演的方式比较真实和有代入感,避免单纯的业务叙述带来误解;异常操作者可以用来提前发现流程中问题,让流程更加完善。

当然,Play 建模法只是通过元模型设计出来的一个例子,在实战中需要继续打磨。根据元模型,我们可以根据一些特殊的场景设计出合适地建模方法,更进一步可以为客户设计专属的建模方法。

5.3 多对多关系主客体分析

多对多关系是软件建模中比较麻烦的场景,如果梳理不清楚对软件架构伤害很大。在不久前的一个项目中,十足的体验了一次多对多关系带来的痛苦。

我们的项目是一种多空间模型,也就是用户可以处于不同的空间,在不同的空间中可以访问空间中的资源。一个空间可以拥有多个用户,用户可以出现在多个空间中。看起来和编程老师在数据库课程中的多对多关系没有区别。

多对多关系
图 32. 多对多关系

对于数据库来说,多对多关系需要一个中间表,一般会使用类似 workspace_user_relation 的名称。假如不对这个中间模型进一步分析,可能会得到 E-R 模型如下。

不确定的中间模型
图 33. 不确定的中间模型

团队使用了 JPA 的 @ManyToMany 注解,导致 workspace 和 user 两个对象无时不刻在一起了。另外,通过 user 可以操作 workspace,通过 workspace 也可以获得 user。

这种设计,不仅在技术上实现困难,对业务的支持也不足。

  1. 用户加入到空间中具有权限,通过这种方式比较难管理。

  2. 空间管理员并没有对用户的修改权利,只有对用户加入、退出、访问空间资源的权利,这种设计诱导了业务提出不合理的需求。比如空间管理员对用户的禁用,其实只是对用户参与到空间中的行为禁用,而非对用户禁用。

  3. 关系表中的创建时间的含义是用户加入空间的时间,使用中间表语义不明显。

隐藏的客体

在很多编程指南和规范中,都有写明不允许使用多对多关系。在一些框架中,虽然实现了多对多关系,但是往往不推荐使用。

因为我们在开始学习编程的阶段中,接受了数据库的关系理论。数据库关系理论是 1969 被英国计算机科学家 Edgar Frank "Ted" Codd 首次提出。关系数据库理论继承了集合论的的思想,在处理数据上有独特优势,被广泛使用。关系数据库理论可以做到降低冗余,提高一致性的能力。

关系模型被用来存储数据、处理数据非常好用。但是,面向对象作为一种流行的编程模型,它是用来模拟现实业务的。面向对象构想的信息结构是树形,而关系模型是集合。

它们有一个天然的鸿沟,就是这两种结构如何转化的问题,因此出现了大量 ORM(对象关系映射) 软件来试图解决这个问题。数据库中的普通关系(一对一、一对多)可以使用面向对象中的 “组合” 来映射,但是多对多关系却极难被处理,这也是一些框架不建议使用的原因,但往往难以说明其中的道理。

其中的道理是什么呢?因为,关系模型中的多对多“关系”,映射到面向对象在本质上是一个“隐藏的模型”。

我们用认识论中的主体-客体思维来看待这个问题,主体-客体可以让认识问题变得更深入。主体是有认识能力和实践能力的人,或者,是在社会实践中认识世界、改造世界的人。客体是实践和认识活动所指向的对象,是存在于主体之外的客观事物。在业务系统中,我们可以把 Controller、Service 这类带有行为能力的对象看做拟人化的主体,而 Entity、Model 看做客体。

回到上面的例子,对于工作空间、用户而言,当把用户加入工作空间的时候。我们发生三步行为:

  1. 使用了用户信息、工作空间的信息,这一步用户、工作空间都是被感知的客体。

  2. 创建了一个关系“工作空间-用户”,这一步“工作空间-用户”是客体。

  3. 把这个关系加入到工作空间,扩充了工作空间的信息,这一步工作空间是客体。

问题的关键是我们往往没有找到一个好的名词来描述“工作空间-用户”这个概念,一旦这个概念被明确下来,我们的模型就清晰了,多对多关系就不存在了。

举例来说,我们可以给“工作空间-用户”找到如下的名字:

  1. 空间成员

  2. 参与者

  3. 空间用户

真实的例子中,我们使用了空间成员来作为这个隐藏模型的名字,因此空间和用户的关系被拆解为 “空间拥有多个成员” 和 “成员可以引用用户” 两个关系。

确定的中间模型
图 34. 确定的中间模型

另外一个例子

大部分的多对多关系都可以通过这种方法消除,不过,除了起名字这个难题外,还有另一个问题。

多出来的这个隐藏模型和谁走?我们使用一个例子来说明这个问题。

在很多系统中,我们都需要使用 “标签”,而标签和特定的资源都是多对多关系。明白上面说的逻辑后,我们把标签存在于某个资源中的关系叫做 “标签项”。但是,如果同时有多个资源都需要使用便签,标签项跟谁走呢?

如果所有类型的标签都跟着标签走的话,可以做出一种通用的标签系统。其结果类似于搜索系统了,通过标签系统处理所有的业务。这样设计会给聚合搜索带来便利,但是标签在具体业务中的使用变的困难。

如果标签跟随具体的业务走,那么隐藏的中间模型就是具体的业务中的一个概念,比如文章专题中的标签、文章中的标签。通过这样的处理,可以让系统解耦良好。不过,代价是聚合搜索能力需要额外的技术来实现。

中间模型跟随的两个方向
图 35. 中间模型跟随的两个方向

这个例子充分说明了模型的建立需要为业务服务,业务人员往往需要明确其业务重心,并做出一些权衡和取舍才能设计出合适的模型。

5.4 领域建模的原则

如果团队和系统的规模不大,可以根据一两个人的经验设计出足够合适的模型。但是,当团队规模非常大、系统极其复杂的时,我们就需要制定一些原则来评审、检查各个团队产出的模型是否合适。

这些原则也许不能指导所有的场景,但是能在一定范围内做出约束。年轻的工程师总是喜欢自由,经过历练的工程师开始理解到约束的好处,想法也变得成熟。

我收集了一些社区讨论的观点,这里整理了一些 DDD 战术建模中的一些原则,作为软件领域建模中的基本要求。

1. 当一个【实体】被多个聚合根使用时,需要将其设计为【聚合根】或者将其拆开,不能再作为实体使用。

如果我们将聚合理解为系统中业务一致性、生命周期相对独立的一组实体,可以作为系统设计的基本单位,那么,一旦出现被多个聚合共享的实体,聚合就不再有意义了。

当两个聚合中出现了相同、相似的实体,有时候我们可能想要减少实体的数量,于是有了将其合并在一起的想法。比如,在分销系统中,销售和退货由两个不同模型实现,但是它们有类似的操作记录。如果将操作记录作为实体,但是处于不同的两个聚合,就会让这两个聚合耦合,让开发人员在开发时摸不着头脑。

类似的,在不同的业务场景中都会使用到附件,如果将附件作为实体存在,会造成混乱,与其这样不如直接设定一个原则,不允许出现共享实体的聚合。

2. 不允许使用【中间表】处理多对多关系,探明多对多原因,明确中间模型的归属。

多对多关系是领域建模的杀手,但在有些地方却会是消除系统耦合的钥匙。

一个多空间系统,用户可以出现在不同的空间下,空间也可以容纳多个用户。看似是一个典型的多对多关系,我们大多数情况下会使用简单的中间表处理。

使用中间表往往意味着没有创建时间、状态等额外字段了。但是我们仔细一分析会发现,这个中间表的创建时间就是用户加入空间的时间,也就是说它是具有业务含义的,只不过被我们疏忽了。

当出现禁用空间下的用户业务时,只是删除中间表无法表达合适的业务需要,于是我们可以在中间表加上状态以满足业务需求。随着业务的丰满,中间模型就会显露出来,慢慢体现其重要意义。

多对多关系的存在,让我们无法建立合适的聚合。也就是说,无法将中间模型的归属问题明确下来。查询空间时,可以获得用户列表,同样的查询用户时,也可以获得空间列表。

那么,是用户拥有空间,还是空间拥有用户呢?

这就变得混沌,我们明确中间的模型为“成员”,明确空间拥有“成员”。当需要根据用户查询所属空间时,本质上是根据用户在空间下的成员信息来筛选空间。

当然,中间模型可能会归属到任何一边,这就需要架构师来拿捏和设计了,但是重要的是,中间模型的归属问题需要尽早的明确下来。

3. 区分【关联】和【拥有】,避免将本应该关联的模型设计到聚合之下,否则聚合非常大。

本条原则可以避免聚合设计过大,也可以避免不合适的生命周期。

以银行信用卡开户流程来说,代入到具体场景,银行账户是一个核心的模型,可以构成一个聚合。相关的,在开户时,会提交一个开户申请,银行的工作人员会对信息做出审核,完成审核后进行开户。

一个不佳的设计是,账户不能将开户申请纳入聚合中,因为申请的生命周期和账户并没有关系。开户申请和账户之间可以存在关联,但是不应该具有拥有关系。

4. 领域模型和数据库保持一致。

本条原则约束了领域模型落地实现的处理方式。

在理想的情况下,领域模型、数据库、API 都能体现系统状态(RESTful 叫做表征状态转移)的变化,如果能一一对应能让系统的复杂性降低,换个时髦的说法是让“熵”足够低。

有时候,我们会偷懒,想要将不同的模型持久化到同一张数据库表中,节省数据库设计。但是,这种差异造成了团队认知负载。如果没有必要,不建议这样操作。

5. 聚合的层级保持在 2 级,最多不超过 3 级。

这条原则非常好理解,层级过会带来落地上的巨大成本。

聚合的大小是领域模型设计中非常难取舍的地方。过大的聚合持久化,更新操作都不好处理;过小的聚合业务一致性得不到保证。

根据经验,2-3 层的聚合已经能满足大部分场景,如果超过 3 级,考虑将部分模型进行分解。

6. 事实数据快照化。

这条原则往往容易被初级的工程师忽略,但是非常重要。

根据范式理论,如果想做到很高的一致性,就不应该冗余过多的数据,这是大学数据库课程的基本内容。但是现实情况不能一概而论,对于重要的交易业务来说,完成业务后不会再更新,不存在一致性要求,反而是应该锁定交易时发生的关键数据。

这是因为一些事实数据本质上是业务合同。举个例子来说,合同的甲方乙方会记录下身份证号码、以及名字,即使当事人去派出所变更了姓名,也不会影响到合同中的主体。

7. 核心交易,设计交易流水或日志,用于审计。

接上一条原则,交易发生后,可能会对一些账户、库存、积分等信息进行变更,需要意识到为这些重要的信息记录流水、操作记录或者日志做备份。

这是因为大部分信息系统都有商业契约性质,为了保护用户利益,需要在系统中留下足够的痕迹,避免未来“扯皮”,在纠纷发生时能提供证据。

8. 抽象类核心模型,提供拓展策略。

如果我们将一类相似的模型抽象统一后,注意设计良好的拓展策略,避免抽象后的模型无法支持拓展。

每位工程师都应该听过编程中追求复用的原则,但是并非所有的工程师认识到复用和抽象带来的制约。当抽象发生时,意味着放弃了一些个性化的数据和行为,被抽象的模型在以后的命运中被绑定到一起。

如果我们想清楚了需要将一组抽象到一起,应该通过“不变点”找到共性,然后通过各种设计模式(例如,适配器模式,策略模式)为“变化点”提供拓展。

举个例子来说,餐饮领域中外卖、堂吃是两种不同的订单,外卖具有送货信息,堂吃具有座位信息。如果我们将两种订单抽象为一起,设计了订单模型,这是合理的,因为订单是“不变点”,和金额、结账、支付有关。

对于送货信息、座位信息可以使用适配器模式隔离出来作为独立的聚合并关联订单,避免订单上挂载送货信息、座位信息这类和场景相关的信息。

9. 当业务变化时,分而治之;当业务稳定后,抽象统一。

接上一条原则,如果在是否将相似模型抽象到一起而犹豫时,说明没有足够的信息输入,以至于缺乏信心。

抽象的模型是通过归纳产生的,如果没有信息做出归纳,可以优先分而治之,待业务明确后再重构为统一的方式。

10. 让合适的人做合适的决策,并做好决策记录,为后续决策提供背景信息。

最后一条是写给架构师的原则。

如果一个团队存在专门的架构师,而且团队又非常庞大时,架构师无法获得完整、足够细致的信息,需要承认无法在任何场景下都做出合理的决策。

架构师应该只关注系统核心的模型,以及划定上下文边界附近的模型归属,确保系统作为一个有机的整体。而对于系统某个角落的模型设计应该交给具体的开发人员来决定,记得做好决策记录就行。因为架构师认识到什么重要,什么不重要比事无巨细的决策更有意义。

5.5 总结

对于领域模型而言,我们是站在结构而非流程的视角上的。我们就不应该把流程、行为赋予领域模型。如果我们在设计一台机器,工件可以看做一个聚合。工件在被用到的各个地方才具有相应的能力和用途。

需要时刻注意的是,领域模型表达的是软件的逻辑结构。沿用工件的例子,一颗螺丝可以被用到婴儿车上,也可以被用到起重机上。但是有些工件却是某个特定机器的零件,虽然它并没有特定的功能,但是有经验的老师傅一眼就能看到它可以被用到那些机器上,并提供某种特定的功能。

大部分情况下,领域模型就是数据库表在代码中类的体现,所以不是特别赞同为了"干净"将领域模型和映射数据库的类分开,大部分情况下领域模型(代码中)就是数据库表(数据库中)的映射。

第 6 章 分层,软件架构和实现

什么是软件架构?通俗的来说,软件架构就是将软件中合适的组件放到合适的地方,这就让软件架构变成了一种混合大量经验的艺术行为。

架构是什么?在维基百科中,软件架构的定义是:

软件架构是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计。 软件架构会包括软体组件、组件之间的关係,组件特性以及组件间关係的特性。

那么我们可以解构一下,软件架构的内涵就是:

  1. 组成软件结构的元素。

  2. 结构之间的关系。

除此之外,还需要有一些原则来指导具体地实施,类似于施工规范和标准。在设计软件架构时候,会做大量的决策才能得出最终元素+关系的形态。但是我也不需要将所有的细致入微的决策都马上做出,而只是做出以后不那么好调整的决策就行。维特根斯坦说“世界是一切发生的事情”,我们也可以说架构是一切决策完成后的事情。

于是我们可以将架构描述为:

软件架构 = 架构中的元素 + 关系 + 实现原则和技术规范。

软件架构的过程 = 一切决策之和。

架构中的元素有可能用不同的视角来表达的,也有可能具有层级结构,于是我们往往通过图形化的方式来描述和表达,也就成了传说中的“PPT 工程师”。

6.1 复杂性管理

从复杂和混乱的信息中找到重点才能定义出元素以及找到关系。架构是一个非常玄学的领域,它不像编写一个确定的中间件一样,有明确的输入条件和输出。架构充满了权衡、取舍和纠结,其原因就在于复杂性问题。复杂的信息越多,系统熵越多,没有能量输入时,系统逐步趋向复杂、无序状态。

《人月神话》里面提到两个概念:本质复杂度和偶然复杂度。本质复杂度是在解决问题时,无法避免的事;偶然复杂度是做事方法不对,人为引入的复杂性。

架构师就是与复杂性对抗的人。

本质复杂度无法避免,而导致偶然复杂度上升的原因有:

  1. 沟通节点增多,软件项目中,人越多,信息节点越多,偶然复杂度越多。

  2. 信息噪音过多,就像信息论描述的那样,当噪音太大真实的声音就被掩盖了。

我们必须能找到办法对抗复杂,我们能用到的工具有:

  1. 获得信息“地图”,通过模型过滤掉多余的信息。

  2. 分而治之,将复杂度隔离到局部,让局部的复杂性减弱,将复杂性进行分层。

  3. 统一语言,降低沟通的熵。

图是复杂信息的索引

在架构设计中,过滤掉多余信息的方法就是做 PPT 或者画图。

PPT 的真正作用是复杂性管理,这也是微软幻灯片软件 PowerPoint 的寓意来源。据说 Gaskins 在给 PowerPoint 起名字时,最初在洗澡时迸发的灵感,想到的名字中包含了这个词。而且在不知情的情况下他的同事 Glenn Hobin 在机场的海报中,一眼看到了 PowerPoint 这个被强调的词。

为什么幻灯片做得好的人升职越快,而且比一线干活儿的人升的更快。因此有时候一线开发人员愤愤不平,为什么做幻灯片不写代码就能获得高薪。实际上做 “PPT 工程师并不轻松”,靠的是对问题深刻的理解、信息的分析、有效的呈现等多种能力。

一套架构用的汇报材料,可能就是某个复杂系统一份极好索引。全景是什么、边界在哪里、骨干的业务逻辑是什么,都需要体现在幻灯片中。

所以做好幻灯片的前提是深刻吃透业务、产品、技术方案,而且还需要具备非常强的表现能力,把方案清晰地表现出来。幻灯片图表并非只是展示,更像是一个索引来描述复杂的系统。

优秀的架构师、咨询师都能做出好的幻灯片,有时候甚至幻灯片就是一种很好的概念模型,这样想就应该能把幻灯片重视起来。没有好的思维结构,就做不出幻灯片,想到的不一定能表达出来,所以幻灯片做的好的人具有特别强的思维能力。

简化和克制的图才是真正有用的图,因为保留的有效信息更为突出,将庞大无比的系统简化到几张 A4 能够被打印出来的纸时,它的复杂性才能真正被驾驭。

分而治之

分而治之不能降低复杂性,只能隔离复杂性。而分而治之可以通过不同的维度来进行,主要分解的方向有三个:

  1. 水平分解。对系统进行分层,下层对上层透明,上层的开发者无需关心下层的变化。

  2. 垂直分解。对系统进行按模块(上下文)切割,上下文之间不需要关心彼此,只在有互相依赖的情况下了解对方。

  3. 按时间分解。对系统的实现很分步骤完成,根据迭代演进,制定产品、架构路线图(RoadMap)。

架构维度
图 36. 架构维度

注意区分广为流传的 AFK 拓展立方体,AFK 拓展立方体更像是对系统容量的分解,而不是对复杂度的拆解。

对于上面的图片来说每一个版本我们可能都会进行垂直、水平分解得到一张平面的架构图,在持续演进的状态下不断更新。

这就印证了我们前面说的架构的过程是一切决策之和。架构决策的影响有时候远远被我们低估,有时候我们的决策是基于上一次决策之后的结果做出的,而不是最初的问题,这就让架构问题变得更加复杂。

由于架构设计不是一次性做出来的,水平分解、垂直分解不能体现真正的复杂性,往往问题的复杂性来源于架构决策的前后因果。

6.2 系统水平分层

分层的目的是水平隔离复杂性,那么怎么定义“一层”呢?由于对具体分层的定义非常模糊,导致了我们实际上分了很多层,但是却觉得没几层。

架构分层的主客体分析

互联网通信依赖的网络协议 TCP/IP 是一个非常经典的分层模型,因为全球网络是一个经典的分布式系统,实际上我们无论在设计哪种形态的分布式架构都可以参考网络协议的设计思想。

我们在学习 TCP/IP 或者 OSI 分层网络时会使用一个常见的“邮差”比喻,来形象的描述网络协议的原理,其中就体现了分层的思想。

Montreal 需要寄送一个信,她在信的结尾写上了自己的名字作为落款,然后通过邮局将其寄出。邮局进一步包装贴上邮局的标签,并发送到运输公司。运输公司将其装箱,并通过不同的交通工具将其递交到目标的站点,并发送到目标邮局,也就是他们在目的地方的客户那里。最终,邮局将信件发送到收信人手中。

我们将整个过程看做三层:用户层,也就是收信人、发信人收发信件的过程;邮局层:邮局的工作人员处理邮件的过程;运输层:物流公司通过不同的交通工具运输货物的过程。

有时候,我们仅仅通过行为来描述分层很难说清楚分层是什么,比如邮局和物流公司的分层在某些情况下可能说不明白。我们可以通过另外一个视角来看待这个问题。

邮差比喻
图 37. 邮差比喻

图片来源:https://www.eecs.yorku.ca/course_archive/2010-11/F/3213/CSE3213_03_LayeredArchitecture_F2010.pdf

任何一个行为都能找到它的操作者以及身份,也就是行为的主体,也能找到被操作的内容,也就是行为的客体。我们可以通过分析主体、行为、客体三个要素来辨析分层之间的关系。这样让分层更加明确。如果能在该层找到明确的主体对象、客体对象,并说明其关系,我们就能将其说清楚。

我们用一张表格来划分,并将其表述地更加精确:

分层 主体 行为 客体

用户层

收信人、发信人

收发信件的过程

原始信件

揽收层

邮局、揽收点

揽收寄件,并打包的过程

包装后信件

运输层

物流公司

运输货物,装箱运输的过程

物流箱

通过主体的明确和客体的明确,主体之间的职责会清晰地浮现出来,主体的权责更加清晰,我们细心分析也会发现这种分层也是社会化分工的体现。主体的性质是截然不同的,邮局、揽收点作为法律主体时,一般不是以自然人的性质出现的。另外物流公司这类主体往往也需要额外的资质、营业许可,侧面的说明了分层的要素。

这是现实中的分层思想,那么在软件中是不是这样的呢?假设以后端业务系统的经典三层结构作为例子,我们来看下它的分层主客体分析:

分层 主体 行为 客体

Controller 层

Controller

处理业务场景

Request/Response

Service 层

Service

处理通用能力

Model

Repository 层

Repository

处理数据持久化

数据/SQL

用主客体来分析,MVC 模型如果没有 Service 时,只能算两层,因为 Model 只是客体(忽略 Model 和其属性之间的主客关系),构不成完整的一层。Service、Repository 层都有对应的主客体关系,能够说清楚它的权责关系。

如果按照网络协议的分层设计,下层是不需要知道上层的信息或者知识的,也就是说理想的情况下 Repository 层的客体应该是无差别的数据才对。所以我们可以看到 JPA 这类 ORM 工具接收了两类参数:数据体 + 领域模型的类型信息。当我们无法实现出无差别的 Repository 层时,才不得不使用持久化对象这类概念。

所以这里总结下对分层的理解:

  1. 分层是主体权责的让渡,通过分层演化出更多主体,实现分工。

  2. 下层需要尽可能地提供无差别的能力给到上层,让上层对下层保持透明。

那么通过辨析主客体的关系,就能提高代码的表达力,尤其是在命名上。所以对客体起名的关键在于定义这个客体的概念,使用拟物的方式起名。对主体的起名需要定义它的职责,使用拟物的方式起名

这样就能通过类似“主谓宾补”(主语:服务对象,谓语:方法,宾语:参数,补语:返回值)的方式编写代码,让我们在编写业务代码时思绪流畅。

应用和服务分离

良好组织代码的关键不是将方法划得足够小,而是对象各司其职。 架构的本质就是将各种库、业务代码、基础设施等架构的组成部分良好的组织到一起,这是在成为架构师的路上必须想通的一环。企业架构框架把信息架构分为四层:业务架构、应用架构、数据架构和技术架构。如何把业务系统中的代码良好的组织起来,就是我们应用架构中的内容。

应用和服务分离 是一个非常简单的原则,在各个地方都有体现,但是没有编程大师像 SOLID 原则一样明确的表述出来,但它又很重要,能给我们一个如何复用代码的准则。

“复用就一定好吗?”

当我向同事问出这个问题的时候,同事一脸茫然,好像软件开发本来就应该这样,所有的代码都应该尽可能的复用。

复用,在多数人的眼里已经是理所当然了,但有时候还是忍不住提醒一下,复用只是手段而非目的。

复用是通过消除重复代码的方式,得到一系列可以重用的代码片段,在需要的地方组合使用即可,提高开发速度的同时,也可以提高整体的一致性。

显然,组合组件用的胶水代码是不需要复用的,因为组合本身就是为了解决场景中的事情,不再具有复用价值。强行复用的后果有两个:

  1. 场景特有的东西被纳入组件,导致组件的复用性降低。信息被泄露到组件中,组件和场景中的代码职责不清晰

  2. 响应业务变化的能力反而降低了,说白了就是不好改。

有时候两段代码虽然看起来只有细微的差异,但是也不要复用它们。对于全栈开发者来说,这个原则对我们设计前后端的代码都有好处。在后端,我们可以使用 DDD 分层中的 application 让代码变得更清晰;在前端,我们可以将业务组件分为 pages 和 components 提升设计。

我们知道,在Eric DDD 的分层架构中,将系统分为了 4 层:

  1. 接入层(Interface)。

  2. 应用层(Application)。

  3. 领域层(Domain)。

  4. 基础设施层(Infrastructure)。

我们可以这样看待应用层:

应用层,负责组织业务场景,编排业务,隔离场景对领域层的差异。

应用层的目的是处理不同应用场景的差异,它被用于不同场景的关注点分离中。例如,用户下单可能会涉及多个原子的操作,订单、支付、积分累积等逻辑。

思考一个问题,为什么 DDD 中引入了一个应用层。没有它我们会面临什么问题?

如果缺乏应用层(在很多微服务系统中都是这样的),导致领域服务和场景绑定,复用性大大降低。例如系统接受用户自己注册,也可以使用微信登录完成一个隐藏的用户注册。另外一个例子,对于新用户,系统会为他赠送一些积分,在没有应用层的情况下,服务被前端直接调用,于是服务不得不定义来自不同渠道的 API。在下面的示例中,微信自动登录会比浏览器注册多好一些内容。

无应用层架构
图 38. 无应用层架构

在一些情况下,大家只是把这层当做一个简单的代理,大量的和场景相关的逻辑进入了领域层,依然会为系统带来麻烦。

有应用层架构
图 39. 有应用层架构

我们重新思考应用层,它到底解决了什么问题呢?

有一个典型的场景,就是管理员和普通用户,在使用场景的差异非常大,看似是具有不同的权限的同一个操作其实未必是同一个用例。例如,用户能通过 API 获得商品列表,管理员能看到未发布的产品列表。对于没有经验的工程师往往会编写一个 API 然后通过一些权限机制来限制它们的访问。

注意,这不是权限的区别!这是用例的区别。

管理员查看商品列表是一个用例,用户查看商品列表是另外一个用例。当我们不再把用例混淆的时候,就能理解应用层了。我们重新看待应用层和领域层两个层次的定位:

领域层,实现具体的业务逻辑、规则,为应用层提供无差别的服务能力。
应用层,组织业务场景,编排业务,隔离场景对领域层的差异。

当我们能把每层的的职责弄清楚之后,代码的组织变的如此清晰,而在此之前我们还在靠把代码划分的更小来实现的。在前端开发中,随着工程化的发展,开发者把组件划分的越来越小的时候,也会有类似的问题。下图表达了 Store 模式的数据流动关系,对应的实现有 Redux、Vuex。

无应用层前端架构
图 40. 无应用层前端架构

从技术的角度看,它的逻辑非常清晰,但是在实际的工程项目中会有一点小问题。

Action 的发生是从 Menu 等这些基础组件中发出的,也就意味者,Menu 组件和全局的状态联系到一起,这个时候 Menu 组件的复用性就降低了。

换个例子,设计一种弹窗组件,这个弹窗组件和全局的 Store 数据联系到一起的话,如果想要做到基础的组件在各个地方干净的使用,那么状态的承接工作就不应该由基础组件来完成。

我经历过几个项目,设计者没有意识到这个问题,带来的后果就是,组件为了复用不得不写很多条件语句。比如模态弹窗不得不使用枚举来区分是那个用途的弹窗。

问题的关键同 “应用和服务分离” 类似。如果页面用于承载状态,组件用于复用,那么两种组件具有了清晰地定位:

Pages,用于承接页面状态,和后端通信等业务逻辑。

Component,用于承载 UI、交付逻辑,需要通过参数、事件和 pages 传递数据。
有应用层前端架构
图 41. 有应用层前端架构

水平划分的权责

服务划分是职责划分的问题,职责划分的问题是权责利的问题。权责利是管理的基本思想,从这个角度上来看,架构设计和管理并无差别。

我们拿几个更具体的例子来说。在一次架构评审会议上,有一个问题大家争执不休,问题的背景是这样的:

某会议软件,具有几十个微服务,这些微服务都需要鉴权,基本的思路是通过 Redis 集群来存储会话数据。不过在是否应该将 Redis 集群直接暴露给微服务使用,在架构设计中有两种声音。
一种声音是为了性能提高,微服务需要直接能访问到 Redis 集群,而不是通过 REST API 等接口方式通过一个服务来中转。因为会频繁调用该接口,性能上难以保障。
另外一种声音是,性能虽然有损失,但是和数据的封装性比起来不值一提,不应该直接暴露 Redis 集群。

在这个案例中,我们不妨问这样一个问题。我们为什么需要封装一个鉴权服务?

原因很简单,需要有专门的人来维护这个服务,并提供相应的能力。直接连接 Redis 会将这份工作让渡给了各个微服务,而不是 Redis 集群的运维团队,毕竟 Redis 集群的运维团队的职责只是提供 Key-Value 数据的存储,而与具体的业务无关。

如果将工作给了各个微服务,也就意味着 Redis 集群的使用权公开了,鉴权工作的考核(利)也分摊了。慢慢的,这个 Redis 集群会变成一个多方共管地区,会有更多的无关数据被写入,也变得危险和不稳定。

将鉴权服务封装起来的目的是权责利的隔离,封装成服务只是手段。这样看来,只要目的达到了,手段可以是多种多样的。我们可以考虑让一个团队构建一个 SDK 来提供会话数据访问的能力,这样既能满足权责利要求,也能避免一次网络通信,提高性能。

还有另外一个例子。我们在规划一个分销系统,分销系统会涉及组织结构、商品维护、订单流转、仓库库存、结算等多个上下文。这里就会出现一个矛盾,订单流转和库存之间会有强烈的耦合,如果将其合并可以减少分布式事务、频繁的跨服务调用的问题。但是,将其合并后,仓库库存和订单流转之间耦合了。

为了清晰地理解这个矛盾,我们可以回到现实中。订单流转是订货、发货方两个销售主体之间的关系,但是物流是基于仓库来说的,仓库是货物的主体。

从职权关系上来说,订单的流转和仓库库存之间的职权是不同的。我们可以将其微服务想象为一个虚拟的电子助手,这个电子助手应该能提供相应的能力,自然也需要承担责任,同时有权利访问对应的数据。

那么拆开后分布式事务怎么看待呢?

在现实世界中,如果交易的双方在地理位置上处于相同的位置,自然可以一手交钱一手交货。如果不幸的是,不能当面交易只能通过书信或者电话远程交易,当交易发起后,其中任何一方返回就会产生冲突。

回到计算机世界,并不需要惧怕分布式事务。让最终一致性的收敛速度足够快,就可以看做强一致性。虽然我们应该尽可能的避免分布式事务,但是作为分布式系统应该坦然的接纳分布式事务的存在。不过需要警惕,无论技术上多先进,收敛速度多快,都会在一定几率上发生冲突。这也并不是大的问题,只需要人工的干预即可。

6.3 系统垂直划分

服务划分的目的是垂直分解复杂性,垂直是指在某一层内的垂直。也就是说,在不同层垂直划分的粒度可能并不相同。

垂直分层
图 42. 垂直分层

图片来源:https://www.eecs.yorku.ca/course_archive/2010-11/F/3213/CSE3213_03_LayeredArchitecture_F2010.pdf

在很多系统的垂直划分时最大的误区是穿透了分层,想象一下我们有一套自己的通讯协议,这套通讯设备同时具备了应用层、网络层、传输层、数据链路层,那么这套通讯协议就很难被归纳到 TCP/IP 协议簇中了。

垂直划分的权责问题

实际上水平分层比垂直分层要简单很多,因为我们很容易根据工作的性质识别到他们边界。比如,网关、业务服务、数据库中间件,很容易就知道他们的分层关系。

我们怎么找到垂直划分的边界呢?

技术类的垂直划分实际上比较简单的,比如接入层,我如果有两种物联网设备接入协议,我们很容易将其根据协议类型划分开。这是因为计算机科学家在这些领域有充分的解决方案。

但是业务服务的垂直划分就非常麻烦了,特别是没有经历过沉淀的创新类软件系统。以企业通讯软件为例,企业通讯录、群组、用户这几个概念若即若离,无论是划分开、还是合并到一起都会有不少的麻烦,有时候甚至没有完美(或者有些架构师称作干净)的解决方案。

我们会发现,垂直划分和水平划分的特点可以被归纳出来,这便于我们对系统进行设计。

划分方式 特点 示例

水平划分

性质具有明显的不同

领域层、网关

垂直划分

性质类似但是职责范围不同

用户服务、会议服务

下面这张图为互联网收银系统的分层架构,水平的方向使用了同样的背景色,他们的性质基本类似。假设这个系统是非常理想的方式设计,接入层为不同的网络接入方式,它取决于应用场景,它的垂直划分非常容易。

但是对于应用层来说,如何清晰地界定那些属于应用,需要对产品设计有非常深刻的理解,以及和产品经理达成共识。对于领域层来说,如何找出相对独立的能力单元也不是那么容易(当我们把领域逻辑和应用逻辑分开后,领域层的垂直划分相对简单一些)。

完整示例
图 43. 完整示例

那么对于业务相关的服务来说,我们有什么线索可以进行垂直划分吗?对于应用层的服务来说,我们可以以使用该应用的业务主体来划分。比如在餐饮系统中,我们可能会有下面几个主体使用该系统:终端用户(店员)、商户、系统管理员、第三方 API调用者,在应用和服务分离部分我们已经详细讨论过这类问题,应用层的划分比较容易。

那么领域层呢?领域层的微服务之间大部分情况下是平等的。由于领域服务和系统状态(有自己的数据库)相关性比较强,这些状态可以通过模型(实体)来表达。这也是为什么我们通常说的微服务划分,实际上是说的领域微服务,它们的划分和上下文划分可以意义对应。所以领域服务的划分,是根据系统所处理的客体来划分的(这是为什么我们划分领域服务需要先找模型,因为模型一般是作为客体出现),这是一个比较好的线索。

这里总结下应用层和领域层的划分线索的区别,以及辨析权责关系:

分层类型 划分方式 权限 职责

应用层

参考业务主体为线索来划分

访问领域层、基础设施层的服务能力,无权修改系统状态的

编排领域层,为业务主体提供个性场景

领域层

参考业务客体(领域模型)的分类来划分

修改系统状态的能力,无权干涉应用场景

提供上下文内对系统状态的管理职责

当权责关系被定义清楚后,开发团队在开发时能减少沟通的成本,但是并不意味着应用层和领域层的鸿沟可以忽略。对于规模非常大的系统来说,让领域层持有所有的系统状态会变得过重,也可以考虑让应用层持有一些局部的领域逻辑。

比如在餐饮系统中,收银机应用中可能会有一些临时数据,这些数据不需要被运营管理后台和商户后台所管理,为了灵活性考虑增加局部的状态,承载方式可以是数据库或者 Redis 等。

架构是供需关系

垂直方向的划分,供需关系也是一个非常重要的线索。

在几年前,我经历了一次红蓝项目。所谓红蓝项目就是类似于军事演习中,为了训练自己的军队,虚拟了一个敌人,通过给虚拟的敌人配置不同的火力来检验自己的战斗力。

但是红蓝的软件项目有点不同,软件项目的红蓝是指业务方提出了需求,不同的研发团队都接了这个任务,最后由公司的高层评估哪一个团队研发的成果更能胜出。往往残酷的是,输掉的团队会被解散到其他团队中,甚至整体裁掉。

当然,实际工作中这种情况发生的更加隐晦。一个公司的研发团队不仅仅面临着其他研发团队的竞争,实际上还有市面上成熟的产品、外包团队等外部的竞争对手。

对于架构师来说,不得不认清的一个现实是,软件开发是一个供需关系,无论发生在公司内部还是外部。供需关系的双方不仅仅局限在研发团队和业务团队两个主体之间,还发生在研发团队和另一个研发团队之间。

当一个服务的 API 频繁被其他团队需要时,这个团队就自然的不会过多的参与终端业务开发了,而是忙着给其他的研发团队提供通用能力。如果公司内部具有 API 调用结算机制,或者提供能力给内部团队也算作一种考核,供需关系就变得更加清晰起来。

这是因为当系统变得极其巨大的时候,系统不再是规划出来的了,是根据供需关系生长出来的,这种效应在越大的公司越明显。这会给我们一个错觉,大型公司感觉非常不专业,时时刻刻都在做无用工,每年规划了几十、上百个系统,然后存活下来的寥寥无几。

反而是创业公司看起来更稳,细致的规划,灵活的调整,而不是像大公司这样大动干戈。于是很多架构师和程序员会有一个疑问,为什么公司不进行细致的规划呢?

如果一线的程序员多和 CTO、架构师们聊聊天的话,会发现一个事实,CTO 们也不是三头六臂将所有事情都规划的妥妥帖帖,因为系统的复杂性必然会超出人的宏观规划能力。

成功的企业解决这些问题背后的方法极其简单粗暴——试错。架构的一切出发点是有业务需求,而且这些业务需求是真实的“生意”才行,当业务部门愿意拿出预算进行研发时,供需关系就产生了。

在一个公司整体的层面上,CTO 更像是一个裁判,他需要有敏锐的眼光找到最适合的人来承接,以及宏观上需要什么,而不是规划、指导怎么研发。对大厂来说,浪费不过是计划之内的事情,这样看来重复建设是为了自然选择。

CTO 别无选择。架构设计,其实也是一种对业务的抽象,如果业务始终在变化,用一套“灵活”的框架满足“无限”变化,是一件不可能的事情,唯一的方法就是淘汰。

6.4 架构演进路线图

架构演进是通过时间维度来分解复杂度的一种方法,在设计时就考虑架构的演进方式,并制定一套架构演进路线图,对架构非常有帮助。

制定架构演进路线的好处有:

  1. 更容易落地,从最小的、最核心的地方落地架构,但是保持某个方向拓展性。

  2. 容易说服关键的干系人,让当期成本、风险变得可以接受。

  3. 保持团队技术战略在同一个目标,以及排列工作优先级。

  4. 跟随技术趋势,在合适的情况下演进到主流的技术上,让技术成本更低。

架构演进路线主要需要包含当前状态、目标状态、关键节点和时间。比如,我们可以使用企业架构标准化制定组织 Open Group 提供的通用图例绘制架构演进路线。

架构演进示例
图 44. 架构演进示例

图片来源:Open Group 文档

我们在架构设计时,往往拿到的不会是一个全新的系统,从一张白纸开始设计。我们往往容易被当前的系统状态限制,将未来、现在两种状态混在到一起。

比较好的做法,我们可以将架构设计工作分为 AS-IS、TO-BE 两套,AS-IS 用来分析现状,将当前的架构信息重建出来,使用 TO-BE 的工作设计未来的架构方案。AS-IS 和 TO-BE 中间还需要考虑分阶段实施方案、数据迁移方案。

架构路线图需要包含 4 个要素:

  1. 确定当前的状态。包括当前架构的问题和矛盾,我们可以对架构图进行还原,并分析出当前架构图中的痛点。

  2. 确定理想的状态。包括未来的状态是什么,需要满足什么样的目标。比如能够支持多大用户量的访问,性能指标,开发成本,需要更新到什么技术栈上等。

  3. 阶段切片(列)。制定每一步可执行的演进活动,比如将 Redis 切换到集群模式。阶段设计,需要根据当前的制约来制定,评估每个阶段的分享,是否会影响正常的业务开发节奏。

  4. 执行序列(行)。我们可能会将一些可以并行执行的演进活动放到架构路线图中,这样可以同步演进,但是会带来协同的问题。因此可以设计一些执行序列。

一些项目管理工具是可以提供一些架构演进工具的,比如 roadmunk.com 网站就提供了如下风格的架构演进地图:

架构演进管理工具
图 45. 架构演进管理工具

图片来源:Enterprise Architecture Roadmap https://www.productplan.com/glossary/enterprise-architecture-roadmap/

在不使用专业工具的情况下,使用表格软件、PPT 也没有问题,关键在于我们的架构应该保持一种活跃的状态,因此在研发资源投入的时候需要将架构演进和持续更新的成本计算在内。

6.5 架构的关键因素

什么是架构中重要的事情呢?在和同事、社区的朋友交流时,收集到的一些架构关键因素。

抓大放小

架构是一个非常时髦的词,既不属于以前的详细设计,也不属于概要设计。但是在一些场合下,却不得不设置这样一个岗位,来统筹规划各个模块之间的交互和依赖。

所以架构设计有两个方向。一个是归纳法,找出已经存在的详细业务,然后进行归纳,得出模型、架构设计。另外一个是演绎法,根据业界的模型出发,在现有的业务中进行演绎。有时候在极其复杂的系统中,可能有几百个场景和功能,我们根本不可能提前整理出全部的模型,并对齐抽象。

在这种情况下,架构师不像是一个建筑师,有条件勘测所有的信息,并作出合理的设计,然后进行评审。更多时候更像是一个园丁,将花园规划好后,任由花草生长,当一些花草探出篱笆时进行干预。如果将架构师比喻成园丁是合适的话,那么园丁就需要快速识别出最重要的事,避免夏天到来后花草快速生长来不及修剪。

对于架构师来说,认识到什么重要,比事无巨细的设计更为重要,因为这会让本来可以分配的开发工作挤占原本就不多的决策时间。

对于微服务项目来说,当团队规模非常庞大时,最重要的事情有这么几个:

  1. 清晰地定义每个服务的职责,以及相互的依赖关系。

  2. 在每个微服务中挑选几个核心模型,建立这些核心模型的关联关系,确保其他的模型都能依附这些模型生长出来。

  3. 拓展点,抽象结束后需要通过不同的策略设计拓展点来满足个性化需要。

  4. 定义建模、架构设计的原则,以便对各个开发团队的产物进行整合以及评审。

因为准确决策会花非常多的时间,所以决策少量的重要事情,比决策大量的不重要事情更加重要。

架构元素和关系

我们做软件架构设计,设计的主体是架构师,客体是软件,这里的软件往往是一个软件系统。系统意味着存在组成部分,以及通过有机的方式组成到一起,并具备一定的能力。

我们说架构就是定义系统的元素和关系。架构设计中往往最让人混乱的是颗粒度问题。当我们说服务这个词汇时,说的是什么呢?有可能是一个可以单独部署的容器,也就是微服务这个粒度。但是其他人可能理解为,某个代码库中的一个服务类。

设计架构时,需要时刻清醒的知道自己工作在哪个层次。如果是微服务层次,我们可以说,这是在做战略架构设计。微服务是战略架构设计中的元素,微服务之间的调用和依赖关系就是系统元素的关系。

当把微服务打开来看,每个类就成为了元素,进入了战术设计的层次。在领域模型部分,基本的类可以再次组合为聚合,以聚合为战术设计的基本元素。通过定义聚合根的概念来明确战术设计的核心元素,分析出聚合的职责就能作为元素的关系。

建模和架构原则

架构师不必事无巨细的评审细节每项内容,并且也不太好通过偏好来评审产出。如果能整理一些架构设计的原则、规范,以符合原则为依据来进行评审和指导开发就行。

制定技术原则时候,有一些“原则的原则”。

  1. 客观类规范需要自动化。在框架上、工具上、流程上做出约束,让团队成员在不经过培训的情况下满足建模原则。

  2. 主观类原则需要轻量级。轻量级也就意味不能在细节上过多的约束,而只是一个底线。掌握一份几十页的文档是几乎不可能完成的任务,非自动化的架构原则必须足够精简。

  3. 可操作性。不能制定一些不切实际的原则,原则需要能够明确的被判定。比如,“服务间依赖合理”不是一个好的原则,而“领域服务之间不允许出现 API 双向依赖” 就能被识别和判定。

比如,这里选择几条领域模型设计的一些比较清晰的原则:

  1. 不允许出现多对多关系,多对多关系造成聚合之间的耦合,应该明确找出中间模型并给予一个合适的名称。

  2. 聚合的深度尽量不超过 2 层,最多不超过 3 层。

  3. 聚合根不共用实体,如果存在共用实体的情况,可以拆开或者将被共用的实体升级为聚合根。

  4. 值对象在持久化时需要将字段展开到所属实体上,不能使用单独的数据库表存储。

这里示例的几条原则可能不会被所有人认可,但是当我们在一个团队中工作时,会降低团队的沟通成本。

拓展点设计

抓大放小的方法之一是找到核心模型,但是过于收敛到核心模型,核心模型的职责就会变重,不利于扩展。

架构师需要抓住核心模型的同时为核心模型设计拓展点,这样架构师负责守护核心模型,并给予一线的开发和技术经理拓展和发挥的空间。

举个例子来说,保险行业往往有两个核心模型:投保单和保单。如果将各种场景都收敛到投保单、保单这两个模型上,这两个模型的内容会非常多。

对于投保单来说,可能有非常多的渠道,对于这些渠道不能直接关联到投保单上。一种设计方法是,抽象出各种各样的投保渠道,这些投保渠道在实现上都是一些策略,这些策略使用到的模型不需要直接关联到核心模型上,让其独立存在即可。

通过拓展点设计有非常多的好处。架构师可以通过拓展点识别到核心模型,并建立核心模型之间的关系,找到系统的核心逻辑;拓展点可以用来研发工作,将不同策略的工作拆分出来,交由不同的开发人员负责,让分工更清晰;当然,显而易见的,拓展点可以支持更多业务,而不必侵入核心模型。

最后一个好处单独拎出来说一下。通过明确拓展策略,可以非常容易的说服业务方(产品经理、BA)克制的设计交互,因为这样可以最大的支持更多业务场景。举个例子,一个餐饮系统,一般有外卖、堂吃两种订单,我们可以设计一个核心模型订单,以及拓展模型外卖、堂吃。如果产品经理需要将其列出在一个列表中,并根据外卖、堂吃的专属字段进行分页搜索,这样就破坏了抽象和拓展策略。当我们能说明白拓展策略时,业务方也能接受一定程度上的取舍和克制了。

如何获得拓展点是一个难题。获得拓展点的前提是找到不变点,也就是一组模型中具有相同内涵的属性。基于此来设计抽象后的模型,如果找不到不变点,也就意味者存在过度设计。

团队契约

9个女人不能在一个月内生孩子,现实是这种要求太多了。架构师的目标是尽可能将团队中的人并行化,这是我们想尽办法拆分系统的重要原因。架构拆分的目的不是让软件设计的多么美妙,而是丢给你几百人能不能在一起工作不发生冲突,这是极为困难的事。

从另外一种角度上来说,既然人多一起工作就必然会产生浪费,接受浪费也是大型系统架构设计的客观需要。

考虑到了拆,还需要考虑合。拆分的越细,合并就越困难。而合并最大的问题是,每个人的做事方法,和想法是完全不同的。如何清晰简单的制定可行的工作规范和产物才能让系统合并运行,这就是架构师需要思考的另外一个问题。

6.7 补充 1:基于主客体的权限设计方法

以权限设计方法为例,说明主客体思维在架构中的应用。

一线工程师:领域服务之间还需要鉴权吗?
架构师:不需要
一线工程师:这样安全吗?
架构师:安全,而且不能因为过度设计造成性能消耗
几秒钟后
架构师:等等,你说的鉴权是什么鉴权。

在设计架构时,鉴权是无法避免且非常重要的一个专题内容。但是当我们说鉴权的时候说的什么呢?是认证(Authentication)、鉴权(Authorization)还是审计(Audit)?

这个问题并不复杂,一般来说:

  • 认证是指系统需要识别是谁来访问。

  • 鉴权是指识别出来的“谁”能不能访问特定的资源。

  • 审计是指对识对别出来的“谁”行为进行记录。

认证、审计可以被单独讨论,这里只讨论分布式系统下鉴权的问题。问题往往在于当我们系统分布式化后在每层“谁”这个概念可能发生了变化,因为“分层是主体权责的让渡”,下层的主体可能已经变化了。

这样说可能有点晦涩,举一个例子来说。用户服务提供了一个 API 用来查询用户信息,我们自然会想到查询用户信息是敏感信息,需要鉴权防止信息泄露。前端和另外一个服务都可能使用这个 API 时,权限应该怎么控制呢?

于是往往会出现两种流派。一种是领域服务只提供通用的能力,无需鉴权,鉴权的点应该由应用层来做。另外一种是领域服务每次的请求也需要知道用户的存在,并在领域服务内检查权限。

这两个流派都不能完全解决问题,如果鉴权只是由应用层来完成,基于不同权限展示的数据无法限制。如果将权限检查留给领域服务实现,会造成 API 的混乱。因此折中的思想是将权限设计为功能权限和数据权限,通过区分这两种权限类型来解决这个问题。

主客体分析

我们不妨使用主客体来分析一下这个问题:

  1. 用户(前端真实的操作者)操作软件,我们识别到的权限主体是真实的用户。

  2. 在系统内部,应用服务调用领域服务,对于领域服务的权限主体是前面的系统,真实操作的用户被消化成了业务的一种参数。而所谓的数据权限只不过是基于某个用户 ID 过滤数据的一种业务规则,虽然都叫权限,实际上并不相同。

  3. 领域服务调用数据库等基础设施,对于数据库来说权限主体是领域服务。

经过主体的分析,我们会发现这些鉴权问题需要分开来看。这种分解并不新鲜,在一些文章中用了一组更为直观的术语:

  1. H2M(Human to Machine)鉴权。人-机鉴权,需要识别的用户的身份的鉴权活动。在单体系统下,默认就是H2M 鉴权,也是大家习惯的模式。

  2. M2M(Machine to Machine)鉴权。机器-机器之间的鉴权,往往是系统之间的鉴权活动。发生在应用服务-领域服务之间、领域服务-领域服务之间、第三方系统-领域服务之间、领域服务和基础设施之间。在内网环境,由于网络隔离,我们常常会忽略这部分的鉴权,并将其和 H2M 鉴权混杂起来。成熟的系统会通过 AK/SK 的方式鉴权,或者提供一种和开发者无关的账号(Service Account)实现鉴权。

  3. D2M(Device to Machine)鉴权。设备-机器(服务器)之间的鉴权,比如餐饮系统,会存在一个账号在多个收银机上登录的情况。一般设备-机器鉴权会通过接入协议转换为统一的人-机鉴权,这也是很常见但是容易忽略的鉴权方式。

基于对主体认知,我们可以将权限检查点映射到到 DDD 的分层模型上,就像下面这张图一样,当管理员管理他能访问的一组数据时,会经历几个检查点,这几个检查点由不同的主体完成:

  1. 应用层处理 D2M鉴权、H2M 鉴权,识别用户的身份,并检查该用户是否能访问相关功能(可能是API)。鉴权完成后,需要提取用户的身份主体(Principal/Subject),最简单就是用户 ID。

  2. 领域层拿到的用户 ID 只是一种业务参数,应用层到领域层的检查点为数据检查,根据用户 ID 过滤合适的数据。有条件的做 M2M 鉴权,但是不应该过重。

  3. 领域层访问数据库的鉴权应该也是 M2M,只不过这种鉴权机制由数据库等基础设施提供,或者强制要求。

权限检查点
图 46. 权限检查点

基于此,我相信关于鉴权的困惑会解决一大半。

主体权限分析的灵感

你可能会疑惑,我是如何将权限这样一个专题的技术方案和主客体思维挂钩的,听起来有点牵强附会。实际上,主客体思维已经成为了西方世界的基本哲学思维之一,我们可以在很多地方找到它们的影子。

我找到了一篇 1995 年古老的论文《Role-based access control (RBAC): Features and motivations》[27],这篇论文就是从主体、客体视角下分析了 RBAC 模型。

这篇文章对 RBAC 做了清晰的论述,简要的思想可以总结为:用户根据角色划分为不同的主体,操作(Operations)可以被看做客体。那么 RBAC 描述的是根据角色对用户群体划分,对其操作的控制。

RBAC 和 主体
图 47. RBAC 和 主体

除此之外,我们还可以在其他地方发现主体的影子。JWT 是一种自编码的鉴权载体,在令牌中就可以解出鉴权相关的用户信息。在 JWT 的 payload 数据域中,约定了一个 sub 字段,这个字段就是 “Subject” 的缩写。

JWT 中的线索
图 48. JWT 中的线索

图片来源:https://jwt.io/

这里可能有人会问,客体不在令牌中吗?答案是肯定的,令牌记录了用户的身份,就像将军的虎符,能被指挥的军队就是客体。

在计算机系统中,我们往往会将权限和功能绑定记录到数据库中当做客体存在,这就引出了下一个需要讨论的话题,鉴权客体的设定,会直接影响是否能开发出高效易懂的权限检查程序。

鉴权的几个陷阱

我们最容易掉入几个鉴权的陷阱中,有了主客体思维,可以轻松的分析它。比如,我们常常将 API 和权限控制绑定到一起,但是麻烦在于 API 不一定和鉴权的单位一一对应,这就导致了方案无法实现。

这里的症结在权限控制的客体没有被清晰地认识到。如果以 API 作为鉴权的客体,那么权限控制就完全和技术设计绑定了,用户在配置权限时一头雾水。

而大多数时候,我们需要控制的客体是功能、数据集。那么,就需要清醒的不要把权限的客体设计成页面、API、菜单。

除非我们权限的控制单位就是它们,这一点同互联网公司喜欢说的颗粒度无关,当客体不匹配时,无论的多小的颗粒度都不能满足灵活配置权限的诉求。

另外一个陷阱是将鉴权的客体和数据的查询、增加、修改、删除绑定到一起,如果存在一个功能会涉及多个数据资源的修改也就无能为力了。

因此涉及权限系统,需要清晰的明白权限限制的主体是什么,以及权限限制的客体是什么。而由于主客体存在嵌套关系,我们需要明白是在哪一个语境下设计的。用户和系统之间?还是系统内部的服务之间?这是一个值得思考的问题留给大家。

更灵活的权限设计

在鉴权的上下文中,将主体和客体重新定义,可以让我们的设计更加灵活。

主体:行为的施动者。可以是一个用户、用户组、带角色的用户、有父子关系的用户、设备、第三方系统、内部系统等。

客体:行为的检查点。可以是一个方法、对象、数据、系统、第三方系统、基础设施。

如果我们设计主体、客体、检查器三个接口,那么是不是可以做的万能的访问控制模型?我找到了一篇文章《A new dynamic access control scheme based on subject-object list》[28]设想了这样一种模型,通过列表管理主体、客体清单来实现更加灵活的权限检查。

理想的情况下,实现不同的检查器就可以对不同的客体进行检查。不过如果抽象太高,就会带来更多的认知负担,实践价值降低。至于需要抽象到什么程度,就需要架构师来根据实际情况选择合适的模型和策略了。

6.8 补充 2:基于主客体来命名

我们说分层的元素是主体、客体、行为,那么如果能给这些元素起个好名字,就能写出表达力强的代码。

首先,我们可以对命名进行分类:

  • 对客体命名。

  • 对主体命名。

  • 对行为命名。

对客体命名

根据 DDD 的统一语言原则,名词往往代表着一个业务概念,并需要在团队中和开发人员、业务人员对齐。编程就是使用特定的算法操作一组数据,这些数据代表着业务中的某些概念。

一个对象就是一个概念,对象中的属性就是这个概念的内涵,这个对象被用来表达的范围就是它的外延。

这里需要普及一下逻辑学中内涵和外延。内涵是指一个概念的典型特征,外延是指它能描述事物的集合。比如兔子有长长的耳朵是内涵,兔子在地球上指代的动物就是它的外延。

当我们说白色的兔子不是兔子的时候,说的是“兔子”这个概念不是“白色兔子”的概念;当我们说白色的兔子是兔子的时候,说的是“白色兔子”概念表达的集合是概念“兔子”表达的集合的子集。

所以对客体起名字的关键在于定义这个客体的概念,使用拟物的方式起名

我们可以通过概念图(可以搜索概念图相关的文章)来定义,也可以直接用语言来表达。比如当我们给系统中用户取相关起名字的时候可以这样定义:

  • 用户:在系统中用来标识软件使用者身份的对象,可以通过关键属性来进行登录。

  • 客户:在系统中关于参与人的个人资料,不具备登录能力,客户可以关联用户也可以不关联。

  • 账户:用户拥有用于存放资金的对象,关键属性为余额。

  • 用户组成员:用户在某个用户组下的身份,持有这个用户组的权限。

  • 商户:在系统中表达一个资源的空间,在实际业务中对应法人。

  • 商户管理员:用户在一个商户下的身份,具有管理这个商户资源的权限。

对于容易混淆的”地址",也可以这样定义:

  • 地址:地址库中的地址,属于站点元数据。

  • 用户地址:用户个人资料下保存的地址,可能引用自地址库也可以不引用。

  • 收货地址:在订单中使用的地址,可以引用自用户地址也可以不引用。

对主体命名

在代码操作中操作客体的对象就可以看做主体,那么主体怎么命名呢?

其实很简单,我们只需要区分好他们的功能就行了。假如有 A、B、C 三个人去荒野求生,他们到了一个小岛靠打猎为生。A 负责打猎,B 负责加工,C 负责存储。反应快的朋友可能知道我要说什么了,这不就是代码中的分层吗。看看这样命名是否合适:

  • A:Hunter。

  • B:Processor。

  • C:Storekeeper。

看下我们代码是不是类似的:

  • 负责处理 API 请求的类叫做 Controller。

  • 处理业务逻辑的类叫做 Service。

  • 负责生成 SQL 的类叫做 Mapper。

所以对主体起名字的关键在于定义他们的能力或者职责,然后使用拟人的方法起名

对行为命名

有了主体、客体,只要给行为一个动词,也就是我们的方法名,我们就可以像主谓宾一样写出句子了,是不是很简单?

但是这个时候很多朋友就犯难了,我除了会 get、take、do 这类词汇之外,找不到其它词汇了。

实际上这是对业务理解不够,或者英语词汇量的限制。这类词汇在英语中叫做小词,往往威力无穷,但表达能力拉胯。这里介绍一个学习英语的技巧,如果我们出国旅游,其实也只需要 get、take、do、I、it 等几个词就够了。如果想要买东西,就指着想要买的东西说,I take it,老板自然就知道你的意思。然后不断用更准确的词去代替这些词,然后英语就可以渐进提升。

*英语的学习的关键不是背单词,关键在于表达能力。*但是不使用更准确的词汇,表达能力就会受限。同理,我们可以使用 doXXX 来完成所有的业务,也能写出整洁的代码,但是表达能力非常弱。

所以对方法进行命名,只需要找一个合适的动词即可。

那么,动词如果真的不够用怎么办?

试想,如果有两个方法,类名、方法名、参数都相同,那么需要思考一个问题,这两个方法的区别是什么?这也是方法签名为什么这样定义的原因。

命名反模式

下面通过一些命名的反模式,来对比主客体命名法的优点。

命名毫无意义

使用 a、b、c 进行命名,就像四川人使用 “大娃、二娃、幺娃子”来命名一样,只能算小名,没人能看得懂。

还有使用拼音(甚至粤语拼音)、符号、不统一的风格,批评这类命名的文章已经很多了,不是本文的重点。

不遵守主客思维

不遵守主客思维的命名有拿物品作为主语,这类命名我称为“成精”命名法。比如我总喜欢用的例子,订单中的结账方法、商品中的发货方法,可读性非常差。

提示一下,由于主客体具有相对性,拟人的不一定不能作为客体,就好比理发师也能被其他理发师理发一样。但是主体我们尽量使用拟人法,特殊情况是当一个对象操作它自己的属性时候,我们能看做一个局部的主客关系,也能作为主体。

过于抽象

在一个系统中,如果看到命名全是 xxxData、xxxMessage、xxxInfo 等非常通用和抽象的词汇,基本没有表达能力,造成混乱。

这是由于我们对客体认识不足造成的,按照前面对客体进行重新定义,这也是设计的一部分。

主体或者客体冗余

在主客体命名法中,行为只需要一个动词,或者动词短语即可,如果你的方法名形如:

  • createUser 保存用户。

  • merchantUpdate 商户更新。

当我们的方法被调用时,带上参数,会看起来别扭:

orderService.createUser(user)

如果能熟练的掌握主客体命名法,就能写出这样的代码:

orderService.create(user)

如果主体、客体能表达完整的含义,行为就是用一个动词即可;如果不能,就使用一个动词短语。

命名驱动设计

很多建模和架构问题甚至不需要费神去解决,找到一个恰如其分的名字可能就解决了。

命名是编程中非常让人头疼的事情,但是你可能不会相信,取一个好名字你的建模问题也解决了,这个问题说起来还真是挺有意思,否则也不值得一提了。

在保险领域,业务有一个需求是在正式提交签约后,保单才具有法律效应,正式生效。但是在受理签约之前,用户会提交一些材料,这些材料几乎和最终的保单一模一样。

最初的开发人员设计了 Policy 这个对象,并增加了一个状态属性,状态为生效后保单才成为合法的保单。这样做看起来没有问题,但是随着业务的变化,签约前和签约后慢慢开始有了差异,仍然使用 Policy 这个对象不是很好。开发人员准备将这些差异分离,这个时候出现了两个派别,并发生了争吵。

主分派:签约前后,这是两个不同对象应该分离。
主合派:他们明明都是 Policy,怎么能分了,再说分开了签约前叫什么呢?
主分派:…… 好像确实不知道叫什么。
主合派:看吧,你都不知道叫什么,还是别拆吧。

这类对话在我培训或者咨询工作中,听到不下 10 次,如果有明确的命名来区分概念,往往大家很认同拆分,但是在不知道如何起名的时候,问题就变得模棱两可。

所以说,命名的问题,本质是一个设计问题。

上面问题最终通过找到一个业界公认的词汇得以解决——投保单,英文中叫做 insurance slip。类似的概念还有客户、用户、账户的三户设计,当我们找到了命名后,建模问题往往迎刃而解。

小的时候几乎每家都有一本书《姓名与人生》,用来给新生儿起名字。它提供了一套根据笔画来判断名字是否足够好的理论,虽然现在看来有点扯,但是也意味着人们对名字的重视情况。

优秀的开发者对待命名应该像对待自己孩子的名字一样,毕竟他们有一个共同点就是,被广泛使用后基本上很难被修改。

第 7 章 把软件团队也看做系统

团队管理是一件非常困难的事情,尤其是在认知能力强的群体中尤其如此(和直觉相悖,认知能力越强的团队越不好管理)。历史告诉我们,缺乏组织的人类群体没有任何战斗力,且在社会化生产的过程中效率非常低下。

在一些公司中,管理问题时时刻刻存在,要么靠管理者的本能管理,要么就是管理混乱,或者是靠经验性的管理框架来进行管理。在 IT 团队中这种现象尤其比较明显,因为往往技术管理者更关注技术本身而非管理。

有意思的是,管理能不能也用 “技术的语言”来表述呢?其实是可以的,作为一个分布式系统的爱好者,慢慢发现分布式系统和团队管理有一些共通之处,能用这些发现解决一些问题。这些问题对于管理者和团队成员都有一些启示,希望您能耐心的看下去。

7.1 团队管理和分布式系统

团队管理是社会学讨论的问题,分布式系统是计算机中的概念。它们能有什么关系呢?在开始写作前,我在和同事聊到这部分内容的想法时,同事笑道:你这个想法非常有意思,但是你可能只是强行将它们联系到一起。

这两个概念甚至都不在一个学科,一个是文科,而一个算工科的内容。但是,世界是非常有意思的,跨学科的碰撞往往能发挥意想不到的作用(以后还会有类似的跨学科联系的文章)。

查理·芒格一生都在不停强调跨学科思维的重要性,在他的模型中,数学、物理、生物、化学、哲学、社会学、心理学等领域都会被突然联系到一起。例如,大家以为的投资是经济学话题,其实是一个社会学或心理学问题 —— 当身边的人开始向你推荐股票,牛市就结束了。

在《分布式计算——原理、算法与系统》[29] 这本书的开篇提到,“分布式系统是一组相互独立的实体构成的集合,这些实体相互协作可以解决任何单独的实体所不能解决的问题”。作者认为,分布式系统在宇宙之初就存在了,从蜂群、微生物系统、甚至由人体细胞构成的各种系统,这些都是分布式系统。

团队是一个能独立承担一定功能和职责的人类群体,那么也应该是一个分布式系统,符合分布式系统的一些基本理论。这是这篇文章的基本前提。

接下来我们会聊到分布式系统的两种模型,分别代表两种典型的团队形态,也代表不同的计算模型:

  1. 主从调度模型。在微观状态适用,当团队人数不多,能被直接调度到,可以看做微观团队系统。

  2. 反馈调节(市场)模型。在宏观状态下适用,当团队规模大到不可能被直接管理的时候,只能通过宏观调节机制来做宏观调控。

大部分情况下,我们不会用到反馈调节模型,但是当我们仔细观察和分析大型企业的工作机制时,就能发现端倪。这部分的内容,这里不过多讨论。

7.2 主从调度模型

基本模型

让我们先看下主从调度模型的基本形态是什么样的,它能指导我们加深对团队的认识吗?这种系统由两个主要的角色构成:Dispatcher 和 Worker,这是主从调度模型的基本逻辑。

基本模型
图 49. 基本模型

回顾一下计算机系统中的这两个角色。基于负载均衡的无状态服务集群,负载均衡器充当了 Dispatcher 的角色,普通的服务器充当了 Worker 的角色;基于主从的 CI 构建系统 Jenkins,它的 Master 节点就是 Dispatcher 角色,负责处理任务调度,Slave 节点用于执行任务构建。

在这种模型下,我们发现如果 Master 节点用来跑具体的任务,会挤压它的调度能力, Master 节点崩溃整个系统也不可用了。

我们回归到团队管理中来,一名团队的 Leader 如果每天关注在自己具体的工作上,让 Worker 角色的工作挤占了 Dispatcher 角色的工作,整个团队会开始混乱。在好的情况下,团队中会有其他成员自发地弥补这部分工作,就有点类似于人体被切除某些器官后发生的代偿行为。然而,团队并不总是有这么好的运气,如果没有人来承担 Dispatcher 的工作时,整个系统就陷入混乱。

所以我们需要将模型做一些简单的修正,一名 Leader 不仅需要作为 Dispatcher 的角色还需要作为 Worker 完成某些具体的任务。这也更反应现实,一名技术经理不仅需要分配工作或者任务,往往还需要完成一部分的开发工作。

承担一定的 Worker 职责是有好处的,如果不熟悉具体的工作是什么很难真正的承担 Dispatcher 的职责,分解的任务不具备可执行。

Leader 的职能
图 50. Leader 的职能

在基本模型中,也可以找到 Dispatcher 和 Worker 的主客体关系,这样更能辨明他们的职责关系。

对于 Dispatcher 来说,主体是他自己,客体是被调度的 Worker 以及调度工作;对于 Worker 来说,主体是 Worker 自己,但是客体是具体业务工作。

多层模型

在主从模型中,Dispatcher 带动 Worker 的能力是有限的,因此为了让系统规模能进一步扩大,多级主从模型就是有必要的了。

根据经验,IT 团队由于工作性质的原因直接管理的人数最多能到 10 人左右,也就是常常说的两个披萨就能吃饱的团队规模,如果人数再多就需要增加系统层级通过间接管理的方式进行管理。

间接管理就形成了多层调度模型,也会产生中间节点。中间节点在上层的的角色就是 Worker,在下层就是 Dispatcher。

多层结构
图 51. 多层结构

在多层模型中,我们可以将系统看做一个由多个子系统组合的系统。为了度量系统的健康状态,需要引入两个概念:

  1. 团队宽度。一个团队的宽度是指每层能够直接调度到的最大节点数量。对于不同的 Dispatcher 由于工作性质和能力不同直接调度的数量可能不同。所以团队宽度又可以分为最大宽度、最小宽度、平均宽度。

  2. 团队深度。一个团队的深度是指信息从团队顶层节点传递到最终端节点的层数。信息在传递的过程中会失真、变形,这也是为什么越来越多的公司追求扁平化(扁平化也不等于就是好)的原因。和团队宽度类似,每一个终端节点向上的路径深度也不一样,也可以分为最大深度、最小深度、平均深度。

通过这些概念我们可以建立起一些指标,实现定量的管理模式。

摩擦的成因

当我们认识到团队满足这样一个基本模型后,可以通过模型识别到团队管理中的问题,也就能针对性的优化团队管理,将有问题的团队带出泥潭。

无领导小组

这种情况发生在私人关系非常好的团队,领导交给几个平级的团队成员一些任务,但是没有经过任务拆分,且没有说明谁为这件事负责。

有时候这种场景是刻意为之,比如在应届生招聘时,会通过群体面试的方式设计无领导小组进行面试。目的是,通过竞争、选举、冲突寻找具有领导能力的潜在调度者。

摩擦-无领导小组
图 52. 摩擦-无领导小组
多任务流团队

主从调度模型中,当一个 Dispatcher 的能力不能满足团队需要时,能否增加多个 Dispatcher?

答案否定的,在分布式系统中,避免这样的模型:多个承担有 Worker 角色的 Dispatcher 构成系统,它会带来状态的一致性问题。在团队管理中,Dispatcher 的负载不会太大,但是需要保证一致性。在一个团队中出现 2 个 PM 会是一个灾难,然而这种场景在各个公司反复上演。

摩擦-多任务流团队
图 53. 摩擦-多任务流团队

这种情况发生在团队出现 2 个事实调度者,当执行者需要接收多个调度者的任务时,会发生下面问题:

  1. 任务逻辑矛盾。

  2. 任务计划被打乱。

  3. 超出正常工作时间能消化的工作量。

跨级指挥

跨级指挥和多调度者类似,对于执行者而言,无法区分哪些任务优先级更高。同时,跨级指挥会对执行者的直接领导者造成影响,对团队的效率产生影响。

跨级指挥的出现,往往导致中间层指挥体系失效的情况。

摩擦-跨级指挥
图 54. 摩擦-跨级指挥
无上升通道的团队

在这种团队中,即使工作非常久也无法从执行者成为调度者。无上升通道的团队是一个僵化的团队,意味着团队规模和业务没有增长,也没有足够的人员流动。

在这种团队中执行者和调度者都没有足够的主观能动性。

摩擦-无上升通道的团队
图 55. 摩擦-无上升通道的团队

另外一种情况造成无上升通道问题的原因是,高层对团队彻底失去信心,决定从外部或者其他团队空降 Dispatcher 从而对抱有升职的现有团队成员造成打击。

“傀儡”Leader

在这种团队中,看似团队具有良好的结构,实际上 Leader 如果因为某些原因没有起到调度者的作用,也会让团队的任务传递出现问题。

和无领导小组不同的是,团队中存在名义上的调度者,阻挡了其他成员补齐这个位置的动力。但民间自发出现调度者时,会造成民间调度者和名义上调度者之间的冲突。

摩擦-傀儡 Leader
图 56. 摩擦-傀儡 Leader
激励失效

激励不仅仅是金钱,还包括很多内容。如果激励出现问题,无法传递、或者不合适的传递到团队成员,会造成严重的问题。

在多级的团队系统中,每一层都需要存在相应的激励。激励体系需要建立一个正反馈,符合“劳者多得”而不是“能者多劳”的局面。

摩擦-激励失效
图 57. 摩擦-激励失效
反馈失灵

这类团队高层无法知道基层的运行情况,无法做出合理的决策,盲目下发政策,造成基层工作无法展开。

反馈失灵虽然不会短期影响团队运行状态,但是系统会持续性恶化。

摩擦-反馈失灵
图 58. 摩擦-反馈失灵

主从调度模型的特点和局限性

主从调度模型具有一些明显的特点,当然在不同的场景下具有一些局限性,当我们了解到局限性带来的相关影响时,就需要做出干预和修正,让系统回归到正常状态。

系统的状态

主从调度模型是有明显状态的,系统的调度状态在 Dispatcher 上,如果 Dispatcher 上的调度信息丢失,可能会造成系统的任务调度混乱。

这时很多人可能会说,在微服务系统中,我们的业务服务是没有状态的,是谁来调度的呢?无状态并不是真的无状态,而是状态被隔离了。例如,负载均衡器-服务器-数据库模型中,状态被隔离到负载均衡器、数据库中,服务器可以做到没有状态,系统状态由负载均衡器和数据库承担。

客户端负载均衡这种方式是不是没有状态?

其实也是有的,即使是通过 HASH 算法直接匹配到目标服务器,通过算法和计算规则实现客户端自己调度,实际上这个规则是数学规律帮我们提供了状态。开个玩笑的说法,就是使用了宇宙的状态。

局限性

这种模型是有中心的,有一些看起来无中心化的系统,实际是由选举机制自动完成中心化的选举,慢慢磨合出真正的领导者。

Dispatcher 的重点在于如何带动更多的 Worker,而不是自己完成工作。Worker 的工作需要 Dispatcher 梳理和分配。

Worker 的主动性受到抑制,工作由 Dispatcher 分配。即使让 Worker 主动挑选任务,其本质并没有变化。主从调度模型有点类似于计划经济,如果调度的层级过深就会出现积极性和效率问题,而这一点正好是市场模型所解决的问题。

主从调度模型的风险大部分来源于 Dispatcher,如果没有建立良好的后备机制和做好知识传递,当 Dispatcher 出现问题后(离职、生病),系统会处于短暂停顿状态。

另外这种系统中,竞争基本上由上层来裁定,会导致腐败和潜规则,带来不良的影响。由于竞争由上层的决定,因此基层的声音被忽视,基层领导者只需要讨好上级,Worker 的诉求可能会被忽视。

7.3 市场模型

基本模型

主从调度模型看起来很完善了,但是却不能描述一些特殊的场景,因此我们需要另外一种模型:市场模型,它是通过反馈调节来完成的。这个系统由 3 种基本元素构成:玩家、市场、调节者,以及一个隐藏的元素:庄家所构成。

这种系统出现在层级较为扁平的公司,各个团队相对独立和灵活,对于巨型公司的上层结构也符合这个模型。对于市场经济为主体的国家来说,整个经济体就是这个模型,所以我借用了市场这个词。

市场模型
图 59. 市场模型

在分布式的计算机系统中,这种模型比较少见,在一些弹性扩容的系统中可以看做这种模型的简单实现。这是因为计算机科学基础决定了的,计算机科学建立在离散数学上,我们使用的计算模型为图灵模型,图灵模型是一种确定的计算模型(可计算性)。反馈调节模型不是一种确定的计算模型,目前的超计算(Hyper computation )就是在研究如何在计算机中应用这类模型。

在这个模型中,Dispatcher 被市场代替了,市场可以认为是一个无形的手,这个手是全体玩家构成的。这种模型是真正的去中心化模型,在生活中如果能细心一点,会到处发现这种模型的影子:生物圈、股市、人体内分泌系统等。

回到团队管理来看,我们可以把市场看做一个大的公司,每个玩家就是一个团队,这些团队可以找其他团队合作,但是都要在市场上来竞争;对于国家而言,这些玩家就是企业。如果我们把场景聚焦到大型企业来看,每个团队都需要在这个企业的生态链中寻求一席之地,和上下游的合作关系就是交易的过程。

市场模型的特点和局限性

这种系统具有和主从调度模型不同的逻辑,很多性质甚至是违反直觉的。

系统具有自我调节能力,且是无中心化的,调节者不是必须的,只要市场在就不会崩溃。由于没有中心化的存在,调节的效率非常高、平滑且精细。但是在一定时间后,由于马太效应的积累,会出现庄家,庄家会控制市场,让市场失去平衡。

另外一方面,玩家具有主动性,市场上出现新需求时,玩家会立即参与,参与者的积极性高。但是在没有明确监管机制的环境下,会快速出现欺骗等不正当竞争行为。

7.5 对管理者的启示

当我们聊完了这两个模型之后,我们可以得出对管理者有价值的一些启示。

选举还是委派

在主从模型中,有两种实现形态。

有一些看似去中心化的自组织系统,实际上也是主从模型。比如蜂群无人机系统、电力行业的网格计算,他们一般使用自组网系统(ad-hoc )。这些系统是通过选举算法完成的。这点在军事中使用的非常多,例如敌后穿插时不同单位的士兵集合到一起后重新组成临时指挥系统,就是通过军衔作为规则实现选举的。

另外一些场景下,他们的主从关系是提前设定的。比如数据库的主从关系、负载均衡等。在团队管理中,团队的关键人员是由上级委派和从其他地方调拨,而不是就地选拔。

效率更高的系统

通过这两个模型,可以分别优化系统效率。

在主从模型中,我们可以使用认知管理模型。也就是使用认知能力强的人来承担 Dispatcher, Dispatcher 负责设定工作方式和流程,然后由认知能力弱的人来作为 Worker。在团队中,人的认知能力会随工作时间成长,因此一般选工作时间长的人作为 Dispatcher。

在反馈调节模型中,我们需要引入调节者来干预市场,防止庄家的出现,庄家会给系统带来风险和效率降低。

系统的稳健性

这两者模型都有一定几率崩溃,但是组织和团队崩溃的原因是多种多样的。外部原因让这两种模型崩溃没有分析价值(比如投资中断等),这里只分析内部原因。

在主从模型中,系统崩溃的原因有:

  • 系统混乱,Dispatcher 没有能力或者无法让 Worker 继续工作。

  • 无上升通道,内部矛盾积累过多,个体会选择退出系统。

  • Dispatcher 权利无法被制约,造成战略失误或一意孤行。

在市场模型中,系统崩溃的原因有:

  • 庄家的形成,造成局部资源枯竭。

  • 调节者被裹挟,资源分配不均衡,失去整理竞争力。

让系统的规则显性化

俗话说,不成文的规则才是真的规则,但是健康的系统尽可能让规则显性化。

在主从模型中 Dispatcher 需要尽可能的把工作方式梳理的规范化、明确化,大多数情况下 Worker 可以依靠规则行事,在少量的情况中 Dispatcher 人为干预,是系统高效的表现。

在反馈调节模型中,竞争规则需要明确,否则调节机制将会失效,破窗效应会让问题进一步恶化。

团队激励的方法

激励团队的方法有两个方向,一个优化团队内部的工作方式,合理的任务调度机制让系统摩擦和阻力减小;另外一个是对团队进行激励,提高能量输入,克服系统内的摩擦。

我们往往只强调了其中一个方面,于是错失了激励团队的最有效的手段。

激励方法
图 60. 激励方法

关于摩擦的部分,我们在前面已经分析的非常多了。那么给团队输入能量的方法有哪些呢?

在过去的工作经历中,我收集了下面一些能量输入的措施:

  1. 有竞争力的薪资。这是最直接的激励有段,但是也是成本最高的手段。

  2. 带他们赢,获得成就感。这是性价比最高的激励手段,每个人都希望有人能带他们赢,尤其是在身陷困局的团队。

  3. 个人的锻炼和学习机会。

  4. 工作具有荣誉感。

  5. 团队关怀和情感价值。

“好人”还是好的管理者?

在很多管理的书籍中,容易把团队管理变成玄学,强调管理者的道德修养。如果我们试图变成一个世俗意义上的“好人”,那么就无法成为一个合格的 Dispatcher。

合格的 Dispatcher 需要合适、不偏颇的分配任务,并近乎冷酷的执行,以及监督执行的情况,才能将巨大系统运转起来。这样看来,通常意义上的“好人”就不适合成为 Dispatcher。

在一个要求理性的位置上,如果出现了一个感性的 Dispatcher 并不是一件好事。

7.6 对被管理者的启示

当然,从站在被管理者的角度理解这两个模型,也会让自己受益。

竞争力

在主从调度模型中,个体需要想办法完成调度者给出的任务。他的竞争力来自于:

  1. 强调个人在相应领域的专业能力,专业性越强竞争力越大。

  2. 任务和自己专业能力的匹配性,在越匹配和擅长的岗位上竞争力越大。

  3. 工作输出的稳定性和效率,能源源不断的完成调度者给出的任务,想法多并不会带来竞争力。

在市场模型中,参与的玩家需要想办法满足市场的需求,扩大自己的生态位。他的竞争力来自于:

  1. 对市场的敏锐嗅觉,识别市场上的需求。

  2. 找到自己的生态位和生存空间。

  3. 达到市场的支配地位。

权力的来源

权力的流动是我们思考这两种模型中很重要的一部分。

主从调度模型中,权力来自上级的授予,而非来自于团队成员的支持(悦上者荣,悦下者蹇),因此需要注意平衡这两者的关系。例如,在某些政策执行中,可能会为团队成员增加额外的工作量,因此需要 Dispatcher 平衡利害关系。

在市场模型中,权力来自市场中的话语权,具有优势地位的玩家,可以获得更多的权力,最经典的是市场定价权。

上升通道

主从调度模型中,Worker 可以上升为 Dispatcher。它背后的逻辑往往是先提高自己的工作效率,协助完成一些 Dispatcher 的工作,成为 Dispatcher 后备力量。

在反馈调节的模型中,个体(玩家团队中的 Dispatcher 或者 Worker)需要成为一个玩家,需要从把关注点放到需求和服务上,识别到市场中微小的需求,想办法满足它。

上升通道
图 61. 上升通道

躲避 “PUA”

如果我们站在上帝视角,管理者和被管理者在某种程度上来说处于相互对立,管理者会琢磨一些“不花钱的激励团队的方法”,而有时候这种激励并不是被管理者所期望的,有时候被管理者所期望的依然是获取自己的实际利益。

“PUA”是一个流行语,起源于两性情感中的某些概念,引申义为一方通过一些隐晦的手段对另一方进行洗脑或精神控制,从对方身上在没有对等付出的情况下获取超额的利益。

对于被管理者来说,不被 “PUA” 的关键在于知道自己在整个团队系统中的位置,并根据权责利对等的原则来看待自己被分配到的工作。

“PUA”行为的关键特征是尽可能的摆脱自己的责任,掌握更多的权利,并获得更多的利益。容易被 “PUA”的特征是:本身并不具备特别多的权利,但是将过多的责任归到自身,不敢争取更多的利益。这也是为什么 “PUA” 管理者并不希望工作环境中具有清晰的权责关系的原因,因为这会让他对系统失去控制。

7.7 补充1:一则管理的隐喻

不知道有驾照的朋友尝试推过失去动力的汽车没有,对于有一些糟糕的团队来说,Leader 变成了推动团队运行的人,就如同一名驾驶员通过推车的形式让汽车跑起来。

虽然我们知道,IT 团队 Leader 往往是技术最好的那一个。但是,团队 Leader 不仅仅技术好就行。我时常去徒步,我发现体力最好的人未必能作为领队,因为领队还需要负责制定路线、判断天气、鼓励团队前进等超越了独自运动的范畴。

如果团队 Leader 没有意识到带领一群人完成艰巨的任务是“驾驶”技术,那么就会陷入无法推动团队前进的情况。甚至由于 Worker 角色占据了他全部的带宽,Dispatcher 的身份就无法充分发挥。

在敏捷软件项目中,我们往往会提前一个迭代设计技术方案,并在下一个迭代进行交付。如果,技术方案设计的不够充分,无法起到帮助下一个迭代更好的指导团队开发的作用,技术 Leader 就不得不花费大量的时间进行支持。

更糟糕的是,有的技术 Leader 觉得交代任务沟通起来非常麻烦,与其交代给团队成员完成还不如自己一个人干掉。实际上,看似完成了工作,但是整个系统却并不健康,因为这个系统不再具备任何的拓展性,无法将更多的团队成员加入进来。

我们必须认识到,在汽车的隐喻中,推动团队前进的是团队成员而不是 Leader。作为驾驶员只能判断路况,将前进的信号传递到车机,当发动机的转速提高后,驾驶员需要变换合适的档位,以便让汽车在合适的工况下工作。毛主席曾指出“人民,只有人民,才是创造世界历史的动力。” 我们也可以充分的发挥群众的力量。

团队也是一样,想要团队良好的前进,就需要在两方面下功夫:减少团队的摩擦,向团队输入能量

7.8 补充 2:管理者和被管理者的认知差异

什么样的团队更好管理?认知能力越强的团队还是平平无奇的团队?和直觉相悖的是,认知能力越强的团队越不好管理。

管理的难度是由管理者和被管理者之间的认知差距造成的,认知差越大越容易管理。相反,如果被管理者的认知差距大于了管理者,那么管理者做出的政策都会被破解和失效。

那么,这种差距体现在什么方面呢?

在广义的解释下,人的认知是能够被度量的,认知能力是指人脑加工、储存和提取信息的能力。包括了概括抽象能力、发现问题的能力、预见性能力等。

在软件工程和团队的范畴下,狭义的认知差距具有更为具体的含义。管理者更需要具备下面的特质:

  1. 能够做出决策以及干预决策的能力,而且需要承担决策后的责任,虽然最终的工作都是由整个团队承担。我们有时候发现,在团队中管理者并非是业务能力最强的人,但是必定是能够“拿主意的人”。在很多时候,团队中具有深刻见解的人并不缺乏,但是能做出果断决策以及持续推进的人并不多。

  2. 发现问题和其根因的能力。能发现团队或者工作中,哪些是迫切需要改进的部分,那些是主要矛盾,哪些是性影响的问题。管理者和被管理者的认知差异在:管理者看到的问题是核心的、关键的、从团队出发的,而不仅仅是从自己出发的。

  3. 具有一定的预见能力。能预见一定时间范围内团队的进展并做出一些提前的准备,能做到大部分问题心里有数。具有预见能力是一项非常重要的认知优势,管理者需要至少能走在团队前面发现问题,在运行良好的时候也能管理好未来的发展心理预期做到“不飘”。

当管理者和团队的认知拉开差距时,矛盾和服从性问题就能解决。“服从”并非一个贬义词,换句话说就是管理者除去行政职权外,对团队的影响力有多大,这决定了其政策和方向能否被团队接受,并实现统一思想。

第 8 章 软件工程中的政治

为什么我们不坦诚地聊聊工作中的政治?

按理说软件工程师是一个技术工种,学习政治会显得奇怪。在 Neal Ford 的《软件架构》中提到了架构师应该学习政治,并且他也觉得提到政治非常奇怪。

但是了解和驾驭政治几乎是架构师的必修课,因为架构师做出的每一个技术决策都可能受到挑战,因为没有决策会是完美并满足所有人诉求的。

架构师必须学会说服相关的利益方。比如说,一个古老的系统不得不进行重构,但是项目经理、产品经理可能完全不能理解为什么不能持续添加功能而是停下来进行重构。有些情况下,技术决策会伤害某些人的利益。在这种情况下,理解政治才能游刃有余。

政治是与人相处并达成目标的艺术,虽然被用到技术行业听起来不道德,但是我们却无时不刻在使用这些技巧,只是从未将其成为“政治学”。

人类社会的竞争主要是智力竞争,暴力竞争并不是主要的竞争。政治并不是仅仅包含国家之间的竞争,而是包含任何单位之间的竞争,个体之间、公司之间、团队之间的竞争都可以用政治的相关理论和认知模型描述。

就像我们前面说的那样,技术问题背后往往是利益问题,而利益问题背后往往是政治问题。无论我们多么瞧不起那些玩弄政治的人,但总是得面对他们、了解他们、学习他们。

这一章主要讨论在工作中发现的一些关乎利益、政治这些"不堪"的现象。

8.1 "不堪" 但是有效的政治

据我所知,有一家做制造的传统公司为了推进数字化转型,为制造、采购等单位开发软件。按照测算,软件上线后可以裁员 40%,并节省大量开支。

但是 IT 部门却发现了一个问题,在需求分析阶段业务部门就非常不配合,并且勉强上线的产品也无法推广。原因是电子化后,一方面监管变得容易,没有灰色操作;另外一方面由于软件的替代作用,业务团队人员数量会被缩减,业务基层领导也不愿意看到这种情况,于是暗中阻挠希望维持团队规模。

IT 部门领导获得公司高层授意后,制定了一条政策:设立一项考试,获得电子化操作证书的员工优先提拔并重新评职级,这条政策本质是 IT 部门制定,却需要由业务部门执行。 这条政策的执行很快让软件的实际使用情况获得迅速提高。

在数字化推进的过程中,这个案例并不鲜见,我们来分析这个案例。

首先,在不使用政治手段时,IT 部门几乎没有办法提高软件使用率,如果把解决问题的方向放在软件体验上,将毫无用处(这条政策也有一个前提,软件的使用即使有一些问题,至少不耽误正常业务运转)。IT 部门使用了"不堪"政治手段,"不堪"的地方在两点。

  1. 电子化是公司利益方向,是大势所趋,站在了名义高地,才能发布如此政策。

  2. 考试的本质是隐晦地解除了基层领导的权利,争取一线员工的支持。

  3. 使用了一线员工竞争基层领导的策略,原本一线员工是潜在裁员对象,其实和公司处于矛盾状态,通过考试筛选的政策,拉拢大部分一线员工,迫使基层领导进行数字化转型。

这是一个"不堪"的政治示例,讲述了一个简单的道理:政治就是让朋友多多的,让敌人少少的。政治是操作矛盾的艺术,操纵民族矛盾对立,就可以消除阶级矛盾,而制造外部矛盾就可以缓解内部矛盾。

拉一个打一个是操纵矛盾的基本手法。

8.2 管理咨询的秘密

拉一个打一个是普通手法,在软件行业中,甚至当没有人可以拉时,可以"买一个",这就是接下来需要讲的故事。

可能有非常多的人和我一样不了解管理咨询公司到底做什么的,在社会中扮演者什么样的角色。我曾经一度对各大管理咨询公司的神秘抱有敬畏之心,直到后来才发现相关他们的秘密。

而这些秘密需要新的认知才能理解。

如今的管理咨询公司有点像古代的谋士或者门客。他们不是用高大上的方法论、专业的数据收集和调研来解决一些具体的问题,这些工作是研究院做的事情,这是一个普遍的误解。

我们换个角度思考,企业为什么需要管理咨询工作,甚至为它付高额的报酬?难道仅仅是因为管理咨询公司对某些领域更专业?请了咨询师不打当前管理者的脸嘛。

本质原因是一些难以推动的事情需要通过管理咨询公司的包装才能进行下去。如果一个公司需要对组织结构进行调整,那么直接动手会遇到极大的阻力。很简单的方法,包装一个高大上的项目和计划,例如进行变革、创新等行动,在行动中顺理成章的实现超出管理能力范围内的目标。

咨询师如果真的给出了"专业"的建议并否定了当前管理者的管理政策,那么必然会碰一鼻子灰。而真正的认知是,读懂并理解当前管理者的意图,并积极给予帮助。

8.3 权力的来源

从管理咨询的作用是作为达成雇主目标的工具这条认知出发,我们需要思考一个问题:权力的来源有哪些?

如果管理者有绝对的权力,下面的人对其言听计用,那么花钱购买咨询服务自然就没有必要了。从某种程度上来看,购买而来的咨询服务就成了行使权力的手段。

和我们常常理解的不同的是,名义上的领导往往无法行使与其表面相称的权力,而是被四处掣肘。

对于公司来说权力的最终来源是人事权、经济权。所以一般情况下,权力的来源是具有人事权的领导授予的,而不是来自团队成员的支持。"悦上者荣,悦下者蹇" 说的就是这个逻辑,取悦领导的任何往往混的比较好,而争取得到下属支持的人却不受优待,这是因为权力来源客观决定的。

虽然权力的来源不是来自下属,却在执行任务时需要得到下属支持,这点容易让人混淆权力的来源。所以实际上的领导争取下属的支持,但是权力的基础却不在这里。某些人拉拢同事对抗领导,相当于平白无故的争取权力,是没有人能接受的。

在写这条认知的时候感到非常残酷,却越想越是如此。我们能在历史中找到非常多类似的示例,在工作中也能发现不少蛛丝马迹。 当然权力的来源不仅仅如此,否则所谓的"政治"就太简单了,权力的另外来源还有能控制的资源。

对于销售为核心的公司,客户资源往往是隐性权力的来源;而对于技术为核心的公司来说,核心技术掌握也是权力的来源;群众的支持也算一种权力的来源,不过微不足道。

这些隐性的权力来源往往会和官方权力来源出现矛盾,这就是"政治"游戏的中心了。从各方的动机上来说,领导者的职责就是不断拆解隐性权力的影响,平衡团队。而隐性权力的受益人来说,自然不肯放弃,这就形成了极其复杂的博弈。

权力另一种的来源更加"不堪",那就是伤害别人和造成破坏的能力。在社会中这类的例子非常多,不过过于敏感暂且不说。即使在软件工程行业,通过"掀桌子"获取权力的案例也比比皆是。最不堪的就是恶化代码质量,人为地提高自己的门槛也不失为一个例子。

除此之外,抢占道义的高地也是获取权力的方式之一,这也是一些公司部门为什么抢着申报"创新"、"变革"等项目,利用这些项目可以对权力体系造成道义影响,行使超出自己职权范围外的权力,俗称"拉虎皮扯大旗"。

8.4 规则和秩序

通过权力来源的分析,我们会发现一个非常有意思的现象。现实生活中,很多事情并不像表面运行的那样,而是通过一些隐性的东西在支配和运转。

我们可以把表面支配我们生活的东西叫做规则。这些规则可能是工作制度、道德、法律,但是更多的是运行在规则之下的规则。有一些人把这类东西叫做"潜规则",而我可能更愿意叫做秩序。

规则是名义上的,而秩序却必然存在,而秩序就是通过权力体系构成的。

比如一个公司表面上由老板决定了如何分配工作和任务,一线的员工却把苦逼的事情额外的给了实习生,在老板无法事无巨细管理的地方由隐藏的秩序支配。

有时候会因为秩序的改变让事情走向截然不同的方向。举个狗血的例子来说,公司老板和经理的管理模式,构成了基本的秩序和权力格局。如果有一天,公司老板和市场部某位女性产生了不正当关系,原有的格局慢慢发生变化,新的秩序便形成了。

新来的员工在只知道明面上规则的情况下,不了解真实的秩序,那么极大可能在不该遇到麻烦的地方碰壁。

一个残酷的事实是,秩序的建立不受道德、公平和正义的丝毫影响。换句话说现实没有公平,只有秩序。

如果在一个学校的某班级中,按照规定应该平等的调整座位,如果班主任不进行干预,那么很快调整座位这件事就会变成秩序的体现,被霸凌的学生就只能永远坐最后一排。

另外一个更残酷的事实是,秩序只被有意愿且有能力影响局面的人影响,或者被某些事件影响。延续前面被霸凌的例子,如果被霸凌者去找老师投诉座位调整问题,如果遇到不负责的班主任老师,往往不会干预甚至觉得烦扰。如果被霸凌者接受了这个事实,那么不公平的秩序就建立了,除非通过某些极端手段改变当前的秩序。

关于秩序的问题,带入到软件公司和团队中并不会有太大差异。某公司一个团队负责人给自己的头衔是“架构师” ,然后请了一个外包项目经理催进度。听起来挺离谱的,但是这名"架构师"却是团队真正秩序的主导者,还不用做催进度的坏人,有时候甚至还可以和开发团队共情。

8.5 分析利益和动机

在公司的环境下,盈利是商业公司的目的,那么秩序往往由利益这只看不见的手控制的。

所以很多看似愚蠢的事情也能理解了。对于能作为领导的人,你以为他很蠢,其实是坏;你以为是坏,但是其实还是为了挣钱。

而为了挣钱就是利益驱动,有的时候个体利益和群体利益毫不相干。把含铜的电线偷来卖掉,获得极少的个人利益,但是却损失了极大的群体利益,这种事情在大型企业中非常常见。

所以,道家的杨朱学派提倡秩序的建立者应当认识到这种自私的人性。形容普通人是"拔一毛以利天下而不为"的本性,热衷于奉献的人是为了在博弈中获得更大的利益,而不是当下的小利益。

利益和动机是了解软件团队中非常有利的方法。匹配其动机的事,不需要推动就会顺利,而不匹配其动机推起来就非常困难,而动机往往是和利益绑定的,即使推动某些事情会损害公司利益。

举个例子来说,推广单元测试这件事情从长期来看无论是对公司、团队领导、开发人员都是非常有益的,但是实际情况却很难推动。

因为没有人具有太多编写单元测试的动机。开发人员短期内会增加工作量,对于团队领导来说看不到快速的收益,无法纳入 KPI 管理。除非有追求的开发者,为了长期构建质量内建坚持推动外,不具备自发性推动的动机。

因此很多公司可以通过利益捆绑,在 KPI 中增加单元测试覆盖率的指标,进而制造编写单元测试的动机。

所以动机和利益给我们启示是:不要说服你觉得合适的人合作,而是筛选有动机的人合作,即使一个人图名,一个图利,甚至只是图个开心也需要有动机才能达成合作。

8.6 形势:群体的动机

了解一个人的动机的威力就已经足够大了,那么如果是群体的动机呢?

这就是接下来需要讲的政治工具:形势。

如果一个人想创业的方向是和国家基本政策违背那么可以说希望渺茫,比如在减少煤炭产量的时候购买煤矿的采矿权,经营煤矿那么会非常惨烈,和前段时间 K12 教育的现象一样。

在公司内部也是一样,如果公司的发展形势是全面上云、技术栈转 Java 等战略方向,那么反其道而行自然不太好。

不过形势很难被分析看到,不然人人都是大师了。有一种说法我比较认可,形势是群体的动机,而群体的动机又是有影响力的人推动的,归根结底是人性和利益的动机。

安逸和方便的生活是整个人类社会的动机,也是科技的基本驱动力,所以搞出一些让事情变得复杂的发明自然不会有人买单。对于公司来说,盈利是其动机,因此所有活动都需要奔着盈利去。对于团队内部,群体的动机往往是领导的 KPI,那么形势也是如此。

形势的作用就像水流和船的关系,水流的方向是宏观形势的推动方向,而风向是微观形势推动的,靠引擎或者人力的推动是主观能动性。

在很多情况下,甚至只有船长具有主观能动性,其他人不过是在船底部的机舱中卖劳力而已。这些人形如我们,看不到水流的方向,也看感受不到风的方向,仅仅知道的是船长的指令。

他们虽然辛苦,但是不用过多地的耗费精神,也感受不到危险,同样也没有决策的压力。如果一个机舱中的船夫跑出来指点船长的工作,甚至舰队领袖的工作,那么大概率的情况是连船夫也没有资格做了。

而对于一名船长来说,观察风向、看清水流、识别礁石等风险才是他的工作,一名和形势对抗的船长某种程度上来说是不合格的。

8.7 职业节点就是行业生态位

博弈的平衡和秩序的形成,产生了生态位,每个人都在自己的生态位生存。从某种程度上来说文明社会并没有比生物圈更文明,只不过作为人类在和平的状态下生存环境更好了。

实力、背景和运气的差距是客观存在的,参与到竞争环境中的个体从来没有平等过,在充分的竞争下形成了某种秩序,这种秩序让个体产生了分层,也就形成了生态位。

如果把《湖南农民考察报告》再多读几遍,发现软件工程和行业也存在相似的结构。软件行业的生态慢慢分化出老板、大厂技术经理、大厂程序员、小厂技术经理、小厂程序员等层次。

如果把精力投资在深入技术上可能得结果是在生态位上被刻画的更死,因此有人把精力投资在生态位的变化上。这两种投资需要保持某种微妙的平衡,既不能太务实,也不能完全务虚。

另外,生态位切换的过程有点类似于甲壳类动物需要蜕壳,虽然在蜕壳完成后可以获得竞争力,但在蜕壳期间很危险。

这样的经历往往就是软件工程从业者焦虑的原因。大多数人都会经历从前端到后端,从业务分析师到项目经理,从技术人员到市场人员来回切换。 切换的过程中充满了不安、竞争力丢失等危险。

一位做前端的朋友告诉我他被提拔为了团队的负责人,直接管理后端、测试、产品等人员。但是他高兴不起来,原因是处于一个非常尴尬的生态位。被提拔的原因是因为在人员不稳定的公司里,他是相对工龄比较久的人。作为团队负责人却没有太多后端、产品等其他经验,无论是在当前岗位还是出去找工作都会不利。

从某种程度上来说,职业发展路线就是在不同的生态位上跳跃,每个生态位就是关键的职业节点。从某些节点上升更加平滑,有些节点上升更加陡峭,而一些节点彻底就是死胡同了。

8.8 价值的创造者和争夺者

在前面职业发展路径中聊到,职业发展就是生态位的提升,那么避免不了的两个方向就是技术和管理。

我们需要思考,为什么看似什么也不懂的管理者,收益却远比技术人员高呢?就像马云曾坦言,他不需要很聪明,但是他能找到聪明的人。这虽然是谦虚的说法,但是也表明了他无可比拟的优势和能力。

他是一名优秀的管理者,而不必做一名出色的技术人员。 其实对于管理者而言,其特质并不像普世价值说的那样:温良谦虚的君子。而更像是狮子在他的领地管理森林、草原和牛羊。

所以做技术的人和做管理者的人在认知上有两项极大的差异: 做技术的人关注创造价值,而做管理的人往往关注争夺价值。

当然这两项特质可能发生的同一个人身上,也可能某些人两者都不具备。创造价值也可以分为为别人创造价值,也可以解读成为自己创造价值。而争夺价值的人同样可以被理解为为团队争夺价值或者在团队内部为自己争夺价值。

对此种种,我们不便用世俗的道德评价它们。往往我们谈论的政治手段,更多的是争夺价值。

在争夺价值的思维里,才会分析权力的构成、形势、动机和利益问题;而在实施争夺价值行为的过程中,才会利用政治的手段。这些手段可以是具体的心理学攻势、谈判、游说等具体的行为。

心理学的技巧有:群体认同、沉默、肢体暗示、恐吓;谈判技巧有:制衡、制造稀缺;游说的技巧:利害转换。这些内容我们挑一些有趣和有用的重点讨论。

8.9 政治的手段:一些心理学

落地政治有写不完的内容,包括一些令人不齿的技巧。由于政治本身就是影响人的技术,从某种程度上来看,也有很多科学性的内容可以使用。

一些心理学的书籍坦诚的就说,某些心理学技巧就是为服务政治而来的。比如 NLP 神经语言程式学(不是自然语言处理的 NLP)就是一种通过心理暗示的方式影响他人的行为和决策。他的理论基础是:人脑是一种通过反馈不断修改和调整自己下一次判断的"程序",这种程序可以自我学习。比如,打一个人伸出的手背,这个人就会缩回,那么下一次只要发出打手的信号,就会不自觉的缩回,这就是一种神经语言程序的修改。

人类有一些固有的思维程序可以加以利用,也可以通过某种手段修改其他人的思维程序。

举一个小的例子,在历史的场合中,和自己行为相似的人往往是同族,于是大多数人都被印上了一段行为。就是和自己行为相似的人,可以信任。于是利用这一点,我们可以通过可以模仿他人的行为、口头禅、习惯来制造信任。

这一点不用多解释,可以试一试。

群体认同

一个和政治手段挂钩的心理学方法是群体认同,这也是从古至今的经典方法。

所谓政治就是把"把朋友搞得多多的,敌人搞得少少的",因此需要想清楚目标,"谁是我们的朋友,谁是我们的敌人"。 而回答这个问题就需要分析什么是"我们"。

有一个段子是四个人的宿舍可以创建出多少个群?如果不算两个人对话形式,三人以上的群可以创建 5 个。 那么这里的"我们"的数量就是 5。

你说这有什么用呢?我们再看一个例子。

一名女性在生活中遭遇不公,可能会有两种抱怨的方式。抱怨性别矛盾:男人都不是好东西;抱怨阶级矛盾:有钱人都不是好东西。

历史上鸦片战争时,一名老百姓也可能会有两种抱怨的方式。抱怨清政府:满人都不是好东西;抱怨国际矛盾:洋人都不是好东西。

我们再补充一个贴近软件行业工作的例子。如果一名程序员受到压迫,被产品经理分配过多的工作量。他会认为这是技术和产品之间的矛盾,抱怨:产品都不是好东西;也可能意识到产品领导和自己的技术领导串通了,抱怨:领导都不是好东西。

"我们" 的概念是人为塑造的,这个过程叫做群体认同。群体认同可以掩盖一些矛盾,这些矛盾可以被掩盖,同样也可以拿出来操作,这便是人类社会最最微妙的部分。在鬼谷子一书中,矛盾被称为"间隙",纵横家的学说便是建立在"间隙"之上的。

利用群体认同可以转移矛盾,有时候非常有效,也是企业破除部门墙的方法之一。如果一个现代化科技企业,按照技术部门、产品部门、业务部门进行了划分,那么部门墙的出现是早晚的,于是技术部门、产品部门、业务部门各自为政架空 CEO 等企业高层。

除了前面说到的使用外部咨询的方式来解决这些问题外,还有一种方式,即通过启动项目制来解决这些问题。

职能部门和项目团队之间构成了矩阵型的组织体系。在这类矩阵型企业的环境中,身处某个项目中员工会出现两个群体认同。他们既有行政部门的身份认同,也有项目成员的身份认同,对于高层来说影响和操作的空间就变得更大。

当然一定程度上也会带来行政管理上的浪费,增加管理成本。当总之来说,通过项目的群体认同,转移跨部门之间的矛盾往往是有效的。

注意这里说的是转移矛盾,而不是消灭。在矛盾论中,矛盾无法被消灭,只能转移,一个大的矛盾消失了,另外一个矛盾就会出现,矛盾是推动事物发展的基本规律,我们只能识别出不能容忍的主要矛盾并将其转化到能容忍的矛盾上。

群体认同的另外一种形式是制胜联盟。这种模式在马基雅维利的《君主论》、《独裁者指南》两本书中都有提到。

一个人想要影响或者控制群体,首先需要通过影响一小部分人,并通过这一小部分人来影响和控制群体,这一小部分人叫做制胜联盟。

制胜联盟的影子在公司、国家和团体中随处可见。公司会通过选择忠诚和利益一致的人来作为核心高层(Core Team),影响和控制整个公司;即使简单的学校班级也会建立一个班委。制胜联盟表达的含义是,总有"我们"比其他"我们"更我们一些。

为什么会这样?通过全民投票不行吗?事实证明,群体决策是盲目的,信息无法很快的在整个群体中传播,另外更重要的是制胜联盟的目的是它成为整个系统的凝结核,并且让其它个体尽量保持个体状态,这样的系统才最稳定。 而最不稳定的系统是系统中充满了各种各样势均力敌的凝结核,这些凝结核会相互对立并导致系统撕裂。

一个系统越大,制胜联盟的必要性就越强,而所谓的民主就越不重要。制胜联盟是群体认同中最有价值的地方。如果要引领一个群体,就需要建立凝结核,合并或者消除其他凝结核。如果想要在一个稳定系统中舒服的生存下去,最好的方法也是想尽办法加入这个凝结核。而加入凝结核的方法是保持忠诚或者制造对等的影响力并值得被最大的凝结核吸收。

8.11 政治的手段:博弈和谈判

政治手段除了通过心理学造成对他人的影响外,还有一个非常重要的内容,就是谈判。 谈判无处不在,有时候甚至不知道存在的情况下就发生了。在软件行业,软件工程师需要谈需求,谈方案。对于所有的人都需要谈薪资,所以掌握谈判能力的人,更具有优势。

最常见的一种谈判能力就是购物时讲价。有一些人天生就能讲价,将其谈判的能力发挥到极致。通过讲价引出的第一个认知变化就是:不要给可能设限,任何场合都有谈判的空间。当一些事情看似已成定局,或者想改变显得不可能,实际上在条件成熟时都有可能变化。

我在工作以前,以为连锁酒店的价格基本无法修改的,越是正规的酒店越没有谈判的空间。工作后,满世界飞,认知被颠覆。 18 年夏天,公司在西安组织了一次国际培训,我有幸是培训师之一。周末带其它讲师到成都旅行,到达成都后还是下午,因为没有提前定酒店,于是在街上挑选合适的酒店。

我们看到一个非常不错的花园酒店,挂牌价格为最低 800 一间。我进去打了个招呼,问了下价格,被告知价格无法修改,于是灰溜溜的出来了。在出来的一刻,同事进来找我,酒店接待的服务员看到我们十来个人,于是马上提出,可以帮我办理一个会员卡,而会员卡的价格是 300。 我被震惊了,挂牌价格和真实价格的落差如此之大,我继续询问是否包含早餐,被告知这个价格已经无法包含早餐了。经过大家七嘴八舌的抱怨后,可能是这家酒店属于淡季,最终依然满足了我们的条件。

这次讲价的经历给了我一个重要的启示:任何场合都具有谈判的空间。。博弈论是系统性被用到了商业谈判中,它有各种各样的分类方法。博弈,是社会中每一个人都需要掌握的能力,甚至比做具体的工作更重要,这里有几种博弈的分类,可以作为在处理博弈问题时作为参考。

合作博弈和非合作博弈 合作博弈是指在这次博弈中能达成共赢的局面,而非合作博弈很难达成共赢。那么,这两种场景中,需要采用不同的策略达成自己的目标。

如果能基于共赢的情况进行博弈,就可以互相摊牌,让对方看到所有信息,优先让对方达成条件,这样对方会驱动一起达成己方的条件。合作博弈 + 信息透明是最完美的合作状态,但是大多数情况下都不能达到。

如果不能做到合作博弈,博弈就变成了非合作博弈。后者在生活中更为普遍,更残酷。非合作博弈中,参与的各方都希望能达到利益的最大化,于是各方尽可能的隐藏自己的信息,了解对手的信息。

完全信息/不完全信息博弈 完全信息/不完全信息博弈是根据信息暴露状态来看的。当我们陷入不同的场景中,就可以利用信息状态的转换来实现博弈目标。

在合作博弈中,转换为完全信息博弈对双方更有利;在非合作博弈中,转换为不完全信息博弈,并且尽可能的少暴露自己的信息,了解对方的信息,成功的几率更大。

对任意一方来说信息状态有下面几种:

  1. 己方不了解对方的信息,己方也不了解己方的信息。

  2. 己方了解对方信息,己方不了解己方信息。

  3. 己方不了解对方信息,己方了解己方信息。

  4. 己方了解对方信息,己方了解己方信息。

如果再组合一下,己方是否了解对方的信息状态,又会有三种情况:

  1. 对方不了解己方的信息,对方也不了解对方的信息。

  2. 对方了解对方信息,对方不了解己方信息。

  3. 对方不了解对方信息,对方了解己方信息。

  4. 对方了解对方信息,对方了解己方信息。

所以,最优的状态是:

  • 己方了解对方信息,己方了解己方信息。对方不了解己方的信息,对方也不了解对方的信息。

最差的状态是:

  • 己方不了解对方的信息,己方也不了解己方的信息。对方了解对方信息,对方了解己方信息。

多阶段博弈 人和人之间如果只是一次交易,互相欺骗的可能性高,但是如果是持续合作,彼此更倾向于不欺骗对方。这种情况被叫做重复博弈或者又叫多阶段博弈。

认识到重复和单次博弈的区别,对我们谈判用处非常大。举个例子,旅游时用餐和在家门口用餐的博弈场景完全不同。景区的餐厅做的都是一次性生意,欺骗性极强。这种博弈在一次交易完成后一般就没有下次了,就应该尽可能保证当次的利益。

如果是商务合作中,长期合作的合作伙伴,在初期合作时,可以采取让利的策略,先建立合作关系,再从后续的交易中获得价值。

多方博弈 多方博弈是指在博弈中,参与方有多个,这样就会产生大量的博弈关系。最经典例子是国际社会中三方会谈,往往有六向双边关系将一个问题变得极其复杂。 当数量超过三时,会变得更加复杂。多方博弈时就需要达成几个目标才能取得较好的效果:

  • 获取足够多的支持,制造合作博弈

  • 尽量减少非合作博弈

  • 找到主要矛盾,将资源投入到核心问题的解决上

结束语

认知是一个似是而非的概念,它看起来没有什么具体的内容,有时候却帮助甚大。当我们限于具体问题苦苦不能解决时,可能换种思维方式便轻松看到问题的本质。

正是因为认识到这一点,我才对认知提升如此着迷,并竭力用到软件开发工作中,因为它切实的改变了我的工作、生活以及对软件的看法。随着认知的变化,开始不再用朴素的眼光看待问题的现象、各式各样的观点以及日新月异的新鲜事物。

另一方面,认知又是非常私人的。每个成熟的人都会有自己认识世界的方式,我也相信每个灵魂的背后都有一个深邃宇宙,只是他们没有像我一样表述出来。所以我时常和身边的同事、朋友交流认知相关的问题,这本小书中的一些内容也源于此,从他们那里获得了一些启发并记录了下来。

实际上,我们也需要读懂别人的认知方式,还需要尊重每个人的认知方式,这是了解一个人以及建立良好合作关系的重要一步。在软件工程中,人是优先于软件本身存在的。在某种程度上来说,软件本身就是软件工程中参与人的思想在计算机中的投影。当我们照顾好身边每一个参与项目的人,软件就已经成功了一半。

而这一切的前提是拥有开放的心态和不断进化的认知模式。不断保持进化,从外界吸收新的认知模式才能让认知提升在实际工作中、生活中发挥作用。用时髦的说法,同世界对话就是用“外脑”来思考。

阅读和写作是一种不错的对话方式,这本小册子的内容使用开源发布,如果您对这个话题有兴趣可以通过邮箱联系共同交流和补充。

如果需要联系我,可以通过以下途径:

  1. 微信公众号:TechLead 少个分号

  2. 网站:http://shaogefenhao.com/

  3. 微信:shaogefenhao

公众号二维码:

wechat oa

1. 一种智力的分类方法,参考图书:赖丁. 认知风格与学习策略[M]. 华东师范大学出版社, 2003.
2. 又称短期记忆,参考文献:袁婉秋. 工作记忆模型的新进展及其研究展望[J]. 西南大学学报:社会科学版, 2011(S1):2.
3. 参考图书:笛卡尔, 王太庆. 谈谈方法[M]. 商务印书馆, 2000.
4. 参考文献:王永昌. 哲学认识论的瞩目之作[J]. 中国社会科学, 1990(4):4.
5. 参考图书:让·雅克·卢梭,王田田. 社会契约论[M].中国人民大学出版社:世界大师原典文库, 201303.234.
6. 参考图书:德黑格尔, 范扬, 张全泰. 法哲学原理[M]. 商务印书馆, 1961.
7. 参考文献:张文宇. “命题”概念源流考略及新探[C]福建省外国语文学会2003年会暨学术研讨会论文集.[出版者不详],2003:48-53.
8. 参考文献:曾繁亮.现代思维模式:真假、好坏、对错[J].中共四川省委党校学报,2000(03):11-14.
9. 参考图书:萨拜因(著), 邓正来(译). 柏拉图:《理想国》[J]. 河北法学, 2007.
10. SuperCell 的中台你们学不会.史凯. https://xw.qq.com/cmsid/20200511A0I1UL00
11. 参考资料:阿里巴巴董事局主席兼首席执行官张勇湖畔大学重磅分享 https://www.sohu.com/a/363219131_120047117
12. 参考文献:Occam’s razor.Robert T. Carroll. http://www.skepdic.com/occam.html
13. 参考文献:彭大均. 正确认识否定之否定规律[J]. 上海大学学报:社会科学版, 1992(2):4.
14. 参考维基百科对于形式语言的定义 https://zh.wikipedia.org/wiki/%E5%BD%A2%E5%BC%8F%E8%AF%AD%E8%A8%80
15. 参考文献:杨海波. 弗雷格《概念文字》理解的两点注记[J]. 逻辑学研究, 2012, 5(4):10.
16. 参考图书:《模型思维》https://book.douban.com/subject/34893628/
17. 参考图书:《商业模式新生代》https://book.douban.com/subject/26904600/
18. 参考文献:邱柳方.“电商黑马”拼多多的商业模式探析[J
19. 参考拼多多商家入驻说明 https://ims.pinduoduo.com/qualifications
20. 数据来源 https://view.inews.qq.com/k/20220527A0AG7300
21. 来源于网络社群Nielsen Norman Group logoNielsen Norman Group 文章 Service Blueprints: Definition https://www.nngroup.com/articles/service-blueprints-definition
22. 参考图书:《软件方法——业务建模和需求》https://book.douban.com/subject/25755508/
23. 参考图书:《领域驱动设计——软件核心复杂性应对之道》 https://book.douban.com/subject/26819666
24. Event storming 网站 https://www.eventstorming.com/
25. 关于四色建模法来源见文章 https://www.infoq.cn/article/xh-four-color-modeling
26. 有意思的是,这种建模方法并非空穴来风,的确有一种使用卡片进行角色扮演的建模方法。
27. 参考文献:Ferraiolo, David, Janet Cugini, and D. Richard Kuhn. "Role-based access control (RBAC): Features and motivations." Proceedings of 11th annual computer security application conference. 1995.
28. 参考文献:Hwang, Min-Shiang, and Wei-Pang Yang. "A new dynamic access control scheme based on subject-object list." Data & knowledge engineering 14.1 (1994): 45-56.
29. 参考图书: 《分布式计算——原理,算法与系统》https://book.douban.com/subject/10785422