查看原文
其他

【第1621期】长夜未央——企业级研发提效的下一阶段

侯振宇 前端早读课 2019-07-17

前言

今日早读文章由支付宝@侯振宇授权分享。

正文从这开始~~

1 回顾与导读

过去一年中不断有读者问上一篇《【第1222期】十倍效能提升——Web 基础研发体系的建立》中描述的,我们团队在支付宝内部提效中所创建的系统搭建平台的落地情况。这里统一答复下,今年统计已经在平台上创建系统近 300 个,创建页面 7000+。同时也完全集成了数据模型、计算任务等通用后端系统能力。研发状态基本达到了通过 WebIDE 可视化操作即可完成系统研发工作。

在实践中我们认为已经走过了两个阶段:

  • 第一个阶段通过工具化和平台化,尽可能将日常工作强管控、自动化。从重复性强、繁琐的工作中提升效能。

  • 将 “平台型、高技能要求的研发工作” 和 “低技能但又暂时不易自动化” 的工作通过技术手段分隔开来,例如一些日常页面开发。将专业人才从第二部分工作中解放出来,投入到第一部分。而将外包或者非专业但有富余力量的工程师动态引入到第二部分。解决人力瓶颈,实现效能提升。

在这两个阶段中,我们越发清晰地看到“组织结构”和“技术架构”对效能的影响远远超过了局部或者细节上的工具化和自动化。其中的技术架构又更为重要,它涉及到了职能划分(招聘)、技术壁垒(技术商业模式)、业务发展对技术的需求等等公司最基础的组成部分,也间接决定了公司的组织结构。

我们在探索中看到了一种全新的技术架构建立的方法,它不局限于某个具体的业务场景,对具体技术的能力要求也可因业务规模而异,具有一定的普适性。因此此文希望分享给所有对技术架构或者效能同样在探索的读者。文章分为三个部分:

  • 《术以润物》将描述部分对此架构方法产生关键作用的技术,它们的发展和价值。

  • 《道以燎原》将描述新技术架构建立的思路。

  • 《以道驭术》将从全局看新技术架构还面临的挑战,讨论落地的问题。

和以前一样,文章会侧重思考的依据和过程,不感兴趣的读者可以直接跳到最后一章。不过抱有耐心,必有所获。

2 术以润物

互联网新技术出现得越来越快,但很多都是某一阶段的产物,并不能适应未来更大的挑战。这一章中我们会从发展的趋势中甄别出几个关键的、会对未来效能产生巨大影响的技术。

2.1 局部自动化

首先是这两年界面技术上讨论得较多一个方向:“设计稿如何自动转视图代码”,实现的方式既有“手动标注”也有“人工智能”。

淘宝的imgcook
微软的人工智能识别

在设计工具的相关交流中,我们还发现了有的团队开始把交互相关的功能也做了进去,例如将页面跳转,后端处理流程逻辑等进行了可视化编辑,自动生成相应的接口和流程代码。这种探索可以概括成:“局部研发链路的自动化”。它是一个初期提效很有用的方法。在对外交流中我们发现了两点有用的建议:

一是任何团队都可以并且也都应该做,不必觉得自己团队研发实力不够,等大公司推出相应方案。局部自动化的关键其实对自己的业务和人员分工的一种总结和思考。在技术上,当自己的业务相对确定时,通过一些简单的方法就能实现。而大公司要考虑的大而全,不一定适合。在人员组织上,几乎所有的自动化方案都有一定的分工要求,这也是因组织而异的。局部的自动化是之后整体架构变革的关键前提。

二是对自动化的关注,是指导团队技术发展的重要指标。大的研发团队中,每个人都想做出技术贡献的时候,往往容易出现一种有害的现象:就是从技术上确定的事情做起,看看自己有什么,再决定做什么。而不太愿意去思考“未来是什么样子,是否需要做”这种不确定的事情。因为这样确定的事情更容易看见结果,收获利益。但往往不确定的,才是问题源头,思考不清楚就开始做,最后往往是无用功,甚至是负担。这种思维在架构中也存在,最后的结果就是研发团队总是在疲于解决当前的问题,没有前瞻,积累的东西又容易被之后的变化冲击掉。我们在下一章《道以燎原》中要讲述的方法正是打破思维惯性的产物。

2.2 应用框架的消失

接下来我们再从一个有趣的现象来发现趋势:

在我们的平台持续集成底层能力的过程中发现,传统意义上的应用框架似乎不见了。

以前开发一个应用至少要学会一个框架,了解它的模块怎么写,怎么对接数据库等。现在变成了调用底层提供的服务即可,很多服务甚至简单配置一下接口就自动生成了,模式化比较强的业务,例如大量的增删改查,甚至页面都可以直接生成。

这个现象的出现对很多框架开发者的触动很大。因为在过去的软件研发中,应用框架一直都是软件质量、开发效能等核心问题的关键。过去讨论研发效率的时候,讨论的都是框架的概念是否易懂,是否能帮助梳理业务逻辑,是否易于写测试等等。在旧文《理想的应用框架》中,也是试图通过“以事件的方式去描述业务逻辑”,实现“业务逻辑可视化” 和 “由结构化的业务逻辑 DSL 生成部分代码”,以此提升软件的可维护性与高效。

以事件系统为基础的应用框架

同样,业界其他关于提效、提升系统可维护性的思路也要依托于框架,例如 FBP、FRP

FBP
FRP

而到了现在,很多公司都有意识地将基础设施和一些比较独立的业务服务化了,在这种环境下,应用开发实际上变成了各种调用已有服务,写应用越来越多的是写胶水代码。这才导致了应用框架在开发者这里感知越来越弱。这种现象正是影响研发效能的相关技术发展的一个缩影,它背后的大趋势是现代互联网架构的的两个基本意识:

  • 一是要将沉淀的能力工具化、平台化,使用方式越来越简单,以此提升效能。

  • 二是要从横向沉淀的数据与计算能力,以及纵向沉淀的业务能力中,挖掘更大的商业或者技术价值。中台概念的流行就是一个例子。

先看第二点,“横向沉淀数据与计算能力,纵向沉淀业务能力”。从这一点出发我们要介绍一个不那么热门但非常有价值的技术手段:打造统一的数据建模平台。在交流中发现很多公司开始或者已经做了。

过去在数据相关上,企业关注点基本在数据库:

  • 一是关注数据库的读写、容灾等能力。

  • 二是关注特定数据结构存储、查询能力,如图数据库。

在业务语意上,虽然一直都有 ORM 这样的技术,但由于“关联查询等场景下的性能”等等问题使得在实际生产中,落地成本较高,大部分落地的公司都是达到了一定规模,从降低研发的成本角度去抵消落地成本的。

而现在,存储能力已经基本稳定成熟。随着挖掘数据业务价值的需要,如何保持数据的业务语意变得越来越重要,这是越来越多的公司开始打造统一数据建模平台的主要动力。当然统一的数据建模平台并不是新概念,很多 ORM 框架可能也发展成了平台。相比于 ORM ,统一数据建模平台的基本特点有两个:

一是平台要记录数据使用需求,由平台按需求来决定存储的数据结构、甚至存储技术。例如有的数据是具备时序特点的,像新闻一样,一段时间内的新数据是热数据,而老的数据可以降级存储。那么在平台上标记一下归档的指标,平台来监测,自动迁移数据。再例如在平台上直接标记出“哪些字段需要被关联查询”,然后由平台创建数据库表结构时生成相应缓存字段。更进一步,利用 GraphQL 等技术,我们可以通过扫描项目来自动获取所有关联请求场景的,这样连人工标记都可以省略掉。

二是由于有了需求记录,存储的技术可以分级以降低成本。底层存储可以使用多种技术混合的方式。

有了这样的平台之后,既能保持住业务语意,又能提升研发效率同时降低成本。实践中我们还发现,它为数据清洗、迁移、备份等常见数据处理工作提供了自动化的基础。例如,如果业务模型产生了变化,我们只要针对变化写个新旧映射关系,就能配合平台自动完成迁移。我们可以使用类似下面这样的语言来描述迁移过程,甚至将不同版本的数据迁移串行起来:

function upgrade(originDB) {
return {
type
: 'db',
children
: [{
type
: 'table',
name
: 'table1',
children
: [{
type
: 'column',
name
: 'name',
data
: () => originDB.table1.fullname.split(' ')[0]
}]
}]
}
}

统一数据建模的技术不像容器等技术一样那么热门,但收益比非常高,对任何企业研发体系的建立都有很大参考意义。

2.3 运行环境的隔离

在沉淀能力的过程中必须要谈到的一个话题就是容器技术的出现和发展。本章描述的框架消失的现象和第一点沉淀能力的意识,很大程度上也和容器技术的发展有关。这个领域内的技术和概念非常多,我们并不准备一一讨论,而是从其背后的发展和问题来思考未来会对研发有什么影响。

首先要正视的一个观点是,容器的出现并不是能力沉淀上划分时代的标志。真正划分时代的标志是,具有“独立运行时的 RPC ”的出现。在此之前,能力的沉淀都依托于具体的运行时框架,以“框架模块”等方式存在,而框架模块往往受到框架所使用的语言、理念所约束。具有独立运行能力的 RPC 通过接口标准让功能的沉淀摆脱了语言和框架的约束。而容器技术的出现,是这种方式的一种延续,它让隔离变得更加彻底和规范。

同样,近两年大热的 Service Mesh 等技术也是“隔离环境管理”在容器技术上的一种延续。在此之前,一些较为成熟的 RPC 架构也已经发展出了类似的能力。这种延续并不是指它不重要,而是指我们应该看到它真正的价值在于:制定了一个完善的服务化架构标准,这个标准里面除了出了描述了隔离环境以外,还通过 sidecar 模式描述了负载均衡等等环境相关的约束。它就像一个新时期的操作系统,类比于操作系统中的“一切皆文件”的哲学,它提供了“一切皆服务”的基础。

但有了这个更为完善的底层,服务化架构中的真正问题仍然存在:服务体系标准的建立。

即我们根据什么决定,什么样的功能应该拆成服务。业务单元要不要也拆成服务?拆分功能的粒度应该是怎样的?回答这些问题的就是“服务体系标准”。

过去大部分公司标准的建立都是一种摸着石头过河、从下至上的方式。拆分出服务的时机无非是“什么时候发现复用的场景很多,就独立出来”或者“某些功能成了瓶颈,需要弹性伸缩,就拆分出来”。这实际是架构的不断重构。DDD 创始人曾经在一次访谈中说过:DDD 的关键就是不断重构,使其时时保持对业务的正确描述。

但架构上的自律比起单一软件的自律来说要难多了。一方面是由于组织的变大、业务的变大,变更成本也越来越大。另一方面是随着技术发展变得越来越快,重构的速度可能都赶不上外部技术进化的速度了。而同时,随着如“打造业务中台”等新的业务挑战越来越多,和挖掘技术商业价值的趋势越来越强,架构上的挑战逐渐产生了质变。我们可能需要比架构自律更好的方式才能适应更大的挑战。

我们在思考中发现,很多不同领域中,如容器、前端等,有一种统一并且强大的思路已经显露出来。这种思路能够,升值已经打开新的篇章。我们且卖个关子到《道以燎原》一章中详述。在讨论之前,我们还要继续铺垫一些其他相关技术的发展。

2.4 以人为本的技术

在人工智能真正到来之前,架构和代码一样,始终是面向人的。IDE 就是面向人的重要技术之一。IDE 能实现“让研发人员写更少的代码,更容易地写代码”。越来越多的公司开始从使用 IDE, 到写 IDE 插件,再到打造自己的 IDE。同时因为 Web 的跨平台、版本统一、开发成本相对较低等特点,很多团队自然地选择了研发 WebIDE。业界的开源 WebIDE 产品也越来越多,近期微软的 VS Code 已经开源并且推出了线上版,无疑又更给这个趋势注入了动力。

这个方向上,具体技术其实已经不成问题,真正的问题回到了专业的交互体验上。什么样的场景适合在 IDE 中集成?是否需要可视化?这些问题并不是一句开发者的简单感受就能回答的。人的视觉接受系统中有“格式塔”等内在规律,工作时脑子里会自然形成预期,即所谓的心流,要系统性地具备交互知识才能正确回答这些问题。它的难度已经堪比消费级互联网产品,但还没有得到足够的重视。我们看到了很多产品中都出现了“用力过猛”的情况,忽略了研发的整体概念,而在局部疯狂地做体验优化。

其次,设计工具的发展也非常值得关注。在互联网产品的领域内,很多公司的设计工具已经从 photoshop 转到了如 Sketch 之类的矢量绘图软件上。这可以看做是针对网页、应用设计场景的专业化,并且这种专业化还在不断发展。Sketch 也开始推出一些原型能力。甚至更有厂商直接将适量绘图搬移到了 Web 上,例如 Figma,进一步加强和前端的融合,尝试从设计直接生产可运行的页面。

Figma
Framer

这些直接影响开发者体验的工具的发展都在效能中扮演了重要角色,并且相比于底层技术,可能在未来承担更重要的角色。

3 道以燎原

这一章将描述指导技术架构建设的新思路。这种思路冥冥之中已经在很多领域出现过了。从不同的角度描述,它有不同的名字。在这里我们不纠结于概念,而是从最朴素的观点看思路的诞生。

在做提效的初期,我们就问过一个问题作为指导思想:代码最少能少到什么程度?在不考虑人工智能补全的情况下,应该能得出一个统一的回答,即只要做业务逻辑的描述即可,所有技术细节都不用写。

在真实写代码时,实际上是同时再做两件事:翻译业务逻辑 和 实现支撑业务逻辑的数据结构或者算法。有经验的程序员往往会自然地将两者分离开来,编程原则中的“将变化的部分隔离出来”通常指的就是将翻译业务逻辑的部分独立处理出来。最典型的例子就是 RBAC 的代码,我们可能会这样做:用二维表形式的数据结构将具体的权限信息单独写出来,需要用权限的地方再去二维表查询。这时这个二维表就可以认为是业务逻辑的 DSL,未来如果权限变了,改这一部分代码就够了。如果未来变化频繁,我们很可能会把权限存在数据库里,再提供一个可视界面给产品经理等角色自主修改,剩余的代码就不用再变化。这时这张表就成了具体的业务逻辑的 DSL。

这个模式可以归纳为“领域 DSL + 针对特定底层技术的驱动代码”。驱动代码又分成两种:“直接接受 DSL 的运行时” 和 “使用 DSL 再生成相应一直框架代码的生成代码”,在本章中主要指的是后者,后面会说明原因。从 DSL 的观点来说其实所有业务代码都可以这样写,它能让代码更干净,也能做更多专业编辑工具。很多时候没有这样写的原因是:业务逻辑的 DSL 设计成本和相应驱动的开发成本太高,不像 RBAC 那么简单,手动翻译业务逻辑反而更快。但不妨想象一下,如果真的企业中所有系统,所有代码都这样写,会怎样呢?它会产生两个意义重大的质变:

  • 完全实现业务逻辑与技术细节的切分,系统的可维护性、适应变化的能力将得到巨大提升

    • 当业务逻辑发生变化时,我们能够直接通过 DSL 的变化来跟踪系统的变更,而不再是人工到文档等非结构化数据中查找。

    • 当底层技术发生变化时,我们只需要重写驱动就够了,不必从头梳理业务逻辑,将极大降低重构成本.

  • 将分别产生“业务逻辑的搜索空间”和“实现逻辑的搜索空间”,两者能互相独立地进行沉淀和复用。过去这两者往往在系统实现之后就直接耦合在了一起的,业务文档随着系统迭代可能出现更新不及时,回溯困难。而实现逻辑的复用性既受到业务场景的限制,也受到框架、语言等等约束。

那我们目前的技术积累,是否能达到这样一种状态呢?更具体一点,是否能将“设计业务 DSL” 和 “开发驱动” 两者的成本降到可以接受的程度?

我们先看前者的现实状况。

不难发现,业务 DSL 实际上一直都有,就是我们的文档、设计稿。只要将他们结构化,就能直接当成 DSL 用。例如前面提到了设计工具的发展已经到了开始产出可用页面的阶段,这方面的探索也可以参考我之前的文章 《设计稿自动生成可用页面的展望》。

但视觉稿对业务的描述仅仅是“产品长什么样”上,我们还要继续看其他的业务描述如何结构化。这时候必然会想到 UML,它的目的就是为了描述业务。但它的主要用户是开发者,其中大部分涉及到了技术细节,例如系统调用时序图。也有小部分只表达业务逻辑,例如其中的用例图。它定义了角色、用例间的关系,和用户与系统间的大体交互,具备充当业务 DSL 的潜质。

除了 UML 外,业界一款著名的 CMS 软件 Drupal 具有极大的启发性,之前文章中也介绍过。从它的观点来看,大部分的互联网产品都是某一种形式的 CMS,于是它将内容的产生、发布等等的概念做了一个抽象,用户只要理解了这个抽象就能开发出相应的产品,并且基本上已经做到了无任何开发经验的用户都可使用。这些概念就可以理解成业务逻辑的 DSL。我们在探索中结合了 Drupal 的观点和 UML 用例图中的部分概念,尝试设计了一种适用于适用于内容发布型互联网产品的 DSL: RACR(Role Action Contont Relation) 。通过对它的演绎来帮助继续思考这个问题。先看以它的部分示例。

简单的论坛举例,描述如下。

论坛 RACR

在示例里面看到,我们主要是在用 RACR 这个四个概念来描述功能。

要多说一句的是,Relation 包含两个意义:

任意两个实体间的关系。既包括内容和内容之间的,也包括内容和角色之间的。当角色和内容之间存在动作,那么默认就存在动作关系。

用于描述实体的“关系定语”。例如用户只能删除“自己的”帖子,“自己的”就是一个关系定语,它的完整语义是“当前用户创建的”。我们这里写成“自己的”只是为了读起来更顺畅。

当然,有些 Action 会比较复杂,比如在聊天软件中的功能:用户—发送消息—>用户,其中的 Action 就包含了 Content。如果一定拆分成原子形式:用户—[创建]—>消息—[推送]—>用户,反而把语义变得更复杂了。这些问题留到之后对 RACR 的详细介绍中再讲。它一定是有局限性的,但这并不影响它在适用领域内的价值。

接下来我们看它是否提供了足够的信息让我们能通过驱动生成可运行的系统。结合上面的例子看看能获取到哪些信息:

  • 用户角色

  • 内容

  • 数据字段

  • 内容之间的关系

  • 如何被使用的

  • 用户操作

根据这些信息可以做到:

  • 生成数据库表结构、页面所需的接口。

  • 通过角色信息为接口自动加上权限控制。

  • 通过《术以润物》中的的统一数据建模平台,根据这些“数据如何被使用的”自动优化存储结构。

相应的生成代码:

function (input) {
system
.generateStorage(input.getEntities())
system
.generateAPI(input.getActionsWithRole())
...
}

存储、接口,再加上设计工具所描述的界面,已经覆盖了应用的绝大部分。用 RACR 来描述用例再加上结构化的视觉稿(原型稿),已经能够基本完备地描述业务,并且看起来驱动也是能实现的。到这里,有的读者可能已经感觉到这和“数据驱动”、“函数式思维”好像很类似。在这些理念已经被普遍接受的今天,为什么我们还没有这样做呢?例如在实践中为什么还会要手动写 Query ,为什么还是可能手动调整表结构?为什么前端还是可能要手动处理 DOM 节点?目前还没有这样写的难点在哪里?

从具体问题我们可以明确看到,都是因为性能。无论哪个领域,都是因为通过简单的映射规则还达不到生产环境的性能要求。但是随着一些关键技术的发展,这个难度已经下降了很多:

  • 在存储层,统一数据建模平台通过“数据被如何使用”的信息,已经可以生产出达到性能要求的存储结构。

  • 服务层的容器技术的发展使得我们已经摆脱框架和语言的束缚,可以将不同性能要求的功能拆分开来,就像主板的南北桥一样,让每个部分都能渐进改良而不必动全身。

同时,在设计驱动时我们采取“生成代码”而不是一次性创建完备的运行时,能进一步降低难度:

  • 生成的代码可以是企业中已有的框架等沉淀好的技术。

  • 生成的代码可以比手写的代码更直白可靠,例如直接将权限控制直接写入接口中,降低问题排查难度。还可以再进一步结合人工进行动态调整。

  • 生成代码本身是一种经验,可以理解为“当需求是 xxx 时,就生成 yyy 的代码”,和指导我们编程的经验几乎一致。复用性比解决某个具体场景的问题更强。改进生成代码是一种优化经验的过程,能够在团队能形成正向循环。

生成代码的理念在一些如 Chromium 之类的大型项目中也已被广泛使用。

除了难度已经足够低了之外,“增量” 这个概念也能在各个层面指导架构或者具体的技术达到相应的性能要求。例如在前端,React 框架实现完全的数据到 DOM 自动映射的关键是:通过 diff 算法解决了 DOM 操作的性能问题,不用每次都完全重新生成 DOM 节点。在容器的部署中也有类似的概念。我们在写驱动时,也可以使用“定义增量叠加的方式”来进行加速。

至此可以看到,“完备的业务描述 DSL” 和 “底层技术上的驱动” 在当前环境下都已经达到了一个可实现的状态,我们只需要这一点点像星星之火的新思维,就完全有可能进入软件研发的下一阶段了!

值得一提的是,张杰从理论的角度提出了一个非常有前瞻性的编程思维——《可逆计算》,其中巨大的工业价值在于:

  • 在这个模式中既保存了系统中所有的语义,使得系统变成了 100% 可控,为质量、自动化等思考提供了全新的空间。

  • 通过将差量的概念提升为一等公民后,用它可以指导所有的 y=f(x) 形式的性能优化。

同时在和他交流时,解决了我关于 DDD 中需要不断重构的疑惑。他指出基于模型的软件设计,使用模型去贴合业务逻辑,需要不断预测,当现实发生变化不符合预测时,当然就要重构了。基于驱动和 DSL 的软件设计则不然,规则是针对相应底层技术的。业务 DSL 是比模型更低一层的概念,是针对业务领域的,我们将变化隔离到了“对 DSL 的使用”这层,因此架构的稳定性要成本将发生质变。

3 以道驭术

我们以两张图来整合通篇的观点。

从图中可以看到要达到下一阶段,针对业务体系的 DSL 的建立和架构增量的标准是关键。本文中描述的 DSL 只适用于内容管理类型的功能,还有许多需要补充,读者针对自己的业务需要完善甚至开发自己的业务 DSL。架构增量的标准,直接决定了架构是否能达到很好地响应生产环境的变化。反而是各种能力的自动化,可以大量复用已有的经验。

在 DSL 的承载上,我们需要的是更好的、能表达用例和交互的原型工具,而不仅仅是视觉设计。这就需要第一章提到的 IDE 、矢量绘图引擎、前端框架等各种技术了。

在架构的落地上,大致要经历三个步骤:

  • 建立业务 DSL。

  • 梳理已有的底层技术,包括局部自动化技术,建立支持增量的底层。

  • 业务逐步迁移成 DSL + 驱动的形式。同时伴随着人员职能、甚至组织架构的调整。例如业务 DSL 使用者可能是产品经理等。以前写胶水代码的工程师则投入到驱动或者 DSL 辅助编辑工具的开发上。

研发效能提升,早已经不再是某个单一技术、某个研发环节提升就能产生收获的阶段。和历史上的其他生产活动一样,谁先察觉了质变的时机,谁先产生了质变,谁就拥有了降维打击一般的竞争力。不过从更长远的事件看来,软件生产仍然是人类生产活动中非常小的一部分,从全社会的角度去产生质变还有很长的路要走。长夜行路,既需要仰望星空,又需要脚踏实地。希望此文为同路人提供些许灵感。共勉。

关于本文
作者:@侯振宇
原文:https://zhuanlan.zhihu.com/p/66474056

他曾分享过


【第1222期】十倍效能提升——Web 基础研发体系的建立


为你推荐


【第1597期】从前端角度看企业软件的研发过程


【第1619期】前端能力中台化之路—Fusion Design 成长史

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存