簡體   English   中英

代碼生成器或T4模板,它們真的很邪惡嗎?

[英]Code Generators or T4 Templates, are they really evil?

我聽說人們說不應該使用代碼生成器和T4模板。 這背后的邏輯是,如果您使用生成器生成代碼,那么通過泛型和模板構建代碼有一種更好的更有效的方法。

雖然我略微同意上面的這個陳述,但我還沒有真正找到有效的方法來構建模板,例如可以說實例化自己。 換句話說,我永遠做不到:

return new T();

另外,如果我想基於數據庫值生成代碼,我發現將Microsoft.SqlServer.Management.SMO與T4模板結合使用可以很好地生成大量代碼而無需復制/粘貼或使用resharper。

我在Generics中發現的許多問題都是令我震驚的是,有很多開發人員不了解它們。 當我為一個解決方案檢查泛型時,有時它會變得復雜,因為C#表明你不能做一些看似合乎邏輯的事情。

你的想法是什么? 你喜歡建造發電機,還是喜歡使用仿制葯? 此外,仿制葯可以走多遠? 我對泛型有很多了解,但是我總是遇到陷阱和陷阱導致我使用T4模板。

處理需要大量靈活性的場景的更合適的方法是什么? 哦,作為這個問題的獎勵,C#和Generics的優秀資源是什么?

你可以做新的T(); 如果你這樣做

public class Meh<T>
  where T : new()
{
  public static T CreateOne()
  {
    return new T();
  }
}

至於代碼生成器。 我每天都使用一個沒有任何問題。 我現在正在使用一個:-)

泛型解決了一個問題,代碼生成器解決了另一個問題。 例如,使用UML編輯器創建業務模型,然后使用此工具生成具有持久性代碼的類無法使用泛型實現,因為每個持久化類都是完全不同的。

至於仿制葯的良好來源。 最好的當然是Jon Skeet的書 :-)

作為T4的創始人,我不得不多次捍衛這個問題,你可以想象:-)

我的信念是,最好的代碼生成是使用可重用庫生成等價值的一步。

正如許多其他人所說,維護DRY的關鍵概念永遠不會手動更改生成的代碼,而是保留在源元數據更改或您在代碼生成器中發現錯誤時重新生成的能力。 此時生成的代碼具有對象代碼的許多特征,並且您不會遇到復制/粘貼類型問題。

一般來說,生成參數化代碼生成器(特別是基於模板的系統)要比正確設計高質量的基礎庫(將使用成本降低到同一水平)要少得多,因此這是一種快速獲取的方法。來自一致性的價值並消除重復錯誤。

但是,我仍然相信完成的系統通常會通過減少總代碼來改進。 如果不出意外,它的內存占用幾乎總是會小得多(雖然人們傾向於認為泛型在這方面是免費的,但他們肯定不是這樣)。

如果您已經使用代碼生成器實現了某些價值,那么這通常會花費您一些時間或金錢或善意來投資從生成的代碼庫中獲取庫。 然后,您可以逐步重新設計代碼生成器以定位新庫,並希望生成更少的代碼。 沖洗並重復。

我所提出的一個有趣的對立點是,在這個主題中出現的是豐富,復雜的參數庫在學習曲線方面並不是最簡單的,特別是對於那些沒有深入沉浸在平台中的人。 堅持將代碼生成到更簡單的基本框架上可以產生冗長的代碼,但它通常非常簡單易讀。

當然,如果您的生成器中存在大量差異和極其豐富的參數化,那么您可能只是為了模板的復雜性而犧牲產品的復雜性。 這是一條很容易滑入的路徑,可以讓維護變得非常令人頭疼 - 請注意這一點。

生成代碼不是邪惡的,它沒有味道! 關鍵是在合適的時間生成正確的代碼。 我認為T4很棒 - 我偶爾只使用它,但是當我這樣做時非常有幫助。 無條件地說,生成代碼是壞的無條件瘋狂!

如果沒有代碼生成,Visual Studio 2010中的很大一部分將無法實現。 實體框架是不可能的。 將控件拖放到表單上的簡單操作是不可能的,Linq也不可能。 要說不應該使用代碼生成是很奇怪的,因為許多人甚至沒有考慮它就使用它。

在我看來,代碼生成器很好,只要代碼生成是正常構建過程的一部分,而不是你運行一次然后保持其輸出。 我添加了這個警告,因為如果只使用代碼生成器一次並丟棄創建它的數據,那么您只是自動創建一個大規模的DRY違規和維護問題; 而每次生成代碼實際上意味着無論你用什么做生成都是真正的源代碼,生成的文件只是你應該忽略的中間編譯階段。

Lex和yacc是允許您以有效方式指定功能並從中生成有效代碼的工具的經典示例。 嘗試手工完成工作將延長您的開發時間,並可能產生效率較低且可讀性較低的代碼。 雖然你當然可以將lex和yacc之類的東西直接合並到代碼中並在運行時而不是在編譯時完成它們的工作,但這肯定會增加代碼的復雜性並減慢它的速度。 如果你真的需要在運行時更改你的規范,那么它可能是值得的,但是在大多數正常情況下使用lex / yacc在編譯時為你生成代碼是一個很大的勝利。

也許它有點苛刻,但對我來說代碼生成氣味。

使用該代碼生成意味着有許多潛在的共同原則可以用“不要重復自己”的方式表達。 它可能需要更長的時間,但是當你最終只有包含真正改變的位的類時,基於包含機制的基礎結構,它會令人滿意。

至於泛型......不,我沒有太多的問題。 目前唯一不起作用的是說

List<Animal> a = new List<Animal>();
List<object> o = a;

但即便如此,在下一版本的C#中也是如此。

更多代碼意味着更復雜。 更復雜意味着更多的地方可以隱藏錯誤,這意味着更長的修復周期,這反過來意味着整個項目的成本更高。

只要有可能,我更願意最小化代碼量以提供相同的功能; 理想情況下使用動態(編程)方法而不是代碼生成。 反思,屬性,方面和泛型為DRY策略提供了許多選擇,將生成作為最后的手段。

對於我來說,代碼生成對於語言,框架等中發現的許多問題都是一種解決方法。它們本身並不是邪惡的,我會說發布語言(C#)非常非常糟糕(即邪惡),並迫使你復制和粘貼(交換屬性,觸發事件,缺少宏)或使用魔法數字(wpf綁定)。

所以,我哭了,但我用它們,因為我必須這樣做。

我已經使用T4代碼生成和泛型。 兩者都很好,有利有弊,適合不同的目的。

在我的例子中,我使用T4基於數據庫模式生成實體,DAL和BLL。 但是,DAL和BLL引用了我構建的迷你ORM,基於泛型和反射。 所以我認為你可以並排使用它們,只要你保持控制並保持小而簡單。

T4生成靜態代碼,而泛型是動態的。 如果你使用泛型,你使用反射,據說它比“硬編碼”解決方案性能更差。 當然,您可以緩存反射結果。

關於“return new T();”,我使用這樣的動態方法:

public class ObjectCreateMethod
    {
    delegate object MethodInvoker();
    MethodInvoker methodHandler = null;

    public ObjectCreateMethod(Type type)
    {
        CreateMethod(type.GetConstructor(Type.EmptyTypes));
    }

    public ObjectCreateMethod(ConstructorInfo target)
    {
        CreateMethod(target);
    }

    void CreateMethod(ConstructorInfo target)
    {
        DynamicMethod dynamic = new DynamicMethod(string.Empty,
                    typeof(object),
                    new Type[0],
                    target.DeclaringType);
        ILGenerator il = dynamic.GetILGenerator();
        il.DeclareLocal(target.DeclaringType);
        il.Emit(OpCodes.Newobj, target);
        il.Emit(OpCodes.Stloc_0);
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Ret);

        methodHandler = (MethodInvoker)dynamic.CreateDelegate(typeof(MethodInvoker));
    }

    public object CreateInstance()
    {
        return methodHandler();
    }
}

然后,我這樣稱呼它:

ObjectCreateMethod _MetodoDinamico = new ObjectCreateMethod(info.PropertyType);
object _nuevaEntidad = _MetodoDinamico.CreateInstance();

泛型和代碼生成是兩回事。 在某些情況下,您可以使用泛型而不是代碼生成,對於那些我認為應該使用的代碼。 對於其他情況,代碼生成是一個強大的工具。

對於您只需要根據某些數據輸入生成代碼的所有情況,代碼生成是可行的方法。 最明顯的,但絕不是唯一的例子是Visual Studio中的表單編輯器。 這里輸入是設計器數據,輸出是代碼。 在這種情況下,泛型實際上根本沒有任何幫助,但是VS很簡單地生成基於GUI布局的代碼。

生成代碼的復制/粘貼類型(如ORMs make)也非常有用......

您可以創建數據庫,然后讓ORM生成以您喜歡的語言表示的數據庫定義的副本。

當您更改原始定義(數據庫),按下編譯並且ORM(如果您有一個好的)可以重新生成定義的副本時,優勢就出現了。 現在,編譯器類型檢查器可以檢查對數據庫的所有引用,並且當您使用不再存在的表或列時,您的代碼將無法編譯。

想一想:如果我在代碼中多次調用一個方法,我不是指我最初給這個方法的名字嗎? 我一遍又一遍地重復這個名字......語言設計師認識到了這個問題,並提出了“類型安全”作為解決方案。 不刪除副本(如DRY建議我們應該這樣做),而是檢查它們的正確性。

ORM生成的代碼在引用表名和列名時帶來相同的解決方案。 不刪除副本/引用,而是將數據庫定義引入您的(類型安全)語言,您可以在其中引用類和屬性。 與編譯器類型檢查一起,這以類似的方式解決了類似的問題:當您引用過時或拼寫錯誤的表(類)或列(屬性)時,保證編譯時錯誤而不是運行時錯誤。

引用:我還沒有真正找到有效的方法來構建模板,例如可以說實例化自己。 換句話說,我永遠做不到:

返回新的T();

public abstract class MehBase<TSelf, TParam1, TParam2>
    where TSelf : MehBase<TSelf, TParam1, TParam2>, new()
{
    public static TSelf CreateOne()
    {
        return new TSelf();
    }
}

public class Meh<TParam1, TParam2> : MehBase<Meh<TParam1, TParam2>, TParam1, TParam2>
{
    public void Proof()
    {
        Meh<TParam1, TParam2> instanceOfSelf1 = Meh<TParam1, TParam2>.CreateOne();
        Meh<int, string> instanceOfSelf2 = Meh<int, string>.CreateOne();
    }
} 

代碼生成器可以被認為是代碼氣味,表明目標語言中存在缺陷或缺乏功能。

例如,雖然這里曾經說過“持久存在的對象無法推廣”,但最好將其視為“C#中的對象自動保存其數據不能在C#中推廣”,因為我當然可以在Python中通過使用各種方法。

但是,Python方法可以通過使用operator [](method_name as string)在靜態語言中進行模擬,根據需要,它可以返回一個函子或一個字符串。 不幸的是,該解決方案並不總是適用,並且返回仿函數可能不方便。

我要說的是,代碼生成器通過為手頭的特定問題提供更方便的專用語法來指示所選語言中的缺陷。

代碼生成,如泛型,模板和其他此類快捷方式,是一個強大的工具。 和大多數強大的工具一樣,它擴大了用戶對善惡的能力 - 他們無法分開。

因此,如果你徹底了解你的代碼生成器,預測它將產生的一切,為什么,並打算出於正當理由這樣做,然后就可以了。 但是不要使用它(或任何其他技術)讓你經過一個你不確定你要去哪里或者如何去那里的地方。

有些人認為,如果你解決了當前的問題並實施了一些行為,那你就是金色的。 對於下一個開發人員(可能是你自己),你留下多少瑕疵和不透明度並不總是很明顯。

為什么能夠真正,快速地復制/粘貼,使它更容易被接受?

這是我能看到的代碼生成的唯一理由。

即使發生器提供了您所需的所有靈活性,您仍然必須學習如何使用這種靈活性 - 這是另一層所需的學習和測試。

即使它在零時間運行,它仍然會使代碼膨脹。

我推出了自己的數據訪問類。 它知道關於連接,事務,存儲過程參數等的一切,我只需要編寫一次所有ADO.NET的東西。

現在已經很久了,因為我不得不用連接對象寫任何東西(或者甚至看一下),我很難記住語法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM