铜剑技校

仝键的个人技术博客

0%

一个蹒跚学步场景的启发

我们家娃最近在学钱的加减,几元几角减去几元几角等于多少这种问题。在这之前,她已经学会了加减法借位等基本数学知识,但做钱的加减还是不行。

个位的元角减去个位的元角没问题,比如7元8角减去5元9角这种没问题。但是这个10元5角减去8元6角这种题目,他就不会借位了,因为她理解的借位只会像一位数借位,10元这个不是个位数,所以总是算错。

我给她讲道理呢,她就说:听不懂……

这就尴尬了,怎么讲他都不会,那就用我的老招数呗,刻意练习,我自己出了几道类似新题,让她练,还是经常算错。有趣的是,也不是每次都不对,有点六脉神剑时灵时不灵的感觉。仔细观察了一下发现呢,竟然是做过的她背过了一部分表象,比如10元减8元要借位怎么借她背过了,但出的新题里,有的题借位有的题不借位,所以就时而对时而错,不过这道理给小孩子讲了没用……

这种场景我在教毕业生学写程序时候也遇到过,有些学员多做几道题自己就悟出来了,好像开窍了一样;有些学员就背过一些表象,于是就六脉神剑时灵时不灵了。因为编程比元角计算可复杂多了,所以一直也不知道怎么破这个局。

如今这个场景又出现我眼前,以一种噪音更少的方式出现了,我依然不能通过多做同类题目的方式解决,这说明这个方向是彻头彻尾错误的。

最后我没招了,我说,来,我把我怎么解题的说一遍,你背下来。我其实也不知道为啥我要这样干,就是个脑子里突然闪过的想法,纯粹死马当活马医吧。

于是我就说,10元5角减8元6角这个题我是怎么解的呢,我们的计算规则是元减元,角减角,但因为5角减不了6角,所以要跟10元借一位,借完之后呢,10元就变成了9元,5角就变成了15角,然后15角减6角得到9角,9元减8元得到了1元,所以最后答案是1元9角。

我就让她背,背的时候,她就在借位环节问问题了,我也解答了问题,全部过程能背下来之后,她就好像突然开窍了一样,换几个题都会了。

背后的“原因”

这神奇结果让我很意外,赶紧分析了一下,为什么给这个实践会起作用。我感觉,大约的原因是,初学者由于领域知识的缺乏,他对于自己为什么不会这事是缺乏语言描述的。甚至于她到底哪个点不会都说不出来。说不出来就是在瞎做,算不得刻意练习,刻意练习非常强调针对性的专项训练,知识工作者真正需要锻炼的是大脑里思考问题的方式,只是做题,如果思路不对的话(比如中间某几步是蒙的),那真正欠缺乏锻炼的部分根本没锻炼到。但找到这个点很难,因为问题往往是隐藏在思考过程中的。

之前的实践恰恰可以针对这个问题产生影响,我给这个实践起了一个名字,叫背诵解题思路。这两个词拆开正好就是我们这个实践的两个关键点,一就是背诵,如果我们只是听一遍解题思路,会出现听起来很有道理觉得自己学到了东西但实际什么都没带走的情况,跟不明觉厉也没差多少。大家都可以自己测试一下,听起来很清楚的道理自己背一遍总会在某个地方卡壳,那个地方可能就是我们需要锻炼的点。

另一个关键点就是解题思路,这就贴合了我们的标题,什么值得背。我们在谈到教学总会比较迷信所谓循循善诱,觉得死记硬背是不好的(都市传说,背诵会扼杀创造力)。但其实记忆是一切的基础,只是要明确,我们记忆的目的是为了什么?最终的最终是为了应用,为了改造世界。我们经常背一些知识点,定理定律规则,这些东西都是死的,不能改造世界,而较少被人关注的解题思路,尤其是高手的解题思路才是改造世界的出路。人跟人思考的过程差异之大是令人乍舌的,有时即使看起来很简单的题,你看一下高手的解题思路,都会发现很大差别。各行各业的高手都是类似的,之前看过科比的一档节目,叫细节,看完之后才知道,运动员在运动场上处理的信息量有多大。

对程序员的价值

这个实践对于我们程序员有什么价值呢?在编程中,有很多问题是没有标准答案的,比如任务分解、比如软件设计等,前面说的需要学生开窍的场景其实都属此类。虽然没有标准答案,但是我们又想提高自己,就只能去学习高手的解题思路。其实,很多工程实践已经是把高手的做事方式讲给我们听了,但做的时候他们脑子里是怎么想的呢?这只有在自己做的时候自己摸索。而这个自己摸索的过程通常效率是很低的,会有很多只得其形不得其神的情况。这对团队中带人的人,像我这样需要教人工程实践的人都要面临的一个困境,很难大规模的培养出优秀的程序员。究其原因可能有很多,但魔鬼一定是隐藏在细节里,现在我觉得背诵解题思路可能是一个解。怎么解呢?

举个例子,工程实践中,tdd是最难学的一个,tdd就是高手的做事方式。最近在参与熊节的tdd练功房,有位同学就第一道最简单的题写了自己的解法:
https://www.jianshu.com/p/13d7a0a0e5be

这个解法到了后面有点过度设计,但本题练的是编程的手艺,所以重点在过程中的细节,那一个个细小的步骤,我敢说是很多程序员会忽略的。

再举个例子,面向对象封装继承多态,最难的就是封装。我们最近出了一道重构的题目给某公司,在重构的过程中要把分散的基本数据类型的数据封装成对象。分散的数据如下所示:

充满坏味道的代码

这个能力其实是最难锻炼的,上面这些数据谁跟谁应该封装在一起,谁跟谁不应该。这也属于没有标准答案的问题。作业结果也证明了这一点。怎么办呢?同样,可以试着背一下高手的解题思路。(想知道这个题的思路吗?等我后面的文章,没错这里是广告)

综上,对于一些实践类的内容,总是会出现不管怎么练也不开窍的情况,比如如何高效的TDD,比如软件设计。这个时候,大家可以试试让被教授的人背诵一下解题思路,说不定会有效果。

能力建设的鬼打墙

我们为了变强,会订阅很多公众号,牛人博客,收藏很多视频,看很多文章。做了很多松鼠的行为,然而最终大多数人并没有变强。
我去看了很多学校,大多数学生面临的成长问题与十几年前并无太大区别。
我去看了很多家公司,大多数公司面临的能力问题跟十几年前并无太大区别。
十几年前,我们买一台电脑都贵得要死,现在跟一台手机差不多钱了。
十几年前,我们想学编程连个像样的教程都没有,现在互联网上的教程不要太多。
为什么会这样呢?
因为信息的多少,并不是学成才的关键。很多人可能知道我又要说刻意练习了,可是按照刻意练习理论,要练一万小时才能成才。但我们从业者练了不止一万小时了吧,每天996,3年就该1万小时了吧,为啥能力还是个问题呢?
因为工作的关系,我不得不思考这个问题,思考的久了,我自己有个理论。我在想,刻意练习把问题过分简化了,学习过程可不是那么简单的,大面上讲,我觉得刻意练习理论是工作的,只是过于粗糙,需要精细化的考虑一下,仔细思考一下刻意练习的背后到底是一个怎样的过程。

刻意练习的元模型

经过对自己的学习过程进行反观内视,加上跟很多人的交流,我大概抽取了一个闭环的元模型。
image.png

我发现首先要有问题供学习者思考,我称之为我思,有些问题比较简单,直接可以思考,有些问题比较难,需要先学习一些基础知识才能理解问题本身,后者就是读书的原因之一。

理解问题之后,就要开始解决问题,毕竟企业里的能力建设都是要解决问题的,必须学以致用。为了能学以致用,那自然就需要练习,但练习不一定是非要专门的练习题,只要将所学知识应用,也算是练习了。练习需要有反馈,知道你做的对还是不对。

经过练习之后,需要对整个过程进行反思、总结,产出经验和洞见。有数据供反思时分析和有反思的习惯是很重要的,我发现傻做的人很多,反思的人很少,真的是大家都在泥坑里,只有少数人会仰望星空。

成才三要素之一——环走通

按这个环来分析,如果想要成才,首先环要走通。
我思的瓶颈在于认知障,所以还是比较简单的,扩大知识面就好了。
我做的瓶颈才是最麻烦的,要走通这个环,最难以达成的瓶颈的其实是我做的环节,在互联网时代,你想获得知识已经很容易了,只要你想,你总能找到可以用的知识。
但是学到了知识能不能实践其实是关键,你学了大量高并发的知识,但是你工作环境下没有高并发的环境供你练习,你也学不会。你想学软件开发的方法学,却在一个简单机械重复的环境下天天垒那种过些日子就没用处的代码,并不会有所提升。
这就是我为什么开始说,道场胜于道法,你没有一个练习场,你学到的知识都卡在第一环节了。
然而一个合格的道场本身并不是那么容易具备的,这个我们在后面慢慢讲。

那么在我反思这个环节上,首先你要有反思的习惯,这个是比较反人性的,所以具备的人本就不多。即便你具备了,一个人的认知又容易受自己的偏见蒙蔽,容易看不清事物的全貌。需要老师的启发式引导,同学的研讨印证。一个具备好的老师同学的环境其实是道场的一部分,你所处的环境里根本没人跟你聊这些,你很容易陷到自己的认知瓶颈里很久都不得突破。

其次要有数据供自己反思,不过只要有了这个习惯和意识,数据什么的好搞,比如编程的话,可以参考我的《编程的精进之法》。

成才三要素之二——提高频率

一旦环走通之后,频率就是我们接下来要面临的问题。
一些简单的内容可以通过设计针对性的练习来提升
能进行针对性练习的环境就是我们最好的道场,而现实并不是这样的。
我当年练习投篮的时候,就发现,如果是自己一个人练的话,不但要投还要捡球。这单位时间内能练习投篮的频率就降低了。
等你把投篮练的差不多了,发现在比赛中能投进和练习时能投进又不一样。
针对这两种,你要建立两个环境,互相不能偏废,我称之为专项训练的环境和综合训练的环境。
在我们教学生编程的时候,就采用了这两种不同的环境,我们会设置一些非常小的练习,针对性的练习结构化编程、集合的处理、数据结构的设计和数据的转换。
然后会设计大作业进行这些技能的组合使用,这个时候需求到具体技术的映射是模糊的。
最后会设计项目,团队协作做项目,这个时候需求本身也是模糊的,团队成员之间的配合也需要锻炼。
前两种都属于专项训练,分别练习的是单个专项技术的使用和多个专项技术的组合,最后一个属于综合训练,锻炼在模糊不确定性下使用确定性的技术来完成需求的能力。

这些良好设计的环境都是为我们和我们的客户的校招新员工设计的,普通员工的日常工作中难以具备这样的环境。
那么只需要考虑一件事就好了,我们的日常工作中有多少机会来进行练习。如何改造我们的工作环境来,使之成为个体提升的环境是关键。传说中,少林寺的和尚吃饭睡觉都在练功其实就是在讲这个简单的道理:提高频率必须从环境改造下手。

成才三要素之三——降低废品率

解决了闭环、频率问题之后,还有一个不得不面对的因素:废品率。
在练习过程当中,并不是所有的练习都是有价值的,大量的练习都是无效的,甚至有些是错误的,在反思阶段会产生反模式。
随着闭环次数的增多,熟练度就会提升,不但正确的做法的熟练度会上升,错误做法的熟练度也会上升,如果反思阶段思考的不足,还会得到一些既不是正确做法也不是错误做法的纯粹的无效做法的熟练度。
这些熟练度都是问题,都是成长过程中的废品,而如何降低废品率,是高段位竞争者之间的胜负手。
这个的获得可以靠反思的时候总结,也可以靠阅读实践者的文章书籍。
比如如果纯自己摸索可能最后总会获得大泥球的架构,但是如果阅读了DDD之类的文章书籍就能知道可以靠上下文和聚合的方式进行解耦。
但是这种实践者写的书籍不像具体的知识那样好懂,通常都是非常抽象的。没有懂行的人指导或研讨,跟看天书也差不多。有时跨过了理解这个坎,又会发现现实环境不允许你进行试验。不管工作流程还是协作方式,从根本上限制了问题的根本解决。(不然问题早就解决了)

道场建设的窘境

在分析了刻意练习的原模型之后,我们会发现,在今天这个时代,能力建设最大的瓶颈是道场,如何将道场建设起来,比学习知识重要的多,好的练习、好的环境、好的同学比其他的都要重要。

而现实中,最大的道场就是实际工作环境。所以不管能力建设在开始看起来多么人畜无害,由于涉及到工作环境的改造,最终都会影响到组织的改造和调整。想想也是合理的,按照康威定律,什么样的组织架构需要什么样的软件架构匹配,什么样的软件架构需要什么样的能力匹配,如果旧的环境能够提供足够的练习场,那么能力建设根本就不是个问题,既然能力建设是个问题了,那么在能力建设的需求下,现有组织架构必然是要进行改进的。

絮絮叨叨的开场

上一篇需求的视角,我们介绍了一种可以让人回归场景的的小工具,避免在实现功能时的需求理解问题。但是在我们很好地理解了需求之后,在实现过程中,就会一帆风顺么?也不是的。

我们工作久了,就会发现有些人资质很好,有些人资质就很差。你会发现,有些人会考虑的比较有深度,有些人就不行。到底什么是深度呢?我不太能理解这个词,于是我继续观察。从外在看呢,我们会发现他们的行为表现有差别。我称之为工人的行为模式和工程师的行为模式。这两种行为模式在面对问题的时候差别尤为明显。

一个工人行为模式的人,遇到问题的时候,采取的方式就是直接把做过的行为再做一遍,期望能有所不同。比如装个环境,遇到问题了,就把执行的命令再执行一遍。

而工程师行为模式的人,遇到问题的时候,却什么都不着急做,往往是盯着出错的屏幕若有所思,片刻之后,突然说“哦,我知道了”,然后一下就把问题解决了,仿佛是在脑子里Debug。

我们打开看呢,你会发现他们的思考问题的维度是有差别的。我一直找不到一个好的模型来解释这种思维模式的差别,直到我在一本叫《创新算法》的书里面看到有个三轴分析的模型,可以较好的描述这个维度差别:

三轴分析

按照三轴分析的模型,我们思考一个机器,要从三个维度去思考,一是操作,就是人是怎么操作这个机器的;一是系统,就是这个系统都有哪些组件,这些组件都有什么关系;三就是因果,就是当人进行某种操作,到看到结果之间,一步步是怎么发生的,哪些组件起了作用,出了问题又是哪些组件没搭配对或者那个组件出故障了,或者操作本身是不是做错了。简单讲因果轴就是前两轴的动态关系。

计算机虽然精密,它也是一台机器,这上面跑的软件也没有任何的魔法,一样是适用于三轴分析的。

所谓像工人行为模式的人,其实就是指关注操作轴的知识,而对下面的系统轴和因果轴关心较少造成的。我曾经在面试的时候,让一个人讲他用到的一个框架的原理,结果他在白板上一顿乱画,我始终没看到组件有哪些,互相又是怎么配合的,最后发现,他是按照界面在描述那个框架的(很多框架都提供跟eclipse集成的GUI操作界面),他说的全都是在哪界面上填什么信息,然后就会有什么效果。只能用操作轴的信息来描述一款框架,这种行为也就是我们所谓的没有深度。

这样的例子在行业里比比皆是,我们在培训中就曾经碰到过一件哭笑不得的事情,比如在进行一些代码操练的工作坊的时候,让学员使用intellij来进行编程,一些有多年工作经验的学员写的代码出了编译错误,说这一定是你这个IDE的问题,我用eclipse这么写就没问题。当面用eclipse打开,依然是编译错误,就没话说了。还有远程就不会断点调试、配了log4j就抱怨e.printStackTrace()不打印了等等奇葩的例子就不展开聊了(但不写出来吐槽一下是真难受,我的一个朋友经常吐槽,为什么这个行业里这么多“业余”的人。说实话,我也是有点绝望的……),其实这些都是一个问题,就是只关注操作轴,关注的久了,所有的技能都只用操作轴的信息来编码,换个界面就武功全废,也是很多人觉得编程只能是青春饭的原因。

而工程师行为模式的人,一定不会只关注操作轴维度的信息,他们会打开系统,看到系统内部构造和系统到底是怎么运转的。由于对这些东西非常了解了,才能盯着屏幕上的日志,在脑子里模拟代码的运行,推理自己可能哪里出错了。

要想做到这一点,其实并没有什么魔法。我们用到的绝大多数框架、库、工具都是开源软件。开源软件都是很慷慨的把自己的机制、代码都放在了网上,完全没有信息壁垒。如果我们在读完入门案例后,还去读读Manual或Reference(有余力也可以读读源代码)我们不但会对系统的组件和系统的运转机制有更深入的了解,而且会在操作层面多一些新技能,当我们施展出来,还会有人惊呼“还有这种操作”。唯一需要做的就是认认真真读文档,照着文档写几个demo,编程初学者的话,把API doc都读读是最好的。然而这一件简单的事,竟然都成了少有人走的路。

所以对于开发人员来说,想要成为一名合格的工程师,只关注操作轴肯定是不行的,在学习的过程中要刻意的逼迫自己去关注系统轴和因果轴。不过这确实不是一件容易的事,不同于普通机器,软件的复杂度要高得多。咱们软件业有一句话说,没有什么问题是不能通过添加一个中间层来解决的(如果有就再添加一层),所以学习过程中会不断的发现,自己被一个抽象层挡住了。需要重新用三轴分析工具分析一下,抽象层的下面又是什么。学无止境,无尽的三轴。

本次讲的刻意练习其实是最简单的:阅读,写demo,这些都是优秀程序员的基本功。也是最难的一个,因为实在是太枯燥了,以刻苦的学习击穿认知的次元壁从来也是少数人才能达成的成就,但其实你仔细去观察那些少数人的时候,你会发现他们有些小技巧,能把苦变得有趣,这里就介绍几个小技巧。

Demo法

写一些小demo其实非常有助于建立起从操作到系统的映射,并可以通过下载开源代码及单步调试来理清中间到底发生了什么。由于demo都很小,很容易快速的建立收获感,你不会觉得做了很久没有任何收获,从而放弃。也不需要很大块的时间来做,导致每次学习都死于起手式。

我的前同事李鹏是此中高手,他有一篇文章《当我拿到一个前后端实现todolist的学习任务时,我应该怎么做?》中,把任务分解(我们下一篇就讲这个)和demo法结合在一起,大家感兴趣可以看看。受他影响,我也做了一个小github组织,来放自己的demo:https://github.com/jtong-demos 可惜最近几个月太忙就没时间更新了,不过也看出了另外一个优点,等我忙过这阵,想要捡起来的时候,这些demo会比网上的什么资料都快。

输出

为了深入到另外两个轴去,阅读当然很重要,但是对于大多数人来说枯燥是最大的退却原因。所以我在之前带学生的时候,都要求他们输出,写博客。有输出,枯燥感就会下降一些。但是写文章本身也很累,而且很容易变成抄书,所以我们后来又找到了另外一个工具:概念图。

概念图工具的概念图

可以用节点来表达组件,用线上的关系来表达组件之间的关系,非常适合表达系统和因果轴,可以更轻量的进行输出(具体怎么用,简书上一搜一大堆,我就不写了)。其实我之前介绍的C4,就可以看作是一种特殊的概念图。

小节

如前所述,我们学习软件相关的知识,可以借用三轴分析法,从操作、系统和因果三个角度扩展自己的认知。只有这样才能成为专业人士,刻意的阅读、输出、写demo,这简单的几件事,能做到的人却很稀有,这也是为什么优秀的工程师非常稀有的缘故,我们公司的数据,一个业务分析师或者QA,只要聪明,进入公司,差不多1年就可以比较独当一面,而开发人员,大都需要三年。大多数人都喜欢走易走的路,而难走的路反而是人少的捷径,坚持做好这几个动作,就是提升自己的捷径。

楔子

在我培养学生的时候,我发现一个很有趣的现象,有些人可以非常好的理解需求,并且能发现需求当中的逻辑矛盾,给出非常好的反馈。更多的人,只是听了需求,然后没有任何反应,去做的时候呢,才发现有问题。最可怕的是,明明需求有问题,做的时候他也没发现问题,最后做出来的功能是有bug的。

想起我吐槽一个学生,让你扫马路,你就把土扫到路两边,只管中间干净。跟你说两边也要干净你就把两边的扫到中间,说两边和中间都得干净就在两边和中间的间隔处推上细细的一条垃圾线。

吐槽虽然可以让人很爽,但不能解决问题,只是享受了优越感。作为以能力建设为己任的培养者,这种优越感反而是一种打脸,毕竟学员能做到才是我的成功,他做不到我能做到有啥好优越的呢,让他做到才是我应该追求的优越感。所以我就陷入了深深的思索,到底怎么能让学员把需求理解清楚呢?看着他们,我开始反观内视我自己,我是如何做到的呢?

首先我想到,这个是个场景思考的问题,虽说思考用户场景,很多公司是PO、BA或产品经理的工作,但所谓好的程序员要能顶上半个产品经理,既然我们说是数字人才,每个人都要能理解软件的使用场景才行。我自己就是会拿到需求后,在脑子里重新构建场景。然而道理说起来简单,做起来并不简单,不管我强调多少次,要关注场景,结果也不会好,会的立刻恍然大悟(那种恍然大悟是一种原来我这种做法还有名字的感觉……),大部分不会的人还是那样……

跳出盒子外

进过长期的观察,我发现他们是在盒子内思考,而跳不出盒子外。

跳到盒子的外面,才能发现场景

什么是盒子内思考呢?比如一支白板笔。

如果我在白板笔的盒子内看的话。我就会看到笔帽,笔芯和笔管。我可能会关注,笔芯是海绵的,笔帽和笔管是塑料的,不是金属的。这样思考就有一个问题等我考虑到颜料的时候,我就不知道应该采用什么样的原料。就必须要问一个问题,这个白板笔是用来干嘛用?因为如果我在盒子里看白板这个东西,对我是没有任何含义的。

那盒子外就很好理解了,同样是描述一支白板笔,我要考虑他,写出来的东西好不好擦。写出来的字迹是否清晰可见?粗细是否适中?能用多长时间?成本是多少是,买一个新的还是加墨水儿更合适?一旦跳到盒子外,我还要考虑多角色比如制造商,分销商,销售终端,买家不同角色看他的视角。小小一支笔,也不是那么简单的。

回归软件开发的上下文,盒子内就是功能视角,比如:学习平台要有写成长日志的功能。盒子外则是业务视角,比如:学员参加了学习后,每天要写成长日志,总结当日所学,练习总结能力,助教要给他们进行检查和反馈。比起前者,后者很容易想到,既然是练习总结能力,是不是应该有模板和范文?

我常试好多的方法,试图让初学者可以跳出盒子外,大部分都没啥效果,不要说初学者不会,很多工作过多年的人也有这类问题,也学不会。也曾一度让我怀疑,这是一种天赋,好在不想对天赋低头的心,最终还是找到了一种实操性很好的方法。

故事线

我会要求学生画一种图。我们管它叫故事线,是一种场景思维的辅助工具。

故事线

这个工具是这样的用的,每当你要做一个功能,就画一条故事线,在故事线里面,首先你要定义故事起点。这个故事的起点是一个业务动机。比如说一个系统可以创建用户,那么这个创建用户的业务动机是什么呢?我问出去的时候,学生们就会开始思考,想到了说创建用户的业务动机是有一个新人入职了。

有了业务动机,别人才会使用你的系统,否则你的系统只是你的自嗨而已。不要说学生了,很多工作多年的人在设计系统的时候,依然不思考这个问题。有了动机,你也可以去对比各种不同的解决方案,去想有没有更好的解决方案?比如在我们这个例子里,动机就是有人入职了要创建用户,但那么有人入职了之后,谁会来这个系统里面创建用户呢?这就涉及到方案的不同。可能我们定义的角色是hr。也可能我们这是一家小公司,反正就是那么几个人,最后就跟一个系统管理员说一声,他登上来录进去就好了,对于一家小公司来说,这也可以接受,所以你就会发现,这两个都是可行的方案。

我们在实践中发现,有了业务动机这个概念之后,大家就会对场景有更多的思考,也能协作着来对比方案而不是瞎吵架了。

故事线的后面就比较好画了。我们就画他在这个系统里面为了满足他的业务动机,他需要一步一步怎么做的。注意只画成功路径,过程中遇到的异常,先不要考虑。这对很多人来说,也是一个很难做到的事情。

在故事线的最后,需要画出来说,这个故事结束了之后,那么最终,业务的下一个起点是什么?

还是以创建用户为例,当我们为用户创建完了之后,我们的下一步是什么呢?下一步可以试试,发个邮件给用户,也可以是,把用户名密码拷给,我们的新员工本人或者他的经理。

学生做的一个案例

小节

如前所述,很多人是没有场景思维的,他想问题只能想到这个盒子内,而跳不到盒子外去关注使用它的场景。对于这种情况,我们就算明确的指出,他没有场景化思维,大部分情况下是不会有任何的作用的,大部分人依然没有场景化思维。他就像我《数字人才的刻“意”练习——开篇》里讲到的画画需要关注线条一样,我们需要告诉大家,应该关注在什么地方才会具有场景化思维。那么在这种情况下,我们只需要给他一个非常好的辅助工具,他就真的可以做到跳到盒子外。故事线就是这样一个工具。实践中发现,哪怕是毕业生,几次练习之后也可以快速掌握这种思维方式,更不要说有多年工作经验的程序员。

虽然在我们项目中是有所谓的业务分析师的,但是优秀的程序员一定会在脑子里重建这些场景。如果程序员不能以这样的方式画出来的话,那么程序员做开发的时候也会出现一些问题,比如没有想全,做出来的程序有问题的情况。所以教给程序员自己这么思考问题是非常重要的,在理解完业务分析师传达的需求之后,可以在脑子里把这条线画出来,非常有价值。

本来是开篇的一部分,结果越写越长,索性就单发了一篇

什么是数字人才

这些年,很多企业都在搞数字化转型,也都非常缺这方面的人才,所以纷纷提出了数字人才的说法。最近似乎都上升到了国家战略的样子:

(七)强化数字人才教育。深化教育改革,建立健全高等院校、中等职业学校学科专业动态调整机制,加快推进面向数字经济的新工科建设,积极发展数字领域新兴专业,促进计算机科学、数据分析与其他专业学科间的交叉融合,扩大互联网、物联网、大数据、云计算、人工智能等数字人才培养规模。进一步扩大和落实高校专业设置自主权,鼓励高校根据经济社会发展需要和自身办学能力,加大数字领域相关专业人才培养。加强数字人才教育师资力量培养培训,推动实现基础教育、职业教育、高等教育普遍开展数字知识和技能教育,逐步建立健全多层次、多类型数字人才培养体系。加大职业教育数字化资源共建共享力度,加快建设适应数字经济发展的职业教育相关专业教学标准体系,进一步优化中等职业学校信息化相关专业设置。(教育部牵头,发展改革委、人力资源社会保障部按职责分工负责)

摘自: http://www.gov.cn/xinwen/2018-09/26/content_5325444.htm

那到底什么是数字人才呢?其实全世界也没有一个明确的定义,但简单粗暴的说,围绕软件开发的各种角色就算是数字人才。这个定义可以算是满足充分条件的,是否必要就见仁见智了。那我这里就是借用了数字人才这个词来表达,软件的构造者这么一个角色。

废了那么大劲就是说软件开发,那为啥我不说程序员呢?因为角色会限制我们的视野,一旦我说了程序员这个角色,我们就会把自己定义在编码这个视角上,什么需求啊、软件设计啊、组织流程啊,跟我没啥关系,这也恰恰是很多人在职业发展后期遇到瓶颈的原因,怪不得别人,全是自己画地为牢。

数字人才的特点

软件开发是一个复杂工作,而且是一个复杂的知识工作。复杂就复杂了,这好理解,为啥强调知识工作呢?知识工作有两个特点:

  1. 所有的工作不是正交的,分工不能切的很完美。

  2. 效率提升的关键点不是在于对how的回答,而是在于对what的回答。

第一点说的是什么呢?在体力工作者的上下文里,我们是拆成流程来工作的,每个环节的人不需要懂上个环节的知识,做好自己这个环节就好了,你想象一下流水线上的工人,彼此其实只要做好自己的事情就好了,不懂上下游的知识和技能也无所谓。这就是所谓的正交,彼此不需要了解,不了解也互不干扰工作。但知识工作不一样,知识工作固然也是拆成流程来做的,但是知识工作里有个极为重要的动作叫反馈,就是下游接收到上游信息的时候,要告诉上游自己理解了没有、上游的信息哪里是落不了地的等等,尤其在复杂知识工作上下文里,反馈可以说是最重要的事情,没有之一。你想要反馈,你就必须至少需要懂得上游的一个环节的人的部分心智模型,否则反馈也反馈不到点子上。注意,是至少,通常你需要懂得要超过一个环节,因为反馈往往不只跨一个环节,如下图所示:

知识工作者协作信息流

如果仅仅是第一点,还不是什么大不了的事情,毕竟大家一起工作,时间久了总能了解自己的上游环节的心智模型,互联网行业里都有好的程序员相当于半个产品经理的说法,问题在于第二点。我们做工作如果只追求按部就班,那就慢慢被竞争对手干掉了,每个企业都是要追求效率提升的。要提升效率就得回答一个问题,到底知识工作者的效率是怎么提升的,所以才有这个第二点,也是彼得德鲁克的观点:知识工作者的效率提升在于对what回答,而不是对how的回答。

什么是对how的回答呢?也就是说在每个环节提问怎么做才能提升效率,每个环节的输入和输出都是固定的,也就是待完成的事情的定义是确定的,只要在这个限制下改变做事的方法,而不用改变事情本身的定义。

而知识工作者的效率提升是一个纵观全局后的颠覆性优化,也就是所谓的颠覆性创新。这个颠覆性往是通过跨环节跨角色来产生的,比如最早敏捷里的技术实践,把测试引入了开发环节,把构建也引入了开发环节,而管理实践也是把验收提前,改变了对交付的定义,后来的DevOps则强调打破Dev和Ops的壁垒,都带来了极大的效率提升。一旦跨了环节跨了角色,那么输入输出都会变,事情本身的定义也就变了,我们需要经常拷问自己到底解决的问题是什么,改变了要解决的问题的定义,所以称之为对what的回答。

一旦问题的定义发生了变化,进而我们甚至要反复拷问自己到底什么是效率,比如,我们的效率到底指的是有100个需求,我们以最短的时间做完了,还是说用户或客户当有一个需求出现,我们可以用最短的时间满足它呢?一旦效率的定义发生变化,整个生产体系都会产生颠覆性变化,也就是我前面提到的颠覆性优化,而且因为这些颠覆性优化往往是局部劣化却达到了整体优化,有时连旧的评价体系都给颠覆掉了,导致按照旧的评价体系来看,新的方法可能还是很糟糕的,在看不到全局的人眼中看来,只会看到颠覆性看不到优化从而产生抵触,这都是正常的。可以说,如果没有抵触发生,都算不上颠覆性优化。

由于知识工作自身的这些特点,软件开发又比一般的知识工作还要复杂,我们觉得数字人才必须是一种多视角人才,这其中至少有三大类视角:商业视角、技术视角和用户视角:

描绘软件的三个视角

所以这个系列文章不会只讲某一种角色的视角,我希望通过这系列文章培养出多视角的人才,所以在标题中淡化了某一种角色的强调,尽管出于我的技术出身,难免技术部分会重一些,最后写出来的数字人才的技术部分的刻意练习会多一些,不过我想也是对读者有价值的。

系列背后的故事

不知道你有没有灵魂画手的体验。所谓灵魂画手,又叫手残党,当它们想画一个东西的时候,画出来的图大都是这样的:

灵魂画作

反正我从小就有这种体验,非常不爽。我特别想能通过画画来表达东西,然而作为一名灵魂画手,一次次拿起画笔就是一次次的挑战不可能,直到最后让我意识到,确实不可能……

那些图画仿佛有魔力,让我完全没办法画出来。我想有些事情还真是有天赋这种东西存在的。

这个想法并没有让我更舒服,表面上让我对天赋这个词更加敬畏,但内心深处其实对它的厌恶也是与日俱增。然而不管我多么厌恶,我还是一名灵魂画手,一个手残党。我也只能继续认为有些事情还真的是有天赋这种东西存在的。

我一直这么认为着,直到几年前我知道了一本书:《像艺术家一样思考》。我曾经的同事熊节学过那本书之后,经过一个多月的修炼,从手残党变成了可以画素描的人,写了一篇博客发出来:http://gigix.thoughtworkers.org/2013/6/7/how-i-learned-sketching/ ,看了之后令我羡慕万分,于是我也买来了那本书,照着练习。

里面的教学法非常奇特,他让你把图画倒过来,照着画,号称五分钟的奇迹。也就是说,五分钟前你是手残党,五分钟后,你就能画画了。我在照着步骤练习了之后发现,奇迹真的发生了。

一旦倒过来,我变的不再关注于整体造型,而是关注于线条,眼中那些有透视感的线条重新变成了平面上的线条,那些图画不再有魔力,变成了我这样的手残党也可以画出来的东西。那种体验,硬要打一个比方,可以说是麻瓜突然学会了魔法的感觉。

而不同于熊节,我没有开始练习画画,反而开始思考思维方式这个东西。我意识到我可能找到了一种把天赋这种东西从神坛上拉到地上的可能性,所谓的天赋,也不过是自然规律被发现之前人类神化自然现象的迷信叙事罢了。只要找对了方法,人与人可能没有那么大的差别。

“人跟人的差别”,我们这个行业里这种话题聊的格外多,往往是以挖苦工作中的某些“傻X”来开始的。在我们工作中,我们很容易上升到人的问题来进行讨论甚至处理。仔细打开我们的思维看一下,我们的潜意识里是认为应该存在某种天堑,决定了人和人的不同。我们认为工作和人之间存在某种匹配关系,某些人就应该做某种工作,某些人就不应该做,如果匹配错了,后天再怎么努力都是徒劳的。

看过这本书后,我仿佛收到了某种感召,我开始以怀疑的眼光来看待这种归因于天赋的逃避主义论调,并且作为一个自认为资质普通的人,内心深处总有某种冲动,想要一个个的破掉这些说不清道不明的天堑。

我开始假设,只要人们想且得法,经过刻意的练习,都可以学会那些看似不可思议的技能。我开始假设,有些技能,人们会觉得那是某种才能、是天赋,实际上只是技能,是可以通过练习获得的。

这些假设开始去驱动我去学习一些东西,试验一些东西,慢慢我就走上了钻研培训教育的道路。这条路一走就是四年。(这一路走来比较出乎我意外的是,路上比我想象的要热闹一些,一路认识了很多有同样想法的人,有些人还是被同样一本书感召了,大家互相交流,一路上并不寂寞。)

走到今天,已经有了不少的方法心得。我现在还在这条路上,一直走下去可能还能发现更多,为了防止狗熊掰棒子,把前面的忘掉,我还是要抓紧写下来,所以要开始这个系列了。

为什么给意加个引号?

不是为了说反话,而是为了强调。刻意练习这个词给人看到的时候,人们经常是关注在”练习”两个字上的,仿佛只要只要逼着自己练就好了。自己不需要做出什么认知上的变化,只需要练习,量变自然会产生质变,练习者自己只需要坚持,其他什么都不需要操心。

经过我的一些摸索,我发现重点在“意”。也就是注意力。所以我觉得刻意两个字特别好,里面体现出了注意力的重要性。我一路走来,发现每一种方法,都是一门注意力放置的艺术,就像画画你学会了把注意力放置在线条上,你突然就会了,而如果你没法把注意力放在线条上,你画细节的时候总是在想全局,总也画不出来。而这其实也只是又一遍证明了100年前美国心理学之父威廉詹姆士就提出的观点:”努力和注意力是一个心理事实的两个名字“。换句话说,所谓的不努力,只是不知道如何放置注意力,放置的不对就会很痛苦,也就看起来不努力。

一个好的方法就是,定义出有哪些放置注意力的点,总结出来之后告诉人们,剩下的人们自己能搞定。虽然要想做好还需要做很多的训练,但只有知道了要把注意力放在哪之后,我们的练习才算得上刻意,否则只能算作随意练习。这个是被很多人所忽视的,也是我后面的文章会关注的,所以加个引号强调一下。

就这样吧,虽然是开篇也不要一点干货都没有。总结一下,希望大家能学到:刻意练习的关键在于注意力的放置,否则就不是刻意练习,而是随意练习,下篇见。

问题

今天这个时代迭代开发已经成为常识,甚至政治正确,随便谁就能给你扯两句mvp。敏捷也从一个开发的名词变成了管理名词,迭代、测试、反馈这类名词满天飞。

人人都在说这些术语,仿佛他们真的就懂怎么做软件了。起码,觉得自己真的懂怎么创新了。然而经不起细聊,一旦深入下去聊一个mvp,聊聊他的迭代计划。就会发现露馅了,张嘴闭嘴谈的都是功能。这个迭代要交付几个功能,这个mvp多了什么功能?他的竞争对手都有哪些功能?却很少听到用户。人人都在喊,以用户为中心。口号喊得震天响,但你看他们的行为模式,他们的语言中,并没有用户的身影,更像只是在否定别人、固执己见的时候拿这个来当借口。

我时常觉得这个事情不太对劲。但是也没有想到更好的方法。敏捷中使用的故事卡比功能的视角要好一点。因为在故事卡里,你要写下用户的价值。但是,我一直也不知道这个价值是从哪儿来的。是先开枪后画靶子我们想做某个功能了,所以硬安的一些价值,还是真的存在的?价值的单位应该是什么呢?没有单位的东西就无法管理。无法管理,也就无法优化。我们交付的价值是越来越多吗?还是交付的不如以前了?用什么来判断?

回答不了这些问题,不管输赢都是有点不明不白的。这些问题的核心问题就是价值的单位应该是什么?怎么算一个价值?一直没想清楚这些问题,直到我看了我们公司设计团队的一个框架MERLIN,又在《创新的窘境》作者的新书《与运气竞争》里看到了理论依据,这个问题在我这里才算是告一段落。我明白了,以用户为中心的软件开发大概应该怎么做。

方法核心

如果我们想以用户为中心进行软件开发。那么我们的分析方法应该是围绕着用户展开的。

这个方向倒是不新鲜,一直以来我们在inception的时候做用需求分析时我们的方法就是围绕着用户展开的,一个典型的分析过程,如下图所示。

用户旅途

我们会在上面画一条轴,标示出用户旅途。这是用户在使用软件的时候的,他的一个全过程。然后在对应的时间点上,标记出我们的功能。这样我们的功能就不是平白出来的。每一个都联系了用户价值。相对于一般人会更容易理解功能,在ThoughtWorks,我们更多标记的是用户故事。比起功能,用户故事增加了有关价值的线索,因为用户故事首先就是要写出价值。

一直以来我觉得这个图还是不够给力。首先,从用户旅途上的点,到功能的映射这一步,简直是个magic move。对未来的读者来说,并不能很好的传递为什么是这样的一个功能,而不是别的功能?毕竟实现一个用户的价值方法有很多。于是后续在执行的过程当中,难免会僵化行事。

其次,上面的旅途,还可以再抽象和封装。简言之,旅途本身也应该是有抽象层次的。一个旅途上的一个点,可能也是一段新的旅途。

所以现在我觉得,一个更系统的做法应该是这样的,首先做服务设计:

更宏观的用户旅途

系统化的分析用户的行为,过程中与企业有哪些触点,在这些触点上,借用《与运气竞争》里的思维框架来讲,用户“雇佣”企业的产品到底是来做什么的,也就是动机有哪些。

然后将这些点再进一步细化,采用故事的模式:
故事板

图上的一行会讲一个故事,就像电影分镜或者漫画一样,来表达用户使用的故事,真正的故事,而不是用户故事那种东西,我们叫这个东西故事板。
在故事板上,我们描绘了一个故事,这个故事里,用户获得了一种体验。一个故事对应一个体验。在基本需求都已经得到满足的今天,体验是新的最有价值的事情,以体验为中心才是以用户为中心。故事板恰好给了我们一个非常符合人类认知习惯的方式来描述什么是一个体验。也就回答了开头的问题,什么是价值的单位。

故事板与用户故事的关系

当我们定义出了价值的单位,就可以从这一单位的价值里面映射出故事卡,来进行开发过程的管理:

故事板是MVP的细分

这里就是我们的重点,我们将来交付的软件、交付的服务、我们交付的一个MVP本质上是交付给了用户一组体验。MVP的迭代则应该是更多的体验或某些旧体验的升级(也就是同一个动机,换了一个不同的故事来满足)。

最终我们把用户的价值很好的表达了出来,并且找到了用户体验的基本单位——故事板,由于故事板也可以转化为用户故事,结合早已经存在的各种敏捷开发方法,也就可以对体验的交付进行度量和管理,达到以用户为中心进行软件开发。

尾声

很早之前我就觉得MVP是TDD思想在产品策略上的延伸,TDD一个很重要的价值就是避免自嗨从而消除浪费。程序员有时候会因为自嗨写出来好多用不到的功能和设计,这些都是浪费。但是程序员能减少的浪费很有限,最终的最终还是要从需求的源头——用户层面来减少浪费才能真的做好。所谓顾客就是上帝,软件开发中用户就是上帝,这句话的意思不是说用户说什么你就做什么,而是说你只有贴近用户,才能得到上帝的启示,现场有神明,就是这么个道理。

有了MVP之后,就像开发有了测试驱动。我们就可以避免很多过度设计。但是MVP作为测试,粒度太大了,不好分析,不好写断言,不能得到精细的反馈。这里我们把它分解到故事板层面,就可以得到精确的测试目标,也就可以做真正精细的测试,真正做到以用户为中心。

抽象的坏味道

上文说过,C4说穿了就是几个东西:关系-线、元素-方块和角色(角色不过是图形不同的方块)、关系表述-线上的文字、元素的描述-方块里的文字,虚线框(如前文所说,在C4里面虚线框的表达力被极大的限制了。)

这些东西一点都不新,我们自己随便找个白板,无非也是用这几个东西来表达架构,它的优点在于引进了一些分层,使得我们思路不是特别混乱,容易给别人看懂我们的思路,也容易帮助自己整理思路。

所以C4不能帮你做好架构设计,但是它能让你的设计中的问题暴露出来。被自己或其他人纠正。

可视化的威力就在这里,但根据我的经验,即便你用上了C4也不见得就能表达清楚,不过好消息是,终于我们可以聊一些高级的表达问题了。

可视化之后,我们能看到自己的表达问题,大概的问题有两个:抽象层次和抽象粒度。这个是表达方面永恒的问题,也就是软件设计永恒的问题,没有万灵丹,但是用上了可视化手段之后还是有机会让生活更美好一点的。

这两个问题可能太抽象了,不容易意识到,那我们可以看图,从图上的具体表现来发现坏味道。一般会有几个迹象表明我们有可视化的坏味道:

  1. 一张图上过分密密麻麻的线
  2. 一张图上太过多元素(也就是方块)也是坏味道
  3. 一张图上太少的元素,比如角色特别少
  4. 每个图上文字表达不契合,有的太泛泛,有的太细节也是问题。
  5. 无限制的画更多张图,基本上也就失去了使用图形化表达的意义。

那么对应的手段就有:

合成更大的元素

当我们发现密密麻麻的线、太多的元素,闻到这个味道的时候。我们可以考虑是不是该把里面的一些元素合成更大的元素了。Component可以合成Container,Container可以合成System,这样就会分成更多的图,每张图就变得没那么多线和元素了。

紧接着会面临下一个问题:怎么合成一个更大的系统,Container是明确的,所以Component合成Container不是问题,问题是Container怎么合成一个系统,为什么是这些Container合成这个系统,而不是另外几个?或者多加几个、减几个?

这个问题没有标准答案,但是有一些其他的框架可以提供一些思考的维度。

比如可以结合akf扩展立方来思考

akf扩展立方

X轴就比较容易,一方面看你的容器本身的描述来发现设计上是不是支持横向复制的,另一方面则是看你的部署图。
Z轴相对难一些,只是比较偏技术。比如当技术上有性能瓶颈,则需要注意这一个维度,有时不得不搞出一些特殊的容器出来,有时已经存在这些容器了,他们可能单独属于一个系统(类似于大数据分析的系统),或者一个系统的某一个局部(这就是我说的虚线框的表达力被限制的地方)。

Y轴给人的感觉是最容易操作的,但实际上却是最难的做好的,Y轴的背后是业务,往往我们觉得就按业务切成多张图就好了么。这种想法就表现出我们其实很看轻理解业务的难度,于是也总是出问题的地方。如果你能跨过这个心理障碍,决定去认真做一下,那么也有一些工具可以帮助我们做好。

领域模型与架构设计

最经典的工具组合就是求助于DDD,结合康威定律和步速,考虑维护的团队、使用的角色、变化的节奏,这块展开就复杂了,有机会再聊。

这里说一个最简单的做法。按照用户角色分。同一种角色,由于它的,公司里的职能,他的职责都是已经被定好的。天然在系统上就有一种隔离性。比如招聘专员、会计、出纳。他们使用的系统肯定是不一样。

但说简单,其实也不简单。我见过一些图,上面的角色只有两个,内部用户和外部用户。而另一些图,细化到了persona的级别,或者把职级都放上去了。所以无论再简单的原则,最后都会掉进抽象的坑。

画一些共识图来忽略掉一些通用的元素

有时候合成了更大的元素,元素依然很多,线条依然很密。画多张图也不够切分的。这个时候我们可以求助于共识。

人与人交流,彼此之间如果已经有一些共识存在就可以少废很多话,共识多到一定程度只需要确认一个眼神就完成交流了。所以毫无疑问做好共识管理,就可以大幅简化我们的架构图。

所以在我们做架构可视化的时候,经常会先画一个技术共识图,比如以一个我们的能力建设的数字平台为例,我们就画了一个下面这样的技术共识图。:

技术共识图

然后在后面画具体的图的时候,我就可以省略掉一些共识的元素,像nginx和数据库就没有了,可以更关注在业务上,而不是技术上来画图。

通过制定主题,限制文字的抽象层次

其实上面的技术共识图就是类似的做法,只是用于技术方面,如果用于业务方面,我们可以用一些抽象的名词或动词来代替一类业务,比如下图:

数字平台系统景观图

上图是一个系统景观图。当前这个主题是希望,人们一眼看清楚这个系统里面的相关角色都在使用什么系统,并且他们关注什么,职责是什么。所以具体学什么,怎么学的,都不是那么重要。所以我们就用学习一词代表了一系列的业务。

当主题确定的时候,很多纷杂的信息就没有了。一定要克制住自己,试图在一张图上,表达足够多信息的冲动。

只画重要的图,剩下的交流的时候再画。

除了像上面说的,不要试图在一张图上给他足够的信息。同时也,不要试图把所有的信息都表达出来。

绝大多数的图可能只在交流具体业务的时候才画,推荐使用动态图。
这个手边没有例子,找到再说吧。

总结

即便有了C4这么,好用的可视化工具。我们依然会看到,自己会掉进抽象的坑。所以在使用的时候一定要注意坏味道,经常检察是不是犯了抽象层次和抽象力度的错,才能做好可视化。这件事上,没有谁能幸免,所以要时常自省,与诸君共勉。

着相是佛家用语,指的是执着于外相偏离了本质。
仙剑奇侠传中有一个故事。讲的是一个成精了的佛珠。想要让更多的人向佛,于是施法,让这些人失去了记忆,只想一心礼佛。使人向佛,本来是好事,但强人所难,脱离了本质,便是着了相,也可以说反而是入了魔。
这个小故事告诉我们,在认知的世界里,我们很容易被表象所欺骗,忽略了本质。为此,佛家发明了这么一个名词来专门指出这种现象。

复用也是一样。复用本来是通过消除重复的方式。得到一系列可以复用的组件。从而在未来的开发工作中,更快速的响应需求变化,也就是所谓的提升响应力。
然而很多复用的结果,会造成代码是变少了,改起来却更难了。复用是增加了,可读性却下降了。考虑到软件开发是一个团队协作的工作,而我们这个行业的离职率又能到百分之二十之多。难以学习的代码确实是难以维护的,尽管你可以抱怨接手的人无能,但总之是降低了响应力,也就违背了复用的本质。
什么情况下会出现这样的场景呢?主要是因为视角的单一,只从自己单一的视角看到了重复而不是在做全局优化。这个说法可能稍微有些抽象,那我说几个相对具体的情况。

当我们只关注功能视角的时候

需求有很多的描述视角,可以只在功能角度描述,比如“网站要有任务卡,任务卡上有文字版学习内容,视频讲解、也有作业题。”也可以加入业务视角,比如“学生要报名特训营,才能参加特训营。学生进入特训营后,就看到了任务卡列表。学生在任务卡上阅读学习资料,阅读完学习资料后做题来验证他是否学到,做完后提交交由助教审阅。”当我们只看功能视角的时候,可能会忽视业务上的不同,变的在功能角度过分抽象,最后当业务变化的时候,反而响应速度比较弱。
一个简单的后台,我们看起来所有东西长得都一样,不过是列表页面,添加页面修改页面,再加点儿删除什么的功能。说穿了都是crud,干脆我把这事弄成一一个组件好了,每个页面只需要简单配置一下,就可以出来自己的一套,增删改查页面。
这种视角完全没有考虑到,不同的实体,它们其实所在的业务是不一样的,关心它们的人也是不一样的。最后,彼此的演化方向也总会出现一些不同,你把它定义成一种东西,对于我每做一个修改,都要背负着其他所有实体的特异性。于是就逐渐拖慢了我改变的速度,降低了响应能力。

无谓的自动化

有追求的程序员一定会考虑提升工作效率,通过一些自动化的手段来缩短流程,提高效率。不过有时候,这种追求也会有害。
在我们的系统里有一个面包屑功能,就是典型的“页面A / 页面B / 页面C”那种面包屑。团队成员提出,一个个页面写面包屑好烦啊,干脆做一个根据URL生成面包屑的功能吧。乍一看好像提高了效率,但实际上URL上的名词和你想显示在面包屑上的名字是可能出现不同的。
比如在我们的场景里,我们提供一个任务卡的预览功能,你的面包屑可能是“xx后台 / xx 训练营管理界面 / xx卡预览”,而学生正式使用任务卡的时候,他可能是 “ 学习中心 / xx 训练营 / xx卡 ”。而他们的url里可能都会出现’/programs/$pid/tasks/$tid’。同样的program、task翻译出来的文字完全不同。你为了支持这点不同,又要扩展一些额外功能来做这种区分,做来做去,可能还不如直接写来的方便,至多抽几个常量来简单的消除一下重复。

当我们只从代码上看重复性的时候

这个我就不举例子了,其实很多犯这个错误的人都是重构的支持者,不过学艺不太精。因为如果你仔细看的话,重构里好多怀味道都有一个跟他对立的怀味道,比如发散式变化和霰弹式修改。如果我们只看代码就会违背复用的本质——更好的响应变化。
这个跟我说的第一个场景,只关注功能视角是类似的问题,这个可能更具象一点,只关注代码。

无视上下文的时候

这个可以看作是只有功能视角的一种情况,很多功能我们觉得有重复性,提升成一个概念,然而其实根本是两个东西,他们只是刚好叫一个名字。
比如过去很多软件里,是有一个统一的用户组概念,不管你在哪个业务上下文里,你都需要扩展这个用户组的概念来管理用户的权限。这个带来的结果就是用户组变得越来越臃肿,每次修改都要改一下别的组的功能。在我们的网校数字平台里,学生学习有学习小组,老师出题有出题小组,这两个小组业务完全不一样,这个时候如果都用统一的用户组来管理的话,那就势必会造成无谓的耦合,损害响应力。

这些故事告诉我们,我们不是在真空里去做复用。我们做的软件都是有它的商业目的。我们的工程实践也都是为商业目的服务的。当我们说tech@core的时候,让我们说技术就是业务的时候。诚然,他给技术人员带来了更多的权利,然而权利越大,责任也越大。技术人员也需要跳出技术,具备更多的业务视角和体验视角。而不仅仅是沉浸在技术得自high当中。才能真正的发挥出各种实践的价值。

(本文不适合初学者阅读,目前只是为了方便培训的时候预习而写,也不适合无后续服务的人阅读)
现在,我们把上一篇的应用变成网络版。这个时候,你至少有了两个应用,一个客户端应用,一个服务端应用。到这一刻,我们就算具有了一个系统。

当我们有一个系统的时候,我们需要一种框架来简化思考我们的应用。这里又需要我们再次展示我们的概念性思考能力,这时我会采用Linux的模型来思考这个问题,所以应用程序一般我会分为三层:

layer-of-application

core层是我的核心逻辑,核心的计算部分放在这里。(Linux里是Kernel,不过Kernel这词比较偏门,咱们这教程的目标是为了尽量降低门槛,还是用core吧)
shell层是我链接核心层和用户的地方,你可以简单理解为解析用户输入,包装核心层的计算结果,变成用户看到的输出。Shell层还有一个作用,当我们有多个应用的时候,彼此之间的shell层是互相交互的,Core层是互相不知道彼此的存在的。
config层比较难理解,从工程的角度,我们的Core层和Shell层,都不应该控制彼此的生命周期,它们所有的依赖都应该是外部配置的,它们只依赖抽象的接口而不是具体实现。config层就是这一层配置,在Linux的Shell里对应的就是环境变量这个概念。

引入这些概念有什么好处呢?如果用这些概念来解释我们的应用,可以支撑非常大的架构的思考。

我们想象一下,系统随着演进而变大,出现原本的小东西也会变的非常庞大。比如上一篇main函数里的几行代码,随着系统变大,相应的Router也不可能自己写了,Router和Command的关系自然也是在文件里配置的,慢慢的我们就开始需要一个IOC容器。Service本身会变得很复杂,彼此之间可能会有关系,而且甚至可能是别的应用提供的Service。如果我们再使用应用框架里的概念,那么思考也好,交流也好,都会变得很低效。所以我们才引出了config,shell,core这三个概念来简化应用的内部,这样就可以思考大量的应用之间的关系是应该是一个什么样的架构了,不过这个方向是一个更复杂的问题,这里就不展开了。

回到我们的系统上,在一个刚刚出现了前后端概念的系统里,我们的新概念们能帮助我们理解架构的演进,下面就基于这些概念带着大家推演一遍现代常用的一些软件框架是因为哪些力量驱动出来的。

Core的重新定义

当我们把系统分为客户端和服务端的时候,那么客户端要做什么呢?服务端要做什么呢?

往往客户端是需要更多的照顾用户体验,填平服务端的接口和用户体验之间的沟壑。

服务端则要保证数据读写的性能,安全性和易于被客户端使用。

从这两点来看,客户端应该考虑的是用户体验,而不是让用户体验为服务端扭曲。所以客户端的core层更多的是那层填平服务端接口和用户体验之间沟壑的那堆代码,而它给shell的接口应该是以shell好调用为导向的。

那么怎么算是好用呢?

边界与无限

刚才谈到,在我们的边界处,好用是一个非常重要的需求。怎么算好用呢?最重要的是边界要找对,如果你过界了,做了事情也会被埋怨。当我们思考系统的时候,边界往往是不好找的。这就需要引入一个新的架构模式: MVC。

所谓的MVC就是Model-View-Controller。一个常见系统,往往Model层负责核心基本元素和基本算法,View层负责展示和表达数据,Controller层负责调度和组合,把Model层和View层连接在一起。

一般来讲,Model层通常是我们的Core,Controller层往往就是我们的shell,View层就是shell返回的数据。这个时候边界的思考就清楚了,谁是我们的model和model相关的核心计算,谁就是我们的core。谁是我们的Controller负责调度组合和内外相连,谁就是我们的shell。而我们的计算结果,也就是我们的View层,是会离开我们的应用供别人使用的,那它是我们的最外面的边界。我们思考边界只需要关注在View层,思考清楚我们的View是否在一个抽象层次上就可以了。

但是,世界不是这么简单的分三层就可以结束了,世界是往复循环以致无穷的。所以我们的MVC也是循环迭代的,也就是说MVC中的某一层还可能再分MVC。那么是哪一层呢?View层。还记得我们第一篇讲得,数据和过程是不严格区分的,所以我们返回的数据,可以被解析为新的过程,新的过程再产生新的数据,从而往复循环以致无穷。

MVC

所以可以认为服务端是原应用的Core层进化出来的。

如果我们把View层进化下去,我们前面提到的客户端的Shell返回的数据,还会再划分,就会有新的组件层(Component),模版层(Layout),页面层(Page,也有人爱用Container)等等。

随着出现了MVC,再进一步思考就会逐渐发现,其实core层不应该关心后端代码,它应该关心前端的领域对象,以用户眼中的模型为基础计算,而不是后端业务人员眼中的模型为基础来计算。所以他会把弥合后端和前端的工作交给shell层,而shell层会夹在三方面很难受,他一方面要对接后端,一方面要对接core,还有一方面要适配真正的前端模型。前端和后端的拉锯战就会出现,在这股力量的挤压之下,我们的BFF就会自然而然的出现,所谓的前后端分离也就是自然而然的事情了。

题外话

题外话1

对于有经验的同学要说一句:数据库不是核心,你的代码才是核心。数据库就是系统的另外一个应用而已,虽然数据库厂商希望你把核心放在它里面,但你不要被厂商的策略绑架了你的自由。

题外话2

面向对象,有一位同学总是在给我纠结这个面向对象怎么画。其实之所以有此问并不是不知道怎么用强类型语言来画图,如我们第二篇所述,你换成类图也是一样的。主要的纠结点在于,那个函数放在哪个类里这个问题。

之所以一直没讲,是因为面向对象是纯粹的人类思考问题的方式,它是非常不精确的,把哪个函数放在哪个类里这个事情是一门艺术而不是一门科学。当我们有一个Dog类的时候,有一个bark方法是非常明显的。当我们有一个货物类(Goods)的时候,算税应该是它的方法吗?当我们有一个数据库实体的时候,比如User,Item等等,那么存储算他的方法吗?如果我们做一个CRM系统,Client明显跟所有的业务都有关系,总不能Client这个类上有所有业务的方法吧?所以我们无论怎么放,都可能是有问题的,而且在变化来临前,我们没有什么客观的标准来判断当前的做法是否合理。

如果只是画图来表达的话,其实我们的方块上,也表达出了哪个函数属于哪个类,这样已经便于有经验的人发现问题并解决问题了。只是没有任何客观的公式可以帮助大家轻松的解决问题。我们只能帮你这么多了,毕竟方法,从来也不是为弱者服务的。

题外话3

你这个是不是六边形架构?
其实可以看作是六边形架构的一种变种,我只是比较讨厌六边形这个词,它太容易让人纠结为啥是六个边,不是八个?比如我叫八卦可不可以?所以我们也不要太纠结他叫什么,领会精神就好。