从1到100 - 模块化的跨平台程序

前几天完成了被当做作业的小程序,名字相当掩人耳目:Finite Digit Summator,一定程度上是向Digital Differential Analyzer致敬,项目被我扔到了Github上,本身并没有太大应用价值,除了那两幅从东方AA摘下来的字符画,以及一黑黑了两个游戏的梗.

从设计上,它的项目结构很大程度上参考了我以前的项目,以一个核心模块囊括主要功能,然后以多个针对不同平台的子模块负责将功能封装并展现给用户,事实上,除了网页版有一个功能是通过JS重新实现了一遍以外,几乎所有的使用了两遍以上的功能都被集成在了核心模块中,因此可以说下一阶段的目标"实现模块化"我已经完成一半了(笑),剩下的看上去无非是将之前没来得及上线的安卓端做完,修修Bug,刷刷单元测试之类的.

听上去通过模块化来实现跨平台就像当年老一辈眼中实现共产主义一样简单,然而一个实际的跨平台项目想要通过模块化来实现在设计上却是困难重重,最主要的问题在于硬件的局限性和需求的不同.还记得刚才说的"以前的项目"不? 2个月前的寒假时我开了一个新坑,用Java复刻(或者叫抄袭?取决于你怎么看待"yet another alternative implementation"这种东西...)一个Era的开源跨平台版,什么是Era?我放个截图你大概就能知道是什么东西了...

era

所以我要做的事就是山寨- 呸,复刻这个糟糕物,我将它称之为EraJ,作为一个后来者,有几个关键功能(也就是所谓的杀手级特性)是绝对不能去掉的,比如更好的调试工具、多语言、更快的启动速度等.按照之前的理解,只要将这些功能集成在核心模块中即可,然而实际上每个平台并非需要全部的功能,比如在手机上并不需要调试工具,脚本的调试应该是在电脑上完成;桌面端追求最快的启动速度,因此它只有一个简单的脚本优化器(作为一个文字游戏,Era的运行瓶颈显然不在CPU上)但却有一个复杂的脚本缓存机制(考虑到脚本开发者会频繁地更改代码),相比之下网页端服务器(唔...在线H游?也许我该向DMM报个到?)并不追求启动速度,事实上它大可以AOT的方式用复杂的优化器将脚本彻底优化一遍,然而它的脚本缓存机制却可以异常简单,因为服务器上的脚本不太可能会频繁更新.

eraj

这些差异还比较好解决,可以将关键的功能抽出来单独作为一个中间层模块,比如JIT模块、缓存模块,但有些需求却十分考验底层的设计,以实例数 - 每个程序中运行的游戏数量为例,在桌面端和移动端中,显然这个数字为1,而对于网页端而言,设计时一个乐观的期望是在一个中等配置(2G物理内存)的VPS上可支撑100个实例,这也是标题上的从1到100的由来,这样当桌面和移动端中有动辄上百兆的内存可供挥霍时,网页端一个实例平均只能有16M的内存预算,一下子就捆住了开发者的手脚,迫使我不得不选择一些对程序友好而不是对开发者友好的设计.这个问题的一个解决方案再进行一次模块划分,分成一个面向桌面和移动端的简单版和面向网页端的高性能版,然而这牵扯到的代码太多了,想实现这样的两个模块几乎就相当于实现了两套核心.

事实上这个问题对于任何自诩为无缝跨平台的程序而言都会遇到,以一个跨平台的游戏引擎LibGdx为例,为了保住安卓端的运行效率(安卓的小对象GC性能不佳),大量使用了小对象池(桌面Java的小对象创建和回收都很快,使用小对象池反而会降低效率),对一些关键的底层数学类甚至采用非线程安全的设计来避免额外的对象开销.跨平台是任何一个有胆识的程序员的梦想,然而这个梦却比我们想象中的要沉重得多,好的设计都是在一次次的抽象与分割中形成,无论是在过去,现在,还是将来,这个问题都是值得设计者们去思考的.