我为什么要使用代码生成器

时间:2010-03-22 21:59:22

标签: language-agnostic code-generation

我最近遇到过这个话题,无法理解为什么需要它们。

你能解释为什么我应该在我的项目中使用它们以及它们如何能够缓解我的生活。

示例会很棒,而且我可以从中学到这个主题。

15 个答案:

答案 0 :(得分:19)

至少你从正确的角度构思了问题=)

使用代码生成器的通常原因是生产力和一致性,因为他们假设解决一致且重复的问题是在其上抛出更多代码。我认为,无论何时考虑代码生成,请查看为什么要生成代码,看看是否可以通过其他方式解决问题。

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

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

宣传代码生成的另一个原因来自MDA / MDE世界(模型驱动架构/工程)。我不会在这方面投入太多资金,而是提供一些表达不好的论点,我只是想选择其他人 - http://www.infoq.com/articles/8-reasons-why-MDE-fails

IMHO代码生成是一个非常狭窄的问题集中的唯一解决方案,无论何时你考虑它,你应该再看看你想要解决的真正的问题,看看是否有一个更好的解决方案。

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

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

有一些代码生成器可以在这个空间中很好地工作(LightSpeed设计师是我能想到的唯一一个),它可以充当设计界面的引擎,但通常 这些代码生成器生成无法维护的可怕代码(例如winforms / webforms设计图面,EF1设计图面),因此可以快速消除因使用代码生成器而获得的任何生产力优势。

答案 1 :(得分:17)

嗯,它是:

  • 你写了250个课程,几乎完全一样,但略有不同,例如做数据访问;
  • ,你需要一个星期的时间,而且很无聊,容易出错

OR:

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

所以代码生成器会给你:

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

优秀的例子:

    Damien Guard的
  • Linq-to-SQL T4 templates使用最佳的Visual Studio 2008秘密 - T4模板

  • PLINQO - 同样的事情,但对于Codesmith的生成器

无数更多......

答案 2 :(得分:6)

任何时候你需要生成大量的重复样板代码,代码生成器就是这个工作的人。上次我使用代码生成器时是为项目创建自定义数据访问层,其中各种CRUD操作的框架是基于对象模型创建的。我没有手动编码所有这些类,而是组建了一个模板驱动的代码生成器(使用StringTemplate)来为我创建。该程序的优点是:

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

答案 3 :(得分:4)

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

答案 4 :(得分:4)

真的,当你使用几乎任何编程语言时,你使用的是“代码生成器”(程序集或机器代码除外)。我经常写几个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

现在,你宁愿维持哪一个?它们在所描述的内容上都是等价的,但是它们的处理时间更长,更烦人。

答案 5 :(得分:4)

这个典型的例子是数据访问,但我有另一个例子。我已经开发了一个通过串口,套接字等进行通信的消息传递系统,我发现我不得不一遍又一遍地写这样的类:

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天的任务(好吧,加上编写代码所需的几天)。

结论:代码生成仅适用于相对较少的问题,但是当您能够使用代码时,它将保存您的理智

答案 6 :(得分:3)

如果符合以下条件,代码生成器非常有用:

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

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

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

答案 7 :(得分:2)

如何好好利用代码生成器?

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

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

答案 8 :(得分:2)

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

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

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

最后,请记住“垃圾输入=垃圾输出”。如果您的整个数据层是同构的并且不是从数据库中抽象出来的,那么请问问自己为什么要打扰数据层。 (除非你需要看起来富有成效:))

答案 9 :(得分:2)

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

一次性项目效率不高,但即使对于这些项目,模型驱动的开发也会引入额外的规则,更好地理解所采用的解决方案,并且通常是更好的演化路径。

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

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

真正需要的是思维方式的改变(比如90年代所需的OOP)......

答案 10 :(得分:1)

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

非常好,真的。

答案 11 :(得分:1)

代码生成有很多用途。

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

  • GWT - Java -> Javascript
  • MonoTouch - C# -> Objective-C

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

  • 编译器
  • 域特定语言

自动执行重复性任务。

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

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

编程语言的当前状态决不会发挥其全部潜力,而且永远不会。我们将一直抽象,以达到比我们今天所处的更高的水平。代码生成是我们的目标。我们可以依靠语言创建者为我们创建抽象,或者自己创建抽象。今天的语言非常复杂,可以让任何人轻松完成。

答案 12 :(得分:0)

如果使用代码生成器您也打算使用片段,请尝试在每次在类中键入ctor + TAB和编写构造函数之间的区别。或者使用代码片段创建与包含许多值的枚举相关的switch语句,以查看您获得多少时间。

答案 13 :(得分:0)

如果您是由LOC支付并为不了解代码生成的人工作,那么这很有意义。顺便说一句,这不是一个玩笑 - 我与多个程序员合作,他们正是为了这个目的而采用这种技术。没有人正式获得LOC的正式支付(我知道,无论如何),但通常希望程序员能够提高工作效率,并且制作大量代码可以让某些人看起来高效。

作为一个只是略微切向的点,我认为这也解释了一些编码人员倾向于将单个逻辑单元代码分解为尽可能多的不同类(曾经使用LastName继承项目,{{1 }和FirstName类?)。

答案 14 :(得分:0)

这是一些异端邪说:

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

相关问题