写在前面

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

认知变化的来源

在 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 张勇在湖畔大学分享时也说:如果一个企业奔着中台做中台,就是死。中台并非没有价值,它描述了一种构建了自己生态体系的企业,如何利用复用的能力去实现更大的价值。问题在于,往中台战略前进时,它早已暗中标好了价格。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

爱因斯坦因此说:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 用否定之否定来看待技术选择

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

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

这就是第一次否定

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3.1 理解概念

曾经参与一个物联网系统的设计,“设备” 这个词在不同的开发者眼里有不同的概念,为此,讨论 “设备” 这个词花费了不少的功夫,最终依然没有怎么定义清楚。

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

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

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

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

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

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

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

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

3.2 形式语言和非形式语言

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

public class Goods {
  private String name;
  private int price;
}

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

Goods{name,price}

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

3.3 概念的内涵和外延

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

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

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

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

概念的内涵和外延在一定条件下或者上下文中被确定的,这取决于参与人的共识。严格锁定概念的内涵和外延,不能帮助我们讨论问题和改进软件模型。随意修改内涵和外延这是典型的偷换概念和诡辩。

概念的内涵和外延是一个此消彼长的兄弟。当内涵扩大时,外延就会缩小,概念就会变得越具体。当内涵缩小时,外延就会扩大,反映的事物就会越多。

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

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

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

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

3.4 定义一个概念

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

同一律

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

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

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

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

假如我们选择的论点是 “网络让人们的生活更方便”。在辩论赛的前期,另外一方为了论证 “网络让人们沉溺虚拟世界”,描述了打电话、写信也可以让人生活很美好,并不会沉溺。这刚好落入我们的逻辑陷阱。我们指出,邮政、电话网络也是网络的一种,对方的逻辑不攻自破。

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

矛盾律

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

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

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

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

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

排中律

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

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

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

排中律看起来好像没有意义,但具有非常大的价值,让讨论最终有结论,而不是处于似是而非的中间状态。

3.6 诡辩

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

偷换概念

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

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

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

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

相关性不等于因果性

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

同样的桥段在各种电视、电影中存在。某一伙人闯入了一个村子,然后这个村子发生了瘟疫,群众认为是这些人带来了不详。

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

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

因果倒置

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

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

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

归纳法的局限

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

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

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

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

责任转移

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

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

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

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

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

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

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

3.7 逻辑工具:概念图

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

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

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

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

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

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

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

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

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

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

概念图的绘制工具

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

  1. PPT。

  2. Keynote。

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

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

3.7.1 “概念”的概念图

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

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

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

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

3.7.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 是一个框架还是一个库,亦或者是一个开发体系?

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