[翻譯] DSL和模型驅動開發的最佳實踐(4/4)
剩下的部分我不再直譯了,累了,呵呵,而且里面摻雜著我的理解.
3 處理模型
解析器 vs 代碼生成 (未評級)
通常大家都會傾向于生成代碼,其實解析也是一種可用的選擇,在我們完成我們的元數據模型后,由解析器讀取這個元模型數據,可以查詢或者遍歷模型,可以直接計算,進行相互處理,UI呈現.
對解析與代碼生成之間進行權衡, 代碼生成的優點:
1. 更加簡單, 生成的代碼我們可以檢查,另外生成代碼采用的模板都是我們通過之前手寫的代碼抽象提煉出來的.
2. 比解析的方式更容易調試
3. 通過擴展代碼的方式,更加強大,定制性強,可以根據需求靈活開發出更加強大的功能,比解析器更加有效
4. 直接可以是任何目標語言和平臺,因為生成的目標語言代碼是由模板控制的,我們只需要定制不同語言的模板即可。如果目標語言支持其它平臺,那這種方式自然也支持,而解析器的方式要求直接運行在目標平臺上
5. 使用生成代碼的方式,使MD*模式更加徹底. 直接模型驅動業務,驅動界面
解析的優點:
1.修改模型不用經過 重新生成/重新編譯/重新測試/重新部署 這些步驟,明顯縮短了周期.
2.整個系統都基于模型, 甚至可以做到直接在運行的系統里修改我們的模模型,使修改立即生效. (比如一些在線saas系統).
3.對于一些案例來說,解析器和模型會比生成代碼還會省事
解析和代碼生成的方式也可以結合使用,直接生成些XML格式的模型數據,然后由3GL語言,比如java解析器來解析,驅動整個系統的運行。另外一種方法理論上也是可行的,在解析器內即時直接生成代碼,提高性能.
單獨的模型檢查機制
元數據模型所表達并不一定是正確的,里面有大量的用戶輸入。模型約束是必須的,并且要貫穿整個模型處理,約束不僅判斷是否有錯誤,還要有具體的錯誤信息能夠提示給用戶. 模型和約束越多,用戶出現的錯誤也就會越多,不過這才能夠保證目標系統的正確性。一定盡量多地做約束檢查!
約束檢查在模型處理過程中應該盡量的早,不能夠把約束放到代碼生成階段,會使模板過于復雜而且復用性差,你如果有不同的語言模板,還需要不同的檢查實現。如果模型有問題,就不應該允許代碼生成,否則只會帶來更多的問題。
需要在模型處理過程中的不同階段,模型的不同部分,檢查不同的約束. 比如在轉換之后需要檢查,在保存分區需要檢查,在用戶的一些輸入后需要檢查,在用戶對模型進行一些操作后需要檢查
在模型的改進過程中,在每個級別都要進行檢查,防止修改過程中的一些級聯影響,另外一定要確保每個級別每個點都檢查到,只是檢查從上一級的正確輸入是肯定不會失敗的,但是用戶并不是的輸入是不可信的.
對檢查結果信息分不成的級別,比如錯誤和警告,如果發生錯誤,就不能夠進行下一步了,警告則不是。
不要修改生成的代碼
即使模型生成了代碼,在大多數情況下,手寫代碼還是必須的。所以我們要把手寫的代碼和生成的代碼結合起來,更不能夠每次生成把手寫的代碼覆蓋掉.大多數工具提供了所謂的保護區,在保護區里你可以插入你的手寫的代碼,來保護不被覆蓋。
這樣也會有一些問題,生成代碼并不是一次性的工作,當修改和完善,導致你的生成代碼有了很多“沉積物”的時候,會往往需要重新生成代碼來清洗這些“沉積物“,如果沒有所謂的保護區,你就可以直接刪除所有的生成代碼全部重新生成了。
使用擴展的方法,向生成代碼里面添加擴展點,采用委托,接口,#include的方式,或者是使用反射,AOP模式,有些語言提供部分類(比如C#),這樣我們能夠使生成的代碼文件與手寫的擴展代碼文件分割開來. 這樣做也有缺點,如果你修改模型生成不同的代碼,手動修改的代碼并不會自動的相應重構(需要實踐驗證),另外這種主要會使代碼的實現不斷增加,編譯時間也會增加.
隨著工具的不斷改善,可能會有更多的可行的方法,不過到現在為止,添加擴展的方法是問題最少的。
控制手寫代碼
生成器生成了一些模型元素的抽象類,期望開發者實現抽象類并且要符合命名約定,但是如果我們只是生成,怎么才能提示開發者來實現自己的代碼呢?
如果具體的實現類被調用或者抽象方法被調用,在編譯的時候會有編譯錯誤,開發者根據錯誤的不同,實現自己的代碼,但是這并不明顯,因為還需要開發者去根據錯誤類型去排查。
有兩個方法解決這個問題:
1. 對生成的代碼生成約束和檢查,能夠由IDE調用評價代碼,如果有錯誤,把消息報告給開發者,必須按照指定的方式實現了代碼,錯誤才會消失.
2. 有些時候生成的代碼不會執行,由IDE直接調用缺失的部分。比如如果需要用戶手寫一個生成的子類,我們就可以生成以下這句:if (false) { GeneratedBaseClass x = new ManualSubclass() }.
關注生成的代碼
不要以為生成的代碼就可以扔掉不管了,與生成代碼集成時,你將不得不看生成的代碼,在一定程度上了解它,甚至在某些時候調試它。因些,要確保生成的代碼文檔規范,命名規范,代碼縮進。其實這些在你的模板文件里都很容易實現,另外在生成的代碼里寫上需要的信息,以供后期查看。
使生成的代碼遵循和手寫的代碼相同的標準.
在一些非常成熟的環境下,可以生成100%的代碼,而不用再手寫代碼來擴展實現,在這種情況下,這里所說的這些就不適用了。
保證生成的代碼不偏離模型
在許多情況下,需要實現一些約束來驗證一些屬性的正確性,為了確保模型的數據都由代碼保存,使用以下兩種方法:
1.生成的代碼不允許違背模型的承諾.例如:不暴露工廠允許組件查找和使用其它組件(直接創建依賴),而是要使用依賴注入提供合理的對象引用.
2.使用結構分析工具(依賴檢查器)驗證手寫代碼,可以很容易從模型秋這些結構分析工具生成檢查規則.
視點級處理
上面提到的視點不僅和模型設計有關,而且對于處理模型也很重要。 有些時候可能需要單獨的對不同的視點模型檢查約束。有些視點可能更適合解釋處理,而不是代碼生成。 當生成代碼時,也可能會考慮基于視點分階段生成.
另外還需要注意,已經有了一個使用模型分區進行分割的機制,每個分區應該被單獨處理。如果分區和視點一致,會很使得很容易處理。
視點配置(未評級)
對于使用視點和分區的系統來說,當模型處理器運行時,經常會有一些選項需要指定:使用約束的一些子集驗證整個模型;僅僅生成子系統的業務邏輯代碼;或者是生成針對生產環境的整個系統的部署代碼.
使用單獨的模型來處理配置是一個不錯的選擇,這樣為模型處理器綁定了“關注的范圍”,通過處理”編譯器配置”也作為一個模型,能夠帶來很多方便,生成模型數據到屬性文件或者XML文件,使得更容易處理.
關注模板
代碼生成模板是模型處理過程中核心資產之一,它包含著DSL表達的領域概念到實現代碼的映射。隨著時間,模板數量肯定會增加,維護起來會有一些問題,關注以下這些技術:
1.把模板分成一些小的模板,相互調用.
2.把一些通用的復雜表達式提煉成方法,能夠被所有的模板調用。
另外還有一個提示: 模板文件只為自己提供縮進方式即可,對于生成的文件,可以直接全局使用代碼格式化工具來處理。
對于一個成功的框架生成代碼,模板代碼的需求總量減少,它們間的只有少數需要改進維護.
M2M轉換簡化代碼生成
在一些情況下,使用一一個Model-to-model轉換器代替代碼生成,比如對于一個描述分層組件架構的DSL,大多數組件運行時平臺不支持這樣的分級結構,所以需要“扁平化”結構。在這種情況下,不要打算使用代碼生成器,而且考慮先使用m2m轉換,然后再寫一個針對扁平沒有分級模型的簡單的生成器.
在這里討論的是模型轉換中的單向轉換,雙向轉換只在這里沒有提到的極少數情況下使用。
使用M2M轉換進行語義分析(未評級)
model-to-model轉換另外一個重要場景就是可以為了更好的語義分析,把一些模型轉換成更容易理解,更容易驗證,工具更容易支持的另外的模型.比如對于一個并發,分布式系統的行為描述,可以把它轉換成Petri網,再使用合適的工具.
允許適配
為了使我們的模型驅動能夠滿足更多的項目, 要保證我能夠以非侵入的方式直接適應一些調整:
1. 我們可以為每個模型元素都添加哈希映射鍵值對,用于存放一些附加的信息,這些信息可以被模型處理的其它地方使用.
2. 代碼生成模板能夠非侵入性的定制,直接支持生成不同的代碼. 可以采用工廠和多態. 不這這個要權衡,沒有必要所有的都需要做到允許調整,可以分為公有和私有,私有的模板就沒有必要了.
級聯效應
開始MD*方法時,可以先定義PIM模型,然后轉換成少抽象,平臺特定的PSM模型,最后生成代碼.不過根據我的經驗,最好是從底部開始,先定義一個DSL來描述系統的軟件架構,然后定義一個生成器來自動完成和實現技術相關的繁重的工作。 直接在DSL中對目標架構的架構概念抽象。
接著在穩定的基本的抽象上建立特定的業務領域,可以使用M2M轉換把更抽象的概念和架構語言中已經存在的抽象映射起來,把他們“喂”給已經存在的生成器.而對于不能夠映射到底級的架構抽象提供特定生成器生成代碼作為架構生成器視點的“業務邏輯實現”(取代之前必須手寫的代碼).
注意永遠也不要修改中間階段模型,它們是用于傳遞的,通常甚至不被存儲。他們是于你的級聯級的各個階段的”數據擴展形式”.如果你想在你的目標模型中添加信息,請使用注釋模型。
注釋模型
使用model-model 轉換可能會遇到和代碼生成同樣的問題,有必要在更進一步地處理前手動標識轉換步驟的結果,其中一種方法是在創建后經過一個轉換來修改模型,不過這種方法會遇到和代碼生成時的保護區一樣的問題。
更好的解決方案是創建一個單獨的模型--注釋模型, 直接引用中間模型中的元素,指定一些附加的模型數據,下游的處理器將處理由上游的model-to-model轉換器和注釋模型組合后的創建的模型.
比如,你從面向對象數據模型創建關系數據模型,你可能自動地 把OO模型的類的名稱當成數據庫中表的名字,如果你需要改變這些名字,使用一個注釋模型指定一個備用名稱,下游的處理器就知道注釋模型中的名字覆蓋了原來模型中的名字.
分類行為
為了更有效的實現行為, 把行為歸類成幾種,比如基于狀態,或者基于業務規則. 為這些行為提供特定的DSL,在很多情況下,你只需要非常有限的配置參數就可以生成行為了.
不要忘記測試
在MD* 中,測試是很重要的環節.
1.約束檢查也是測試的一種形式,就比如程序語言中的編譯器
2.測試代碼生成器的時候,不要測試生成代碼的語法,直接編譯代碼,使用單元測試,測試代碼的語義.
3.針對轉換的結果中的具體數據寫一些約束檢查來測試模型轉換
4.測試代碼生成器,建立一個測試模型來測試語言的所有的功能,這是生成器開發者來做的,而不是生成器使用者.
如果生成器經過完整的測試,相對成熟后,沒有必要再用生成器在項目里測試生成的代碼,不過仍然可以用單元測試測試整個系統.
真正的生成系統和測試不要使用同一個模型,因為可能會導致在錯誤的系統上進行的錯誤測試反而測試通過.
4.處理和組織
迭代
一些人借MD*來應用瀑布型,花幾個月時間開發語言,工具和框架.這并不是一個成功的方法.而是應該采用迭代的方式. 可以先開發一小部來深刻理解,先建立語言的一小部分,構建一小部分生成器,開發一個小例子模型來驗證所做的.然后再逐步來按照需求實現.
概念與語言共同發展
構建語言,使你更加清晰概念和理解領域.相輔相成.
文檔是必須的
構建了DSL和模型處理器還不足夠讓MD*成功,還必須和用戶溝通,讓DSL和處理器能夠使用起來,這時候就必須有文檔來說明語言,編輯器,生成器,怎么來手寫代碼,怎么來集成等等.同樣可以錄制視頻,截圖,討論都是不錯的選擇.
定期評審
DSL限制在某些方面限制用戶的一些自由,只能在DSL的限制內表達事情。而且一些實現的決定并不是由DSL的使用者決定的,而是直接由模型處理器直接處理了。
即使再好的DSL,用戶仍然會犯錯誤,可能會濫用DSL。一定要定期Review,相當重要,也很關鍵。在Review中發現的經常性的錯誤,有可能就是使用者經常犯的錯,我們就可以添加一個約束檢查來自動檢查這個問題。或許這并不是錯誤,而用 期 的是對的,這時候就可以適當地調整一個語言了。
知人善任
MD*里能夠讓大家都做最擅長的工作:
1.對于目標技術專家,可以深入研究目標架構中的一些技術細節,研究的更深更透,這樣才能夠最佳處理方式,才能夠把這些知識抽象成生成器模板,才能夠復制起來廣泛使用。他們只需要和生成器和語言設計者溝通就行了。
2.語言設計者和領域專家一起定義抽象,符號,約束。和平臺專家,架構師一起定義代碼生成器或者是解釋器。
你必須確保你團隊里的人都了解語言設計,知道領域對象和理解目標平臺,都要有MD*這此思想,否則MD*方法就不會成功.
領域用戶需要寫程序嗎?
域用戶并不是程序員,他們只是描述領域知識,如果讓領域用戶理解你的DSL,可能并不是他們的過錯,而你的語言不適合這個領域,這時候需要一直關注了。
領域用戶 vs 領域專家 (未評級)
建立DSL時這兩個角色可以發揮不同的作用,領域專家參與領域分析和DSL本身的定義,域用戶 可以使用DSL來表達具體的領域知識。
元數據級產品
一般是少部分人開發的這些語言,約束,生成器,被多部分人來使用。這需要建立好的機制,需要時不時地就讓生成器的部分開發者直接參與實際的開發項目,讓他們了解這些是否真的適應實際的應用。
兼容組織
MD*可能需要跨項目工作,應用在多個項目和環境下時,需要很好的處理好跨領域。
忘掉發布的真實案例
許多新的軟件開發方法通過發布案例來宣傳,雖然通過例子來展示一些用處,但是并不根據案例就做出真正的決定。來決定DLS和MD*是否適合的唯一方法就是做一個原型。
5 開放的問題
在我們結束這個最佳實踐的時候,還有一些懸而未決的問題,為此,社區和工具提供商必須找到讓人滿意的解決方法:
(1)混合符號,沒有可用的工具直接支持把文本符號直接嵌入在圖形模型中. 或者使用類似公式化的編輯器,半圖形語法構建DSL. 軟件有這方面的趨勢,但是工具還跟不上.
(2)語言模塊化和組合在某些環境下也是一個挑戰, 尤其是文本語言,基于解析技術,組合解析需要不太好處理, Jetbrains的MPS把文本模型作 為元數據結構存儲是有優勢的, MetaEdit+在語言模塊化方面處理的非常好.
(3)元數據級的重構在大多數系統中是不支持的. 這個其實沒有太大的挑戰,我認為只是還沒有人做.
(4)模型/代碼重構就沒有這么簡單. 對于依賴于從模型自動生成代碼,而手寫的自己的代碼,如果修改模型,重新生成代碼, 怎么處理手寫的代碼? 經常是不做任何處理, 而理想情況下是希望手寫的代碼能夠自動改變,能夠直接適應模型的變化
(5)模型自動遷移也是一個沒有解決的問題. 你的語言變化后你將如何處理你的模型? 全拋棄他們,因為他們已經不能夠再打開了? 在新的編輯器打開他們,但是原來模型中的一些標記與新語言不兼容?自動嘗試遷移? 這些都是可選擇的方案,但是真不知道最好的做法是什么樣子.
(6)模型調試即在模型級別上調試正在運行的系系統. 雖然你可以手工構造特定的解決方案,但是還沒有工作直接支持這樣的調試器的實現.
(7)解釋和代碼生成通常被認為兩種選擇,而不是一個統一體. 你可能需要的只是一個解析器,那么你可以在解析器比較慢的地方部分選擇使用代碼生成.只還在研究中,沒有可用的處理.
(8)處理龐大的模型,或者是模型數量過多時也是一個問題.如何擴展基礎的架構?在一些東西改變后如何做影響分析? 如何導航瀏覽大而多的模型?如何有效率地搜索和查詢他們?如何逐步地呈現他們?
(9)生成的模塊如何組合? 如何定義這些模塊的接口?如何這些模塊已經間接依賴于你生成的代碼了,該如何處理?
太多挑戰了,讓我們開始吧!
[翻譯] DSL和模型驅動開發的最佳實踐(1/4)
[翻譯] DSL和模型驅動開發的最佳實踐(2/4)
[翻譯] DSL和模型驅動開發的最佳實踐(3/4)
原文: http://www.jot.fm/issues/issue_2009_09/column6/index.html
由于篇幅太長,所以分幾部分翻譯。翻譯水平有限,如果英語不錯,最好直接閱讀原文.
向模型驅動開發的同學們強烈推薦此文!
作者:孤獨俠客(似水流年)
出處:http://lonely7345.cnblogs.com/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

浙公網安備 33010602011771號