Aimee.NET - 重构 Lucene.NET:项目设置






4.98/5 (23投票s)
重构 Lucene.NET,使其遵循 .NET 的最佳实践和约定,而不是 Java 的编码风格和限制。一系列文章将在 www.codeproject.com 上发布,涵盖从头到尾的整个过程。
引言
这是关于重构 Lucene.NET 以遵循 .NET 最佳实践和约定,而不是 Java 的编码风格和限制的系列文章的第一篇。这些文章将在 The Code Project 上发布,涵盖从头到尾的整个过程。
我假设您熟悉 Visual Studio 2010,因此不会详细介绍如何设置项目、添加引用等任务。
本系列文章
背景
我一直在研究使用 Lucene.NET 为 The Code Project 社区改进搜索体验的可能性。Lucene 库在索引和搜索内容方面是一个设计和实现都非常出色的组件。它非常易于使用,速度很快,并且提供出色的搜索结果。只是它的设计和实现没有使用 .NET 开发者熟悉的模式和类,因此获得的开发者支持不多。
最近,我读了一篇博客,Lucene.Net 需要你的帮助(否则它将消亡),警告说 Lucene.NET 项目正处于危险之中。这是因为该项目已有多月没有多少活动了。由于我目前正在将 Lucene.NET 整合到 The Code Project 中,我已经审查了源代码,并认为我理解了原因。
- 首先,代码不遵循 .NET 的约定和最佳实践。
- 代码几乎是 Java 源代码的直接复制。
- 因此,它不使用属性,而是使用 get 和 set 方法。
- 它不使用枚举,而是使用通过类模拟枚举的模式。
- 单元测试花费的时间太长,使其对于 TDD 无效。
- 代码没有利用 .NET Framework 的类库来提高性能和代码质量。
- 内联文档不是 XML 文档格式。
因此,我征得了老板 Chris Maunder 的许可,花费一些时间为 .NET 开发者社区清理代码,并希望得到社区的帮助。
目的是生成一个更适合 .NET 开发者使用和改进的代码库。转换将包括 Lucene.NET SVN 仓库中 1032168 Revision 的所有文件。
项目目标
- 为 Code Project 社区提供最佳的搜索体验。
- 为社区提供一种途径,可以通过公开项目来贡献于此改进。
- 为 .NET 社区提供一个更好的 Lucene 库的 .NET 版本。
Using the Code
代码存储在 CodePlex。在重构的各个阶段,我以及希望的 CodeProject 社区成员团队将在“下载”标签下发布稳定版本。如果您想获取最新的开发中版本,所有签入都可以在“源代码管理”标签下找到。
项目启动
选择一个名字
此过程的第一步是为项目选择一个名字,因为 Lucene 是 Apache.org 的版权所有。
为了将名字保留在 Code Project 系列中,我选择了 **Aimee.NET**。Aimee 是我们销售和营销团队的成员之一。她来 The Code Project 的时间比我长。她曾担任前台、助理会计、广告运营,现在是她的当前职位。因此,这里 The Code Project 的所有人都认识她,并且她知道所有隐藏的“骨头”(隐喻,指事情的细节和秘密)。
创建 Codeplex 项目
在 CodePlex 上创建项目是一个非常简单的过程。只需访问 CodePlex 主页,然后点击“创建项目”按钮。这将引导您完成项目设置的步骤。由于我已经完成了这个步骤,我将不再一步一步地解释。
填充源代码控制存储库
我从官方 Lucene.NET SVN 仓库获取了 Lucene.NET 的副本。这包括 Lucene.NET、Demos、Contributed Extensions 和 Unit Tests 的源代码。下一步是将其签入 CodePlex 源代码控制。
CodePlex 使用 Team Foundation Server 2010 进行源代码和项目管理。在家,我使用 Visual Studio 2010 中的 TFS 客户端进行 CodePlex 开发。在工作中,我们使用 SVN。幸运的是,CodePlex 在访问源代码控制时同时支持这两种客户端,因此我在工作时不必切换配置。
经过几次尝试,我终于以一种令我满意的方式将源代码放入了存储库。一个小小的警告,不要在凌晨 3 点进行初步的签入。在进行操作之前,您需要仔细考虑应该将什么放在哪里。
转换为 .NET 4 和 VS 2010
一旦有了良好的起点,我就创建了一个 Visual Studio 2010 解决方案文件,并将各种 Lucene.NET 项目添加进去。这些项目是 Visual Studio 2005 项目,VS2010 会弹出转换向导来更新项目以适应 VS2010 格式。
要将每个项目转换为 .NET 4,我打开每个项目的属性页,并将“目标框架”设置为“.NET Framework 4”。
然后,我将项目之间的所有依赖关系更改为项目依赖项。
Lucene.NET 的单元测试有两个外部依赖项:SharpZipLib 和 NUnit(版本 2.5.8)。我将它们放在一个“Lib”目录中,并添加了一个名为“Dependencies”的解决方案文件夹。所需的 DLL 已添加到此目录。然后,我将引用更改为使用该目录中的 DLL。
经过一些调整,解决方案可以编译了。我将其签回了。您可以在右侧看到这一点。
使测试运行起来
我使用两个测试运行器来执行单元测试:NUnit GUI 和 CodeRush 测试运行器。它们在执行测试时确定 AppDomain 基目录的方式存在差异。我修改了 `Test.nunit` 配置文件,将基目录设置为 `bin\debug` 目录,这使其与旧版本的 NUnit 和 CodeRush 测试运行器兼容。
此外,单元测试在 Windows 7 中与系统临时目录存在一些问题,所以我修改了
- `TestBackwardsCompatibility.UnZip` 方法以显式使用 `AppDomain.BaseDirectory`
- 并且 `_TestUtil.GetTempDir` 方法使用 `bin/debug` 目录下的一个“temp”目录。
- 将所有 `System.IO.Path.GetTempPath` 的出现都更改为调用 `_TestUtil.GetTempDirName`,这是一个新方法,只返回“temp”。
我修复了 `Test_Search_FieldDoc` 中的一个 bug,该 bug 会在测试期间抛出异常时导致无限循环。
此外,`Index.TestTransactions.TestTransactions_Renamed` 没有终止。可能是它在等待测试创建的所有后台线程结束。我添加了一个 `[Ignore]` 属性,以便测试能够运行。我稍后会处理这个问题。
`Store.TestDirectory.TestDirectInstantiation` 在 `MMapDirectory` 的构造函数处中止。添加了一个 `[Ignore]` 属性,因为它会停止测试运行。我稍后会修复这个问题。这个问题也存在于 `Store.TestWindowsMMap.TestMmapIndex`。它也被设置为 `[Ignore]`。
同样,`Messages.TestNLS.???` 测试似乎失败了,因为我的区域设置为 `en-ca` 而不是 `en-us`。我稍后也会研究这个问题。
`Search.Function` 测试运行缓慢。快速审查表明,测试设置方法重新创建了一个应该在 Fixture 设置方法中创建的大型集合。
一旦所有测试都运行完毕,请将所有内容重新签入。我们现在有信心我们拥有一个可以编译并按预期运行的项目,除了少数小问题。
加快测试速度
现在我们有了一套单元测试,我们可以尝试让它们变得有用。目前,执行时间太长,无法用于 TDD 风格的工作,而这对于重构来说至关重要。我们需要在每个重构步骤中知道我们没有破坏任何功能。
在 NUnit GUI 中运行单元测试会显示以下状态。
在一台功能强大的机器上,这需要 45 分钟。这显然不适用于 TTD 风格的开发或重构,因此需要改进测试,并且必须选择一个有意义的子集,将总持续时间缩短到一分钟以内。
为了确定哪些测试需要性能改进,我将测试结果保存到 XML 文件中,您可以 在此处 获取。该文件包含每个测试和测试组的执行时间。我发现 LinqPad 是分析几乎任何来源数据的绝佳工具。在 LinqPad 中输入以下代码并执行代码,会生成两个表。第一个表是所有运行时间超过 5 秒的测试用例,按时间降序排序。第二个表是所有运行时间超过 10 秒的测试套件,按时间降序排序。
var doc = new XmlDocument();
doc.Load(@"C:\Users\matthew.CODEPROJECT\Documents\Work\Aimee.Net\Article1 -
Starting the Project\TestResult.xml");
//doc.SelectNodes("descendant::test-suite").Dump();
var testcases=doc.SelectNodes("descendant::test-case");
var testdata = (from XmlNode c in testcases
where c.Attributes["executed"].Value == "True"
select new {
Suite = c.ParentNode.ParentNode.Attributes["name"].Value,
Name = c.Attributes["name"].InnerText,
Time = float.Parse(c.Attributes["time"].Value)})
.OrderByDescending (d => d.Time);
testdata.Where(td => td.Time >= 5).Dump();
testdata.GroupBy(td => td.Suite)
.Select ( grp => new { Suite = grp.Key, Time = grp.Sum(td => td.Time)})
.Where( ts => ts.Time >= 10)
.OrderByDescending(Su=> Su.Time)
.Dump();
这使我能够查看持续时间最长的测试套件和用例。我将解决其中一些问题,并在下一篇文章中汇报我为实现一套允许 TDD 方法进行重构的测试所做的更改。
历史
- 2010 年 11 月 18 日 首次发布
- 2010 年 12 月 8 日 - 添加了系列中其他文章的链接