20 代码生成器
当木匠面临一再地重复制作同一样东西的任务时,他们会取巧。他们给自己建造夹具或模板。一旦他们做好了夹具,他们就可以反复制作某样工件。夹具带走了复杂性,降低了出错的机会,从而让工匠能够自由地专注于质量问题。
作为程序员,我们常常发现自己也处在同样的位置上。我们需要获得同一种功能,但却是在不同的语境中。我们需要在不同的地方重复信息。有时我们只是需要通过减少重复的打字,使自己免于患上腕部劳损综合症。
以与木匠在夹具上投入时间相同的方式,程序员可以构建代码生成器。一旦构建好,在整个项目生命期内都可以使用它,实际上没有任何代价。
|
提示29 |
|
|
Write Code That Writes Code |
|
代码生成器有两种主要类型:
1. 被动代码生成器只运行一次来生成结果。然后结果就变成了独立的——它与代码生成器分离了。在198页的邪恶的向导中讨论的向导,还有某些CASE工具,都是被动代码生成器的例子。
2. 主动代码生成器在每次需要其结果时被使用。结果是用过就扔的——它总是能由代码生成器重新生成。主动代码生成器为了生成其结果,常常要读取某种形式的脚本或控制文件。
被动代码生成器
被动代码生成器减少敲键次数。它们本质上是参数化模板,根据一组输入生成给定的输出形式。结果一经产生,就变成了项目中有充分资格的源文件;它将像任何其他文件一样被编辑、编译、置于源码控制之下。其来源将被忘记。
被动代码生成器有许多用途:
l 创建新的源文件。被动代码生成器可以生成模板、源码控制指示、版权说明以及项目中每个新文件的标准注释块。我们设置我们的编辑器,让它在我们每次创建新文件时做这样的工作:编辑新的Java程序,新的编辑器缓冲区将自动包含注释块、包指示以及已经填好的概要的类声明。
l 在编程语言之间进行一次性转换。我们开始撰写本书时使用的是troff系统,但我们在完成了15节以后转向了LaTeX。我们编写了一个代码生成器,读取troff源,并将其转换到LaTeX。其准确率大约是90%,余下部分我们用手工完成。这是被动代码生成器的一个有趣的特性:它们不必完全准确。你需要在你投入生成器的努力和你花在修正其输出上的精力之间进行权衡。
l 生成查找表及其他在运行时计算很昂贵的资源。许多早期的图形系统都使用预先计算的正弦和余弦值表,而不是在运行时计算三角函数。在典型情况下,这些表由被动代码生成器生成,然后拷贝到源文件中。
主动代码生成器
被动代码生成器只是一种便利手段,如果你想要遵循DRY原则,它们的“表亲”主动代码生成器却是必需品。通过主动代码生成器,你可以取某项知识的一种表示形式,将其转换为你的应用需要的所有形式。这不是重复,因为衍生出的形式可以用过就扔,并且是由代码生成器按需生成的(所以才会用主动这个词)。
无论何时你发现自己在设法让两种完全不同的环境一起工作,你都应该考虑使用主动代码生成器。
或许你在开发数据库应用。这里,你在处理两种环境——数据库和你用来访问它的编程语言。你有一个schema,你需要定义低级的结构,反映特定的数据库表的布局。你当然可以直接对其进行编码,但这违反了DRY原则:schema的知识就会在两个地方表示。当schema变化时,你需要记住改变相应的代码。如果某一列从表中被移走,而代码库却没有改变,甚至有可能连编译错误也没有。只有等你的测试开始失败时(或是用户打电话过来),你才会知道它。
另一种办法是使用主动代码生成器——如图3.3所示,读取schema,使用它生成结构的源码。现在,无论何时schema发生变化,用于访问它的代码也会自动变化。如果某一列被移走,那么它在结构中相应的字段也将消失,任何使用该列的更高级的代码就将无法通过编译。
|
图3.3 主动代码生成器根据数据库schema创建代码
|
你在编译时就能抓住错误,不用等到投入实际运行时。当然,只有在你让代码生成成为构建过程自身的一部分的情况下,这个方案才能工作。
使用代码生成器融合环境的另一个例子发生在不同的编程语言被用于同一个应用时。为了进行通信,每个代码库将需要某些公共信息——例如,数据结构、消息格式、以及字段名。要使用代码生成器,而不是重复这些信息。有时你可以从一种语言的源文件中解析出信息,并将其用于生成第二种语言的代码。但如下一页的图3.4所示,用更简单、语言中立的表示形式来表示它,并为两种语言生成代码,常常更简单。再看一看268页上练习13的解答,里面有怎样把对平板文件表示的解析与代码生成分离开来的例子。
代码生成不一定要很复杂
所有这些关于“主动这个”和“被动那个”的谈论可能会给你留下这样的印象:代码生成器是复杂的东西。它们不一定要很复杂。最复杂的部分通常是负责分析输入文件的解析器。让输入格式保持简单,代码生成器就会变得简单。看一看练习13的解答(286页):实际的代码生成基本上是print语句。
|
图3.4 根据语言中立的表示生成代码。在输入文件中,以‘M’开始的行标志着消息定义的开始。‘F’行定义字段,‘E’是消息的结束
|
代码生成器不一定要生成代码
尽管本节的许多例子给出的是生成程序源码的代码生成器,事情并不是非如此不可。你可以用代码生成器生成几乎任何输出:HTML、XML、纯文本——可能成为你的项目中别处输入的任何文本。
相关内容:
l 重复的危害,26页
l 纯文本的力量,73页
l 邪恶的向导,198页
l 无处不在的自动化,230页
练习
13. 编写一个代码生成器,读取图3.4中的输入文件,以你选择的两种语言生成输出。设法使它容易增加新语言。 (解答在286页)









