几年前,Facebook 声称他们只招聘全栈程序员(Full Stack Developer),要求对大部分技术都要有所涉猎和了解。于是国内也出现了这样一种风潮,全栈程序员成为大家趋之若鹜的新方向。
然而在微服务、BFF、微前端等一系列技术日趋流行的今天,全栈程序员已不再意味着效率,也让我们对它所宣传的效能产生了诸多困惑。那么我们不禁要问:
-
为什么过去几年大家对于 Full Stack 有着非常多的强调,最近却讲得越来越少了?
-
如果想达成全栈所承诺的效率提升和人员成长,背后有什么先决条件?
-
围绕着这个先决条件,Full Stack 真的实现了自己的承诺吗?
甚至应该继续追问,在今天的环境下成为什么样的程序员是更值得关注的?这就是我们今天想展开讨论的一些话题。
我们做项目时肯定会用到技术栈,所谓的技术栈其实是一种类比。比如数据库会用到 Redis、mongo、SQL 等,中间可能会用 Backend 的一些系统,包括 Java、Spring、Ruby on Rails,前台有 JavaScript、HTML 等。也就是按照分层架构的方式,从前端到后端去排列时,就好像从上到下的一个栈。
全栈程序员就是要求沿着栈的方向,掌握所涉猎的主要技术点。也应该知道,当谈论 Full Stack 时,它的重点是按照技术架构,以及技术架构里所采用的各种组件和技术,让我们沿着栈的方向都能有所了解。
全栈之所以能够产生作用,在当时有两个重要背景。一是
代码集体所有
(Collective Code Ownership)。无论前台、后台还是数据库,当做环境部署时,有关代码都会放到统一的版本控制当中去。而版本控制的这些东西,所有人都可以直接访问。
我想大家可以很容易联想到行业里的一些最佳实践,比如 Google 的 Monorepo 就是一个大的单一仓库,而我们会把所有的项目和组件都放到这些仓库里。很显然,在 Monorepo 里会看到大量 Java、JavaScript 的数据库的代码。所以在当时那个状态下,很多大的互联网公司都在强调把代码由小团队所有转向集体所有。
而作为全栈工程师,当你调试或使用别人的组件的时候,如果对技术栈所使用的技术都能有所了解,你的效率和工作方法就会得到很大的提升。这是一个出发点。
第二个出发点,或者说第二个让 Full Stack 变得有吸引力的点,就是行业内所强调的一种
端到端的交付模式。
所谓端到端的交付模式,是跟之前分模块的交付模式相区分的,之前可能是有一帮人写数据库,还有一帮人落地后台,前台也是如此。而在端到端的交付过程中,既要交付数据库,也要交付后台,还要交付前台。要想在这样的工作模式下变得更有效率,就需要对全栈的信息有更多的了解。
在这种情况下,我们认为在一段时间内,全栈工程师对于整个行业想要的研发效能和业务模式的转变是能够适应的。所以在四五年前,全栈工程师的概念就受到了行业内的一些追捧。
而今天我们很少再去讲全栈了,最显而易见的原因是随着用到的技术越来越多,我们很难对全栈作出明确的定义,比如到底要
全到什么程度
才叫全栈呢?
当然,除此之外还有几个很客观的因素,首当其冲的就是行业里引进了以微服务为主的新的架构风格。
在微服务组件进行划分时,有一个主要特点,就是用 API 分隔了前台和后台。看上去好像跟之前做前后台分离、按组件交付没有什么区别,但实际上其中的差异性是巨大的。
前后台被 API 分隔后,带来的结果就是业务能力和 User Journeys 实际上就被 API 隔离开了。而之前,之所以要做端到端的交付,是因为我们在业务能力和 User Journeys 上并不做明显的区分。
大概十几年前,我们去做系统的时候可能会用 Spring MVC、Map-Works、Ruby on Rails 等,服务端和客户端之间的契约主要是在页面流、用户体验和用户流程上产生的。
而在今天,API 已经和用户体验、UI 层完全解耦了,我们就会认为被 API 封装和分隔的这种前后台,可以变成一个独立被交付的组件了。而在过往的时候,它们并不具有独立交付的意义。
比如说一家企业要做微服务,服务封装了企业内某个核心的业务能力,那么它是有独立交付的价值和意义的。但在十几年前,如果后台系统封装了三五个页面跳转的逻辑,以及在页面跳转逻辑上所需要的数据,这种独立交付的后台并没有对应的前台系统,所以也就没有任何可以被独立交付的意义。
而微服务告诉企业,就算没有前台,那么它对于业务能力的更新和改进,放到企业生态中也仍然是有意义的。所以对于独立交付的边界,就产生了非常大的思路上的转变。我们今天的系统是有明确交付边界的,它不再是一个从头到尾需要去交付的东西了。
如图所示,我们从下往上看,后台是 API 层,可以通过把业务能力封装成服务去访问数据库。中间是 BFF,通常会在这一层去重组 User Journeys。
就以 BFF 为例,可能会出现中间我有个 API 的客户端,它会去访问后台的叫 Business capabilities 的 API,借由这些 API 以及由 API 包含的模型,我们可以重组 User Journeys,也就是页面对应跳转的流程。
再往前,可能就是一个真正的客户端,比如 UI 的组件、UI 的交互,以及由 UI 组件和交付触发的客户端的调用,去调用到 BFF 上访问的这样一个过程。
在今天这样一个场景下,我们不禁要问这样的问题,全栈工程师难道还像之前的概念一样,跨越客户端、BFF、Microservice,整个串在一起才叫 Full Stack 吗?还是说在独立的交付边界内,只要掌握了独立交付边界内的内容,就可以叫做 Full Stack 了?
如果用图来表示,Full Stack 在我们传统意义上来讲,应该这么画(左侧黄线)还是这样画(右侧黄线)?
实际上这也是最近一段时间,行业内不再过分讨论 Full Stack 的一个重要因素,因为我们默认的架构风格和架构的上下文发生了改变。
我们现在倾向于使用比原来更小粒度的范围,把它变成一个独立可交付的组件。那么我们可以说,难道只要在组件内去使用和掌握的这些技术栈归为了一个,就会被认为是 Full Stack 了吗?
这就让 Full Stack 这个概念变得非常尴尬。要么我不切实际地希望你能掌握从头到尾所有使用到的技术,或者像之前一样,只要把它分散到小的模块中,就会被认为是 Full Stack。那么 Full Stack 又与我们之前讲的后端开发、前端开发到底有什么样的差异和区别呢?
在今天的架构风格和架构环境下,大家不太知道 Full Stack 到底能带来什么样的价值和效用,反而变成了一个非常容易让人困惑的概念。
可能有人会说 Full Stack 就是一个风潮,一个趋势,过去就过去了。我觉得我们不能这样讲。我们需要深度反思一下,当年在提倡 Full Stack,或者当 Full Stack 被行业疯狂采纳,甚至很多人预言未来需要的一定是全栈工程师,不再需要单点工程师了,如此种种都表明我们当时充分认识到了 Full Stack 的价值和意义。
因此我们要进一步追问的是:
Full Stack 背后的逻辑到底是什么?
围绕着这个逻辑,在今天的环境下成为什么样的程序员才是更有价值的?
如果我问得再极端一点,Full Stack 这个概念在历史上真的产生过它所应许的这些效果吗?我的答案是
没有,甚至是从来都没有
达成过它想要的这些目的和宣称的好处。
除了刚才讲的代码集体共享和端到端交付外,Full Stack 最核心的出发点其实是
协同效应,
也就是所谓的 1+1>2。比如当两个全栈工程师在一起工作时,他们产生的效能会大于二。
要知道,全栈工程师追求的,并不是一个全栈工程师比非全栈工程师的效率能高多少。它其实追求的是,
一对全栈工程师
在一起工作的效率,将会比非全栈工程师的效率高很多倍,因为他们产生了协同效应。
我们强调全栈,并不是指个人在单点上的,而是指他在团队中发挥的效率和效能,换句话说,全栈工程师可以在团队中产生和创造出更多的协同效应。
举个例子。全栈工程师强调开发者需要对整个栈上的内容都要有所了解,一个非常直接的影响就是能看懂系统中的所有代码。如果出现 Bug 需要调试,不会出现“我不了解,需要等另一个人来帮我做”的情况。而是我可以一直调试下去,直到找到问题的根源所在,甚至完成修改。
这是全栈工程师所发挥的一个重要作用,他掌握了别人知识领域里的内容,能在工作过程中产生一种协同的效用。
再往深了讲,为
什么在软件开发中需要协同效应呢?
我在很多场合都讲过这个话题,而且这也是我的谈话节目《八叉说》的一个元命题,即
软件工程是一种知识工作。
谈及什么是软件工程时,我们一般会把它类比成盖房子,其中有做图纸的人、有搬砖的人、有放水泥的人,等等。实际上并不是这样的,在软件开发过程中真正产生的只有知识,所以这是
一个知识被传递、消化和吸收的过程。
我们可以把它想象成客户,或者是真正花钱购买软件的人。在他的头脑中,有一种帮助业务成功的方式和方法,并把这种方法告诉给了 BA 等角色。当 BA 充分理解了客户的上下文、上下文到底代表什么样的含义,就会把这种知识产生成另外一种形式,也就是需求文档,或者是方案设计加上需求文档。
产生了方案设计和需求文档之后,他就需要讲给开发者听。开发者理解吸收了方案文档中所指定的业务上下文,以及业务上下文中所有的含义,就可以将其转化成代码。这个时候,
知识就从一个空想的概念转化成了一种可执行的形态。
最后按照我们传统的流程,代码会被放到一名 QA 或测试人员的手里,验证客户最开始提出来的问题是否在他的上下文中得到了唯一的解决。
这就是我想强调的在今天的环境下,我们可以把软件工程的整个开发看成一个知识生产和传递的过程。
而知识生产和传递的过程是需要协同效应的,这很容易理解。如果开发者完全不懂业务上下文,就不太能理解需求是什么。如果不能理解需求,那他的代码大概率也写不对。所以当我们把软件开发变成一个知识工作的时候,就非常需要协同效应。
当然这里有一个更严谨的说法,源自管理学领域大师中的大师彼得·格鲁克。他说知识工作最大的颠覆是我们价值的产生点变了,知
识工作者的价值并不取决于产生,而取决于消费。
举一个更实际的例子。一个做业务分析的人说自己的需求文档写得特别好,有两百页,但所有人都看不懂写的是啥。他可能写的全部正确,但是因为别人看不懂,消费不了,他就没有起到有效的知识传递的作用,他的价值就是 0。作为一个知识工作者,他的价值需要以消费来度量。
同样,对于开发者而言,如果写出来的功能和代码没有被消费,那就没有任何意义。没有被消费,指的是不能转化成实际的功能被别人使用,甚至是写出来的这些功能,其他人不能继承和调用。
比如说我写了一个 API 库,用了一大堆设计模式,最后的代码非常炫酷,但没人看得懂。没有人看得懂,就没有人会去用,这个框架也就没有任何意义。
这是非常重要的一点。如果我们把自己看作知识工作者,我们的价值就不由工作决定,而是由消费来决定。对于一个知识工作者而言,关注的应该是
消费的价值和消费的效率
,而不是产生了多少价值或者产生了多少效率。
很显然,
协同效应将会提升知识消费的效率
,这是一个非常简单的推论。比如说你在一个项目上工作了三年,大家会觉得你的水平提高了,工作效率也非常高。与另一个刚加入公司两三天的人相比,他的效率可能怎么都赶不上你。
但并不是你的水平有多高,可能很大一个原因是你对于环境上下文有了更深入的了解,跟团队其他人的协同效应更高,最终产生的结果和效率也就更高。我们彼此说的话都懂,沟通成本更小,知识在传递过程中的失真和损失更小,这本身就会提高知识传递的效率和效果。
到这里我们可以发现,全栈程序员最原始的假设是,当我们在工作栈上了解了所有内容时就会产生协同效应。
这个假设本身是没有错的。
接下来要继续追问的是,既然全栈工程师的基本假设和前提是对的,那么全栈的方式是否真的增加了知识的消费呢?答案是
一点点,而且仅仅是在代码层面上
(Sort of,and at code level)。
在代码层面上,意味着我可以看懂你的代码,但并不代表我可以完全理解代码背后所指代的业务上下文和业务逻辑。
我们生活中都有这样的经验,比如说一个人的英语很好,但并不代表他能看懂一篇专业领域的英语论文。很可能他能看懂里面所有的单词和语法,但是根本读不懂那篇论文到底在讲什么。
所以当我们去理解全栈上所使用的技术点时,只能说我们拥有理解最终产出物的能力。但软件开发真正的过程是围绕着
知识传递
来展开的,那么仅仅理解最终产出物,并不代表能理解产出物背后所包含的业务上下文和业务逻辑等知识。这个时候我们可以说,
协同效应是不会被大量产生的。
另外也是工作经验告诉我们,对于技术栈的掌握,并不代表任何含义。哪怕使用的是相同的技术栈,但因为处在不同的业务和领域,两个开发者也经常是鸡同鸭讲。所以分享的知识仅仅是按照栈的方向去走的话,其实很难产生必要的协同效应。
那么什么样的协同效应是我们真正需要的呢?
无论是什么样的开发方式,我们最终要做的事情都是理解真正的问题,得到解决方案,并把解决方案分解成更小的任务,执行,验证我们的问题是否被解决了。
请注意,我们验证的不是方案,而是那个真正的问题是否被解决了。如下图所示,从上到下是知识产生并被传递的过程(Konwledge generates and transfer),从后往前是知识被消费了的过程(Konwledge consumption)。
这个时候我们可以把软件开发过程看成一个围绕着知识的 Stream,也就是一个知识传递和消费的流。那么我们可以得出这样一个结论:
如果沿着流的方向去共享更多的知识,就会产生更多的协同效应。
知识是沿着流的方向传递并最终实施成软件的,如果我们在流的方向上增加知识的共享和知识的分解,而不是按照栈的方向(不是垂直的方向)去处理,协同效应将会产生得更多、更强。
我想大家可以想到很多具体的技术都符合这个原则。比如说,统一语言的目的就是在流的方向上进行更多的知识分享,让大家对业务上下文都有相似的理解,然后让开发人员和业务人员进行更有效的沟通,从而产生更强的协同效应,这是一种简单且直接的方式。