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

我听说人们说不应该使用代码生成器和T4模板。 这背后的逻辑是,如果您使用生成器生成代码,那么通过generics和模板构建代码有一种更好的更有效的方法。

虽然我略微同意上面的这个陈述,但我还没有真正找到有效的方法来构建模板,例如可以说实例化自己。 换句话说,我永远做不到:

return new T(); 

另外,如果我想基于数据库值生成代码,我发现将Microsoft.SqlServer.Management.SMO与T4模板结合使用可以很好地生成大量代码而无需复制/粘贴或使用resharper。

我在Generics中发现的许多问题都是令我震惊的是,有很多开发人员不了解它们。 当我为一个解决方案检查generics时,有时它会变得复杂,因为C#表明你不能做一些在我脑海里看似合乎逻辑的事情。

你的想法是什么? 你喜欢建造发电机,还是更喜欢使用仿制药? 此外,仿制药可以走多远? 我对generics有很多了解,但是我总是遇到陷阱和陷阱导致我使用T4模板。

处理需要大量灵活性的场景的更合适的方法是什么? 哦,作为这个问题的奖励,C#和Generics的优秀资源是什么?

你可以做新的T(); 如果你这样做

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

至于代码生成器。 我每天都使用一个没有任何问题。 我现在正在使用一个:-)

generics解决了一个问题,代码生成器解决了另一个问题。 例如,使用UML编辑器创建业务模型然后使用此工具生成具有持久性代码的类无法使用generics实现,因为每个持久化类都是完全不同的。

至于仿制药的良好来源。 最好的当然是Jon Skeet的书 ! 🙂

作为T4的创始人,我不得不多次捍卫这个问题,你可以想象:-)

我的信念是,最好的代码生成是使用可重用库生成等价值的一步。

正如许多其他人所说,维护DRY的关键概念永远不会手动更改生成的代码,而是在源元数据更改或您在代码生成器中发现错误时保留重新生成的能力。 此时生成的代码具有目标代码的许多特性,并且您不会遇到复制/粘贴类型问题。

一般来说,生成参数化代码生成器(尤其是基于模板的系统)要比正确设计高质量的基础库(将使用成本降低到同一水平)要少得多,因此这是一种快速获取的方法。来自一致性的价值并消除重复错误。

但是,我仍然相信完成的系统通常会通过减少总代码来改进。 如果不出意外,它的内存占用率几乎总是会小得多(虽然人们倾向于认为generics在这方面是免费的,但他们肯定不会这样做)。

如果您已经使用代码生成器实现了某些价值,那么这通常会花费您一些时间或金钱或善意来投资从生成的代码库中获取库。 然后,您可以逐步重新设计代码生成器以定位新库,并希望生成更少的代码。 冲洗并重复。

一个有趣的对应点已经提到我,并且在这个post中出现的是,丰富,复杂的参数库在学习曲线方面并不是最简单的,特别是那些没有深入沉浸在平台中的人。 将代码生成放在更简单的基本框架上可以产生冗长的代码,但它通常非常简单易读。

当然,如果您的生成器中存在大量差异和极其丰富的参数化,那么您可能只是为了模板中的复杂性而牺牲复杂性。 这是一条很容易滑入的路径,并且可以让维护变得非常令人头疼 – 请注意这一点。

生成代码不是邪恶的,它没有味道! 关键是在合适的时间生成正确的代码。 我认为T4很棒 – 我偶尔只使用它,但是当我这样做时非常有帮助。 无条件地说,生成代码是坏的无条件疯狂!

如果没有代码生成,Visual Studio 2010中的很大一部分将无法实现。 entity framework是不可能的。 将控件拖放到表单上的简单操作是不可能的,Linq也不可能。 要说不应该使用代码生成是很奇怪的,因为许多人甚至没有考虑它就使用它。

在我看来,代码生成器很好,只要代码生成是正常构建过程的一部分,而不是你运行一次然后保持其输出。 我添加了这个警告,因为如果只使用代码生成器一次并丢弃创建它的数据,那么你只是自动创建一个大规模的DRY违规和维护问题; 而每次生成代码实际上意味着无论你用什么做生成都是真正的源代码,生成的文件只是你应该忽略的中间编译阶段。

Lex和yacc是允许您以有效方式指定function并从中生成有效代码的工具的经典示例。 尝试手工完成工作会延长您的开发时间,并可能产生效率较低且可读性较低的代码。 虽然你当然可以将lex和yacc之类的东西直接合并到你的代码中并在运行时而不是在编译时完成它们的工作,但这肯定会给代码增加相当大的复杂性并减慢它的速度。 如果您确实需要在运行时更改规范,那么它可能是值得的,但在大多数正常情况下,使用lex / yacc在编译时为您生成代码是一个很大的胜利。

也许它有点苛刻,但对我来说代码生成气味。

使用该代码生成意味着存在许多潜在的共同原则,这些原则可以用“不要重复自己”的方式表达。 它可能需要更长的时间,但是当你最终只有包含真正改变的位的类时,基于包含机制的基础结构,它会令人满意。

对于generics……不,我没有太多的问题。 目前唯一不起作用的是说

 List a = new List(); List o = a; 

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

更多代码意味着更复杂。 更复杂意味着更多的地方可以隐藏错误,这意味着更长的修复周期,这反过来意味着整个项目的成本更高。

只要有可能,我宁愿最小化代码量以提供相同的function; 理想情况下使用动态(编程)方法而不是代码生成。 反思,属性,方面和generics为干旱战略提供了许多选择,将发电作为最后的手段。

对于我来说,代码生成对于语言,框架等中发现的许多问题都是一种解决方法。它们本身并不是邪恶的,我会说发布一种语言(C#)非常非常糟糕(即邪恶),并迫使你复制和粘贴(交换属性,事件触发,缺少宏)或使用魔法数字(wpf绑定)。

所以,我哭了,但我用它们,因为我必须这样做。

我已经使用T4代码生成和generics。 两者都很好,有利有弊,适合不同的目的。

在我的例子中,我使用T4基于数据库模式生成实体,DAL和BLL。 但是,DAL和BLL引用了我构建的迷你ORM,基于generics和reflection。 所以我认为你可以并排使用它们,只要你保持控制并保持它小而简单。

T4生成静态代码,而generics是动态的。 如果你使用generics,你使用reflection,据说效率低于“硬编码”解决方案。 当然,您可以缓存reflection结果。

关于“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(); 

generics和代码生成是两回事。 在某些情况下,您可以使用generics而不是代码生成,以及我认为您应该使用的代码。 对于其他情况,代码生成是一个强大的工具。

对于您只需要根据某些数据输入生成代码的所有情况,代码生成是可行的方法。 最明显的,但绝不是唯一的例子是Visual Studio中的表单编辑器。 这里的输入是设计器数据,输出是代码。 在这种情况下,generics实际上根本没有任何帮助,但是VS只是基于GUI布局生成代码是非常好的。

代码生成器可以被认为是代码气味,表明目标语言中存在缺陷或缺乏function。

例如,虽然这里曾经说过“持久存在的对象无法推广”,但最好将其视为“C#中的对象自动保存其数据不能在C#中推广”,因为我当然可以在Python中通过使用各种方法。

但是,Python方法可以通过使用operator [](method_name as string)在静态语言中进行模拟,根据需要,它可以返回一个函子或一个字符串。 不幸的是,该解决方案并不总是适用,并且返回仿函数可能不方便。

我要说的是,代码生成器通过为手头的特定问题提供更方便的专用语法来指示所选语言中的缺陷。

生成的代码的复制/粘贴类型(如ORMs make)也非常有用……

您可以创建数据库,然后让ORM生成以您喜欢的语言表示的数据库定义的副本。

当您更改原始定义(数据库),按下编译并且ORM(如果您有一个好的)可以重新生成定义的副本时,会带来优势。 现在,编译器类型检查器可以检查对数据库的所有引用,并且当您使用不再存在的表或列时,代码将无法编译。

想一想:如果我在代码中多次调用一个方法,我不是指我最初给这个方法的名字吗? 我一遍又一遍地重复这个名字……语言设计师认识到了这个问题,并提出了“类型安全”作为解决方案。 不删除副本(如DRY建议我们应该这样做),而是检查它们的正确性。

ORM生成的代码在引用表名和列名时带来了相同的解决方案。 不删除副本/引用,而是将数据库定义引入您的(类型安全)语言,您可以在其中引用类和属性。 与编译器类型检查一起,这以类似的方式解决了类似的问题:当您引用过时或拼写错误的表(类)或列(属性)时,保证编译时错误而不是运行时错误。

quote:我还没有真正找到有效的方法来构建模板,例如可以说实例化自己。 换句话说,我永远做不到:

返回新的T();

 public abstract class MehBase where TSelf : MehBase, new() { public static TSelf CreateOne() { return new TSelf(); } } public class Meh : MehBase, TParam1, TParam2> { public void Proof() { Meh instanceOfSelf1 = Meh.CreateOne(); Meh instanceOfSelf2 = Meh.CreateOne(); } } 

代码生成,如generics,模板和其他此类快捷方式,是一个强大的工具。 和大多数强大的工具一样,它扩大了用户的善意和邪恶 – 他们无法分开。

因此,如果你彻底了解你的代码生成器,预测它将产生的一切,为什么,并打算出于正当理由这样做,然后就可以了。 但是不要使用它(或任何其他技术)让你经过一个你不确定你要去哪里或者如何去那里的地方。

有些人认为,如果你解决了当前的问题并实施了一些行为,那你就是金色的。 对于下一位开发人员(可能是您自己),您在路径中留下了多少瑕疵和不透明度并不总是很明显。

为什么能够真正,快速地复制/粘贴,使其更容易被接受?

这是我能看到的代码生成的唯一理由。

即使发生器提供了您所需的所有灵活性,您仍然必须学习如何使用这种灵活性 – 这是另一层所需的学习和测试。

即使它在零时间运行,它仍然会使代码膨胀。

我推出了自己的数据访问类。 它知道关于连接,事务,存储过程参数等的一切,我只需要编写一次所有ADO.NET的东西。

现在已经很久了,因为我必须用连接对象来编写(甚至查看)任何东西,我很难记住手写的语法。