Pierre Couzy
WinWise

摘要:代码生成器是您日常生活中的一部分,即使您没有意识到这一点。Pierre Couzy 说明了如何在项目中利用它们。

*
本页内容
简介 简介
工具和示例 工具和示例
版本控制 版本控制
小结 小结

简介

假设您在一家由 DBA 统治一切的公司里工作:您不能生成只是“到 Oracle 那里去取一些记录”的应用程序。您只能依靠存储过程,因为在该级别存在安全层。

生成应用程序通常涉及到下列步骤:

1.

创建一批存储过程。

2.

创建能够与存储过程通信的 C# 类。

3.

创建将管理 Microsoft ASP.NET 表单或 Microsoft Windows 窗体或者形成另一个层的更高级别的类。

步骤 1 和步骤 2 是密切相关的:它们共享大量结构,它们消耗另一个步骤所产生的东西,等等。问题在于,您拥有两种不同的语言(在该示例中,为 PL-SQL 和 C#),并且无法从其中一种语言引用另一种语言。

您说这没有什么大不了的?团队中的第一个开发人员将使一切保持正常,因为他了解 .NET、PL/SQL 以及安全模型的本质。他可能将编写两个东西:首先是一组帮助器类,然后是一个有效的示例 — 于是,其他开发人员将使用这些帮助器类,复制/粘贴有效示例,并针对他们的需要进行修改。

如果您已经在软件行业中工作了足够长的时间,就会知道接下来将会发生什么事情:随着要求的增加,帮助器类将慢慢变大,并且其中一些将很快过时。当然,没有人敢于修改它们,因为这可能会毁坏较旧的项目。同时,开发人员将重用不再与新规则同步的有效示例,并且他们将没有办法了解哪些部分仍在使用,以及哪些部分可以毫无风险地丢弃。您将陷入一片混乱之中(复制/粘贴编程)。

有很多克服这一复杂性的办法,而我将介绍您可能已经忽视的一种有用的办法 — 代码生成器。其实您已经使用了它们,即使您没有意识到这一点:每当您创建一个新的 ASPX 页时,隐藏机制就会将它转换为 C# 或 Microsoft Visual Basic 类。当您在项目中引用 COM 组件、Web 服务甚至数据结构(类型化数据集)时,会发生相同的事情 — 有一个类被自动生成,它隐藏了您不希望了解的复杂性。

工具和示例

您将遇到的第一批代码生成器只是一些隐藏复杂性的工具。它们不允许您解释如何生成代码,并且不让您随后更改生成的代码(它们将清除您进行的更新)。

例如,“Add Web Reference Wizard”甚至不向您显示插入到项目中的代码(除非您请求它显示)。


图 1. 显示为 Web 引用生成的代码

Windows 窗体设计器也是一个代码生成器。在这里,您可以查看代码,但最好不要动它。

 1     #region Windows Form Designer generated code
 2      /// <summary>
 3      /// Required method for Designer support - do not modify
 4      /// the contents of this method with the code editor.
 5      /// </summary>

 6      private void InitializeComponent()
 7      {
 8         this.button1 = new System.Windows.Forms.Button();
 9         this.SuspendLayout();
10

让我们更进一步地探讨该问题:代码生成器可以提供格式规范的主干,以供您随后添加自己的代码。您可能知道 Reflector,但您是否知道 Reflector 的一些外接程序 (http://www.dotnetwiki.org/Default.aspx?tabid=52) 包含代码生成器?它们使您可以执行以下工作:


图 2. 使用 Reflector 生成代码

当您按下“Generate”按钮时,将创建一个新文件:

 1// Generated by Refly
 2namespace MyTestNameSpace
 3{
 4    using System;
 5    
 6    
 7    /// <summary>Test fixture for the <see cref="SomeBusinessClass"/> class
 8    ///</summary>
 9    /// <remarks />

10    [TestFixture()]
11    public class SomeBusinessClassTest
12    {
13        
14        /// <summary />
15        /// <remarks />

16        private SomeBusinessClass _someBusinessClass = null;
17        
18        /// <summary />
19        /// <remarks />

20        public virtual SomeBusinessClass SomeBusinessClass
21        {
22            get
23            {
24                return this._someBusinessClass;
25            }

26        }

27        
28        /// <summary>Tests the EstimateSomeNumber method</summary>
29        /// <remarks>
30        ///   <para>Test coverage (estimated): 100,0%</para>
31        ///   <para>Target path:</para>
32        ///   <code>/* 0 */ return 0x2a;
33        /// </code>
34        /// </remarks>

35        [Test()]
36        [Ignore()]
37        public virtual void EstimateSomeNumber0()
38        {
39            throw new System.NotImplementedException();
40        }

41        
42        /// <summary>Tests the IsSomethingAlreadyInDataBase method</summary>
43        /// <remarks>
44        ///   <para>Test coverage (estimated): 100,0%</para>
45        ///   <para>Target path:</para>
46        ///   <code>/* 0 */ return false;
47        /// </code>
48        /// </remarks>

49        [Test()]
50        [Ignore()]
51        public virtual void IsSomethingAlreadyInDataBase0()
52        {
53            throw new System.NotImplementedException();
54        }

55        
56        /// <summary>Sets up the fixture</summary>
57        /// <remarks />

58        [SetUp()]
59        public virtual void SetUp()
60        {
61            throw new System.NotImplementedException();
62        }

63        
64        /// <summary>Releases resource allocated in the fixture</summary>
65        /// <remarks />

66        [TearDown()]
67        public virtual void TearDown()
68        {
69            throw new System.NotImplementedException();
70        }

71    }

72}

73

您不了解有关“单元测试”的任何内容,但是您仍然能够生成有效的测试类。与上一个示例的不同之处在于,这一次您必须在生成的类的内部编码。可是,我们仍然无法创建我们自己的模板。

下一个步骤是创建我们自己的代码生成器。我们需要的全部东西就是一种根据一组常用信息(例如,将要获得的列的名称、要用来请求信息的方式、要允许的事务种类等等)来生成文本文件(C#、PL-SQL 等等)的方式。当然,您无法生成所有东西,因此需要一个能够生成自定义主干的工具,而某个开发人员随后将添加特定的实现细节。

该工具可以是 Perl、Microsoft VBScript 或普通的旧式 ASP(就本文而言)— 毕竟,它是一种可以根据参数生成文本文件的很好的工具。您可能使用 ASP 生成 HTML 文件,但是它还适用于生成 Microsoft Excel 或 CSV、WML — 因此,为什么不用它来生成 T-SQL 或 C# 呢?

您的 ASP 文件可能如下所示:

 1<%@ Language=VBScript %>
 2<%if request("Generate").count = 0 then%>
 3<HTML><BODY>
 4   <form method=post>
 5   <P>Property name :<INPUT name=PropertyName></P>
 6   <P>What is the type of your property ? <INPUT name=PropertyType></P>
 7   <P>Read only <input type=checkbox name=ReadOnly value=true></P>
 8   <P><INPUT type=submit value="Generate !" name=Generate></P>
 9   </form>
10</BODY></HTML>
11<else 
12dim PropertyName, PropertyType, ReadOnly, PropertyModifier
13PropertyName = Request("PropertyName")
14PropertyType = Request("PropertyType")
15ReadOnly = (Request("ReadOnly")="true")
16if ReadOnly then PropertyModifier = "ReadOnly" else PropertyModifier = ""
17Response.ContentType = "text/plain"
18%>
19
20Private _<%=PropertyName%> as <%=PropertyType%>
21
22Public <%=propertyModifier%> Property <%=PropertyName%> as <%=PropertyType%>
23
24   Get
25      Return _<%=PropertyName%>
26   End Get
27<%if not ReadOnly then%>
28   Set (ByVal Value as <%=PropertyType%>)
29      _<%=PropertyName%> = Value
30   End Set
31<%end if%>
32End Property
33<%end if%>
34
35

当您执行该文件时,您将获得如下所示的内容:

图 3. 生成的 Web 页


图 4. 生成的代码

您还可以在 Internet 上找到代码生成器。我通常使用 CodeSmith;它是免费的并且易于理解,但是您可以找到很多其他的代码生成器。图 5 是一个屏幕截图,它显示了一种等待泛型的聪明方式;它生成一个强类型的哈希表。


图 5. CodeSmith

这些工具通常接受两个输入 — 一个模板文件(像我们的 ASP 文件)和一个 XML 参数文件(像表单的内容),并产生一个新文件。

开发人员被赋予这些模板,并且他们通常通过同一组参数来使用两个不同的模板。这样,他们将相同的信息给予这两个模板,并且获得共享信息的一个 T-SQL 文件和一个 C# 文件,同时无需承担遗忘或键入错误的风险;这样就为您完成了此工作的乏味部分。

版本控制

当然,在使用模板和自动生成的代码时,事情会变得复杂。在现实生活中,模板会演化(例如,您可能希望赋予 C# 类一种调用某些存储过程的异步方式);如果是这样,那么您需要一种相应的办法,以应付开发人员在主干生成之后放入的实现。

为此,您必须确保开发人员添加的代码独立于您的模板所生成的代码。实现此目的最简单的技术是创建两个不同的类:一个用于生成的代码,另一个从第一个继承,但将不生成。


图 6. 对生成的代码进行版本控制

由于该技术所具有的继承机制,因此它在您生成 .NET 代码时非常适用;但是,如果您要生成其他种类的代码(VBScript、SQL),则将无法以这种方式工作。在那种情况下,您主要依靠自己来解决问题。我使用的机制很简单:我在生成的代码中提供了占位符(在 C# 中为区域;在 SQL 中为特定注释所围绕的块),并且当代码被重新生成时,只有这些区域中的代码被保留。传输自定义代码本身同样简单:模板始终接受 PreviousFile 参数。当该参数存在时,将分析以前的文件以获得自定义代码,并将其重新插入到当前的生成文件中。

如果没有办法恢复原样,则千万不要弄乱生成的代码:您将发现自己正在进行复制/粘贴编程。

小结

下面是在使用代码生成器时需要记住的一些事情:

如果不打算对生成的代码进行修改,请预先声明。

不要忘记,开发人员没有时间了解生成代码的内部结构,因此请为他们提供帮助,并且尽可能详细地对入口点进行说明。

考虑进行版本控制,并明确说明您将允许人们做什么。或许您希望开发人员只在代码的特定区域中添加自定义代码,或者您希望他们从您的类继承。

模板的新版本应当始终能够重新生成旧版本所生成的代码。如果新版本需要比旧版本更多的代码以便正确工作,则请将需要代码的位置变得明显一些,并且插入一些能够生成错误的代码(在编译时 — 如果可能的话)。

如果您决定中断模板的版本控制机制(例如,通过添加在应用新版本时无法恢复原样的代码),请保留该模板和您使用的参数的副本。如果您需要为某个旧项目生成另一个类似的代码文件,并且您已经丢失了在开发该旧项目时使用的模板版本,则会发生很糟糕的事情。我只是将模板和参数与项目一起放在 SourceSafe 中。

在发布模板之前,对其进行彻底的测试。模板会在开发人员之间飞快地传播,因为他们无需多少知识就可以解决问题。如果更新生成的文件意味着丢失自定义,那么程序错误将很难查找,并且更加难以修复。

使用该方法能够走多远?简单说来,代码生成器使开发人员可以获得一个抽象级别。他不必将精力集中于技术过程(因为该过程已嵌入到模板中),并且可以考虑集成该过程。它使开发人员能够更好地理解客户需求,并使技术过程拥有更好的质量。Microsoft Visual Studio 已经广泛使用了这些技术,并且即将问世的版本已经添加了很多改进(引人注目的是一个类建模程序和不完整类机制,可用于将生成的代码与自定义代码分隔开)。

现实世界中的 .NET

Pierre Couzy 是一个专门研究分布式系统体系结构的培训师和顾问。作为 20 余部书籍的作者,他目前被 WinWise 聘为 ASP.NET 和 BizTalk 专家。他自 2003 年以来一直是地区主管,并且在法国的许多重要会议上发表了演讲。如果您要与他讨论法国爵士乐、桥牌(是的,就是那种简单的游戏)甚至计算机,请通过 Pierre.couzy@winwise.fr 与他联系。

转到原英文页面



posted on 2005-07-22 12:30  forrestsun  阅读(533)  评论(0编辑  收藏  举报