從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的小對象創建和回收都很快,使用小對象池反而會降低效率),對一些關鍵的底層數學類甚至採用非線程安全的設計來避免額外的對象開銷.跨平台是任何一個有膽識的程序員的夢想,然而這個夢卻比我們想象中的要沉重得多,好的設計都是在一次次的抽象與分割中形成,無論是在過去,現在,還是將來,這個問題都是值得設計者們去思考的.