為何需要DSL
DSL只是一種工具,關(guān)注點有限,無法像面向?qū)ο缶幊袒蛎艚莘椒ㄕ撃菢樱l(fā)軟件開發(fā)思考方式的深刻變革。相反,它是在特定條件下有專門用途的一種工具。一個普通的項目可能在多個地方采用了多種DSL——事實上很多項目這么做了。
DSL有其自身的價值。當(dāng)考慮采用DSL時,要仔細(xì)衡量它的哪些價值適合于我們的情況。
1)提高開發(fā)效率
DSL的核心價值在于,它提供了一種手段,可以更加清晰地就系統(tǒng)某部分的意圖進(jìn)行溝通。拿格蘭特小姐控制器的定義來說,相比于采用命令–查詢API,DSL形式對我們而言更容易理解。
這種清晰并非只是審美追求。一段代碼越容易看懂,就越容易發(fā)現(xiàn)錯誤,也就越容易對系統(tǒng)進(jìn)行修改。因此,我們鼓勵變量名要有意義,文檔要寫清楚,代碼結(jié)構(gòu)要寫清晰。基于同樣的理由,我們應(yīng)該也鼓勵采用DSL。
人們經(jīng)常低估缺陷對生產(chǎn)率的影響。缺陷不僅損害軟件的外部質(zhì)量,還浪費開發(fā)人員的時間去調(diào)查以及修復(fù),降低開發(fā)效率,并使系統(tǒng)的行為異常,播下混亂的種子。DSL的受限表達(dá)性,使其難于犯錯,縱然犯錯,也易于發(fā)現(xiàn)。
模型本身可以極大地提升生產(chǎn)率。通過把公共代碼放在一起,它可以避免重復(fù)。首先,它提供了一種“用于思考問題”的抽象,這樣,更容易用一種可理解的方式指定系統(tǒng)行為。DSL提供了一種“對閱讀和操作抽象”更具表達(dá)性的形式,從而增強了這種抽象。DSL還可以幫助人們更好地學(xué)習(xí)使用API,因為它將人們的關(guān)注點轉(zhuǎn)移到怎樣將API方法整合在一起。
我還遇到過一個有趣的例子,使用DSL封裝一個棘手的第三方程序庫。當(dāng)命令–查詢接口設(shè)計得很糟糕時,DSL 慣常的連貫性就得以凸現(xiàn)。此外,DSL只須支持客戶真正用到的部分,這大大降低了客戶開發(fā)人員學(xué)習(xí)的成本。
2)與領(lǐng)域?qū)<业臏贤?/strong>
我相信,軟件項目中最困難的部分,也是項目失敗最常見的原因,就是開發(fā)團隊與客戶以及軟件用戶之間的溝通。DSL提供了一種清晰而準(zhǔn)確的語言,可以有效地改善這種溝通。
相比于關(guān)于生產(chǎn)率的簡單爭論,改善溝通所帶來的好處顯得更加微妙。首先,很多DSL并不適用于溝通領(lǐng)域問題,比如,用于正則表達(dá)式或構(gòu)建依賴關(guān)系的DSL,在這些情況下就不合適。只有一部分獨立DSL確實應(yīng)用這種溝通手段。
當(dāng)在這樣的場景下討論DSL時,經(jīng)常會有人說:“好吧,現(xiàn)在我們不需要程序員了,領(lǐng)域?qū)<铱梢宰约褐付I(yè)務(wù)規(guī)則。”我把這種論調(diào)稱為“COBOL謬論”——因為COBOL曾被人寄予這樣的厚望。這種爭論很常見,不過,我覺得這種爭論不值得在此重復(fù)。
雖然存在“COBOL謬論”,我仍然覺得DSL可以改善溝通。不是讓領(lǐng)域?qū)<易约喝慏SL,但他們可以讀懂,從而理解系統(tǒng)做了什么。能夠閱讀DSL代碼,領(lǐng)域?qū)<揖涂梢灾赋鰡栴}所在。他們還可以同編寫業(yè)務(wù)規(guī)則的程序員更好地交流,也許,他們還可以編寫一些草稿,程序員們可以將其細(xì)化成適當(dāng)?shù)腄SL規(guī)則。
但我不是說領(lǐng)域?qū)<矣肋h(yuǎn)不能編寫DSL。我遇見過很多團隊,他們成功地讓領(lǐng)域?qū)<矣肈SL編寫了大量系統(tǒng)功能。但我仍然認(rèn)為,使用DSL的最大價值在于,領(lǐng)域?qū)<夷軌蜃x懂。所以編寫DSL的第一步,應(yīng)該專注于易讀性,這樣即便后續(xù)的目標(biāo)達(dá)不到,我們也不會失去什么。
使用DSL是為了讓領(lǐng)域?qū)<夷軌蚩炊@就引出了一個值得爭議的問題。如果希望領(lǐng)域?qū)<依斫庖粋€“語義模型”的內(nèi)容,可以將模型可視化。這時就要考慮一下,相比于支持一種DSL,是不是只使用可視化會是一種更有效的辦法。可視化對于DSL而言,是一種有益的補充。
讓領(lǐng)域?qū)<覅⑴c構(gòu)建DSL,與讓他們參與構(gòu)建模型是同樣的道理。我發(fā)現(xiàn),與領(lǐng)域?qū)<乙黄饦?gòu)建模型能夠帶來很大的好處,在構(gòu)建一種Ubiquitous Language [Evans DDD] 的過程中,程序員與領(lǐng)域?qū)<抑g可以深入溝通。DSL提供了另一種增進(jìn)溝通的手段。隨著項目的不同,我們可能發(fā)現(xiàn),領(lǐng)域?qū)<铱赡軙⑴c模型和DSL,也可能只參與 DSL。
實際上,有些人發(fā)現(xiàn),即便不實現(xiàn)DSL,有一種描述領(lǐng)域知識的DSL,也能帶來很大的好處。即使只把它當(dāng)做溝通平臺也可以獲益。
總的來說,讓領(lǐng)域?qū)<覅⑴c構(gòu)建DSL比較難,但回報很高。即使最終不能讓領(lǐng)域?qū)<覅⑴c,但是開發(fā)人員在生產(chǎn)率方面的提升,也足以讓我們大受裨益,因此,DSL值得投入。
3)執(zhí)行環(huán)境的改變
當(dāng)談及將狀態(tài)機表述為XML的理由時,一個重要的原因是,狀態(tài)機定義可以在運行時解析,而非編譯時。在這種情況下,我們希望將代碼運行于不同的環(huán)境,這類理由也是使用DSL一個常見的驅(qū)動力。對于XML配置文件而言,將邏輯從編譯時移到運行時就是一個這樣的理由。
還有一些需要遷移執(zhí)行環(huán)境的情況。我曾見過一個項目,它要從數(shù)據(jù)庫里找出所有滿足某種條件的合同,給它們打上標(biāo)簽。他們編寫了一種DSL,以指定這些條件,并用它以Ruby語言組裝“語義模型”。如果用Ruby將所有合同讀入內(nèi)存,再運行查詢邏輯,那會非常慢,但是團隊可以用語義模型的表示生成SQL,在數(shù)據(jù)庫里做處理。直接用SQL編寫規(guī)則,對開發(fā)人員都很困難,遑論業(yè)務(wù)人員。然而,業(yè)務(wù)人員可以讀懂(在這種情況下,甚至編寫)DSL里有關(guān)的表達(dá)式。
這樣用DSL常常可以彌補宿主語言的局限性,將事物以適宜的DSL形式表現(xiàn)出來,然后,生成可用于實際執(zhí)行環(huán)境的代碼。
模型的存在有助于這種遷移。一旦有了一個模型,或者直接執(zhí)行它,或者根據(jù)它產(chǎn)生代碼都很容易。模型可以由表單風(fēng)格的界面創(chuàng)建,也可以由DSL創(chuàng)建。DSL相對于表單有一些優(yōu)勢。在表述復(fù)雜邏輯方面,DSL比表單做得更好。而且,可以用相同的代碼管理工具,比如版本控制系統(tǒng),管理這些規(guī)則。當(dāng)規(guī)則經(jīng)由表單輸入,存入數(shù)據(jù)庫中,版本控制就無能為力了。
下面會談及DSL的一個偽優(yōu)點。我聽說,有人宣稱DSL的一個好處是,它能夠在不同的語言環(huán)境下執(zhí)行相同的行為。一個人編寫了業(yè)務(wù)規(guī)則,然后生成C#或Java代碼,或者,描述校驗邏輯之后,在服務(wù)器端以C#形式運行,在客戶端則是JavaScript。這是一個偽優(yōu)勢,因為僅僅使用模型就可以做到這一點,根本無需DSL。當(dāng)然,DSL有助于理解這些規(guī)則,但那是另外一個問題。
4)其他計算模型
幾乎所有主流的編程語言都采用命令式的計算模型。這意味著,我們要告訴計算機做什么事情,按照怎樣的順序來做。通過條件和循環(huán)處理控制流,還要使用變量——確實,還有很多我們以為理所當(dāng)然的東西。命令式計算模型之所以流行,是因為它們相對容易理解,也容易應(yīng)用到許多問題上。然而,它并不總是最佳選擇。
狀態(tài)機是這方面的一個良好例子。可以采用命令式代碼和條件處理這種行為,也確實可以很好地構(gòu)建出這種行為。但如果直接把它當(dāng)做“狀態(tài)機”來思考,效果會更好。另外一個常見的例子是,定義軟件構(gòu)建方式。我們固然可以用命令式邏輯實現(xiàn)它,但后來,人們發(fā)現(xiàn)用“依賴網(wǎng)絡(luò)”(比如,運行測試必須依賴于最新的編譯結(jié)果)解決會更容易。結(jié)果,人們設(shè)計出了專用于描述構(gòu)建的語言(比如Make和Ant),其中將任務(wù)間的依賴關(guān)系作為主要的結(jié)構(gòu)化機制。
你可能經(jīng)常聽到,人們把非命令式方式稱為聲明式編程。之所以叫做聲明式,是因為這種風(fēng)格讓人定義做什么,而不是用一堆命令語句來描述怎么做。
采用其他計算模型,并不一定非要有DSL。其他編程模型的核心行為也源自“語義模型”,正如前面所講的狀態(tài)機。然而,DSL還是能夠帶來很大的轉(zhuǎn)變,因為操作聲明式程序,組裝語義模型會容易一些。
評論
查看更多