【问题标题】:Why should I use code generators为什么我应该使用代码生成器
【发布时间】:2011-01-30 13:34:22
【问题描述】:

我最近遇到了这个话题,不明白为什么需要它们。

你能解释一下为什么我应该在我的项目中使用它们以及它们如何让我的生活更轻松。

示例会很棒,我可以从哪里学到更多关于这个主题的知识。

【问题讨论】:

  • 问题是为什么不;真的!为什么要使用编译器?
  • 似乎很多时候需要代码生成,其背后存在一些难以解决的问题。它通常也与数据层相关。
  • @Mark NP“SOAP 问题”可能正在发挥作用。

标签: language-agnostic code-generation


【解决方案1】:

如何很好地使用代码生成器的示例?

这使用 t4 模板(Visual Studio 内置的代码生成器)从 .less 文件生成压缩 css: http://haacked.com/archive/2009/12/02/t4-template-for-less-css.aspx

基本上,它允许您在样式表中定义变量、真正的继承,甚至行为,然后在编译时从中创建正常的 css。

【讨论】:

    【解决方案2】:

    嗯,要么是:

    • 你写了 250 个类,几乎都一样,但略有不同,例如进行数据访问;需要你一周的时间,而且很无聊、容易出错并且很烦人

    或者:

    • 您投入 30 分钟来生成代码模板,然后让生成引擎在另外 30 分钟内处理繁琐的工作

    所以代码生成器为您提供:

    • 速度
    • 重现性
    • 错误少了很多
    • 更多空闲时间! :-)

    优秀的例子:

    • Linq-to-SQL T4 templates 由 Damien Guard 使用,使用保存最完好的 Visual Studio 2008 机密 - T4 模板,在您的数据库模型中为每个类生成一个单独的文件

    • PLINQO - 相同,但用于 Codesmith 的生成器

    还有无数......

    【讨论】:

    • “你写了 250 个类,几乎都一样,但略有不同”我不禁想到,如果我发现自己处于这个位置,我会“做错了” .在此过程中的某个地方,我应该退后一步,试图弄清楚为什么我如此重复自己并构建更好的东西(创建一个父类,或其他东西)。
    • @MGOwen:但继承并不总是有帮助。以这种方式排除通用代码并不总是可能的。考虑数据层的情况,其中表示两个实体的类之间的差异将包括每个实体的列集。您无法使用继承将其排除在外。
    • @MGOwen:如果您有一个包含 250 个表的数据库,并且需要编写某种通用数据访问存储库,该怎么办?所有的表都基本相同 - 列和行 - 但列因表而异。不确定在这种情况下继承是否有很大帮助 - 但您仍然需要在 250 个数据库表和内存中的某些对象模型之间编写一个映射层......
    • Polaris878:我已经好几年没有奢侈地定义自己的数据库了。在大多数情况下,我必须与其他供应商的系统互操作,而我无法更改他们的架构或制作存储过程。为什么我不使用 codegen?
    • @gabe:我认为他的意思是您的对象模型应该是数据库的抽象,而不是直接映射到它。
    【解决方案3】:

    使用 GUI 构建器为您生成代码是一种常见做法。多亏了这一点,您无需手动创建所有小部件。您只需拖放它们并使用生成的代码。对于简单的小部件,这确实节省了时间(我在 wxWidgets 中经常使用它)。

    【讨论】:

      【解决方案4】:

      只要您需要生成大量重复的样板代码,代码生成器就是您的最佳选择。上次我使用代码生成器是在为项目创建自定义数据访问层时,其中各种 CRUD 操作的骨架是基于对象模型创建的。我没有手动编写所有这些类,而是将模板驱动的代码生成器(使用StringTemplate)放在一起为我制作。该程序的优点是:

      • 速度更快(要生成大量代码)
      • 我可以一时兴起重新生成代码,以防我检测到错误(代码有时在早期版本中可能存在错误)
      • 不易出错;当我们在生成的代码中出现错误时,它无处不在,这意味着它更有可能被发现(并且,如前一点所述,修复它并重新生成代码很容易) .

      【讨论】:

        【解决方案5】:

        如果使用代码生成器您还打算使用 sn-ps,请尝试在您的类中每次键入 ctor + TAB 和编写构造函数之间的区别。或者检查使用 sn-p 创建与具有许多值的枚举相关的 switch 语句获得了多少时间。

        【讨论】:

          【解决方案6】:

          至少你已经从正确的角度提出了这个问题 =)

          使用代码生成器的通常原因是生产力和一致性,因为它们假设解决一致和重复性问题的方法是向其添加更多代码。我认为,任何时候你在考虑代码生成,看看你为什么要生成代码,看看你是否可以通过其他方式解决问题。

          一个典型的例子是数据访问;您可以生成 250 个类(架构中的每个表 1 个)有效地创建表网关解决方案,或者您可以构建更像域模型的东西并使用 NHibernate / ActiveRecord / LightSpeed / [pick your orm] 来映射丰富的域模型到数据库中。

          虽然手卷解决方案和 ORM 都是有效的代码生成器,但主要区别在于生成代码的时间。对于 ORM,它是在运行时发生的隐式步骤,因此本质上是单向的。手卷解决方案需要在开发过程中生成代码的明确步骤,并且生成的类可能需要在某些时候进行自定义,因此在重新生成代码时会产生问题。开发过程中必须执行的显式步骤会在开发过程中引入摩擦,并经常导致代码违反 DRY(尽管有些人认为生成的代码永远不会违反 DRY)。

          吹捧代码生成的另一个原因来自 MDA/MDE 世界(模型驱动架构/工程)。我对此并没有太多关注,但我没有提供一些表达不佳的论点,我只是打算选择其他人 - http://www.infoq.com/articles/8-reasons-why-MDE-fails

          恕我直言,代码生成是极少数问题中唯一的解决方案,每当您考虑它时,您可能应该再看看您正在尝试解决的真正问题,看看是否有更好的解决方案。

          真正提高生产力的一种代码生成类型是“微代码生成”,其中使用宏和模板允许开发人员直接在 IDE 中生成新代码,并通过占位符(例如命名空间/类名等)。这种代码生成是 resharper 的一个特性,我每天都大量使用它。在大多数大规模代码生成失败的情况下,微生成受益的原因是生成的代码不会绑定到必须保持同步的任何其他资源,因此一旦生成代码,它就像所有其他代码一样解决办法。

          @约翰
          将“基本类”的创建从 IDE 转移到 xml / dsl 中经常出现在进行大爆炸式开发时——一个典型的例子是开发人员试图将数据库逆向工程为域模型。除非代码生成器写得很好,否则它只会给开发人员带来额外的负担,因为每次他们需要更新域模型时,他们要么必须进行上下文切换并更新 xml / dsl,要么必须扩展域模型然后将这些更改移植回 xml / dsl(有效地完成了两次工作)。

          有一些代码生成器在这个领域工作得很好(LightSpeed 设计器是我能想到的唯一一个 atm),它们充当设计表面的引擎,但通常 这些代码生成器会生成无法维护的糟糕代码(例如 winforms/webforms 设计界面、EF1 设计界面),因此会迅速抵消从使用代码生成器获得的任何生产力优势。

          【讨论】:

          • 你的领域模型没有重复代码吗?真的吗?您不会对每个字符串属性使用相同的代码吗?
          • @John - 不知道你在这里得到了什么,你的论点缺乏实质内容。 @Michael - 我只对我需要的东西进行建模,如果我需要一个大型域,我会对其进行建模,但存储问题不应该泄漏到域问题中,这就是从数据库进行代码生成时会发生的情况。 NH + FNH(或 ConfORM)允许您在代码中表达模型和映射,并且是一种非常强大的方法,尤其是与约定优于配置时。 NH 确实允许生成 DDL 来创建数据库,这是代码 (SQL) 生成的少数“适当”案例之一。
          • @Neal:我没有争论,我问了一个你没有回答的问题。我也没有说数据库中的代码生成。代码生成可能来自 XML 或您的域模型的其他表示。
          • @Neal:我不知道您使用了哪些糟糕的代码生成工具,但没有理由让它们不灵活。假设可以将类之间的差异分解为声明性数据,那么重复代码结构是生成代码的完美理由。
          • @Neal:我没有做出这样的笼统声明。我一直在说代码生成对于从您的域模型(在 XML 或某些 DSL 中)创建基本类很有价值。然后,您可以使用域逻辑扩展域类,但创建类的基本结构是一个机械过程,并且可以机械地执行。
          【解决方案7】:

          如果您由 LOC 支付报酬并为不了解什么是代码生成的人工作,那么这很有意义。顺便说一句,这不是玩笑——我曾与不止一个程序员一起使用这种技术来达到这个目的。没有人再正式从 LOC 获得报酬(我知道,无论如何),但程序员通常被期望具有生产力,并且大量编写代码可以让某人看起来有生产力。

          作为一个稍微切题的点,我认为这也解释了一些编码人员倾向于将单个逻辑代码单元分解为尽可能多的不同类(曾经继承具有LastNameFirstName 和 @ 的项目) 987654323@ 上课?)。

          【讨论】:

            【解决方案8】:

            真的,当您使用几乎任何编程语言时,您都在使用“代码生成器”(汇编代码或机器代码除外。)我经常编写 200 行的小脚本来生成几千行 C。也是您可以获得的有助于生成某些类型代码的软件(例如,yacc 和 lex 用于生成解析器以创建编程语言。)

            这里的关键是将代码生成器的输入视为实际的源代码,并将其输出的内容视为构建过程的一部分。在这种情况下,您使用的是一种高级语言,需要处理的实际代码行数更少。

            例如,这是我(没有)在修改基于 Quake2 的游戏引擎 CRX 的工作中编写的一个非常长且乏味的文件。它从两个标头中获取所有#defined 常量的整数值,并将它们转换为“cvars”(游戏内控制台的变量)。
            http://meliaserlow.dyndns.tv:8000/alienarena/lua_source/game/cvar_constants.c

            这是在编译时生成该代码的简短 Bash 脚本:
            http://meliaserlow.dyndns.tv:8000/alienarena/lua_source/autogen/constant_cvars.sh

            现在,您更愿意维护哪个?就它们所描述的而言,它们都是等价的,但其中一个要长得多,处理起来也更烦人。

            【讨论】:

              【解决方案9】:

              对于域驱动或多层应用,代码生成是创建初始模型或数据访问层的好方法。它可以在 30 秒内生成 250 个实体类(或者在我的情况下,在 5 分钟内生成 750 个类)。这让程序员可以专注于通过关系、业务规则或 MVC 中的派生视图来增强模型。

              这里的关键是当我说 initial 模型时。如果您依靠代码生成来维护代码,那么真正的工作是在模板中完成的。 (正如 Max E 所说。)请注意这一点,因为维护基于模板的代码存在风险和复杂性。

              如果您只想“自动创建”数据层以便“在 2 天内使 GUI 工作”,那么我建议您使用面向数据驱动或两个数据驱动的产品/工具集层级应用场景。

              最后,记住“garbage in=garbage out”。如果您的整个数据层是同质的并且没有从数据库中抽象出来,请问问自己为什么要费心拥有一个数据层。 (除非你需要看起来富有成效:))

              【讨论】:

              • “5 分钟 750 节课”,现在这是一个大型领域模型!
              【解决方案10】:

              实际上,我正在为我被聘用的项目使用的代码生成器添加最后润色。我们有一个巨大的 XML 定义文件,在一天的工作中,我能够生成 500 多个 C# 类。如果我想为所有类添加功能,比如说我想为所有属性添加一个属性。我只是将它添加到我的代码生成中,点击 go,然后 bam!我已经完成了。

              真的很好,真的。

              【讨论】:

                【解决方案11】:

                这方面的典型例子是数据访问,但我还有另一个例子。我曾在一个通过串行端口、套接字等进行通信的消息传递系统上工作,但我发现我不得不一遍又一遍地编写这样的类:

                public class FooMessage
                {
                    public FooMessage()
                    {
                    }
                
                    public FooMessage(int bar, string baz, DateTime blah)
                    {
                        this.Bar = bar;
                        this.Baz = baz;
                        this.Blah = blah;
                    }
                
                    public void Read(BinaryReader reader)
                    {
                        this.Bar = reader.ReadInt32();
                        this.Baz = Encoding.ASCII.GetString(reader.ReadBytes(30));
                        this.Blah = new DateTime(reader.ReadInt16(), reader.ReadByte(),
                            reader.ReadByte());
                    }
                
                    public void Write(BinaryWriter writer)
                    {
                        writer.Write(this.Bar);
                        writer.Write(Encoding.ASCII.GetBytes(
                            this.Baz.PadRight(30).Substring(0, 30)));
                        writer.Write((Int16)this.Blah.Year);
                        writer.Write((byte)this.Blah.Month);
                        writer.Write((byte)this.Blah.Day);
                    }
                
                    public int Bar { get; set; }
                    public string Baz { get; set; }
                    public DateTime Blah { get; set; }
                }
                

                如果你愿意的话,试着想象一下,为不少于 300 种不同类型的消息编写这段代码。一遍又一遍地编写同样无聊、乏味、容易出错的代码。在我决定只写一个代码生成器对我来说更容易之前,我设法写了其中的 3 个,所以我这样做了。

                我不会发布代码生成代码,这是很多神秘的 CodeDom 东西,但最重要的是我能够将整个系统压缩成一个 XML 文件:

                <Messages>
                    <Message ID="12345" Name="Foo">
                        <ByteField Name="Bar"/>
                        <TextField Name="Baz" Length="30"/>
                        <DateTimeField Name="Blah" Precision="Day"/>
                    </Message>
                    (More messages)
                </Messages>
                

                这容易多少? (反问。)我终于可以呼吸了。我什至添加了一些花里胡哨的东西,所以它能够生成一个“代理”,我可以编写这样的代码:

                var p = new MyMessagingProtocol(...);
                SetFooResult result = p.SetFoo(3, "Hello", DateTime.Today);
                

                最后我想说,这节省了我编写 7500 行代码的时间,并将 3 周的任务变成了 3 天的任务(好吧,加上编写代码生成所需的几天)。

                结论:代码生成只适用于相对少数的问题,但当你能够使用一个时,它会让你保持理智

                【讨论】:

                • 我使用 Google 的 protobuf 库 (code.google.com/p/protobuf) 来生成高性能的消息序列化/反序列化代码。在这种情况下强烈推荐它。
                • @Neal:现在扩展您的解决方案以处理子消息、多态消息(当然他们重复使用消息 ID)消息序列、小端/大端编码、自定义文本编码、校验和以及进入现实消息系统的所有其他内容,并实际编写序列化程序,而不仅仅是示例消息类。而且您仍然有一个解决方案明显更冗长、更不透明、更难扩展(即使用部分类)、更容易出错(不能强制执行特定类型,即使用 XSD),而且只有50% 完成(没有请求/响应配对或代理)。
                • 程序员往往非常乐观——他们观察任何复杂的系统,做出十几个简化的假设,然后说“呸,我能做到!”不要以为我没有彻底调查基于反射的序列化程序的可能性。哦,请记住,这必须支持 .NET 2.0 框架,因此不允许使用 Linq 或表达式树或任何有趣的东西。
                • @Neal:没有没有额外的上下文。代码生成推断它。多态消息使用IsCustom = "True",它们生成部分类,序列消息使用IsSequence = "True",因此代码生成器使用可枚举/列表。生成代理不需要更多信息,语法比代码简洁得多,XSD 提供编译时验证(属性映射不可能做到),最重要的是,生成的序列化代码是几个订单比反射更有效,这对于低级协议很重要。
                • @Neal:它们不是“秘密”,但我受到多个 NDA 的约束,这可能从匿名代码中显而易见。需要明确的是,我并没有声称自己完成了任何艰巨的任务。我的断言很简单,开发代码生成比开发完整的序列化库要快。我认为您可能高估了将 XML 转换为 CodeDom 的难度;整个工具不超过500 LOC左右。
                【解决方案12】:

                代码生成器在以下情况下很有用:

                1. 编写和维护代码生成器的成本低于编写和维护它正在替换的重复的成本。

                2. 使用代码生成器获得的一致性将在一定程度上减少错误,使其值得。

                3. 调试生成代码的额外问题不会使调试效率低到足以超过 1 和 2 的好处。

                【讨论】:

                  【解决方案13】:

                  这里每个人都在谈论简单的代码生成,但是模型驱动的代码生成(如 MDSD 或 DSM)呢?这可以帮助您超越简单的 ORM/成员访问器/样板生成器,并为您的问题域生成更高级别概念的代码。

                  这对于一次性项目来说并不高效,但即使对于这些项目,模型驱动的开发也引入了额外的纪律,更好地理解所采用的解决方案,并且通常是更好的发展路径。

                  就像 3GL 和 OOP 通过基于更高级别规范生成大量汇编代码来增加抽象一样,模型驱动开发允许我们再次提高抽象级别,同时又提高了生产力。

                  MetaCase 的 MetaEdit+(成熟)和 Isomeris 的 ABSE(我的项目,处于 alpha 阶段,信息位于 http://www.abse.info)是模型驱动代码生成最前沿的两种技术。

                  真正需要的是改变思维方式(就像 90 年代需要的 OOP)...

                  【讨论】:

                  • @Rui:虽然很容易查看您的个人资料并了解您与 ABSE 的关联,但我建议您在回答中明确说明,这样您就不会被视为垃圾邮件发送者。
                  • 对不起,约翰。你说得对。我通常将 ABSE 称为“我的项目”(查看我的其他帖子),但不知何故我这次错过了。我已经编辑了答案。
                  【解决方案14】:

                  代码生成有很多用途。

                  用熟悉的语言编写代码并为不同的目标语言生成代码。

                  • GWT - Java -&gt; Javascript
                  • MonoTouch - C# -&gt; Objective-C

                  以更高的抽象级别编写代码。

                  • 编译器
                  • 领域特定语言

                  自动化重复性任务。

                  • 数据访问层
                  • 初始数据模型

                  忽略所有先入为主的代码生成概念,它基本上是将一种表示(通常是更高级别)转换为另一种(通常是较低级别)。牢记这个定义,它是一个非常强大的工具。

                  编程语言的当前状态还没有完全发挥其潜力,而且永远也不会。我们将始终进行抽象以达到比我们今天所处的更高的水平。代码生成是我们到达那里的原因。我们可以依赖语言创建者为我们创建抽象,也可以自己做。今天的语言已经足够复杂,任何人都可以轻松做到。

                  【讨论】:

                    【解决方案15】:

                    这里有一些异端:

                    如果一个任务是如此愚蠢以至于它可以在程序编写时自动化(即源代码可以由脚本生成,比如说 XML),那么同样也可以在运行时完成(即一些表示XML 可以在运行时解释)或使用一些元编程。所以本质上,程序员是懒惰的,没有尝试解决真正的问题,而是采取了简单的方法,编写了一个代码生成器。在 Java/C# 中看反射,在 C++ 中看模板

                    【讨论】:

                    • 虽然我非常喜欢反射和元编程,但在运行时执行它通常会影响性能
                    • 真的。但是对于运行在 3+GHz 的 n 核 CPU,在客户端就不再那么重要了。
                    • 在客户端的开发环境中,是的,完全可以接受。
                    • 取决于平台。如果我在 .NET 中尝试这个“运行时”的东西,我会让我的框架在运行时生成并编译代码,之后我将运行编译后的代码。
                    猜你喜欢
                    • 1970-01-01
                    • 2010-09-13
                    • 1970-01-01
                    • 1970-01-01
                    • 2019-11-28
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多