赞同科技蒲云:锋锐之“锈”可为战略利器
关于作者
作者拥有银行 IT 领域近 20 年行业经验, 现任赞同科技股份有限公司 CTO; 作者入行即从事工具平台研发, 支撑并推动赞同科技在各线产品平台形成规模化快速应用开发配套工具体系, 构建关键竞争力要素; 作者主持研发的渠道系统前后端中间件平台, 顺应并支撑业务快速发展创新, 在细分领域市场份额排名常年位居榜首; 作者愿意在立足技术面向业务的发展道路上, 与同业共同探索更进一步的价值创造。
"锈"是什么
此"锈", 意指 Rust 编程语言生态。Rust 语言是系统级编程语言, 其性能可以媲美 C / C++, 其内存安全及并发安全具备较高的"免 BUG"可靠保障, 其生产率方面有着完善基础配套和"宏扩展"特性带来的强劲助力。
从 2016 年以来到现在,Rust 每年皆荣登 StackOverflow 最受喜爱的语言榜首。2021 年 2 月,Rust 基金会宣布成立, 华为、AWS、Google、微软、Mozilla、Facebook 等科技巨头首批加入,Rust 生态体系的建设和推广得到加快。
目前 Rust 的主要使用领域包括, 命令行工具、浏览器内高性能 wasm 组件、网络服务、嵌入式等, 可谓上天入地, 在诸多热门关键领域皆有所踏足。为何如此, 因为 Rust 语言在思想机制方面做出了很多创新进步, 能够在非常广泛的领域形成普遍竞争助力, 值得各领域尝试使用 Rust 发挥其特性优势。
因何而利
这么多年来我们见惯了很多新兴语言的诞生, 但真正不容忽视并对整个行业产生重大影响的屈指可数,Rust 真的需要提高到战略层面来对待吗?
在这里, 我不想把 Rust 的全部特点一一道来, 而是结合我自己通读学习完官方著作《The Rust Programming Language》后, 最令我受到触动的一些特性, 展开探讨, 供大家评估。
集"简易"和"轻便"于一身的内存管理
不知从何时开始, 但凡是新诞生的开发语言, 普遍不再像古老的 C / C++ 那样需要关心内存的回收问题, 似乎内存中的回收清理问题已经有了世所公认的答案, 那就是通过各种运行时虚拟机所提供的垃圾回收机制, 依赖各种条件触发内存的扫描遍历, 把不再需要使用的空间释放出来。这符合利用电脑解放人脑的大趋势。
垃圾回收所带来的"简易"显而易见, 然而代价也非常可观。这需要付出空间和时间两方面代价, 一是牺牲背负更大运行时的空间代价, 浪费内存, 二是牺牲运行时在内存中反复进行遍历、标记、搬运从而阻碍顺畅运算的时间代价, 浪费 CPU。
而 Rust 创造性地设计了一种在源码编译检查时识别各个变量所关联内存生命周期的回收机制, 非严格意义上说, 这是一种编译检查时、而非运行时的的垃圾回收机制, 既可以"简易"地不必像 C / C++ 那样冒着搞错时序与地址的风险费尽力气, 也可以"轻便"地不必像 Java 那样背负一个启动慢占内存的庞然大物还要周期性陷入垃圾回收时的挂起状态。
当然,Rust 这种集"简易"与"轻便"于一身的好处, 也并非全无代价, 但这种代价却是另一种角度的好处, 你不得不将程序写得极为规范清晰, 因为 Rust 的编译检查会让各种模糊无所遁形, 有隐患的程序常常都根本无法通过编译。这一特点对于管理者而言, 简直应该是太爱了。当然, 这可能是 Rust 学习的关键门槛, 需要一种完全不同于 Java 等引用型变量的思想理解。
举个例子, 在 Java 中声明一个对象变量, 你完全可以直接拿来跨线程使用, 甚至拿来多线程并发操作它, 这样未经保护的对象就有了在并发处理情况下状态出错的隐患, 编译检查不会阻止你故意或者不小心写出有隐患的程序并将它们部署运行在生产环境里面。而在 Rust 中, 一个变量甚至能否改变都需要关键字修饰, 从而在编译检查时就能够严格确保同一个值对象在任何时刻要么只有一个写引用合法要么若干个读引用合法, 这是其一; 任何值对象要想以互斥机制更改必须加锁使用, 常常需要封装在一个 Mutex 泛型类型中, 在时序上完成保护, 这是其二; 任何值对象要想跨线程分享引用, 常常需要封装在一个 Arc 泛型类型中克隆出若干引用值对象转移给各线程分别独占使用, 因为变量的传递就是值对象 (也包括引用型值对象) 的传递, 这是其三。当然还有很多其他差异, 总之,Rust 是一门非常不一样的语言, 它的简易与它所要求的清晰约定息息相关。
强制源代码高质量高可靠
在 Rust 语言中没有 null, 不会出现几乎所有程序员都无比熟悉的空指针异常。Null 这个编程概念的发明者 Tony Hoare 曾在 2009 年做过一篇名为"空引用: 十亿美元错误"的演讲, 深切表达了为贪图一时方便而给这世界带来巨大损失的悔意。那么在 Rust 中, 当你真的需要保留一个存放或不存放值对象的不确定性的时候, 你需要用到 Option 的泛型枚举, 在编译检查的监督下明确处理好该对象无值的可能, 强制程序员们算无遗策。
在 Rust 语言中需要 mut (即 mutable) 关键字对每个需要可变操作的变量进行明确声明标注。这在前面也提到了编译器对于同一变量产生多处读写引用的控制原则, 确保受保护的数据不会被不小心更改或访问失效。另外, 如果变量 a 没有声明为 mut, 那么即使我们可以通过 a.b.c 这样的表达式访问到内部的值对象, 也无法直接进行任何令其状态改变的操作, 这种权限控制对于引用泛滥的 Java 来说几乎无法做到。当你拿到一个 Java 对象, 它自身的权限与它所属的容器没有任何关联。而 Rust 对于对象的权属关系管理地十分彻底, 编译检查就是这么铁面无私。除非专门使用 RefCell 的泛型类型产生一个 mut 变量引用来, 值对象的访问权限是完全被根引用牢牢控制住的。这种机制, 强制程序员们不仅关心对象的可见性也不得不牢牢控制好对象的可变性。
在 Rust 语言中, 错误异常与正常返回结果一般是被一起封装在泛型类型 Result 中, 而不是使用 throw 和 try-catch 这种区块包装的语法。用 try-catch 风格最大的隐患是当 catch 的异常类型有时过于宽泛时, 往往在 try 的区间内一次性容纳了过多潜在异常的步骤, 这种语法风格使得各种异常往往得不到更准确恰当的处理, 否则会使程序写得臃肿不堪。Rust 相比而言, 让 throw 回归了其 return 的本质, 并通过 Result 类型本身提供的方法, 或者结合特有的 match 语法, 以及"?"操作符的简便异常传播机制, 可以简练而又清晰表达各种异常的处理和传递, 把异常管理得井井有条。同时, 编译检查器会对返回 Result 类型的表达式进行专门关照, 确保所有 Result 都得到明确处置。
在 Rust 语言中, 还可以奇葩地把示例代码写在文档注释中成为可以执行的自动化测试用例, 结合自动化测试和文档发布, 让每个 Rust 程序员的程序说到做到, 言出必践。利用好这一特性, 可以大大减少文档、程序、以及测试这三者之间的脱钩问题。
更加贴合业务需求的设计思想
软件设计贴合业务需求非常重要, 将业务本质深刻理解并烙印在程序中能够得到无穷的好处。这也是为什么面向对象几乎成为现代语言设计能力的重要考量。在面向对象设计中,"继承"是非常重要却存有争议的一个特性, 它同时立足于代码复用和多态抽象。如今越来越多的程序设计, 不再如过去一般热衷于使用"继承", 因为通过继承建立出来的一套类型系统, 往往强制子类继承了过多不必要的父类特性, 在实践中缺少灵活性。我们需要更加贴合业务需求的设计思想。
在 Rust 中可以利用 Trait 这种类似于接口的概念, 对任意一个已经存在的类型进行能力扩展, 也就是在 X 已经被定义的情况下, 在遇到出现 Y 接口定义的场景时, 再追加声明出 impl X for Y {...} 这样的补丁写法, 为 X 这个类型后天追加扩展实现 Y 这个接口特性并实现。这就特别贴合了领域驱动设计中更进一步的 DCI 架构思想, 即 Data 数据-Context 场景-Interaction 交互的分解融合, 三者是从模型本质到应用场景再到最终落地的递进设计, 而不是通过一个类把数据和方法在第一时间全部定义固化, 让内在本质被外在行为封闭起来, 这并不符合现实世界。在业务建模第一时间, 我们关心的是业务本质, 例如账户是由账号、姓名、余额等一系列要素组成, 但是我们并不应该急于在第一时间去定义账户应该具备什么样的功能, 而是在一个上下文场景中, 我们可以需要一种叫作查询的操作, 这时我们再扩展原来这个结构体, 为它扩展赋能, 把查询逻辑以信息整理返回的方式进行落地实现。Rust 这种 Trait 概念以及搭积木一样扩展实现既有类型的方式, 特别适合设计实现一套高度灵活, 可扩展的, 面向领域业务的类型体系。
在 Rust 中的枚举类型能够接纳不同数据结构的成员, 更加符合实际场景对于异构枚举的需求。一般大多数程序语言的枚举其实是同构枚举, 其本质不过是一个有限整数集合或者是某种固定类型的数组, 这其实是不太符合很多实际需求中由异构对象共同组成枚举的情况。例如一个抽象的报文消息类型, 它的本质是由不同数据结构的指令形成的枚举。在 Rust 中, 可以定义为类似 enum Message {Quit, Move { x: i32, y: i32}, Write (String), ChangeColor (i32, i32, i32),} 这样的形态, 而其中 Quit、Move、Write、ChangeColor 都是 Message 的一种, 同时都需要保留其各自不同的数据结构, 例如, 退出指令 Quit 不含更多数据, 移动指令 Move 含有坐标值 x 与 y, 写指令 Write 需要字符串对象, 换色指令 ChangeColor 需要三色值, 他们各不相同, 却都是 Message 的枚举内容。Rust 满足了设计者使用更贴合业务的异构枚举设计需求。
用武之地
即使 Rust 是一门十分优秀的语言, 但在各个应用领域的技术体系都有先来后到, 已经存在的技术体系假如没有出现明显的瓶颈和痛点, 或者 Rust 不能有效解决这些问题的话, 那么人们是不太可能为 Rust 的到来打开大门的。
判断究竟 Rust 适合用在哪里, 是一个比较主观的行为, 这往往带着观察者自身所处领域及其关切方向的局限性。就本文而言, 更加关注于围绕银行 IT 业系统建设的用户交互、生产工具、服务架构等方面, 供大家参考。
前端高性能运算处理模块
在体验为王的前端领域, 性能瓶颈一直是最容易让体验大打折扣的关键问题。一方面, 是随着业务需求积累不断加剧前端资源开销, 另一方面, 是随着技术维度扩展而需要引入的音视频能力与 AI 能力, 前端领域的性能瓶颈问题在某些应用场景下已经有所显露。
利用 Rust 可以开发出运行于浏览器引擎的高性能 WASM 组件, 能够以被广泛使用的 npm 包的形式被现有 H5 前端开发技术导入使用。
所以, 我们可以结合自身在前端领域的瓶颈问题, 对那些存在于模块中的或者是能够通过高性能模块解决的具体问题有的放矢, 而不必把现有整个前端的技术体系推翻重建。
编译打包加载的工具体系
不论是前端领域 vue、react 技术体系, 还是服务端领域的 SpringBoot 技术体系, 它们核心竞争力中最重要的组成部分, 在于区别于上一代技术体系的开发方式。而在这一套方式的下面, 是由一套编译、打包、加载 (部分或者全部) 的工具体系所提供的有力支撑。
循着这个思路, 具备一定研发能力的企业, 一般也不会缺席于该领域的探索创新, 立足于自身需求延伸发展相关能力, 获得优于行业跟随者水准的竞争优势。
同时, 对于深耕某一专业领域的企业, 如果能够洞悉并抓住该领域的业务本质, 并形成体系化的抽象能力, 那么就有条件将之转化为该企业的核心竞争力。这种竞争力向技术落地的转化, 就会需要在编译、打包、加载的技术环节得到自主可控的技术保障。
我们注意到现有工具体系较多使用脚本语言开发, 在许多应用开发环节存在着工具性能表现欠佳的问题, 有些已经成为开发生产的瓶颈。这些工具最大的关切点是要求小巧高效, 并且因为独立性较强, 在保持功能不变的前提下替换下来不会对应用开发者产生太多冲击, 因此在这一领域应用 Rust 对基础工具升级换代的可行性值得思考。
边车等服务配套
随着分布式架构的广泛接纳和发展, 许多原来功能全面、由众多软件模块组成的软件架构, 演变为了由若干功能相对单一独立的微产品或微服务跨网络协同起来的部署架构。那些被拆下来的模块, 有一些会演化为服务网格 ServiceMesh 中的边车 SideCar, 成为各个容器应用的配套基础设施。由于这些容器普遍会分配较小的资源, 所以减少边车消耗, 把资源留给应用服务, 成为大家的普遍共识。
除了边车以外, 过去服务端常常会有一些代理配套, 例如监控或者日志搜集代理, 有的也会采用 Java 这样比较易于开发但却略微有些吃资源的技术体系。在过去因为应用服务本身更加吃内存, 所以这些服务配套对于资源的消耗并没有那么突出。但现在容器化了, 每个容器所分配的内存空间大大缩小, 那么它们的瘦身也变得迫在眉睫。
这些服务配套, 对于应用开发可以相对不用过多关心, 并且瘦身提效能够为主体服务让出更多资源, 在内存空间成为关键性指标的情况下可以考虑使用 Rust 技术体系把这些服务配套进行改写升级。
运算密集型的关键服务
在服务端, 除了那些配套部分, 还有一些对于高并发、高稳定、高性能有极致要求的关键服务, 例如网关、数据运算、流媒体处理等。也可以考虑借助 Rust 高效、稳定、安全的特性, 对存在性能瓶颈问题的关键服务, 进行改造升级。
利器而非重器
Rust 目前也许能够在许多关键点发挥其突出优势, 是利器; 却不太可能在短时间内形成大规模效应, 而非重器。尤其在银行 IT 领域, 不太容易。
技术上, 银行 IT 领域相对保守, 对于无法直接在某方面形成填补空白的新技术, 而只是让其解决既有技术体系的瓶颈与痛点, 这一推广过程将比较缓慢。
业务上, 银行 IT 领域的大规模建设, 大量依赖的是应用级开发技术人才, 而非系统级研发技术人才, 奢望这样结构组成的群体大范围掌握应用 Rust 技术并不现实。
商业模式上, 银行 IT 领域对于人员密集型的合同更加友好, 几百万的人力合同远远比几十万的产品合同更容易拿下,Rust 优势难以直接透过强力产品大量获得商业价值回报。
社会层面上,Rust 技术人才还相对稀缺, 中文资料相对匮乏, 国内的声势还不够壮大。
因此,RUST 技术体系, 更有可能成为部分企业在关键领域的秘密武器, 是例不虚发的小李飞刀。
结语
武器是影响战争的重要因素, 但不是决定因素, 决定成败的是人不是物。技术, 必须始终要服务于业务。企业的战略, 无法绕开第一生产力的发挥, 更无法背离跻身于一个行业的初心使命。让我们对技术敬畏, 更对业务敬畏, 对新事物新概念保持开放心态, 同时也保持独立的思考判断, 不被表象迷惑, 追求真实的价值创造, 在投身发展的行业行业的发展中, 脚踏实地, 跬步千里。最后也非常欢迎更多的有志之士加入赞同科技, 以业务为基, 以技术为器, 共同探讨与发现行业更深层的价值之美。
