微信扫描登录

美团前端驱动的内部系统快速构建实践

2016年3月19日,由互联网技术联盟(ITA)举办的1024前端技术峰会,在中关村WEPAC盛大举办!

400+位经验丰富的前端工程师共同参与,是一场业内最顶级讲师阵容的前端技术峰会,而且,这是一场不落幕的峰会,因为3月开始的每一周,都有线上的分会场如期分享着各个一线互联网公司在前端技术方面的最佳实践。

美团高级前端工程师韩聪分享话题:前端驱动的低成本内部系统快速构建 。

enter image description here

如下是1024前端技术峰会上,韩聪的分享实录。

今天在这边主要和大家聊一下内部系统的前端开发。 我要先明确一个概念,什么是这里所讲的内部系统,简单说内部系统就是指企业内部员工所使用的系统,与此对应的讲到外部系统就是外部用户所使用的系统。明确这个概念之后进入今天的主要内容。

enter image description here

最近几年,特别是06年以后前端技术发展非常快的,这段时间之内推出的各种新技术,新标准,比如H5,CSS3,ES6,同时我们的开源社区也会推出不同的技术,都能够帮我们解决项目开发效率上的问题。

enter image description here

虽然这是技术方面的进步,但是在现实中业务需求也在变化,我们的用户对我们的产品的体验要求越来越高,这些需求的增长就会促使我们的项目复杂度逐渐增加。这几年积累下来的技术成长所带来的效率的提升,可能就会被我们复杂需求所抵消掉。

更多时候我们新的需求不仅仅是让N个系统复杂度有所增加,同时还会催生出更多新的系统,对于我们本来已经很紧张的前端开发资源来说,这样的一个系统的增长对于我们的开发压力的增加是非常巨大的和恐怖的,老是加班,很苦。

enter image description here

我们如何在人力有限的情况下应对这些大量的系统开发呢?可以回想一下现实中一个场景,更多做法其实是把系统划分一个权重,权重高的系统可以获得较多的开发资源,权重低的系统平分下来之后资源就少得可怜。

我们怎么对这些系统分配权重的呢?最简单的就是直接面向用户的系统分配很高的权重,用大量的开发人力和良好的设计维护它,使得它能够及时快速响应用户的需求,对于内部系统来说就比较可怜了,可能开始的时候连一个好的前期设计都没有,设置维护过程中由于开发人员的变动,包括技术上的不足,就会导致我们项目慢慢越来越复杂,越来越混乱。

enter image description here

经常会出现的一个情况迫于时间压力,我们在内部系统开发过程中可能会做一些原本设计不允许的事,比如引入大量的库,有时候相互冲突。我曾经见过一个系统里面使用几种组件,对于这样的系统来说时间久了难以维护的,而且当原始维护人员转岗或者离职之后,新的进来维护它成本非常之巨大。

enter image description here

如果我们对我们所开发的项目都希望它非常俊美,能力突出,上天入地。由于刚才提到的问题,特别是开发资源的匮乏会导致内部系统慢慢变成这个样子,可能自我感觉良好,但是我们看起来它就是非常丑陋的系统。

enter image description here

讲了这么多,我们可以做一个简单的总结,内部系统前端开发中遇到的一些问题。

1、开发资源非常有限,人力、技术、时间都非常稀缺,内部系统开发过程中使用的技术栈也广;

2、大量技术栈不断加入内部系统,就会使得系统变得混乱,毫无美感。更多的时候内部系统都要有多个内部系统,多个内部系统之间有的是新创建的,内部系统之间技术架构,包括具体技术栈都会差异非常大。

3、异构的系统如果多了,对于我们的维护成本来说也是非常大的一个影响。

enter image description here

有了这样的问题,我很少在一些场合中听到大家对内部系统的思考,是不是能有一种方案解决掉刚才的问题,让内部系统开发更为简单,而且这样的方案开发出来未来一段时间内可维护性也能有所保障呢?至少到现在为止圈内可能还缺少这样的思考,或者是不多。

接下来我们来看看我们是否能找到一种方案满足刚才的需求,开发快速、维护简单。

enter image description here

首先看一下在传统前端开发过程中最复杂的地方,这点对于大家来说其实是有共识的,最复杂的是UI交互,UI交互是由js来控制的。这就会涉及到浏览器的DOM模型,还有用户行为的不确定性,这需要大量的代码处理它,会看到前端项目中最多代码是js代码。但是这些js代码中也有绝大部分的东西都是在处理UI交互,那么我们耗费这么大成本处理UI交互,最容易出问题也是UI交互代码,而且由于量大,逻辑复杂,还会非常难以维护,很少的人能在短时间内看懂另外一个人处理UI交互的逻辑。

现在我们找到了基本上有了对前端复杂度共识之后,我们是不是可以把UI交互部分的工作从js中剥离出来,使得我们系统开发人员不用关注UI以交互的开发。其实现在是有技术的,比如组件化,确实在一定程度上让开发人员不再关注UI。但是不管怎么样,你始终要做黏合性js代码,去把UI组合起来,甚至有的时候没有符合我们的需求的UI,这时候还得写。

enter image description here

我们要把UI交互的工作从js剥离出来,剥离出来放哪了?我们不可能不要UI交互,如果没有UI交互系统的话相当于不能接受输入,没有输入就没有输出,利益就不存在了。我们需要找到一个地方能够让这个地方承载UI交互的工作,对于前端来说我们通常使用的技术其实无外乎就是js、css、HTML。css可能吗?从我的理解或者大家有一定经验可能知道css不能完成这点。剩下唯一可选择技术就是HTML,这个在我们开发中被我们经常忽略的HTML这门技术,能够承担起重要的工作吗?

下面做一个闹洞大开的设想。

最简单的,我们现在假设有一个浏览器,无所不能,能够提供满足我们所有开发需求中所需要的所有HTML元素,比如说现在有一个场景,页面里面包含两个元素,第一个是按纽,一个是对话框,我需要在点击按纽的时候打开这个对话框,对话框里面要显示一段文本就是hello world,这样的需求与理想化的方式转化成代码会是什么样子?

enter image description here

这是一种可能的方式,首先页面中关注点在于两个元素,这两个元素都是浏览器为我们提供的。在这个元素中最重要的,现在看button元素,有一个onclick属性,这个指令要执行的结果就是打开名叫helloworlddialog的对话框,是不是只需要简单,完全和需求对应起来,应用场景对应起来的代码就完成了这个需求的开发呢?

当然这样的方式其实是非常理想的,但是这点对于程序员来说可能会觉得有点违和感。最大违和感是命令,这是人类的中英文混合的,那我们要做简单的修改让它看起来没有违和感,我们关注就就可以在属性执行的事上。

首先可以把双引号变成大括号,大括号里面的内容插入函数,需要通过这种方式向浏览器传达这样一个消息,如果这个按纽被点击了请帮我执行这个js函数,至于这段js函数做什么事,浏览器可以不用操心。

接下来为了配合打开dialog动作,需要自己写一段js代码,内部有一个dialog对象,这是上面浏览器为我们提供的dialog标签的附属产物,就是js环境中创建dialog的对象,通过dialog对象可以想页面中所有的对话框发送指令,可以让你打开,关闭等等的。

enter image description here

这么一来的话,代码稍微复杂一点点,但是更加符合我们的直观体验,我们看一下整个代码是非常直观的,我们仅仅需要通过HTML来描述,哪怕不看下面的js代码,也能猜到这个场景大概做什么事。

enter image description here

接下来看一下如果刚才的场景放到现实传统开发过程中会是什么样,先假设开发页面中引入了UI库或者jQuery,首先需要HTML代码,里面主要是容器,可以有一个按纽,这个按纽就是承载点击事件,触发dialog打开的按纽,都是标准的HTML元素。

接下来没有HTML什么事了,剩下是js,首先需要创建一个dialog,设置文本为helloworld,把dialog填充到容器中,组件事件通信做桥梁。这么一来就完成了使用传统方式完成了这个页面的开发,那我们可以看到,对比刚才上面看到的图,可以明显看出HTML代码里面,传统代码中HTML代码,仅看HTML代码不知道这个页面要干什么的,而且使用了dialog,UI组件,还是要使用这么多代码,相比之前是不是会变得更复杂。如果这个项目是真实场景的话会更复杂,我们js代码就会更多,理解这样的业务场景必然会耗费很多的时间。

enter image description here

简单的对比首先传统开发中我们以js为中心的,整个页面的开发是用js驱动的。然后在这里面HTML页面可能就是配角,完全创建页面,创建某个容器而已。那在我们理想化方式中,刚好情况做了反转,HTML成为主角,用很自然的方式描述我们的需求。接着js只是配合,在这个场景里面只做了组件间的通信,在真实场景中可能会做数据处理,网络通信等等。

现在回到现实,刚才我讲的时候有人在笑,但是没问题我可以理解大家为什么会笑,首先刚才讲了这么多,所有的都是基于不切实际的想法,怎么可能会有一款浏览器为我们提供所有的HTML元素。

enter image description here

当我们遇到这个点的时候,在探索一个新的方案改变现在开发方式,如果遇到一个难题就一就退缩的时候永远改变不了这个方式,所以这里用了《喜剧之王》的图,我们是有理想的,刚才我们想要找到一种新的开发方式改变内部系统开发的现状,我们认为是我们的理想就要为这个理想奋斗。

enter image description here

接下来我们一起去探索一下如何才能做到理想化的开发方式。首先分析一下如果把刚才场景放到现实中,我们需要做什么才能达到那样的效果?我把刚才的代码拿过来,这里关注点主要集中在两点,第一点dialog的标签,如果映射在传统开发中,刚才代码已经体现出来了,其实就是dialog的UI组件。但是不是意味着我们只需要开发UI组件放到这个地方,我们要在形式上有了这样的功能。第一件事需要做的就是把传统UI组件做HTML标签化。第二点刚才所做的小的改动,流动其要理解的指令,这里是大括号语法,对于传统HTML这个功能是不可以实现的,我们现在需要一种新的方式使得HTML能够支持插入动态。这样一来再看其它地方好像没有什么其它问题,跟我们现有开发方式没有太大变化,div、属性没有任何问题。

enter image description here

现在我们已经找到了要实现这样理想化的开发方式所遇到的现实中的两个技术问题,现在来看看能不能解决掉这两个问题。

首先看第一个,组件需要标签化来说现在已经有了很多的技术,包括今天前几位进师都在提到这些技术,除了Webcomponent没有提。这三个里最好的应该是Webcomponent,因为这是官方标准。第一种是13年出的,到现在为止至少某种程度上能够让我们组件看起来像HTML标签,第三个也是类似,只有Webcomponent才是最原生的技术。至少有了三种技术为我们组件标准化做支持。接下来看看面临的第二个问题。

enter image description here

第二个问题无非让HTML支持动态数据的插入,这个问题如果直接思考,我觉得不一定能找到解决的方案,但是我们可以退一步,看看我们现在开发中是怎么样在动态加载数据的。无非就是模版技术,无论前端还是后端,它写的就是HTML,只不过插入了动态数据而已。这点上稍微有点不同,就是我们不能选用后端模版,我们需要在页面里,HTML页面里插入当前运行的环境的函数,这点对于后端系统就不适合了,所以只能选择前端。

刚才理出来的两个关键技术点已经有了对应的解决方案,接下来怎么选择这些方案。对模版技术来说不管是前端模版,还是后端模版,开源产品也非常多,但是核心功能差距其实不太大。

所以我们可以随便选,对于组件技术首先是Webcomponent非常好的,但是Webcomponent有一个比较大的问题,现在使用量不大,而且浏览器支持程度不高,如果我们选用这样技术可能就会成为一个坑,如果遇到问题解决不了,包括未来可能需要支持内部系统做兼容性上支持的话,也是很大的难题。

enter image description here

接下来就剩下第一和第三,看第三个稍做修改也能满足我们的需求,但是它不是纯UI解决方案,它还会有其它的一些开发上的问题,但是对于现在的产品就是纯UI解决方案,如果有第三个解决了我们的问题同时可能有新问题,我们目标就是要把这些复杂度降低,肯定不能引入更多的问题导致项目变复杂。剩下唯一选择就是第一个,对于它来说也需要做一点点小修改才能符合我们的需求,但是有一个好处本身是专为UI而生,同时更重要的是它本身还支持jsX语法,说白了就是模版系统,这点非常契合我们对模版的需求,是不是我们可以把这两种技术融合一下。但是现在来看模版系统好像要支持jsX语法可能还有一点小问题,这是我们最重要的,但是好在开发模版系统成本不算高,或者是非常低。

这么一来已经确定的选择的结果第一点要自己开发一个模版系统,这个模版系统要能够支持jsX语法,第二点开发组件的第一个技术也要做改造,使得能够更符合我们的需求。

enter image description here

接下来怎么改造它们,组合一起,为工程师提供一个抽象的环境,让工程师开发系统可以做到理想化的开发。如果这个问题放在几年前可能确实是个问题,因为那时候前端工程师很难跳出浏览器的限制,但是至少流都出现之后,我们有了更多的选择,可以完成跳出浏览器的限制,虽然我们在运行时必须在浏览器里,但是开发的时候不一定非要完全映射到浏览器模型上。

这时候做的就非常简单了,通过漏斗js打造这样一个系统,需要做的事是什么?第一个技术创建的元素组件提供给开发者,使得开发者在使用的时候不用关心什么技术创建的,只要看成环境所提供的原生HTML元素即可。第二点我们系统要屏蔽掉一些js模版上技术细节,使得工程师不用关心这些细节。更重要jsX语法写在路径里,都可以通过环境做处理,因为他们是有固定模式的,所以说只要我们限制明显,这块代码完全可以被隐藏掉的。

enter image description here

同时,由于我们自己创建的环境,所以说我们也可以引入一些小特性,比如说可以让我们的工程师所写的js和css这种引入,或者可以让我们的页面加载初始化数据自动化等等,只要你觉得合理的,有效的,提升开发效率的技术都可以应用到环境中。

这样一来在打造出这样的环境之后,其实对于开发我们的系统开发的工程师来说,他所要关注的东西就非常简单了,我就是写大量的HTML,然后按照需求整理出来,一条一条描述出来,用HTML方式描述出来就可以了,期间可能需要关注一些数据处理,网络交互,以及组件间的通信。但是这样的代码相对于我们之前所说的UI交互的工作,它们是非常容易管理的,而且也不会造成非常大的混乱。

当我们工程师在面向这样的环境,把系统开发完成之后,他所需要做的就是使用环境所提供的静态编页的功能,把我们写的最普遍的HTML代码和js代码真正转化成能在浏览器里运行的代码,这样一来通过创建黑盒,为我们的工程师屏蔽掉一些细节,应该是能够提升我们的,然后为它提供一个非常理想化的开发环境,这样的方式对于开发效率来说是有非常大的提升。

假如有了这样的方案,并且有了这样的环境,开发模式会变成什么样呢?首先这样的开发模式中会有两个虚拟的角色,一种是现有内部系统维护的工程师,相对传统的开发方式,现在对它的要求,技术要求可能就不需要那么高了,你可能不需要了解js细节,只需要了解一点点的小部分的js,不用了解DOM操作,去了解HTML的使用方式就可以了,你甚至可以是后端开发工程师,都没有问题的。

对于另一个虚拟角色称之为组件开发者,一般是由专业的前端开发人员来担当的,但是对于他们来说,在新的开发模型下,他所要做的变化无非就是把以前所使用的某种组件开发技术换掉,这样转化成本应该是非常之低的。

整体来看,组件开发者应用之间交互只有是需求上的交互,在技术上完全没有任何耦合,当业务开发者需要开发一个系统的时候,使用HTML代码描述需求,然后由组件开发者提供所必须的HTML元素,最终构建完之后通过当前所说的方案的实现,来为我们产出在浏览器运行的代码,这样可以映射到之前的模型上去。

enter image description here

接着来看一下在这样的模式下开发流程图,蓝色部分是应用开发者所需要关注的部分,橙色部分是组件开发者需要关注的流量,首先是需求的产生,我们的组件开发者需要分析需求,转化成需求代码,接着会在转化过程中查找组件库,或者HTML标签是否足够,能不能满足,如果有了就直接进行下一步,编js,控制组件元素。

如果没有需要和组建开发者沟通说有新的元素支撑开发,这时候就可以使用技术开发组件,再继续下面的流程。其实这个过程看起来是同步的,但是是可以做异步的,组件开发者开发过程中需要付出的技术成本是非常低的,组件开发者是没有多大变化的。

enter image description here

最后来看一下使用这样的方案之后,从比较高的角度,或者公司角度来看,整个公司内部系统的开发变化是什么。首先使用之前,可能会每个公司有多个业务线,每个业务线都会维护多个内部系统。这些内部系统之间可能会有历史遗留关系导致他们之间使用的技术栈完全不一样,这时候对于我们团队内部的维护工作来说,维护工程师来说压力是非常大的,因为不仅技术上的压力,还有经济上的压力,当项目非常庞大的时候无法修改以前的代码,因为不知道动了这里代码别的地方会不会出问题,我也不敢删掉这些代码。

再把层面放大一点,业务线与业务线之间,由于之间的技术没有共同性,之间的交流或者交叉维护,或者由于部门的组织架构变动导致我们系统的维护权的移交,当我们的业务线2接收到业务线1的维护工作,他们也觉得是噩梦。新办法还是原来场景,每个业务线面对同样的方案,不管内部外部都是同步的。而且方案本身提供的可以非常快速进行,各个业务线之间开发的UI组件是能够零成本互用,就会促进整个公司内部,平台么不的代码的互用。如果对于更常见的场景内部工程师职位变动或者是离职带来的项目交界成本会有非常明显的降低。

到现在为止我要讲的最核心的内容基本讲完了,我们从无到有慢慢构建出了一个理想化的开发模式,这样的模式是不是有一个实现呢?原本我打算好好讲一下实现的,但是由于一些临时原因,我们实现本身也是在不断变化之中,我们也在摸索,所以我不会在这细讲。接下来只会简单讲一下作为结束。

enter image description here

首先美团网内部实现就叫CMIS构建平台,我们的目标就是为内部系统而开发的开发环境,目标就是为了同构内部系统开发。CMIS除了实现刚才的方案之外,我们还为CMIS提供了内置开发服务器,主要为了让开发人员能够不需要搭建任何多余的环境,就能够访问我们的页面,调试我们的页面,最重要的一点是对数据的mock,前端数据如果依赖后端是非常不好的效果。

enter image description here

第二点我一直没提到css,前端开发除了HTML、js,还有css,我为什么不提呢?css我们到现在没有很好的方案,本身看起来非常简单,但是某些程度也是非常复杂的,到现在为止没有一个很好的方案解决。所以我们会在CMIS内部内置一套非常完整的UI样式库,它里面已经有了各种各样的UI模版,工程师内部开发过程中不需要关注样式,只需要把HTML拷贝过来,因为样式已经内置了,放到我们页面中就能展示出那个样子。这基本上就是CMIS除了实现之外提供的最主要的功能。

enter image description here

最后看一下CMIS提供的命令列表,算是抛砖引玉,如果大家以后有这样的想法,我们能给大家有启发。

首先最重要的一条是build命令,我们开发的是中间技术,我们工程师除了组件开发技术在浏览器应用,应用开发者写出的代码都是中间格式,这就需要浏览器运营就要编译,build就是触发了CMIS内部编译器。相对应自己会有自动编译,增量编译,下面是CMIS start,启动开发服务器,支付多服务器,因为我们开发项目可能会有多个。下面是release,当我们开发完项目之后,我们需要使用这个命令生成最终完整的HTML前端代码,只需要使用这个代码打包上线就可以得到一模一样的能够在实际项目中运行的代码了。下面两个跟组件开发有关,第一个是publish,发布组件的命令,一般是组件开发者需要使用的,当组件开发者接到应用开发者需求之后,需要用中央仓库中推送开发的组件代码。然后接下来一个命令就是install命令,安装组件依赖,这是应用开发者需要的命令,声明对什么组件的依赖,我们环境就会自己安装好组件代码,除此以外应用开发者不需要做任何事,直接可以在HTML代码里使用这些组件。

————Q&A————

问:我之前看过支付宝开源一套UI语言,您的方向好像和他类似,用于后台系统开发,您看过支付宝的UI语言吗?

韩聪:我不太了解这个技术,从您描述中如果是UI组件我这边是有本质区别,组件只是辅助手段,我们更多改变开发方式,明确划分。应用的开发者只需要面向我们所提供的抽象环境进行开发即可。而我们所提供的抽象环境可以让应用开发者通过HTML来描述项目的需求,仅需少量简单的JS代码即可完成项目的开发。在开发的过程中, HTML可以提供和人类思维非常相似的方式描述应用,使得新的项目维护者可以以非常低的成本介入项目,从而减小了新人对项目的理解成本。同时,由于大量减少了JS代码,还保证了项目后期的可维护性。

问:dialog做标签,实际中标签化了哪些?

韩聪:最强大的首先是数据列表,我们发现内部系统中,至少美团内部,甚至我经历过的公司内部内部系统最多还是展示数据,内部系统就是做数据的,可能只是操作方式有一点点不一样,但是大部分都是数据表格来展现。这个date table功能首先可以自己描述,首先提供了分页功能,第二提供了过滤器功能,最简单当前显示多少条,显示男生女生,时间或者区间这样的一些数据,通过这样的功能过滤现在读取到的数据。第三内置对数据的增加、删除、更新功能,只需要使用非常低的成本就可以自动不用写添加数据和删除数据更新数据的代码,只要使用HTML描述就可以。

问:我看了您的好多都是组件化的,可能我需求是很多样的,比如说登录,好多登录用邮箱或者手机号之类,假如项目是要拉丁文登录,想找HTML组件就找不着,这样的问题怎么解决?

韩聪:最开始例子就是想要举登录对话框,但是例子会复杂,所以就没举。对于你这个首先要解决的是最主要的核心问题国际化的问题,拉丁文显示。