第221期 / March 4, 2016

研發新視界

分享到臉書!分享到維特!分享到噗浪!分享到Google+!分享到微博!轉寄友人友善列印

T4文字範本說明與應用

作者/陳新颺

[發表日期:2016/3/4]

前言

對於程式設計師而言,撰寫程式碼是再平常不過的事情了,平常的例行事項就是打開電腦,啟動軟體開發工具,將規格轉換成程式碼一行行輸入至螢幕上。有可能是調整舊有的程式碼,去除惱人的程式錯誤,滿足各戶的新需求。但也有許多時候面對的是全新的系統,全新的架構,全新的程式清單。當然,對於熟練的程式設計師來說,寫程式並不是什麼難事,特別是現在為了提高程式的開發效率,以及統一整體的程式架構與流程,通常我們都會參考過去的程式碼,或者是另外再撰寫一些標準的範例,所有開發者就以該程式碼作為基底,去構築新的一套系統。這麼一來不只能夠縮短開發的時間,還可以讓未來維護程式碼時,省去許多不必要功夫的,畢竟有許多的程式碼其實是十分相近。但日復一日寫著相同的程式碼,將基底的架構複製過去,依照規格調整各自不同的部分,是否也開始覺得這樣動作也是不必要的呢?

是否曾經有想像過,既然我們寫的程式碼有許多都十分的類似,都是依照固定的規則來完成,那麼這樣子定型化的工作,為什麼不可以交給程式來替我們完成呢,相信許多人一定有撰寫過一些小工具來協助開發,可能是執行轉檔工作、產生測試資料等,那麼怎麼能夠缺少自動化生成程式碼這件事呢?


《圖一》MSDN T4 Text Templates說明


本篇文章中,將會介紹微軟所提供的T4範本工具,它可以讓你定義程式碼的樣本與邏輯,利用程式碼去產生程式碼,達到自動化產生程式碼,當未來有架構需要調整時,也只要調整最原始的那份T4範本,就可以重新生成所有相關的程式碼,進而大幅提升程式的開發效率。

T4範本的運作方式與專案建立

T4的全名為Text Template Transformation ToolkitT4(以下稱作T4範本),是由文字區塊與控制邏輯所組成,不需要將T4範本視為一種新的語言,要撰寫一份完整的T4範本僅僅只需要懂得C#或者是Visual Basic,當然他能產生的程式碼並不限於此,你可以將其視為包含了大量邏輯的文章產生器,只是通常是用於內容有著類似規則的程式碼。


《圖二》T4範本的一小段範例


前面的圖二簡單地顯示出T4範本中的文字區塊與控制邏輯,可以看出不管是文字區塊或者是控制邏輯其實就很類似平常所撰寫的程式碼。

過去在還不知道T4範本這個工具之前,若希望寫一支程式去產生其他程式碼或文字文件時,最簡單的方法就是直接利用像是StringBuilder等之類字串處理類別,然後配合著一些邏輯判斷來調整輸出的內容,這樣的寫法雖然也很直觀,但通常完成後的程式碼會難以閱讀,特別是為了要調整輸出檔案的排版,因為像是換行這類的特殊字元,程式碼中與我們實際看到的就不一樣。

而T4範本的原理,其實就是提供更容易閱讀的編寫方式,接著在內部進行一次轉換的動作,變成如下圖所示的程式碼。


《圖三》T4範本的一小段範例之二


T4範本並不是一個獨立的專案,如同前面所介紹的,他其實類似於一支轉換的程式,當你撰寫了一支T4範本的程式碼,系統就會自動將該範本轉換成另一支程式,最後使用該程式去產生文字文件。因此建立專案時,並不會看到T4範本這樣的選擇,要做的就是直接建立一個新的C#或Visual Basic之類的專案,接著於該專案中新增一個新項目,在這裡就可以看到有叫做「執行階段文字範本」的選項,其產生的副檔名為.tt,同時還會帶有一個.cs檔案(或是.vb)。加入新項目中,你可能還會注意到還有一個稱做「文字範本」的選項,其副檔名也是.tt,這兩種雖然都是用來產生文字文件用,但適用的時機與處理的方法會有些不同,「執行階段文字範本」就如同本段前述所說,程式設計師負責撰寫.tt的範本元件,而編輯器或將其轉換成.cs的檔案,最後產生我們要的檔案則是靠.cs來做產生,而「文字範本」或者又稱為「設計階段文字範本」則缺少了中間轉換成.cs的步驟,因為他自身就是用來產生結果。

使用「設計階段文字範本」的場合,通常是想要讓某個程式碼能夠自動做更新,像是隨著DataBase裡面有值更新後,程式碼能夠相應做調整,因為一個文字範本就會直接綁定一個輸出文件。而「執行階段文字範本」則通常是用來產生其他大量的程式碼用,藉由給定不同的輸入資料,產生其對應的輸出,例如讀取一些設計文件規格來產生結果。這邊我們將以「執行階段文字範本」來做介紹。


《圖四》加入一個T4範本項目



《圖五》執行階段文字範本會同時包含一個.cs文件


最後在撰寫T4範本之前,通常會再替該文件產生一個Partial Class,前面有提到通常我們會有些類似文件之類的輸入檔,建立一個Partial Class可以讓我們去紀錄該文件內需要的資料,例如現在是的需求是要自動化產生網頁,文件內可能會就定義網頁的名稱、欄位的資料與功能等等,而就會有另一段程式將這些資訊搬入至該物件中,方便之後的使用。

文字區塊與控制邏輯

在檔案都建立完成後,就可以開始撰寫T4範本了,因為是藉由T4範本來產生最終的程式碼,理所當然產生出來的程式碼或文字文件必然是沒有經過編輯器檢查過,當然連能不能跑起來都無法確定。因T4範本若某段控制邏輯十分複雜的話,很容易使得該段程式不容易閱讀,往後要調整當然也會有些困難,通常為了避免事後不斷調整T4範本檔,同時減少程式無法編輯的可能性,常會採用先人工完成第一支程式,確認程式可以正常運作後,將該程式整段貼入至T4範本中,接著再逐行進行修正,這樣一來比較能夠確保程式的大架構是正確的,未來有需要做調整時,改動的幅度也會較小。

以下就以一支網頁生成的T4範本作為範例,該網頁為.NET,包含了前端的aspx與後端的aspx.cs,功能為基本的資料庫檔案維護,包含查詢、新增、修改、刪除等,畫面上會有查詢視窗、以及其結果與明細。不過本篇不會將整個完成的程式碼都逐一描述,但其實做法概念都十分相似,這邊僅藉由介紹T4範本的同時,一併展示這份範例,藉此更方便理解其用法。

依照前面章節的步驟產生一個T4範本後,可以看到該檔內以預先產生了一些程式碼,包含了定義範本的語言(template language),這裡所指的語言並不是產生出來的程式碼,而是該T4範本裡對於控制邏輯的程式碼,是屬於哪種語言,會預設與你的專案類型一樣,當然也可以另外做修改,例如我們將範本語言設定為VB,儲存後可以發現原本跟著他的.cs檔案也一併調整成.vb了,這也就代表這個範本內所使用的語法就是VB。

以上一段程式碼將拿來示範T4範本的基本撰寫,這段程式碼為.NET網頁程式前端中,用以定義後端的部分。每一份網頁程式來說,我們希望都包含這段,因此我們就會在T4範本中保留這部分的程式碼,不過可以發現這段程式碼中,因為要定義後端程式的檔名,所以會隨著不同的程式也有所差別,因此就需要控制邏輯來調整這部分。

T4範本中,會使用<# #>來表示該段落為控制邏輯的部分,將控制邏輯放於兩個括號與井字號中間,在前面的範例中,我們需要他在不同文件中顯示不同的名稱,就可以選擇在先前提到的Partial Class中宣告一個變數,這裡命名為AspxName,接著從定義的文件中取得該名稱,放入此變數中,最後就可以在T4範本中將該變數顯示出來,若要顯示某一變數,使用的符號為”=”,最後這段控制邏輯就會寫成<#= AspxName #>,這樣一來T4範本在產生這段程式時,就會自動將此段替換成該變數,也就達到我們的目標了。以下就展示調整後的程式碼供參考。


《圖六》利用控制邏輯來輸出變數


在知道如何輸出變數後,就有辦法完成一些簡單的程式了,但是當然大部分的程式都沒有這麼單純,可以這麼簡單直接變數輸出的方式就完成,這裡就需要更複雜的控制邏輯來處理了。但這部分說起來也稱不上複雜,前面有稍微提到控制邏輯其實就像是平常在寫的程式碼,我們可以直接將操作流程用的程式碼放於控制邏輯區段中,例如常見的分歧判斷或迴圈處理。

///
/// 負責基本資料的新增、修改、刪除、查詢功能。
///

以上該段程式碼,可能是後端其中一段程式的註解,這邊我們可能希望說,假如文件中沒有定義註解的話,不要顯示註解那行,包含其前面的反斜線,甚至是連前後remarks那兩行都不要顯示。平常若要直接寫一份程式碼處理這樣的邏輯,很容易可以想到利用IF分岐來處理,而在T4範本中的處理方式也是相同,前段在介紹T4範本的運作原理時,有說到編譯器會將T4範本先轉換成另一份檔案,其中文字區塊的會被轉換成以下的程式碼。

this.Write(" /// \r\n");

這代表的就是文字區塊最後會直接作為輸出部分,然而控制邏輯部分的轉換其實更為簡單,假設在沒有使用前面變數提到的”=”等之類特殊字眼,那麼控制邏輯區塊事實上就是將程式碼直接轉到另一份去。

///
<#if(Description != ""){#>
/// <#= Description #>
<#}#>
///

上面這段程式碼,簡單示範了在控制邏輯區塊放入分岐判斷的用法,當輸入了這段程式碼並儲存後,可以看到後端轉換後的程式碼,也就包含了以下內容。

this.Write(" /// \r\n");
#line 29 "C:\ModelGenerator\AspxCsTemplate.tt"
if(Description != ""){
this.Write(" /// ");
#line 30 " C:\ModelGenerator\AspxCsTemplate.tt"
this.Write(this.ToStringHelper.ToStringWithCulture(Description));

藉由這一段轉換後的程式,可以看出幾個轉換的重點,文字區塊的轉換在前面提到了,而控制邏輯的部分,可以看到說他會先註明該邏輯位於原T4範本中的哪一行,並且完整複製過來,下面同樣是一段文字區塊,包含了一個變數的輸出,而Description這個變數理所當然的是定義在Partial Class之中。

迴圈部分的寫法也就大同小異,將迴圈放入控制邏輯區塊中,配合著變數與文字區塊就可以達到該效果,這邊同樣也提供一段程式碼供參考。

private SelectList GetOrderByFieldList()
{
var items = new List();
items.Add(new SelectListItem { Value = "Column1", Text = "欄位1" });
items.Add(new SelectListItem { Value = "Column2", Text = "欄位2" });
(下略)


《圖七》控制邏輯-迴圈的範例


撰寫一份T4範本最簡單的方式就是貼上一整份寫好的程式碼,並且替換到中間的部分,前面的迴圈範例中,function的宣告與List的宣告都是必要的部分,因此保留作為文字區塊,而後面需要依照文件定義的不同,產生不同且不固定行數的程式碼,所以需加入迴圈的控制邏輯,先保留原本的程式部分繼續作為文字方塊,在前後增加控制邏輯,接著再替換掉像是Value與Text等等。

到這裡,T4範本的用法已介紹完成,因為控制邏輯就如同寫程式一樣,所以想要做到其他的效果,都可以將程式碼放於控制邏輯區塊來完成,像是函式的呼叫,變數值的變動,或者是在控制邏輯裡在宣告其他物件也是可以的。因此就看往後需要產生什麼樣的程式,去完成必要的程式碼即可。

最後要將檔案做輸出則是使用TransformText()函式,這個函式就位於編輯器替我們產生的後端程式之中,他會回傳一個String物件,我們可再將其物件輸出至檔案上。

控制邏輯的特殊字元

在前段我們看到了控制邏輯區塊除了放操作流程的程式碼外,藉由<#= #>也可以做到輸出變數的效果,這部分稱作運算式控制區塊,在轉換的程式碼中,他會將其內容以ToString來做輸出,藉此達到輸出字串的效果。
另外還有個特殊字元在一開始就有看到了,<#@ assembly name="System.Core" #>之中的<#@ #>也是一種特殊字元,除了像此段拿來定義使用的語言外,還可以用以匯入命名空間與外部參考。
最種一種為<#+ #>,這部分用以定義屬性或方法,其實就等同於前面一直在使用的Partial Class,這邊其實建議為了讓T4範本不至於太複雜,應避免使用這種定義方式,畢竟這內容是可以都移至Partial Class中來做定義的。

總結

為了節省開發的時間,通常我們會盡量讓同類型的程式碼架構一致,因此也很常採用先完成一個範例程式碼,再由程式設計師去後續補完其他程式碼。但現在有了T4範本這樣的工具,就可以藉此撰寫一份T4範本,來自動化產生相關程式碼,往後若要再新增新的程式碼也可以立即產生。或者是與資料庫做連結,當有資料增減時,可以相應產生像是列舉型態之類的程式碼,不用擔心程式碼與資料庫不同步的問題,這些方便的設計工具對於我們的開發都有著十足的幫助。

參考資料

1. 程式碼產生和T4 文字範本 - MSDN - Microsoft
2. 使用T4 文字範本在設計階段產生程式碼 - MSDN - Microsoft
3. 撰寫T4 文字範本的方針 - MSDN - Microsoft

(本文轉載自RUN!PC)