简体   繁体   English

代码生成器或T4模板,它们真的很邪恶吗?

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

I have heard people state that Code Generators and T4 templates should not be used. 我听说人们说不应该使用代码生成器和T4模板。 The logic behind that is that if you are generating code with a generator then there is a better more efficient way to build the code through generics and templating. 这背后的逻辑是,如果您使用生成器生成代码,那么通过泛型和模板构建代码有一种更好的更有效的方法。

While I slightly agree with this statement above, I have not really found effective ways to build templates that can say for instance instantiate themselves. 虽然我略微同意上面的这个陈述,但我还没有真正找到有效的方法来构建模板,例如可以说实例化自己。 In otherwords I can never do : 换句话说,我永远做不到:

return new T();

Additionally, if I want to generate code based on database values I have found that using Microsoft.SqlServer.Management.SMO in conjunction with T4 templates have been wonderful at generating mass amounts of code without having to copy / paste or use resharper. 另外,如果我想基于数据库值生成代码,我发现将Microsoft.SqlServer.Management.SMO与T4模板结合使用可以很好地生成大量代码而无需复制/粘贴或使用resharper。

Many of the problems I have found with Generics too is that to my shock there are a lot of developers who do not understand them. 我在Generics中发现的许多问题都是令我震惊的是,有很多开发人员不了解它们。 When I do examine generics for a solution, there are times where it gets complicated because C# states that you cannot do something that may seem logical in my mind. 当我为一个解决方案检查泛型时,有时它会变得复杂,因为C#表明你不能做一些看似合乎逻辑的事情。

What are your thoughts? 你的想法是什么? Do you prefer to build a generator, or do you prefer to use generics? 你喜欢建造发电机,还是喜欢使用仿制药? Also, how far can generics go? 此外,仿制药可以走多远? I know a decent amount about generics, but there are traps and pitfalls that I always run into that cause me to resort to a T4 template. 我对泛型有很多了解,但是我总是遇到陷阱和陷阱导致我使用T4模板。

What is the more proper way to handle scenarios where you need a large amount of flexibility? 处理需要大量灵活性的场景的更合适的方法是什么? Oh and as a bonus to this question, what are good resources on C# and Generics? 哦,作为这个问题的奖励,C#和Generics的优秀资源是什么?

You can do new T(); 你可以做新的T(); if you do this 如果你这样做

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

As for code-generators. 至于代码生成器。 I use one every day without any problems. 我每天都使用一个没有任何问题。 I'm using one right now in fact :-) 我现在正在使用一个:-)

Generics solve one problem, code-generators solve another. 泛型解决了一个问题,代码生成器解决了另一个问题。 For example, creating a business model using a UML editor and then generating your classes with persistence code as I do all of the time using this tool couldn't be achieved with generics, because each persistent class is completely different. 例如,使用UML编辑器创建业务模型,然后使用此工具生成具有持久性代码的类无法使用泛型实现,因为每个持久化类都是完全不同的。

As for a good source on generics. 至于仿制药的良好来源。 The best has got to be Jon Skeet's book of course! 最好的当然是Jon Skeet的书 :-) :-)

As the originator of T4, I've had to defend this question quite a few times as you can imagine :-) 作为T4的创始人,我不得不多次捍卫这个问题,你可以想象:-)

My belief is that at its best code generation is a step on the way to producing equivalent value using reusable libraries. 我的信念是,最好的代码生成是使用可重用库生成等价值的一步。

As many others have said, the key concept to maintain DRY is never, ever changing generated code manually, but rather preserving your ability to regenerate when the source metadata changes or you find a bug in the code generator. 正如许多其他人所说,维护DRY的关键概念永远不会手动更改生成的代码,而是保留在源元数据更改或您在代码生成器中发现错误时重新生成的能力。 At that point the generated code has many of the characteristics of object code and you don't run into copy/paste type problems. 此时生成的代码具有对象代码的许多特征,并且您不会遇到复制/粘贴类型问题。

In general, it's much less effort to produce a parameterized code generator (especially with template-based systems) than it is to correctly engineer a high quality base library that gets the usage cost down to the same level, so it's a quick way to get value from consistency and remove repetition errors. 一般来说,生成参数化代码生成器(特别是基于模板的系统)要比正确设计高质量的基础库(将使用成本降低到同一水平)要少得多,因此这是一种快速获取的方法。来自一致性的价值并消除重复错误。

However, I still believe that the finished system would most often be improved by having less total code. 但是,我仍然相信完成的系统通常会通过减少总代码来改进。 If nothing else, its memory footprint would almost always be significantly smaller (although folks tend to think of generics as cost free in this regard, which they most certainly are not). 如果不出意外,它的内存占用几乎总是会小得多(虽然人们倾向于认为泛型在这方面是免费的,但他们肯定不是这样)。

If you've realised some value using a code generator, then this often buys you some time or money or goodwill to invest in harvesting a library from the generated codebase. 如果您已经使用代码生成器实现了某些价值,那么这通常会花费您一些时间或金钱或善意来投资从生成的代码库中获取库。 You can then incrementally reengineer the code generator to target the new library and hopefully generate much less code. 然后,您可以逐步重新设计代码生成器以定位新库,并希望生成更少的代码。 Rinse and repeat. 冲洗并重复。

One interesting counterpoint that has been made to me and that comes up in this thread is that rich, complex, parametric libraries are not the easiest thing in terms of learning curve, especially for those not deeply immersed in the platform. 我所提出的一个有趣的对立点是,在这个主题中出现的是丰富,复杂的参数库在学习曲线方面并不是最简单的,特别是对于那些没有深入沉浸在平台中的人。 Sticking with code generation onto simpler basic frameworks can produce verbose code, but it can often be quite simple and easy to read. 坚持将代码生成到更简单的基本框架上可以产生冗长的代码,但它通常非常简单易读。

Of course, where you have a lot of variance and extremely rich parameterization in your generator, you might just be trading off complexity an your product for complexity in your templates. 当然,如果您的生成器中存在大量差异和极其丰富的参数化,那么您可能只是为了模板的复杂性而牺牲产品的复杂性。 This is an easy path to slide into and can make maintenance just as much of a headache - watch out for that. 这是一条很容易滑入的路径,可以让维护变得非常令人头疼 - 请注意这一点。

Generating code isn't evil and it doesn't smell! 生成代码不是邪恶的,它没有味道! The key is to generate the right code at the right time. 关键是在合适的时间生成正确的代码。 I think T4 is great--I only use it occasionally, but when I do it is very helpful. 我认为T4很棒 - 我偶尔只使用它,但是当我这样做时非常有帮助。 To say, unconditionally, that generating code is bad is unconditionally crazy! 无条件地说,生成代码是坏的无条件疯狂!

A good percentage of what is in Visual Studio 2010 would not be possible without code generation. 如果没有代码生成,Visual Studio 2010中的很大一部分将无法实现。 Entity Framework would not be possible. 实体框架是不可能的。 The simple act of dragging and dropping a control onto a form would not be possible, nor would Linq. 将控件拖放到表单上的简单操作是不可能的,Linq也不可能。 To say that code generation should not be used is strange as so many use it without even thinking about it. 要说不应该使用代码生成是很奇怪的,因为许多人甚至没有考虑它就使用它。

It seems to me code generators are fine as long as the code generation is part of your normal build process, rather than something you run once and then keep its output. 在我看来,代码生成器很好,只要代码生成是正常构建过程的一部分,而不是你运行一次然后保持其输出。 I add this caveat because if just use the code generator once and discard the data that created it, you're just automatically creating a massive DRY violation and maintenance headache; 我添加了这个警告,因为如果只使用代码生成器一次并丢弃创建它的数据,那么您只是自动创建一个大规模的DRY违规和维护问题; whereas generating the code every time effectively means that whatever you are using to do the generating is the real source code, and the generated files are just intermediate compile stages that you should mostly ignore. 而每次生成代码实际上意味着无论你用什么做生成都是真正的源代码,生成的文件只是你应该忽略的中间编译阶段。

Lex and yacc are classic examples of tools of allow you to specify functionality in an efficient manner and generate efficient code from it. Lex和yacc是允许您以有效方式指定功能并从中生成有效代码的工具的经典示例。 Trying to do their jobs by hand will lengthen your development time and probably produce less efficient and less readable code. 尝试手工完成工作将延长您的开发时间,并可能产生效率较低且可读性较低的代码。 And while you could certainly incorporate something like lex and yacc directly into your code and do their jobs at run time instead of at compile time, that would certainly add considerable complexity to your code and slow it down. 虽然你当然可以将lex和yacc之类的东西直接合并到代码中并在运行时而不是在编译时完成它们的工作,但这肯定会增加代码的复杂性并减慢它的速度。 If you actually need to change your specification at run time it might be worth it, but in most normal cases using lex/yacc to generate code for you at compile time is a big win. 如果你真的需要在运行时更改你的规范,那么它可能是值得的,但是在大多数正常情况下使用lex / yacc在编译时为你生成代码是一个很大的胜利。

Maybe it is a bit harsh, but for me code generation smells. 也许它有点苛刻,但对我来说代码生成气味。

That code generation is used means that there are numerous underlying common principles which may be expressed in a "Don't repeat yourself" fashion. 使用该代码生成意味着有许多潜在的共同原则可以用“不要重复自己”的方式表达。 It may take a bit longer, but it is satisfying when you end up with classes that only contain the bits that really change, based on an infrastructure that contains the mechanics. 它可能需要更长的时间,但是当你最终只有包含真正改变的位的类时,基于包含机制的基础结构,它会令人满意。

As to Generics...no I don't have too many issues with it. 至于泛型......不,我没有太多的问题。 The only thing that currently doesn't work is saying that 目前唯一不起作用的是说

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

But even that will be possible in the next version of C#. 但即便如此,在下一版本的C#中也是如此。

More code means more complexity. 更多代码意味着更复杂。 More complexity means more places for bugs to hide, which means longer fix cycles, which in turn means higher costs throughout the project. 更复杂意味着更多的地方可以隐藏错误,这意味着更长的修复周期,这反过来意味着整个项目的成本更高。

Whenever possible, I prefer to minimize the amount of code to provide equivalent functionality; 只要有可能,我更愿意最小化代码量以提供相同的功能; ideally using dynamic (programmatic) approaches rather than code generation. 理想情况下使用动态(编程)方法而不是代码生成。 Reflection, attributes, aspects and generics provide lots of options for a DRY strategy, leaving generation as a last resort. 反思,属性,方面和泛型为DRY策略提供了许多选择,将生成作为最后的手段。

Code generation is for me a workaround for many problems found in language, frameworks, etc. They are not evil by themselves, I would say it is very very bad (ie evil) to release a language (C#) and framework which forces you to copy&paste (swap on properties, events triggering, lack of macros) or use magical numbers (wpf binding). 对于我来说,代码生成对于语言,框架等中发现的许多问题都是一种解决方法。它们本身并不是邪恶的,我会说发布语言(C#)非常非常糟糕(即邪恶),并迫使你复制和粘贴(交换属性,触发事件,缺少宏)或使用魔法数字(wpf绑定)。

So, I cry, but I use them, because I have to. 所以,我哭了,但我用它们,因为我必须这样做。

I've used T4 for code generation and also Generics. 我已经使用T4代码生成和泛型。 Both are good, have their pros and cons, and are suited for different purposes. 两者都很好,有利有弊,适合不同的目的。

In my case, I use T4 to generate Entities, DAL and BLL based on a database schema. 在我的例子中,我使用T4基于数据库模式生成实体,DAL和BLL。 However, DAL and BLL reference a mini-ORM I built, based on Generics and Reflection. 但是,DAL和BLL引用了我构建的迷你ORM,基于泛型和反射。 So I think you can use them side by side, as long as you keep in control and keep it small and simple. 所以我认为你可以并排使用它们,只要你保持控制并保持小而简单。

T4 generates static code, while Generics is dynamic. T4生成静态代码,而泛型是动态的。 If you use Generics, you use Reflection which is said to be less performant than "hard-coded" solution. 如果你使用泛型,你使用反射,据说它比“硬编码”解决方案性能更差。 Of course you can cache reflection results. 当然,您可以缓存反射结果。

Regarding "return new T();", I use Dynamic Methods like this: 关于“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();
    }
}

Then, I call it like this: 然后,我这样称呼它:

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

Generics and code generation are two different things. 泛型和代码生成是两回事。 In some cases you could use generics instead of code generation and for those I believe you should. 在某些情况下,您可以使用泛型而不是代码生成,对于那些我认为应该使用的代码。 For the other cases code generation is a powerful tool. 对于其他情况,代码生成是一个强大的工具。

For all the cases where you simply need to generate code based on some data input, code generation is the way to go. 对于您只需要根据某些数据输入生成代码的所有情况,代码生成是可行的方法。 The most obvious, but by no means the only example is the forms editor in Visual Studio. 最明显的,但绝不是唯一的例子是Visual Studio中的表单编辑器。 Here the input is the designer data and the output is the code. 这里输入是设计器数据,输出是代码。 In this case generics is really no help at all, but it is very nice that VS simply generates the code based on the GUI layout. 在这种情况下,泛型实际上根本没有任何帮助,但是VS很简单地生成基于GUI布局的代码。

The copy/paste type of generated code (like ORMs make) can also be very useful... 生成代码的复制/粘贴类型(如ORMs make)也非常有用......

You can create your database, and then having the ORM generate a copy of that database definition expressed in your favorite language. 您可以创建数据库,然后让ORM生成以您喜欢的语言表示的数据库定义的副本。

The advantage comes when you change your original definition (the database), press compile and the ORM (if you have a good one) can re-generates your copy of the definition. 当您更改原始定义(数据库),按下编译并且ORM(如果您有一个好的)可以重新生成定义的副本时,优势就出现了。 Now all references to your database can be checked by the compilers type checker and your code will fail to compile when you're using tables or columns that do not exist anymore. 现在,编译器类型检查器可以检查对数据库的所有引用,并且当您使用不再存在的表或列时,您的代码将无法编译。

Think about this: If I call a method a few times in my code, am I not referring to the name I gave to this method originally? 想一想:如果我在代码中多次调用一个方法,我不是指我最初给这个方法的名字吗? I keep repeating that name over and over... Language designers recognized this problem and came up with "Type-safety" as the solution. 我一遍又一遍地重复这个名字......语言设计师认识到了这个问题,并提出了“类型安全”作为解决方案。 Not removing the copies (as DRY suggests we should do), but checking them for correctness instead. 不删除副本(如DRY建议我们应该这样做),而是检查它们的正确性。

The ORM generated code brings the same solution when referring to table and column names. ORM生成的代码在引用表名和列名时带来相同的解决方案。 Not removing the copies/references, but bringing the database definition into your (type-safe) language where you can refer to classes and properties instead. 不删除副本/引用,而是将数据库定义引入您的(类型安全)语言,您可以在其中引用类和属性。 Together with the compilers type checking, this solves a similar problem in a similar way: Guarantee compile-time errors instead of runtime ones when you refer to outdated or misspelled tables (classes) or columns (properties). 与编译器类型检查一起,这以类似的方式解决了类似的问题:当您引用过时或拼写错误的表(类)或列(属性)时,保证编译时错误而不是运行时错误。

quote: I have not really found effective ways to build templates that can say for instance instantiate themselves. 引用:我还没有真正找到有效的方法来构建模板,例如可以说实例化自己。 In otherwords I can never do : 换句话说,我永远做不到:

return new T(); 返回新的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();
    }
} 

Code generators could be considered a code smell that indicate a flaw or lack of functionality in the target langauge. 代码生成器可以被认为是代码气味,表明目标语言中存在缺陷或缺乏功能。

For example, while it has been said here that "Objects that persist can not be generalized", it would be better to think of it as "Objects in C# that automatically persist their data can not be generalized in C#", because I surely can in Python through the use of various methods. 例如,虽然这里曾经说过“持久存在的对象无法推广”,但最好将其视为“C#中的对象自动保存其数据不能在C#中推广”,因为我当然可以在Python中通过使用各种方法。

The Python approach could, however, be emulated in static languages through the use of operator[ ](method_name as string), which either returns a functor or a string, depending on requirements. 但是,Python方法可以通过使用operator [](method_name as string)在静态语言中进行模拟,根据需要,它可以返回一个函子或一个字符串。 Unfortunately that solution is not always applicable, and returning a functor can be inconvenient. 不幸的是,该解决方案并不总是适用,并且返回仿函数可能不方便。

The point I am making is that code generators indicate a flaw in a chosen language that are addressed by providing a more convenient specialised syntax for the specific problem at hand. 我要说的是,代码生成器通过为手头的特定问题提供更方便的专用语法来指示所选语言中的缺陷。

Code generation, like generics, templates, and other such shortcuts, is a powerful tool. 代码生成,如泛型,模板和其他此类快捷方式,是一个强大的工具。 And as with most powerful tools, it amplifies the capaility of its user for good and for evil - they can't be separated. 和大多数强大的工具一样,它扩大了用户对善恶的能力 - 他们无法分开。

So if you understand your code generator thoroughly, anticipate everything it will produce, and why, and intend it to do so for valid reasons, then have at it. 因此,如果你彻底了解你的代码生成器,预测它将产生的一切,为什么,并打算出于正当理由这样做,然后就可以了。 But don't use it (or any of the other technique) to get you past a place where you're not to sure where you're headed, or how to get there. 但是不要使用它(或任何其他技术)让你经过一个你不确定你要去哪里或者如何去那里的地方。

Some people think that, if you get your current problem solved and some behavior implemented, you're golden. 有些人认为,如果你解决了当前的问题并实施了一些行为,那你就是金色的。 It's not always obvious how much cruft and opaqueness you leave in your trail for the next developer (which might be yourself.) 对于下一个开发人员(可能是你自己),你留下多少瑕疵和不透明度并不总是很明显。

Why does being able to copy/paste really, really fast, make it any more acceptable? 为什么能够真正,快速地复制/粘贴,使它更容易被接受?

That's the only justification for code generation that I can see. 这是我能看到的代码生成的唯一理由。

Even if the generator provides all the flexibility you need, you still have to learn how to use that flexibility - which is yet another layer of learning and testing required. 即使发生器提供了您所需的所有灵活性,您仍然必须学习如何使用这种灵活性 - 这是另一层所需的学习和测试。

And even if it runs in zero time, it still bloats the code. 即使它在零时间运行,它仍然会使代码膨胀。

I rolled my own data access class. 我推出了自己的数据访问类。 It knows everything about connections, transactions, stored procedure parms, etc, etc, and I only had to write all the ADO.NET stuff once. 它知道关于连接,事务,存储过程参数等的一切,我只需要编写一次所有ADO.NET的东西。

It's now been so long since I had to write (or even look at) anything with a connection object in it, that I'd be hard pressed to remember the syntax offhand. 现在已经很久了,因为我不得不用连接对象写任何东西(或者甚至看一下),我很难记住语法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM