語言的自由度
自由度這個概念在不同領域有不同的定義,我們借鑑數學中構成一個空間的維數來表達其自由度的做法,在此指的是:解決同一個問題彼此不相關的設計方法學數量。
例如,解決一個比如商品打折的問題,如何設計順序、提取函式,具體的思路可能有很多,但是這可能都是從程式導向(OP)的角度,同樣解決這個問題,如果另一門語言還支援物件導向(OO)的設計方法,那麼我們認為後者的自由度要多一些,因為OO提供了幾乎完全從另一個角度解決問題的能力。
既然自由度可以借鑑“維數”的定義,我們來嘗試分析一下計算機語言的“維數”,在此之前,我們有必要簡單分析一下語言是怎樣一步一步變得複雜的。
本文關注的重點是命令式風格的計算機語言。
第一步,出現了結構體(資料結構)、常量、變數、算符、順序、分支、迴圈等這些體現“命令”的基本方面;
第二步,例程的出現,包括函式、過程等;
第三步,巨集的出現,包括巨集、模板、泛型;
第四步,對客觀世界在結構化上抽象能力出現,包括OO等;
第五步,超程式設計能力的出現,如註釋、反射等等; …
從計算機語言歷史來看,以上步驟不一定按照時間順序展開,我們更關注的是語言能力提升帶來的意義。其中,第二步的完成,標誌著結構化程式設計方法的出現,對大型軟體工程提供了較好的支援,第三步是對第二步的進一步抽象,第四步所代表的意義更加重大,其中非常重要的一點,意味著終於可以支援實現“層次化”,可以實現將“核心”與“外圍”做分離,將相對穩定與潛在變化的部分分開,也就是說,編碼所表達的內容不再只能扁平化,終於進化出了“階級”。
從本質上來講,以上演進反映了語言自身抽象能力的不斷提升。
這裡非常有意思的一個現象是,抽象化的不斷提升,會使得語言的維度提升至某個分數維——抽象的本質是在空間上提供了某種自相似的遞迴對映,從而體現出“分形”的結構形式,分形結構表現出在原有空間中增加了分數維,但是得到一個新的整數維是困難的,即比如1維可以提升至1.5維,但是無法到達2維。
所以,目前絕大部分計算機高階語言的維度是1.X。
但是第五步,意味著語言開始真正走向一個更高的維度。
事實上,實現超程式設計有多種方式,從語言本身來講,可以分為兩類:增強型API與新的語法實現,前者的代表是反射,後者的代表為Annotation。
我們來看一個例子:
public class TestCase{
@Before
public void setUp() throws Exception{}
@After
public void tearDown() throws Exception{}
@Test
public void add() {}
}
複製程式碼
上面是Java語言中使用Annotation型別定義了一個單元測試的三個階段,在這裡: @Before、@After、@Test用“變數”定義了“變數”,同時定義了執行的順序,這裡是“對編碼再進行編碼”的過程,是超程式設計的一種典型的實現。
我們當然也可以通過增強型API(反射或者用設計約束(比如摸版方法))來解決,但是無論哪一種,都不如Annotation的方式要簡單直接明瞭。
根本的原因,在於增強型API的實現方式與原有程式碼這兩個表達邏輯的維度存在過多的“相關性”,即1.X維的,但Annotation的方式在相關性上大大減少,兩個維度更加解耦,所以後者的自由度更高。
如下JS基於Mocha的單元測試程式碼:
describe('測試過程1', function() {
it('1+1', function() {
expect(fn_add(1, 1)).to.be.equal(2);
});
});
複製程式碼
我們期望如下程式設計風格:
'@test(step=測試過程1,name=1+1,expect=2';
var step0 = function(){
return fn_add(1, 1);
}
複製程式碼
###JS實現基於註釋的超程式設計
我們嘗試將Annotation的機制引入JS,如下:
'@Log(level=info,dateFormat=YYYY-MM-DD HH:mm)’;
var logInfo = function(_msg){
console.log(_msg);
}
複製程式碼
複雜的場景,考慮多個註釋的相關性:
'@Start';
var serverStart = function(){}
'@Rule(fileType=.(html|htm))';
var proHtml = function(_req,_res){}
'@Rule(fileType=.(jpg|gif|webp))';
var proPic = function(_req,_res){}
'@Finish';
var serverFinish = function(){}
複製程式碼
At-js框架
基於以上想法,我們實現了At-js框架並開源,At-js的實現思路非常簡單,在Node.js端,通過覆蓋執行時JS檔案載入機制實現對Annotation型別的識別判斷並對原生檔案進行Enhance處理,為效能考慮,At-js採用了正則掃描而非AST的方式。
At-js使用方法包括:定義註釋與使用註釋。
定義註釋:
require('at-js').define('helloworld',{//annotation's name
scope: 'var', build: function () {//the scope of it's effected
return "return function(_msg) {console.log('[helloworld]'+_msg);};"//the real script
}
})
複製程式碼
使用註釋:
'@helloworld';
var sayHello = function(){}
sayHello('here')
複製程式碼
執行效果:
[Helloworld]here
以下程式碼描述了一個單元測試過程(https://github.com/CheMingjun/at-test):
'@test.start';
var start = function () {
ds = {};
}
'@test.step(timeout=2000)';
var test0 = function* () {
ds.test0 = 'finish';
var rtn = yield (function(){
return function(_next){
setTimeout(function(){
_next(null,3);
},2000)
}
})();
assert.equal(rtn,3);
}
'@test.step';
var test1 = function () {
ds.test1 = 'finish';
return ds;
}
'@test.finish';
var fh = function () {
ds = null;
}
複製程式碼
At-js支援Var級及File不同級別的註釋定義,上例屬於File級別複雜的註釋定義,兩者的API如下:
Var型註釋定義:
{
scope:'var',
build:function(_ctx, _argAry){
//_ctx
{
filePath,//應該該註釋的檔案位置
name,//註釋名稱
desc,//註釋中的變數表(key-value)
refName,//被註釋的變數名稱
refType//被註釋的變數型別(undefined|function|generator|object)
}
//_aryAry 被註釋變數簽名中的引數列表
return //返回該變數被替換之後的程式碼
}
}
複製程式碼
File型註釋定義:
{
return {
which: {//針對改組annotation中的每一項做處理
'test.start': function (_ctx, _argAry) {
//_ctx 與 _argAry 同上定義
//處理邏輯
}
}, script: function () {
return //返回該檔案追加的程式碼
}
}
}
複製程式碼
在實際生產過程中,如下一套註釋實現了ORM:
'@dao.column';
var id;
'@dao.column(name=name)';
var name;
'@dao.column(name=status)';
var status;
'@dao.column(name=creator_id)';
var creatorId;
'@dao.column(name=creator_name)';
var creatorName;
'@dao.column(name=gmt_create)';
var createTime = function (_time) {
var mm = require('moment');
return mm(_time).format("YYYY-MM-DD HH:mm:ss");
}
'@dao.column(name=gmt_update)';
var updateTime = function (_time) {
var mm = require('moment');
return mm(_time).format("YYYY-MM-DD HH:mm:ss");
}
'@dao.column(name=type)';
var type;
複製程式碼
總結
本文給出了語言自由度的簡單定義,並在此基礎上論述了在語言發展過程中呈現的不同複雜性,並探討了超程式設計是如何從根本上增加語言的自由度的。在第二部分,我們嘗試在JS語言基礎上增加原生的超程式設計能力並介紹了該思路的實現:At-js框架。