写在前面

2018 年,Airbnb 放弃了继续使用 React Native,个中原因主要有两方面:

  • 技术:成熟度、配套设施/类库建设成本、首屏性能硬伤等没能很好地解决
  • 团队组织:工程师要求高、跨技术栈/跨团队调试/测试等也产生了新的问题

实际上,跨端方案遭遇的问题远不止这些,一些时候 Write Once, Run Everywhere 只是美好愿景

一.技术困境

一言以蔽之,触碰到能力边界之前,跨端方案里的一切都是美好的

以 React Native 为例:

P.S.图有些旧,但不影响理解原理,更新的 React Native 架构图见[(React Native 架构演进]http://www.ayqy.net/blog/reac...

在这样的技术架构中,写的和实际执行的都是 JavaScript,调用由 Native 提供的视图渲染能力及平台特定能力,Facebook 称之为Scripting native 方案,姑且叫做容器化 Native 跨端方案

(摘自移动端跨平台技术之下的变与不变

容器能力在很大程度上决定着开发效率,在容器提供了一致的标准支持范围内,能够愉快地一人搞定多端。然而,一旦触及能力边界,就会面临高成本的多层联合开发(Native 层、JavaScript 引擎层、特定业务领域层、业务层……),人效上并没有优势。另一方面,如何保持多端一致性,也是个极其复杂并且充满技术挑战的问题

此外,相关的配套能力也直接关系到开发效率:

  • 调试:跨技术栈调试一直是难题,问题排查成本越来越高
  • 性能:跨线程、跨页面耗时分析困难、性能工具链缺失
  • 工程链路:由于技术方案的特殊性,需要量身定制许多配套设施(包括但不限于 IDE、调试、性能、CI/CD、监控),才能将各个环节的成本降下来

总的来说,技术上最大的困境在于:

  • 抹不平的多端差异
  • 掀不开的 JavaScript 引擎盖
  • 跟不上的配套能力

多端一致性在技术投入上几乎是无底洞,底层的平台架构差异(UI 渲染方式、事件机制、系统 API)根深蒂固,以各类跨端方案目前的成熟度仅能覆盖极其有限的一部分,留有非常大的空白需要通过扩展容器能力来填补,包括通用的基础能力(如 UI、交互),以及面向业务领域的特定能力(如多媒体、定位)

引入 JavaScript 引擎虽然获得了动态执行代码的能力,但也带来了技术上的不确定性,几乎无法跟踪解决 JavaScript 引擎内部的崩溃或异常行为

通用的基础设施大多无法直接用于跨端场景,边边角角的配套能力都需要花力气建设,而为了满足业务快速生长,总是优先建设核心关键能力,配套支持通常是滞后的,同样影响着效率

Flutter 能带来一些不同吗?

事实上,Flutter(目前看起来)同样面临这些技术困境,技术实现的变化并未彻底改变局面

据 2020 Q1 调查结果,Flutter 开发者认为最重要的 6 个问题是:

  • 调试错误和崩溃
  • 测试确保 App 能够跨多平台运行
  • 选用状态管理方案
  • 理解和处理布局问题(如文本溢出)
  • 根据设计稿创建 UI
  • 排查特定平台问题

同时,认为最困难的 6 个问题是:

  • 排查特定平台问题
  • 内存问题的诊断和修复
  • CPU 使用率问题的诊断和修复
  • 接入现有的 Native API
  • UI 卡顿问题的诊断和修复
  • 开发特定平台的 Flutter 插件

因此,跨端方案的调试、性能之痛仍在 Flutter 延续,多端差异以及配套能力的困境并没有改变

二.团队组织困境

与单端开发模式相比,跨端方案的协作成本更高,体现在:

  • 跨团队
  • 链路长
  • 容器团队压力大
  • 职责边界不清晰

跨端方案下,跨团队协作成为了最主要的协作方式,需求串讲、开发、联调、问题排查等多个环节都需要跨团队沟通/协作,沟通成本不容忽视

长链路意味着技术细节散落在多层,各自只拥有一小部分知识

表层业务逻辑
-----------------------------
特定业务领域框架
-----------------------------
通用前端框架/类库
-----------------------------
JavaScript引擎(扩展)
-----------------------------
Native Module | 特定业务领域能力
-----------------------------
Native通用框架
-----------------------------
Native View
-----------------------------
平台操作系统

由于每个团队都看不到全景,每一个原因不那么显而易见的问题就都要一层层向下排查,甚至涉及特定业务领域能力的部分又分为许多层……

另一方面,如此繁多的层次也造成了复杂度堆积,越往下层复杂度越高,因为不确定的可变输入越多,越难弄明白来龙去脉,排查问题的成本也越高

理想情况下,按漏斗模型逐层过滤,每一层只需要检查自己的输入输出,但滞后的配套能力让表层业务难以识别出问题所在的范围,于是容器团队成为了问题流转的过滤阀,上接纷繁的 JavaScript 业务,下连复杂的特定领域能力,大量的时间耗费在了弄清楚来龙去脉上,容器能力扩展被迫降速,反复排查已知问题……

业务视角下,对业务之下的层次职责划分并不十分清楚,因此很容易找错层/人,产生无效的“重定向”。而容器层同样也不具备全景视图,问题流转轨迹变得相当曲折,沟通成本充斥在各个环节中,制约着开发效率

三.个体困境

对个体而言,面临的最大困难是跨端方案与 Web 标准存在些许差异,并且这些许差异不像 W3C 标准一样能写得清清楚楚:

也就是说,通用的 Web 经验不完全适用,学习曲线并不十分友好,例如:

  • rem、媒体查询、scale/zoom等适配经验都不一定适用
  • 减少 DOM 操作、合并 JavaScript 文件、开启硬件加速等常规优化措施也不一定能产生明显的性能优化效果
  • (像学习 Web 一样)只了解浏览器之上的标准能力是不够的,想要真正高效地完成业务开发工作,容器原理甚至部分实现细节都要理解

就像有 Native 背景的开发者学习TypeScript一样,初接触无师自通,熟悉的ClassInterface静态类型用起来游刃有余……然而,熟知 TypeScript 的开发者一定知道个中细节存在着多少奇怪的地方

四.跨端的真正意义是什么?

React Native 最初的出发点是:

(摘自React Native 简史

因此,从需求角度来看,开发效率是次要的,动态化的灵活性、快速迭代助业务先赢才是其跨端的主要意义,或者说追求的是生产效率,而不仅是开发效率,更短的迭代周期,更快速的触达用户都是直接的生产效率进步

然而,在三大困境之下,开发效率实际上也严重影响着生产效率,但还不足以抵消快速迭代、动态发布的重大进步,此消彼长也算是一种平衡,一种可接受的妥协

五.在困境中寻找生门

理想情况下,容器应该是趋于标准化的,提供多端一致、丰富稳定的能力支持,之上的业务栈极少触及容器能力边界,从而使得容器层能够不断优化探索,更好地满足业务发展的需要

另一方面,跨端方案只是将多端不一致性带来的复杂度下沉到了容器层,独立于平台的语言环境(JavaScript 引擎、Dart 虚拟机等)能够保证上层业务逻辑的一致性,但容器层仍然需要在多端各自实现一套,如何保证容器能力的多端一致性,仍然是个大问题,也并不比非跨端方案下容易多少

因此,首先要解决容器能力丰富度的问题,将边界拓宽,从根源上减少问题。转而集中火力到真正的难题上,攻下最有价值的难点部分。同时通过虚拟架构等方式建立全职能的业务支撑团队,降低沟通成本

  • 业务要有自研能力:化解下层资源瓶颈,共同丰富容器能力
  • 专注必须花大力气投入的点:调试能力、标准化、性能分析以及持续跟踪、工程配套设施
  • 要有全职能的业务支撑团队:有能力兜住所有问题,真正提供一揽子解决方案,消除无意义的沟通重定向

其中,业务自研能力先要有标准的扩展方式,要求容器实现上易扩展,业务开发者不需要了解过多细节也能快速进入开发。调试能力在长链路的技术栈下至关重要,问题识别成本越低、准确率越高,效率越高,所能释放出来的资源就越多

从业务开发角度来看,更需要的可能是一层网关,请求过去响应回来,而不是一系列路由表,需要一跳一跳地跟踪。全职能的业务支撑团队组成局域网,让网关之后的流量得以快速流转,高效协作的同时提升业务开发的幸福感

参考资料

有所得、有所惑,真好

关注「前端向后」微信公众号,你将收获一系列「用原创」的高质量技术文章,主题包括但不限于前端、Node.js以及服务端技术

本文首发于 ayqy.net ,原文链接:http://www.ayqy.net/blog/cros...

03-05 17:08