动态原型设计






4.15/5 (10投票s)
了解什么是动态原型设计以及如何进行
目录
引言
在这篇文章中,我想谈谈一种叫做“动态原型设计”的东西。这种软件开发方法的想法出现在我的脑海中,因为我厌倦了所有开发者都面临的永无止境的运行-重新编译-运行循环。所以在这篇文章中,我将解释动态原型设计的概念,讨论其实现,并演示其用法。
问题
软件开发任务有大有小——有些庞大,有些微小。根据您尝试实现的功能,一个任务可能需要一分钟或一个月。一分钟长度的任务非常烦人,因为它们的验证(我们暂时忘记单元测试)比修正本身花费的时间更长。它们更烦人,因为它们会打断我的注意力。
由于这种不满,我开始探索摆脱与重新编译周期相关的往返的想法。本质上,只有两种处理方法。
第一个选项是依赖 DLR 来处理需要修改的代码段。然而,这种方法会导致代码速度变慢,并且还会让我们使用我们最初可能不愿意使用的奇怪语言。
第二个选项,也是我偏爱的选项,是进程内编译。由于每个 .NET 程序都能够从内部编译其他 .NET 程序,因此能够修改正在运行的程序并且——更重要的是——能够立即看到结果是现实可行的。
基本基础结构问题
由于勤奋的开发人员可能会立即发现这种方法的几个问题,我将事先解决一些显而易见的担忧。
速度
要解决的第一个问题是:程序会因此从根本上变慢吗?传统观点认为会,很简单,因为我的许多读者已经在考虑跨域封送处理之类的事情。事实上,您可能已经注意到我从未建议卸载程序集。实际上,这对于使事情运行起来并不是必需的。
为什么不呢?嗯,主要是因为没有什么能阻止您加载同一程序集的多个副本。当然,您会为此付出代价,并且需要一些额外的调整,但最终结果是
- 您更改的程序集在您完成更改后会立即编译并加载。新加载的程序集会立即取代以前的版本投入使用。
当然,需要谨慎的规划和深思熟虑的开发实践,以确保在加载和使用新程序集时不会出现问题。我们稍后会讨论这些。
往返和发布
所以下一个问题有点多方面。即,以下问题浮现在脑海中
- 您如何同时打包源代码并将其编译到程序中?
- 您如何确保内存中的更改实际“返回”到程序中?
- 您如何确保这些黑客行为都不会进入已部署的程序?
让我逐一解决这些问题
首先,源代码是同时编译并嵌入在应用程序中的。因此,您已经有了可用的二进制表示,以及您也可以使用的文本表示。
其次,内存中的更改会返回到程序中,因为实际上没有什么能阻止您将一些文本序列化到..\..\SomeClass.cs。您的断点(提示!)当然会断开,但除此之外,一切都会正常运行。
第三,提议的基础设施旨在(并不是说这是你应该做的)只在调试模式下使用。然而,实际上它不必如此——你可以将程序的源代码持久化到某个地方。当然,你也可以对其进行加密,使其不可读。
为了进一步阐述最后一点,其理念是源代码通常可在解决方案文件夹中找到,并且不会以任何方式打包。相反,它只是保存在它需要的地方——项目目录中!现在,为什么没有真正的往返问题应该(希望)很明显了——因为我们正在使用原始程序文件进行实现!
架构变更
好的,您可能会想象,加载一个包含与之前相同类型的新程序集将需要重新连接 IoC 容器中每个必要的类型,您当然是对的——这就是它的完成方式。然而,这个要求无论如何都符合最佳实践,反对它就像说我们不应该刷牙一样。
实现
所以,是时候写一些代码了。为了本文的目的,我将介绍一个用于 HTML 编码的程序的最小实现。这个想法是,程序的 HTML 编码部分是一个可编辑的服务,我们将为此创建规定。
默认实现
好的,假设我有一个名为 IConverter
的接口
public interface IConverter
{
string Convert(string source);
}
随后我创建了我的(最小)HTML 编码实现
public class HtmlConverter : IConverter
{
public string Convert(string source)
{
var sb = new StringBuilder(source);
sb.Replace("<", "<");
return sb.ToString();
}
}
现在,我是一个好孩子,我将 IConverter
注入到我的主窗口中,并用它来执行转换
public partial class MainFrame : Form
{
private IConverter converter;
public MainFrame(IConverter converter)
{
this.converter = converter;
InitializeComponent();
}
private void btnConvert_Click(object sender, EventArgs e)
{
tbConverted.Text = converter.Convert(tbSource.Text);
}
}
最后,我配置容器……
static void Main()
{
var uc = new UnityContainer();
uc.RegisterType<IConverter, HtmlConverter>();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(uc.Resolve<MainFrame>());
}
然后应用程序就可以使用了
显示编辑器
好的,为了本次练习的目的,我将假设(这意味着我现在不会实现它)所有与动态原型设计相关的东西要么被包裹在 #if DEBUG
中,要么前面有 [Conditional("DEBUG")]
。考虑到这一点,我们可以显示编辑器了。
如果您想知道我们如何找出要打开的文件,答案很简单:通常,您需要编写调试专用代码来实际显示此窗口,因此您不妨硬编码文件名。如果您想要一个更具工业强度的解决方案,请随时创建您自己的基础设施,其中包含漂亮的小菜单,让您指定要编辑的类型/文件。
重建类型
好的,我们有一个编辑器,它允许我们直接从解决方案中编辑文件,并且自然地,允许我们将其持久化回原来的位置。接下来呢?哦,我们还得编译和加载它。让我们先处理编译。
编译实际上很容易,唯一的注意事项是您需要引用您的原始可执行文件和任何相关的 DLL。这是一个例子
public class InProcessCompiler
{
public object CompileAndInstantiate(string sourceCode)
{
var ps = new CompilerParameters {GenerateInMemory = true, GenerateExecutable = false};
ps.ReferencedAssemblies.Add("System.Drawing.dll");
ps.ReferencedAssemblies.Add("System.Core.dll");
ps.ReferencedAssemblies.Add("DynamicPrototypingSample.exe");
var po = new Dictionary<string, string> {{"CompilerVersion", "v4.0"} };
var p = new CSharpCodeProvider(po);
var results = p.CompileAssemblyFromSource(ps, new[] {sourceCode});
if (results.Errors.HasErrors)
{
var sb = new StringBuilder();
foreach (var e in results.Errors)
sb.AppendLine(e.ToString());
throw new Exception(sb.ToString());
}
var ass = results.CompiledAssembly;
var mainType = ass.GetTypes()[0];
return Activator.CreateInstance(mainType);
}
}
上面有很多黑客技术——指定要添加为引用的显式 DLL 和我们的 EXE,在 4.0 下编译源代码,最后但并非最不重要的一点是——创建所有可用类型中的第一个。实际上,最后一点是审慎的,因为在这种情况下,我只对编译一种类型感兴趣,那就是在生成的程序集中首先找到的类型。
最后润色
创建了生成类型的实例后,我们做一件简单的事情:将其分配给变量。因此,整个过程如下所示
- 抛出编辑 UI
- 尝试编译类型
- 如果成功,保存新实例
- 用新源覆盖 .cs 文件
这是我们编辑按钮的事件处理程序
private void btnEditProgram_Click(object sender, EventArgs e)
{
string path = @"..\..\HtmlConverter.cs";
var ce = new CodeEditor(path);
if (ce.ShowDialog() == DialogResult.OK)
{
try
{
converter = (IConverter) Compiler.CompileAndInstantiate(ce.SourceCode);
File.WriteAllText(path, ce.SourceCode, Encoding.UTF8);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
就这样了!
结论
好的,如您所见,实现并不太复杂。当然,它可以“修剪”,使得这种进程内编辑的检测仅在调试代码中可用。而且,通过在代码的编译和“吸收”方式上投入更多时间,您可以使您的原型开发更容易一些。
我做的一件有用的事情(但无法在本文中展示)是使用一个商业控件进行 C# 编辑,而不是仅仅将其作为纯文本编辑。除此之外,我使用的设置与我这里描述的非常相似。
感谢阅读!欢迎评论!
历史
- 2010年11月3日:初次发布