引擎接口设计漫谈(一)

370 阅读6分钟
原文链接: zhuanlan.zhihu.com

引擎

引擎这个词是英文单词 engine 音译过来的,最早就是指汽车的发动机,后来引入到软件开发里尤其是游戏行业形成了图形算法和渲染的解决方案,现在从游戏到影视工业诞生了各种物理引擎,光照模型引擎,粒子特效引擎等等细分引擎,正是因为引擎足够职责单一和高内聚,使得它可以成为一个黑箱由专业人员去开发和维护,以API或接口的方式对外提供服务,引擎开发和引擎的使用成为了现今软件开发的现象概念。

游戏中最常见的两个引擎:虚幻和Unity

微引擎架构

微引擎架构在原来对接单一引擎接口之上调整为对接若干个职责分明的小型引擎,就好像原来的CPU都是单核心的,后来改用多核心,不是为了无限提高性能而是为了降低功耗。类似地,单一引擎内部资源消耗的问题尤其突出,使用一个功能要引入若干个毫不相干的功能,只因为他们都是引擎整体打包的,所以我们能看到微引擎架构多核心的方式实际上跟 npm 以及 webpack 插槽式地配置有异曲同工之秒。我们用一张图来展示这样的概念,后面再做详细地分析。

在业务层根据类型拆分出不同的领域模型,开发者只关注特定模型的实现,形成了现在灵活的结构

接口设计

我在游戏引擎公司的时代获得一项重要的经验————引擎的设计越来越趋向于提供能力而非功能,当时并不是特别理解。

把错综复杂的连线、焊点从一颗庞大的电路板上拆分出来,这本身一点也不酷,但是最酷炫的是那些整齐好看的金手指真的就让你如此简单地组合这些盒子,不管你内部使用的是多么牛叉的算法还是仅仅是做了有限穷举,我都能在我所见的范围内优雅地使用它,这就是接口设计的魅力所在。而引擎实现看似高大上,也更加地神秘,我却发自肺腑地感慨:接口设计才是引擎开发人员最为津津乐道的方面,至少我是这样。

错综复杂的接口就像迷宫,消耗了开发人员的时间,消磨着使用者的耐心

DSL一种接口形式

归根结底,引擎的设计还是要落实到运行效率以及开发成本上来。而好的接口设计降低了太多使用者猜测开发者意图、别扭地使用蹩脚API,以及甚至更为恼火地不得不用hack的方式才能让在你机器上没有问题而在我机器上就不行的问题,而这些跟开发无关的内容全部都是开发成本。

在最近完成的项目中使用了微引擎架构来封装一些常用的功能,包含一系列诸如当某个控件被选择了某个值的时候显示或隐藏一些选项,我们发现这样一个描述实际上包含了一个通用的需求,如果要提取变量来适用于大多数情况,那就是某个控件、某个值以及显示或隐藏哪些属性,这些变量应该被设计为让需求的提出者去操作、实验和更改,因为他们比程序员更懂业务。

但是如果设计一个引擎来包办所有的事情,是不是就是最好的情况了呢?

实际上我们只是把过去的经验总结起来并重新组织代码,让它能够更好的适应不太会出现变化的重现情景而已。

上面的这个需求我只需要设计一个函数名为 changeComponentsVisualStateWhenValueHasBeenSelected 然后辅以一个参数列表涵盖我们如上总结的变量(targetComponet, valueSeleted, changeComponents, visualState) 然后我们发现这样一个引擎需要成千上万个这样类似的函数,来完成高度相似但是总有那么10%的差异,无论是参数列表还是函数名称甚至是实现体。

可以说这样的接口设计就是传统的面向功能的思路,对于同一种类型的功能能够高度复用,但是对于稍微变化一点的功能就要设计新的接口来封装。

这样的引擎接口设计实际上只能总结过去的经验,却不能创造新的可能,而现实生活中我们正是用锤子扳手这些普通的工具创造了飞机大炮。

一个经常需要改动来适应新需求的引擎并不会带来效率的提升,因为:

所有看起来引擎满足了你变化的需求所节省的开发资源都被投入到了让引擎来适应你的新需求场景之上了,而这样的场景再次出现的概率并不大,对于引擎的修改却比直接修改逻辑代码成本更加高昂,因为引擎诞生之初就默认是要给所有人用的。

那新的引擎接口设计边界在哪里呢?我们需要它被设计得足够简单和原子性。

类和方法足够简单么,函数足够简单么?不,他们都太复杂了,最为灵活的接口实际上是语言,在特定领域下的语言DSL,而我们正是在一个操着不同语言的世界里做着国际化分工。我用 nojson 理念来承载这样的接口设计,但是这次我要让它足够简单。

最简单的DSL--字段组合

笔者最早设计了独立语法的DSL(领域特定语言)妄图代替所有编程语言和脚本语言实现大一统,但是最终也要解析成语义树来进行下一阶段的执行,这一过程没有捷径,而实际上回归到人工操作往往更有效率。 举一个更加直观的例子,你花了8-9年学习外语看起来也达不到本地人的优雅,而刚刚学习不久的外国仁用仅有的词汇量拼凑表达也能让你理解个八九不离十,既然载体都是json,而我们的目的是设计接口,何不让json字段名承接接口的角色呢?目前大部分前端框架已经使用了类似的方式来定义接口,你说他有固定的形式么?没有,因为json结构体本来就是灵活多变的,你说一定要执行什么函数或方法么,好像作为配置本身也没有这样的入口,那么我们看下面的这个例子,不需要执行类似于render或者onLayout这样的主动或者被动函数,就是按照文档上标注的字段输入一堆参数就好了呀

json是一种很好地承载DSL的结构


待续...