Pierre Couzy
WinWise
摘要:代码生成器是您日常生活中的一部分,即使您没有意识到这一点。Pierre Couzy 说明了如何在项目中利用它们。
本页内容
简介
工具和示例
版本控制
小结
简介
假设您在一家由 DBA 统治一切的公司里工作:您不能生成只是“到 Oracle 那里去取一些记录”的应用程序。您只能依靠存储过程,因为在该级别存在安全层。
生成应用程序通常涉及到下列步骤:
-
创建一批存储过程。
-
创建能够与存储过程通信的 C# 类。
-
创建将管理 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 窗体设计器也是一个代码生成器。在这里,您可以查看代码,但最好不要动它。
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
让我们更进一步地探讨该问题:代码生成器可以提供格式规范的主干,以供您随后添加自己的代码。您可能知道 Reflector,但您是否知道 Reflector 的一些外接程序 (http://www.dotnetwiki.org/Default.aspx?tabid=52) 包含代码生成器?它们使您可以执行以下工作:
图 2. 使用 Reflector 生成代码
当您按下“Generate”按钮时,将创建一个新文件:
// Generated by Refly
namespace MyTestNameSpace
{
using System;
/// <summary>Test fixture for the <see cref="SomeBusinessClass"/> class
///</summary>
/// <remarks />
[TestFixture()]
public class SomeBusinessClassTest
{
/// <summary />
/// <remarks />
private SomeBusinessClass _someBusinessClass = null;
/// <summary />
/// <remarks />
public virtual SomeBusinessClass SomeBusinessClass
{
get
{
return this._someBusinessClass;
}
}
/// <summary>Tests the EstimateSomeNumber method</summary>
/// <remarks>
/// <para>Test coverage (estimated): 100,0%</para>
/// <para>Target path:</para>
/// <code>/* 0 */ return 0x2a;
/// </code>
/// </remarks>
[Test()]
[Ignore()]
public virtual void EstimateSomeNumber0()
{
throw new System.NotImplementedException();
}
/// <summary>Tests the IsSomethingAlreadyInDataBase method</summary>
/// <remarks>
/// <para>Test coverage (estimated): 100,0%</para>
/// <para>Target path:</para>
/// <code>/* 0 */ return false;
/// </code>
/// </remarks>
[Test()]
[Ignore()]
public virtual void IsSomethingAlreadyInDataBase0()
{
throw new System.NotImplementedException();
}
/// <summary>Sets up the fixture</summary>
/// <remarks />
[SetUp()]
public virtual void SetUp()
{
throw new System.NotImplementedException();
}
/// <summary>Releases resource allocated in the fixture</summary>
/// <remarks />
[TearDown()]
public virtual void TearDown()
{
throw new System.NotImplementedException();
}
}
}
您不了解有关“单元测试”的任何内容,但是您仍然能够生成有效的测试类。与上一个示例的不同之处在于,这一次您必须在生成的类的内部编码。可是,我们仍然无法创建我们自己的模板。
下一个步骤是创建我们自己的代码生成器。我们需要的全部东西就是一种根据一组常用信息(例如,将要获得的列的名称、要用来请求信息的方式、要允许的事务种类等等)来生成文本文件(C#、PL-SQL 等等)的方式。当然,您无法生成所有东西,因此需要一个能够生成自定义主干的工具,而某个开发人员随后将添加特定的实现细节。
该工具可以是 Perl、Microsoft VBScript 或普通的旧式 ASP(就本文而言)— 毕竟,它是一种可以根据参数生成文本文件的很好的工具。您可能使用 ASP 生成 HTML 文件,但是它还适用于生成 Microsoft Excel 或 CSV、WML — 因此,为什么不用它来生成 T-SQL 或 C# 呢?
您的 ASP 文件可能如下所示:
<%@ Language=VBScript %>
<%if request("Generate").count = 0 then%>
<HTML><BODY>
<form method=post>
<P>Property name :<INPUT name=PropertyName></P>
<P>What is the type of your property ? <INPUT name=PropertyType></P>
<P>Read only <input type=checkbox name=ReadOnly value=true></P>
<P><INPUT type=submit value="Generate !" name=Generate></P>
</form>
</BODY></HTML>
<% else
dim PropertyName, PropertyType, ReadOnly, PropertyModifier
PropertyName = Request("PropertyName")
PropertyType = Request("PropertyType")
ReadOnly = (Request("ReadOnly")="true")
if ReadOnly then PropertyModifier = "ReadOnly" else PropertyModifier = ""
Response.ContentType = "text/plain"
%>
Private _<%=PropertyName%> as <%=PropertyType%>
Public <%=propertyModifier%> Property <%=PropertyName%> as <%=PropertyType%>
Get
Return _<%=PropertyName%>
End Get
<%if not ReadOnly then%>
Set (ByVal Value as <%=PropertyType%>)
_<%=PropertyName%> = Value
End Set
<%end if%>
End Property
<%end if%>
当您执行该文件时,您将获得如下所示的内容:
图 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 与他联系。