<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <title>实践优化 - 老赵点滴 - 追求编程之美</title>
    <link>http://blog.zhaojie.me/practice/</link>
    <description>先做人，再做技术人员，最后做程序员。打造国内最好的.NET技术博客。</description>
    <language>zh-cn</language>
    <managingEditor>jeffz@live.com (老赵)</managingEditor>
    <webMaster>jeffz@live.com (老赵)</webMaster>
    <pubDate>Wed, 31 Aug 2011 16:25:50 GMT</pubDate>
    <lastBuildDate>Tue, 29 Mar 2011 14:00:32 GMT</lastBuildDate>
    <ttl>60</ttl>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>C#的设计缺陷（1）：显式实现接口内的事件</title>
      <link>http://blog.zhaojie.me/2012/05/csharp-design-flaws-1-explicitly-implement-interface-event.html</link>
      <guid>http://blog.zhaojie.me/2012/05/csharp-design-flaws-1-explicitly-implement-interface-event.html</guid>
      <description>&lt;p&gt;其实使用C#这么多年，我时不时会遇到一些令人不爽的设计缺陷。这些缺陷大都是些限制，虽说无伤大雅，也很容易避免，但一旦遇到这些情况，总会令人心生不快，毕竟都是些无谓的限制。而且令人遗憾的是，虽说去除这些限制也不会带来什么问题，但我认为C#设计团队也基本不会去修复这些问题了，毕竟它们大都是些细枝末节。作为一名用C#的纯种码农，我突然一时兴起也要把这些设计缺陷记录下，也方便和大伙一起讨论下。那么这次就先从实现接口内的事件说起，当我们需要显式实现一个接口内的事件时，会发现我们必须提供add和remove访问器，这还会稍许影响到事件常用的使用模式。&lt;/p&gt;

&lt;p&gt;这个问题听上去有些绕，不过看代码便一清二楚。例如，在项目中我会定义一个这样的INotifyPropertyChanged接口，其中包含一个PropertyChanged事件：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;INotifyPropertyChanged&lt;/span&gt;&amp;lt;TPropertyIdentity&amp;gt;
{
    &lt;span style="color: blue"&gt;event &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;TPropertyIdentity&amp;gt;&amp;gt; PropertyChanged;
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;TPropertyIdentity&amp;gt; : &lt;span style="color: #2b91af"&gt;EventArgs
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;PropertyChangedEventArgs(TPropertyIdentity propertyIdentity)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.PropertyIdentity = propertyIdentity;
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;TPropertyIdentity PropertyIdentity { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }
}&lt;/pre&gt;

&lt;p&gt;可以看出这个接口和.NET内置的INotifyPropertyChanged事件可谓如出一辙，其实他们的目的也一样，就是向外通知该对象的某个属性发生了改变。不同的是，系统内置的PropertyChangedEventArgs对象使用属性名，也就是一个字符串标识一个属性，而在如上带泛型的PropertyChangedEventArgs里，则可以使用任意类型的对象来标识属性，这无疑带来的更多的灵活性。例如，我们可以使用连续的整型数值来标识对象，这样我们就可以使用数组来创建一个索引，它的性能会比使用字符串为键值的字典要高出一些。&lt;/p&gt;

&lt;p&gt;不过，我们实现系统自带的INotifyPropertyChanged属性时，并非是要“自行使用”，而往往是想让通知其他组件，例如ORM框架或是UI控件。因此，它其实已经是.NET平台上的统一约定，即便有所不足，也不能舍弃它。因此，我们往往需要在一个对象上同时实现两种INotifyPropertyChanged接口，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Item &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;INotifyPropertyChanged&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;INotifyPropertyChanged&lt;/span&gt;
{
    &lt;span style="color: blue"&gt;public event &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt;&amp;gt; PropertyChanged;

    &lt;span style="color: blue"&gt;event &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventHandler INotifyPropertyChanged&lt;/span&gt;.PropertyChanged
    {
        &lt;span style="color: blue"&gt;add &lt;/span&gt;{ &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;(); }
        &lt;span style="color: blue"&gt;remove &lt;/span&gt;{ &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;(); }
    }
}&lt;/pre&gt;

&lt;p&gt;以上是Visual Studio为两个事件实现自动生成的代码框架，且看第二个事件，它要求我们提供add和remove访问器。为什么？我不知道，&lt;a href="http://stackoverflow.com/questions/2268065/c-sharp-language-design-explicit-interface-implementation-of-an-event#2268472"&gt;C#开发团队自己可能也已经不太清楚这么规定的原因&lt;/a&gt;：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Interesting question. I did some poking around the language notes archive and I discovered that this decision was made on the 13th of October, 1999, but the notes do not give a justification for the decision.&lt;/p&gt;

  &lt;p&gt;Off the top of my head I don't see any theoretical or practical reason why we could not have field-like explicitly implemented events. Nor do I see any reason why we particularly need to. This may have to remain one of the mysteries of the unknown.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="http://stackoverflow.com/users/88656/eric-lippert"&gt;Eric Lippert&lt;/a&gt;是老牌C#团队成员了，经常在Stack Overflow或是博客上写一些C#的设计内幕，可惜在这个问题上连他也认为是个“不解之谜”。此外，“自动属性”让这个限制进一步显得“无厘头”了，因为我们完全可以这么显式实现接口里的属性：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;INameProvider
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;string &lt;/span&gt;Name { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyNameProvider &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;INameProvider
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;string &lt;/span&gt;&lt;span style="color: #2b91af"&gt;INameProvider&lt;/span&gt;.Name { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;

&lt;p&gt;既然如此，事件跟它又有什么本质区别呢？&lt;/p&gt;

&lt;p&gt;顺便一提，我们知道，在C#里不能把显式实现的接口成员标注为抽象成员，这对于事件来说还存在一些额外的问题。且看以下代码片段：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Base &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;INotifyPropertyChanged&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;
{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;&amp;gt; PropertyChanged;

    &lt;span style="color: blue"&gt;protected void &lt;/span&gt;OnPropertyChanged(&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt; args)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;propertyChanged = &lt;span style="color: blue"&gt;this&lt;/span&gt;.PropertyChanged;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(propertyChanged != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            propertyChanged(&lt;span style="color: blue"&gt;this&lt;/span&gt;, args);
        }
    }
}&lt;/pre&gt;

&lt;p&gt;Base是个基类，因此它往往会暴露个OnXyz方法，以便子类触发Xyz事件。在OnPropertyChanged方法中，我们会先判断_propertyChanged是否为null，因为null表示还没有人注册过事件——这是事件使用时的常见模式。事件本身没有注册任何处理器，则意味着事件本身不触发亦可，同样意味着我们甚至可以不去创建事件所需的EventArgs参数。但是，如果我们是要在子类里触发事件（即调用OnXxx方法），则没有办法检查该事件有没有注册处理器。假如这个EventArgs对象创建起来成本较高，就会造成一定的性能损失。&lt;/p&gt;

&lt;p&gt;解决方法倒也简单，例如，在基类里增加一个事件：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Base &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;INotifyPropertyChanged&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;
{
    &lt;span style="color: blue"&gt;public abstract event &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;&amp;gt; MyIdentityPropertyChanged;

    &lt;span style="color: blue"&gt;event &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;&amp;gt; &lt;span style="color: #2b91af"&gt;INotifyPropertyChanged&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;.PropertyChanged
    {
        &lt;span style="color: blue"&gt;add &lt;/span&gt;{ &lt;span style="color: blue"&gt;this&lt;/span&gt;.MyIdentityPropertyChanged += &lt;span style="color: blue"&gt;value&lt;/span&gt;; }
        &lt;span style="color: blue"&gt;remove &lt;/span&gt;{ &lt;span style="color: blue"&gt;this&lt;/span&gt;.MyIdentityPropertyChanged -= &lt;span style="color: blue"&gt;value&lt;/span&gt;; }
    }
}&lt;/pre&gt;

&lt;p&gt;或干脆加一个“延迟”构造EventArgs的重载：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Base &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;INotifyPropertyChanged&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;
{
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;&amp;gt; _propertyChanged;

    &lt;span style="color: blue"&gt;event &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;&amp;gt; &lt;span style="color: #2b91af"&gt;INotifyPropertyChanged&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;.PropertyChanged
    {
        &lt;span style="color: blue"&gt;add &lt;/span&gt;{ &lt;span style="color: blue"&gt;this&lt;/span&gt;._propertyChanged += &lt;span style="color: blue"&gt;value&lt;/span&gt;; }
        &lt;span style="color: blue"&gt;remove &lt;/span&gt;{ &lt;span style="color: blue"&gt;this&lt;/span&gt;._propertyChanged -= &lt;span style="color: blue"&gt;value&lt;/span&gt;; }
    }

    &lt;span style="color: blue"&gt;protected void &lt;/span&gt;OnPropertyChanged(&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt; args) { ... }

    &lt;span style="color: blue"&gt;protected void &lt;/span&gt;OnPropertyChanged(&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;&amp;gt; argsFactory) { ... }
}&lt;/pre&gt;

&lt;p&gt;于是在基类里触发事件时即可：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;this&lt;/span&gt;.OnPropertyChanged(() =&amp;gt; &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PropertyChangedEventArgs&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;&amp;gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyIdentity&lt;/span&gt;()));&lt;/pre&gt;

&lt;p&gt;如果您觉得在没有事件处理器的情况下创建一个委托对象也是一种浪费，那么就自己想办法解决咯。没什么困难的，不应该想不出。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2012/05/csharp-design-flaws-1-explicitly-implement-interface-event.html#comments</comments>
      <pubDate>Sun, 20 May 2012 13:07:12 GMT</pubDate>
      <lastBuildDate>Sun, 20 May 2012 13:07:12 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>编写一个“绑定友好”的WPF控件</title>
      <link>http://blog.zhaojie.me/2012/05/wpf-binding-friendly-user-control.html</link>
      <guid>http://blog.zhaojie.me/2012/05/wpf-binding-friendly-user-control.html</guid>
      <description>&lt;p&gt;最近在搞WPF开发，这对我来说是个陌生的领域。话说回来，可能是缺少耐心的缘故，我现在学习新事物的方式主要是“看一些入门文档”，“看一些示例”，然后“猜测”其实现并摸索着使用。在很多时候这种做法问题不大，但一旦有地方猜错了，但在一段时间里似乎和实践还挺吻合的，则一旦遇到问题就会卡死。上周五我就被一个WPF绑定的问题搞得焦头烂额，虽说基本搞定，但还是想验证下是否会有更好的做法，特此记录一下，欢迎大家指正。&lt;/p&gt;

&lt;h1&gt;目标与障碍&lt;/h1&gt;

&lt;p&gt;简单地说，我想做的事情是编写一个“绑定友好”的用户控件，它可以像Telerik的RadNumericUpDown控件那样使用：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;telerik&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: #a31515"&gt;RadNumericUpDown &lt;/span&gt;&lt;span style="color: red"&gt;Value&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;{&lt;/span&gt;&lt;span style="color: #a31515"&gt;Binding &lt;/span&gt;&lt;span style="color: red"&gt;Path&lt;/span&gt;&lt;span style="color: blue"&gt;=NumberValue}&amp;quot; /&amp;gt;
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;我们可以通过控件属性的形式直接绑定一个值上去，看上去应该是最基本的要求吧？那么我们就来实现一个类似的控件，他有两个属性，一个是Text字符串属性，另一个是Number整型属性，分别交由一个文本框和一个滑动条来控制。&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;UserControl&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;Grid&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;StackPanel&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;TextBox &lt;/span&gt;&lt;span style="color: red"&gt;Text&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;...&amp;quot; /&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;Slider &lt;/span&gt;&lt;span style="color: red"&gt;Minimum&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;0&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;Maximum&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;100&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;Value&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;...&amp;quot; /&amp;gt;
        &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;StackPanel&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;  
    &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;Grid&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;UserControl&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;自然，MVVM是不可或缺的，因为在真实环境中一个用户控件的逻辑也会颇为复杂，我们需要对模型和界面进行分离。这个最简单的ViewModel定义如下（自然，实际情况下还需要实现INotifyPropertyChanged接口）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ValueInputViewModel &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;INotifyPropertyChanged
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Text { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public int &lt;/span&gt;Number { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;

&lt;p&gt;我之前都是使用DataContext作为ViewModel的容器，例如在BadValueInput.xaml.cs中：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public partial class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BadValueInput &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;UserControl
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;BadValueInput()
    {
        InitializeComponent();

        &lt;span style="color: blue"&gt;this&lt;/span&gt;.DataContext = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ValueInputViewModel&lt;/span&gt;();
    }

    ...
}&lt;/pre&gt;

&lt;p&gt;于是便可以在BadValueInput.xaml里绑定：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;TextBox &lt;/span&gt;&lt;span style="color: red"&gt;Text&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;{&lt;/span&gt;&lt;span style="color: #a31515"&gt;Binding &lt;/span&gt;&lt;span style="color: red"&gt;Path&lt;/span&gt;&lt;span style="color: blue"&gt;=Text, &lt;/span&gt;&lt;span style="color: red"&gt;UpdateSourceTrigger&lt;/span&gt;&lt;span style="color: blue"&gt;=PropertyChanged}&amp;quot; /&amp;gt;
&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;Slider &lt;/span&gt;&lt;span style="color: red"&gt;Minimum&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;0&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;Maximum&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;100&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;Value&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;{&lt;/span&gt;&lt;span style="color: #a31515"&gt;Binding &lt;/span&gt;&lt;span style="color: red"&gt;Path&lt;/span&gt;&lt;span style="color: blue"&gt;=Number}&amp;quot; /&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;然后再定义两个依赖属性即可。接着我们在MainWindow.xaml里使用这个类，同样使用MVVM模式：创建MainWindowViewModel类型，包含MyText和MyNumber两个属性，实例化并赋值给MainWindow的DataContext，然后在XAML里绑定至BadValueInput的两个属性上：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;view&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: #a31515"&gt;BadValueInput &lt;/span&gt;&lt;span style="color: red"&gt;Text&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;{&lt;/span&gt;&lt;span style="color: #a31515"&gt;Binding &lt;/span&gt;&lt;span style="color: red"&gt;Path&lt;/span&gt;&lt;span style="color: blue"&gt;=MyText}&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;Number&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;{&lt;/span&gt;&lt;span style="color: #a31515"&gt;Binding &lt;/span&gt;&lt;span style="color: red"&gt;Path&lt;/span&gt;&lt;span style="color: blue"&gt;=MyNumber}&amp;quot; /&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;从我的设想中，这种做法没有任何问题：父控件（MainWindow）和子控件（BadValueInput）都有自身的DataContext，互不影响。父控件将自己的MyText和MyNumber分别绑定至子控件的Text和Number属性上，也符合直觉，但执行后的结果却并非如此：&lt;/p&gt;

&lt;pre class="code"&gt;System.Windows.Data Error: 40 : BindingExpression path error: 'MyText' property not found on 'object' ''ValueInputViewModel' (HashCode=6943688)'. BindingExpression:Path=MyText; DataItem='ValueInputViewModel' (HashCode=6943688); target element is 'BadValueInput' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'MyNumber' property not found on 'object' ''ValueInputViewModel' (HashCode=6943688)'. BindingExpression:Path=MyNumber; DataItem='ValueInputViewModel' (HashCode=6943688); target element is 'BadValueInput' (Name=''); target property is 'Number' (type 'Int32')&lt;/pre&gt;

&lt;p&gt;在Output窗口中出现了这样两条错误信息，意思是ValueInputViewModel上没有MyText和MyNumber两个属性。于是我就搞不懂了，为什么定义在MainWindow里的绑定使用的会是BadValueInput的DataContext，而不是当前上下文，即MainWindow的DataContext？我始终觉得这是种违反直觉的逻辑。&lt;/p&gt;

&lt;h1&gt;不使用DataContext作为ViewModel容器&lt;/h1&gt;

&lt;p&gt;我在微薄上提出这个问题之后收到了不少回应，很多朋友说是使用&lt;code&gt;RelativeSource就可以解决问题，也就是让子控件可以找到父控件的DataContext，甚至说直接在子控件里直接指定父控件ViewModel路径。对于这个做法我不敢苟同，在我看来子控件应该是可以独立地自由使用的一个组件，它不应该根据父控件去调整自己的实现。&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;因此，即便这样的做法可以解决这一场景下的问题，但在我看来这完全属于在“凑”结果。&lt;/code&gt;&lt;code&gt;我需要的是尽可能完善的解决方案，就像RadNumericUpDown那样干净清爽。我认为程序员还是需要一点完美主义，而不是仅仅为了解决问题而运用Workaround。&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;其实解决方案也很简单，&lt;a href="http://weibo.com/waynebabywang"&gt;@韦恩卑鄙&lt;/a&gt;告诉我，假如要避免出现这种情况，应该避免使用DataContext作为ViewModel容器，严格来说这是一种轻度滥用。其实只要遵循这个原则，这个问题也很容易解决。例如，在ValueInput.xaml.cs里定义个ViewModel属性：&lt;/code&gt;&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public partial class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ValueInput &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;UserControl
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;ValueInput()
        : &lt;span style="color: blue"&gt;this&lt;/span&gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ValueInputViewModel&lt;/span&gt;())
    { }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;ValueInput(&lt;span style="color: #2b91af"&gt;ValueInputViewModel &lt;/span&gt;viewModel)
    {
        InitializeComponent();

        &lt;span style="color: blue"&gt;this&lt;/span&gt;.ViewModel = viewModel;
    }

    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DependencyProperty &lt;/span&gt;ViewModelProperty =
        &lt;span style="color: #2b91af"&gt;DependencyProperty&lt;/span&gt;.Register(&lt;span style="color: #a31515"&gt;&amp;quot;ViewModel&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;ValueInputViewModel&lt;/span&gt;), &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;ValueInput&lt;/span&gt;));

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ValueInputViewModel &lt;/span&gt;ViewModel
    {
        &lt;span style="color: blue"&gt;get &lt;/span&gt;{ &lt;span style="color: blue"&gt;return &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;ValueInputViewModel&lt;/span&gt;)GetValue(ViewModelProperty); }
        &lt;span style="color: blue"&gt;set &lt;/span&gt;{ SetValue(ViewModelProperty, &lt;span style="color: blue"&gt;value&lt;/span&gt;); }
    }
}&lt;/pre&gt;

&lt;p&gt;然后在ValueInput.xaml里绑定时指定特定的成员：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;UserControl &lt;/span&gt;&lt;span style="color: red"&gt;x&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: red"&gt;Class&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;WpfUserControl.Views.ValueInput&amp;quot;
             &lt;/span&gt;&lt;span style="color: red"&gt;x&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: red"&gt;Name&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;Self&amp;quot;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;Grid&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;StackPanel&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;TextBox &lt;/span&gt;&lt;span style="color: red"&gt;Text&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;{&lt;/span&gt;&lt;span style="color: #a31515"&gt;Binding &lt;/span&gt;&lt;span style="color: red"&gt;ViewModel&lt;/span&gt;&lt;span style="color: blue"&gt;.Text, &lt;/span&gt;&lt;span style="color: red"&gt;ElementName&lt;/span&gt;&lt;span style="color: blue"&gt;=Self, &lt;/span&gt;&lt;span style="color: red"&gt;UpdateSourceTrigger&lt;/span&gt;&lt;span style="color: blue"&gt;=PropertyChanged}&amp;quot; /&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;Slider &lt;/span&gt;&lt;span style="color: red"&gt;Minimum&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;0&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;Maximum&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;100&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;Value&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;{&lt;/span&gt;&lt;span style="color: #a31515"&gt;Binding &lt;/span&gt;&lt;span style="color: red"&gt;ViewModel&lt;/span&gt;&lt;span style="color: blue"&gt;.Number, &lt;/span&gt;&lt;span style="color: red"&gt;ElementName&lt;/span&gt;&lt;span style="color: blue"&gt;=Self}&amp;quot; /&amp;gt;
        &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;StackPanel&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;Grid&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;UserControl&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;如今的绑定不光指定Path，还会使用ElementName将Source定义成当前控件对象。不过接下来的问题是，如何将控件的Text和Number属性，与ViewModel中的属性关联起来呢？目前我只知道使用代码来实现这种同步，这需要我们在ValueInput.xaml里添加更多代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;ValueInput(&lt;span style="color: #2b91af"&gt;ValueInputViewModel &lt;/span&gt;viewModel)
{
    InitializeComponent();

    viewModel.PropertyChanged += (_, args) =&amp;gt;
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(args.PropertyName == &lt;span style="color: #a31515"&gt;&amp;quot;Text&amp;quot;&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(!&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Equals(viewModel.Text, &lt;span style="color: blue"&gt;this&lt;/span&gt;.Text))
            {
                &lt;span style="color: blue"&gt;this&lt;/span&gt;.Text = viewModel.Text;
            }
        }
        &lt;span style="color: blue"&gt;else if &lt;/span&gt;(args.PropertyName == &lt;span style="color: #a31515"&gt;&amp;quot;Number&amp;quot;&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(!viewModel.Number.Equals(&lt;span style="color: blue"&gt;this&lt;/span&gt;.Number))
            {
                &lt;span style="color: blue"&gt;this&lt;/span&gt;.Number = viewModel.Number;
            }
        }
    };

    &lt;span style="color: blue"&gt;this&lt;/span&gt;.ViewModel = viewModel;
}

&lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DependencyProperty &lt;/span&gt;TextProperty =
    &lt;span style="color: #2b91af"&gt;DependencyProperty&lt;/span&gt;.Register(
        &lt;span style="color: #a31515"&gt;&amp;quot;Text&amp;quot;&lt;/span&gt;,
        &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;string&lt;/span&gt;),
        &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;ValueInput&lt;/span&gt;),
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;FrameworkPropertyMetadata&lt;/span&gt;(OnTextPropertyChanged) { BindsTwoWayByDefault = &lt;span style="color: blue"&gt;true &lt;/span&gt;});

&lt;span style="color: blue"&gt;private static void &lt;/span&gt;OnTextPropertyChanged(&lt;span style="color: #2b91af"&gt;DependencyObject &lt;/span&gt;o, &lt;span style="color: #2b91af"&gt;DependencyPropertyChangedEventArgs &lt;/span&gt;args)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;valueInput = (&lt;span style="color: #2b91af"&gt;ValueInput&lt;/span&gt;)o;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(!&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Equals(valueInput.ViewModel.Text, valueInput.Text))
    {
        valueInput.ViewModel.Text = valueInput.Text;
    }
}

&lt;span style="color: blue"&gt;public string &lt;/span&gt;Text
{
    &lt;span style="color: blue"&gt;get &lt;/span&gt;{ &lt;span style="color: blue"&gt;return &lt;/span&gt;(&lt;span style="color: blue"&gt;string&lt;/span&gt;)GetValue(TextProperty); }
    &lt;span style="color: blue"&gt;set &lt;/span&gt;{ SetValue(TextProperty, &lt;span style="color: blue"&gt;value&lt;/span&gt;); }
}

&lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DependencyProperty &lt;/span&gt;NumberProperty =
    &lt;span style="color: #2b91af"&gt;DependencyProperty&lt;/span&gt;.Register(
        &lt;span style="color: #a31515"&gt;&amp;quot;Number&amp;quot;&lt;/span&gt;,
        &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;int&lt;/span&gt;),
        &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;ValueInput&lt;/span&gt;),
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;FrameworkPropertyMetadata&lt;/span&gt;(OnNumberPropertyChanged) { BindsTwoWayByDefault = &lt;span style="color: blue"&gt;true &lt;/span&gt;});

&lt;span style="color: blue"&gt;private static void &lt;/span&gt;OnNumberPropertyChanged(&lt;span style="color: #2b91af"&gt;DependencyObject &lt;/span&gt;o, &lt;span style="color: #2b91af"&gt;DependencyPropertyChangedEventArgs &lt;/span&gt;args)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;valueInput = (&lt;span style="color: #2b91af"&gt;ValueInput&lt;/span&gt;)o;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(!valueInput.ViewModel.Number.Equals(valueInput.Number))
    {
        valueInput.ViewModel.Number = valueInput.Number;
    }
}

&lt;span style="color: blue"&gt;public int &lt;/span&gt;Number
{
    &lt;span style="color: blue"&gt;get &lt;/span&gt;{ &lt;span style="color: blue"&gt;return &lt;/span&gt;(&lt;span style="color: blue"&gt;int&lt;/span&gt;)GetValue(NumberProperty); }
    &lt;span style="color: blue"&gt;set &lt;/span&gt;{ SetValue(NumberProperty, &lt;span style="color: blue"&gt;value&lt;/span&gt;); }
}&lt;/pre&gt;

&lt;p&gt;为了将控件上属性的改变同步至ViewModel，我们在定义依赖属性的时候提供propertyChangedCallback参数。同理，为了将ViewModel上属性的改变同步至控件，我们会监听ViewModel对象的PropertyChanged事件，这样的做法虽然麻烦，但的确管用。&lt;/p&gt;

&lt;p&gt;如果我们要在MainWindow里使用这个控件，我们可以继续使用DataContext，此时子控件的DataContext属性会获取到父控件的DataContext对象，这自然不会出现取不到属性的问题。当然，如果父控件本身也希望成为一个独立控件的话，也可以使用同样的做法，即创建自身的ViewModel属性并使用ElementName指定Source，继续避免对DataContext产生依赖。&lt;/p&gt;

&lt;h1&gt;示例代码与疑问&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/samples/tree/master/WpfUserControl"&gt;本文的示例代码已存放至GitHub&lt;/a&gt;。我周五遇到的这个问题算是这么解决了，但其实我还是有一些疑问，例如：有没有更简单的做法？ValueInput.xaml.cs里用于同步控件属性与ViewModel属性的代码实在太多，也很容易写错。此外，能否在控件上定义一个只读的属性？例如代码中我额外添加的ReadOnlyValue依赖属性：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DependencyProperty &lt;/span&gt;ReadOnlyValueProperty =
    &lt;span style="color: #2b91af"&gt;DependencyProperty&lt;/span&gt;.Register(&lt;span style="color: #a31515"&gt;&amp;quot;ReadOnlyValue&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;int&lt;/span&gt;), &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;ValueInput&lt;/span&gt;));

&lt;span style="color: blue"&gt;public int &lt;/span&gt;ReadOnlyValue
{
    &lt;span style="color: blue"&gt;get &lt;/span&gt;{ &lt;span style="color: blue"&gt;return &lt;/span&gt;(&lt;span style="color: blue"&gt;int&lt;/span&gt;)GetValue(ReadOnlyValueProperty); }
    &lt;span style="color: blue"&gt;private set &lt;/span&gt;{ SetValue(ReadOnlyValueProperty, &lt;span style="color: blue"&gt;value&lt;/span&gt;); }
}&lt;/pre&gt;

&lt;p&gt;但是一旦缺少公开的setter，在XAML里就无法绑定这个属性了，即便我把绑定的Mode设为OneWayToSource。初学WPF，疑问很多，希望大家多多帮助。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2012/05/wpf-binding-friendly-user-control.html#comments</comments>
      <pubDate>Sun, 13 May 2012 15:35:25 GMT</pubDate>
      <lastBuildDate>Sun, 13 May 2012 15:35:25 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>使用Jscex改进Node Club（4）：改写首页</title>
      <link>http://blog.zhaojie.me/2012/03/jscexify-nodeclub-4-jscexify-home-page.html</link>
      <guid>http://blog.zhaojie.me/2012/03/jscexify-nodeclub-4-jscexify-home-page.html</guid>
      <description>&lt;p&gt;上次我们分析了Node Club的首页实现，了解了它的功能以及目前的实现方式。不过在我看来，如今使用EventProxy来辅助页面开发并没有解决部分异步编程中的主要问题。甚至可以说，就目前的EventProxy的使用方式而言，即便不借助任何类库，单纯基于JavaScript也可以得到有过之而无不及的编程体验。这次我们便来使用尝试使用&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;来改进首页的逻辑。&lt;/p&gt;

&lt;h1&gt;最后准备&lt;/h1&gt;

&lt;p&gt;还记得首页的逻辑吗？其中可是用到了许多异步方法：&lt;/p&gt;

&lt;pre class="code"&gt;tag_ctrl.get_all_tags(&lt;span style="color: blue"&gt;function &lt;/span&gt;(err, tags) { ... });
topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, topics) { ... });
topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, hot_topics) { ... });
user_ctrl.get_users_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, users) { ... });
user_ctrl.get_users_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, tops) { ... });
topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, no_reply_topics) { ... });
topic_ctrl.get_count_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, all_topics_count) { ... });&lt;/pre&gt;

&lt;p&gt;如果要用Jscex来实现首页，这些方法都必须变成Jscex异步方法。之前有朋友说，在一个现成的项目中使用Jscex代价太高，因为每个函数都必须Jscex化。但事实上，我们完全可以在一个现有的项目逐步地引入Jscex，因为我们轻易地将已有的异步操作“封装”为Jscex异步方法，例如&lt;a href="https://github.com/JeffreyZhao/nodeclub/commit/cb7082873452261a175c282c8c5ee6f2257a8f46"&gt;在controllers/topic.js文件中&lt;/a&gt;：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;/********** Jscex ************/
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;Jscex = require(&lt;span style="color: maroon"&gt;&amp;quot;../libs/jscex&amp;quot;&lt;/span&gt;).Jscex;
&lt;span style="color: blue"&gt;var &lt;/span&gt;Jscexify = Jscex.Async.Jscexify;

exports.get_topics_by_query_async = Jscexify.fromStandard(get_topics_by_query);
exports.get_count_by_query_async = Jscexify.fromStandard(get_count_by_query);&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/nodeclub/commit/a1b7635ea846828045b4e539887b20b0c6a472ff"&gt;其他两个方法的封装&lt;/a&gt;就不列举出来了。可以发现，我们完全不需要一次性把所有的依赖都“重新实现”，可以用到哪儿再封装哪儿，稍后再进行真正的Jscex化——有时候甚至完全无需基于Jscex再写一遍。Jscex适合编写异步操作之间相对复杂的交互，但对于原本就十分简单的异步操作来说，Jscex也并不会带来明显的附加优势。此时我们完全可以保留最普通的异步回调写法，这从任何角度来说都造成问题。&lt;/p&gt;

&lt;h1&gt;实现首页&lt;/h1&gt;

&lt;p&gt;还记得之前提出的首页逻辑吗？&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(request, response) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;tags = tag_ctrl.get_all_tags(); &lt;span style="color: #006400"&gt;// 标签
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;topics = topic_ctrl.get_topics_by_query(...); &lt;span style="color: #006400"&gt;// 最新话题
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;hot_topics = topic_ctrl.get_topics_by_query(...); &lt;span style="color: #006400"&gt;// 热门话题
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;stars = user_ctrl.get_topics_by_query(...); &lt;span style="color: #006400"&gt;// 明星用户
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;tops = user_ctrl.get_users_by_query(...); &lt;span style="color: #006400"&gt;// 得分最高用户
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;no_reply_topics = topic_ctrl.get_topics_by_query(...); &lt;span style="color: #006400"&gt;// 无回复话题
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;topic_count = topic_ctrl.get_count_by_query(...); &lt;span style="color: #006400"&gt;// 话题总数

    &lt;/span&gt;response.render(&lt;span style="color: maroon"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;, { ... }); &lt;span style="color: #006400"&gt;// 输出HTML
&lt;/span&gt;}&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/nodeclub/commit/5959221771f3bfac27abc466de019cd394f80654#diff-0"&gt;使用Jscex来实现这个逻辑&lt;/a&gt;的话，与上述“伪代码”可谓完全一致，唯一的区别只是多了些前后附加的计算逻辑：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;indexAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(req, res) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;page = Number(req.query.page) || 1;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;limit = config.list_topic_count;

    &lt;span style="color: blue"&gt;var &lt;/span&gt;data = {
        current_page: page,
        list_topic_count: limit
    };

    data.topics = $await(topic_ctrl.get_topics_by_query_async(...));

    data.hot_topics = $await(topic_ctrl.get_topics_by_query_async(...));

    data.stars = $await(user_ctrl.get_users_by_query_async(...));

    data.tops = $await(user_ctrl.get_users_by_query_async(...));

    data.no_reply_topics = $await(topic_ctrl.get_topics_by_query_async(...));

    &lt;span style="color: blue"&gt;var &lt;/span&gt;all_topics_count = $await(topic_ctrl.get_count_by_query_async(...));
    data.pages = Math.ceil(all_topics_count / limit);

    data.tags = $await(tag_ctrl.get_all_tags_async());

    &lt;span style="color: #006400"&gt;// 计算最热标签
    &lt;/span&gt;data.hot_tags = _.chain(data.tags)
        .sortBy(&lt;span style="color: blue"&gt;function &lt;/span&gt;(t) { &lt;span style="color: blue"&gt;return &lt;/span&gt;-t.topic_count; })
        .first(5);

    &lt;span style="color: #006400"&gt;// 计算最新标签
    &lt;/span&gt;data.recent_tags = _.chain(data.tags)
        .sortBy(&lt;span style="color: blue"&gt;function &lt;/span&gt;(t) { &lt;span style="color: blue"&gt;return &lt;/span&gt;-t.create_at.valueOf() })
        .first(5);

    res.render(&lt;span style="color: maroon"&gt;'index'&lt;/span&gt;, data);
}));

exports.index = Unjscexify.toRequestHandler(indexAsync);&lt;/pre&gt;

&lt;p&gt;最后一段代码，是使用一个&lt;a href="https://github.com/JeffreyZhao/nodeclub/commit/5959221771f3bfac27abc466de019cd394f80654#diff-1"&gt;辅助方法&lt;/a&gt;，将一个Jscex异步方法转化为一个普通的HTTP Request Handler：&lt;/p&gt;

&lt;pre class="code"&gt;Jscex.Unjscexify = {
    toRequestHandler: &lt;span style="color: blue"&gt;function &lt;/span&gt;(fn) {
        &lt;span style="color: blue"&gt;return function &lt;/span&gt;(req, res, next) {
            fn(req, res).addEventListener(&lt;span style="color: maroon"&gt;&amp;quot;failure&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                next(&lt;span style="color: blue"&gt;this&lt;/span&gt;.error);
            }).start();
        }
    }
}&lt;/pre&gt;

&lt;p&gt;这里我们将发生的任何错误都通过next向外传递，这是标准的处理方式，当然您也可以使用您自己的错误处理策略。您会发现，我们无需复杂的错误处理方式，在编写Jscex方法时，错误会像普通异常一样向外抛出，直到被统一捕获。&lt;/p&gt;

&lt;p&gt;我这里还想提一下Node Club代码里的一些问题。Node Club在我看来是一个比较粗糙的项目，代码质量不太好，一方面是代码风格有些乱（例如使用Tab，符号周围空格等等），还有便是把一些简单的代码写得复杂，例如上面“计算最热标签”这样的逻辑，目前的实现是：&lt;/p&gt;

&lt;pre class="code"&gt;tags.sort(&lt;span style="color: blue"&gt;function &lt;/span&gt;(tag_a, tag_b) {
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(tag_a.topic_count == tag_b.topic_count) &lt;span style="color: blue"&gt;return &lt;/span&gt;0;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(tag_a.topic_count &amp;gt; tag_b.topic_count) &lt;span style="color: blue"&gt;return &lt;/span&gt;-1;
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(tag_a.topic_count &amp;lt; tag_b.topic_count) &lt;span style="color: blue"&gt;return &lt;/span&gt;1;
});&lt;/pre&gt;

&lt;p&gt;其实这里完全可以一句话实现：&lt;/p&gt;

&lt;pre class="code"&gt;tags.sort(&lt;span style="color: blue"&gt;function &lt;/span&gt;(tag_a, tag_b) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;tag_b.topic_count - tag_a.topic_count;
});&lt;/pre&gt;

&lt;p&gt;但我更喜欢使用&lt;a href="http://documentcloud.github.com/underscore/"&gt;Underscore&lt;/a&gt;提供的函数式编程方式，很重要的一点是它保持了输入数据的不变性。数组的sort方法是对本身进行排序，因此Node Club不得不创建一个数组拷贝。使用Underscore可以简化许多代码逻辑，就好比在实现相同功能时，C#代码比Java语言要简单许多。&lt;/p&gt;

&lt;h1&gt;并发&lt;/h1&gt;

&lt;p&gt;并发是好事，不过在上面的实现中，所有的操作是串行的。有人会说这么做起不到并发的效果，但其实我并不在意，因为我们目前的应用是一个服务器端程序，本身就是并发地承受客户端的请求。如果把处理一个请求看作一个事务的话，我们认为单个事务时串行的，但是已经有大量并发的事务。即便事务里的每个操作都并发起来，但单个事务还是要等到所有操作结束后才能完成（即用户看到页面）。由于系统的负载并没有降低，单个事务处理的总时长并没有减少。&lt;/p&gt;

&lt;p&gt;您可以做个实验，硬盘上有10个1G大小的文件，您使用顺序的方式复制所有文件，和同时复制这10个文件，所花时间分别是多少？做过服务器压力测试的同学一定知道，我们加大并发量时，处理相同数量请求所需的总时长并不会减少，甚至随着并发量增加，单位时间内的请求处理能力会明显降低。因此，现代的服务器一般都会在并发量增大的情况下采取保护措施，例如对请求排队，或是返回Service Unavailable错误等等。&lt;/p&gt;

&lt;p&gt;并发还有个问题，就是当一个操作失败时，很难“取消”其他操作，这会造成无谓的资源浪费。因此，如果我们盲目地将可以并发的操作都并发起来，对服务的健康并没有什么好处，除非可以确定这个并发操作的确可以同时进行（例如网络访问和磁盘读取），否则我并不倾向于（在一个服务器应用程序里）并发访问。不过作为一个演示，我便&lt;a href="https://github.com/JeffreyZhao/nodeclub/commit/39f75d74cce1bcd8fc9921aaf969c334e37336c2"&gt;将所有操作都并发起来&lt;/a&gt;吧：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;indexAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(req, res) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;page = Number(req.query.page) || 1;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;limit = config.list_topic_count;

    &lt;span style="color: blue"&gt;var &lt;/span&gt;data = $await(&lt;span style="color: red"&gt;Task.whenAll&lt;/span&gt;({
        topics: topic_ctrl.get_topics_by_query_async({}, {
            skip: (page - 1) * limit,
            limit: limit,
            sort: [[&lt;span style="color: maroon"&gt;'last_reply_at'&lt;/span&gt;, &lt;span style="color: maroon"&gt;'desc'&lt;/span&gt;]]
        }),
        hot_topics: topic_ctrl.get_topics_by_query_async({}, {
            limit: 5,
            sort: [[&lt;span style="color: maroon"&gt;'visit_count'&lt;/span&gt;, &lt;span style="color: maroon"&gt;'desc'&lt;/span&gt;]]
        }),
        stars: user_ctrl.get_users_by_query_async(
            { is_star: &lt;span style="color: blue"&gt;true &lt;/span&gt;},
            { limit: 5 }
        ),
        tops: user_ctrl.get_users_by_query_async({}, {
            limit: 10,
            sort: [[&lt;span style="color: maroon"&gt;'score'&lt;/span&gt;, &lt;span style="color: maroon"&gt;'desc'&lt;/span&gt;]]
        }),
        no_reply_topics: topic_ctrl.get_topics_by_query_async(
            { reply_count: 0 },
            { limit: 5, sort: [[&lt;span style="color: maroon"&gt;'create_at'&lt;/span&gt;, &lt;span style="color: maroon"&gt;'desc'&lt;/span&gt;]] }
        ),
        tags: tag_ctrl.get_all_tags_async(),
        all_topics_count: topic_ctrl.get_count_by_query_async({})
    }));

    data.current_page = page;
    data.list_topic_count = limit;
    data.pages = Math.ceil(data.all_topics_count / limit);

    &lt;span style="color: #006400"&gt;// 计算最热标签
    &lt;/span&gt;data.hot_tags = _.chain(data.tags)
        .sortBy(&lt;span style="color: blue"&gt;function &lt;/span&gt;(t) { &lt;span style="color: blue"&gt;return &lt;/span&gt;-t.topic_count; })
        .first(5);

    &lt;span style="color: #006400"&gt;// 计算最新标签
    &lt;/span&gt;data.recent_tags = _.chain(data.tags)
        .sortBy(&lt;span style="color: blue"&gt;function &lt;/span&gt;(t) { &lt;span style="color: blue"&gt;return &lt;/span&gt;-t.create_at.valueOf() })
        .first(5);

    res.render(&lt;span style="color: maroon"&gt;'index'&lt;/span&gt;, data);
}))&lt;/pre&gt;

&lt;p&gt;Jscex类库“不提倡”盲目并发，它的并发是“可选”的。如果您想要并发哪些操作，将其放在Task.whenAll即可。Task.whenAll是一个辅助方法，您可以输入一个保存Task的对象或是数组（甚至是对象和数组的嵌套），whenAll返回的Task对象会同时发起那些操作，并等待它们全部返回。返回的结果对象，其结构会和输入时完全一致，或是对象，或是数组（甚至对象和数组的嵌套）。在Jscex中，哪些操作串行，哪些操作并发都是由开发人员决定的，完全可以将其轻松地混合起来。例如，我们串行地执行一些前期处理，例如用户认证，然后再将后续的数个操作并发起来。&lt;/p&gt;

&lt;p&gt;在Node Club中有个问题就是“盲目并发”，它是用EventProxy将所有的操作并发起来，而并非有选择的处理。EventProxy适合“盲目并发”的场景，但是对“有选择的并发”支持很差，我们很难选择部分操作串行执行。例如我之前的一个示例“&lt;a href="https://github.com/JeffreyZhao/jscex/blob/master/doc/async/samples/copy-dir-cn.md"&gt;复制完整目录&lt;/a&gt;”，&lt;a href="https://github.com/JeffreyZhao/talks/blob/master/nodeparty-20120108/copy-dir/copy-dir.js"&gt;使用Jscex只要如普通编程那样直接实现逻辑&lt;/a&gt;即可，而无需&lt;a href="https://github.com/JeffreyZhao/talks/blob/master/nodeparty-20120108/copy-dir/copy-dir-raw.js"&gt;如传统编程那样使用各种回调&lt;/a&gt;，但如果非要“事件驱动”，非要生搬硬套EventProxy，事件和回调交织在一起，&lt;a href="https://github.com/JeffreyZhao/talks/blob/master/nodeparty-20120108/copy-dir/copy-dir-eventproxy.js"&gt;实现便会变得非常复杂&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;作为一个面向开发人员的工具，Jscex除了隐藏必要的复杂度之外，还要让目标程序“可控”，无论是串行、并发还是逻辑表达——Jscex使用JavaScript语法，保证了程序逻辑的灵活与可控，尽可能地避免出现&lt;a href="http://en.wikipedia.org/wiki/Leaky_abstraction"&gt;Leaky Abstraction&lt;/a&gt;。EventProxy的确提供了一种“完全并发”的抽象，但是对于需要“可控并发”，或是“串行执行”的逻辑和场景便显得无能为力了。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2012/02/jscexify-nodeclub-1-prepare-nodeclub-website.html"&gt;使用Jscex改进Node Club（1）：运行Node Club网站&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2012/02/jscexify-nodeclub-2-import-jscex.html"&gt;使用Jscex改进Node Club（2）：引入Jscex类库&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2012/02/jscexify-nodeclub-3-home-page-implementation.html"&gt;使用Jscex改进Node Club（3）：分析首页实现&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;使用Jscex改进Node Club（4）：改写首页&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2012/03/jscexify-nodeclub-4-jscexify-home-page.html#comments</comments>
      <pubDate>Sat, 10 Mar 2012 06:11:50 GMT</pubDate>
      <lastBuildDate>Mon, 12 Mar 2012 02:00:53 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>使用Jscex改进Node Club（3）：分析首页实现</title>
      <link>http://blog.zhaojie.me/2012/02/jscexify-nodeclub-3-home-page-implementation.html</link>
      <guid>http://blog.zhaojie.me/2012/02/jscexify-nodeclub-3-home-page-implementation.html</guid>
      <description>&lt;p&gt;上次我们已经将&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;成功地引入项目，现在便可以正式开始关注Node Club的实现了。Node Club中存在大量基于回调的JavaScript代码，颇有无从下手的感觉。既然如此，我们便随便挑一个，从首页入手吧！&lt;/p&gt;

&lt;h1&gt;首页逻辑&lt;/h1&gt;

&lt;p&gt;我们先从首页的JavaScript代码开始。首页的目标其实很简单，加载几部分数据组成一个对象，再交给模板引擎生成HTML代码并输出。就目前来说，显示首页需要加载以下数据：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;标签 &lt;/li&gt;

  &lt;li&gt;最新话题 &lt;/li&gt;

  &lt;li&gt;热门话题 &lt;/li&gt;

  &lt;li&gt;明星用户 &lt;/li&gt;

  &lt;li&gt;得分最高的用户 &lt;/li&gt;

  &lt;li&gt;无回复的话题 &lt;/li&gt;

  &lt;li&gt;话题总数 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对于传统Web开发技术来说，要做到这点着实容易，伪代码如下：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(request, response) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;tags = tag_ctrl.get_all_tags(); &lt;span style="color: #006400"&gt;// 标签
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;topics = topic_ctrl.get_topics_by_query(...); &lt;span style="color: #006400"&gt;// 最新话题
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;hot_topics = topic_ctrl.get_topics_by_query(...); &lt;span style="color: #006400"&gt;// 热门话题
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;stars = user_ctrl.get_topics_by_query(...); &lt;span style="color: #006400"&gt;// 明星用户
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;tops = user_ctrl.get_users_by_query(...); &lt;span style="color: #006400"&gt;// 得分最高用户
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;no_reply_topics = topic_ctrl.get_topics_by_query(...); &lt;span style="color: #006400"&gt;// 无回复话题
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;topic_count = topic_ctrl.get_count_by_query(...); &lt;span style="color: #006400"&gt;// 话题总数

    &lt;/span&gt;response.render(&lt;span style="color: maroon"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;, { ... }); &lt;span style="color: #006400"&gt;// 输出HTML
&lt;/span&gt;}&lt;/pre&gt;

&lt;p&gt;但是在Node.js这个大环境中，这些方法都是用回调函数来返回结果的，因此代码往往会写成：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(request, response, next) {
    &lt;span style="color: #006400"&gt;// 标签
    &lt;/span&gt;tag_ctrl.get_all_tags(&lt;span style="color: blue"&gt;function &lt;/span&gt;(err, tags) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        
        &lt;span style="color: #006400"&gt;// 最新话题
        &lt;/span&gt;topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, topics) {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
            
            &lt;span style="color: #006400"&gt;// 热门话题
            &lt;/span&gt;topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, hot_topics) {
                &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
                
                &lt;span style="color: #006400"&gt;// 明星用户
                &lt;/span&gt;topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, stars) {
                    &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
                    
                    &lt;span style="color: #006400"&gt;// 得分最高用户
                    &lt;/span&gt;user_ctrl.get_users_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, tops) {
                        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
                        
                        &lt;span style="color: #006400"&gt;// 无回复话题
                        &lt;/span&gt;topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, no_reply_topics) {
                            &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
                            
                            topic_ctrl.get_count_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, topic_count) {
                                &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
                                
                                &lt;span style="color: #006400"&gt;// 输出HTML
                                &lt;/span&gt;response.render(&lt;span style="color: maroon"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;, { ... });
                            });
                        });
                    }); 
                });
            });
        });
    });
}&lt;/pre&gt;

&lt;p&gt;很多人不喜欢这类层层嵌套的代码，但老实说，因为这里没有涉及逻辑判断，循环等令人头大的问题，所以在我看来其实这种串行的逻辑其实十分清晰（尽管不太漂亮），一眼就能看清楚在做什么。其实让我不喜欢的倒是这里不断出现的错误处理代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);&lt;/pre&gt;

&lt;p&gt;我一直把反复出现的错误处理代码作为传统异步编程中最麻烦（又不可遗漏）的方面之一。可惜在大量的示例代码中，这反而会被人忽略掉，造成其实“不怎么麻烦”的假象。我认为，作为一个优秀的异步类库，都应该在错误处理上花点功夫，避免开发人员在各个地方不断浪费青春。例如，在这方面各类Promise模型作的都不错。&lt;/p&gt;

&lt;h1&gt;首页实现&lt;/h1&gt;

&lt;p&gt;Node Club使用&lt;a href="https://github.com/JacksonTian/eventproxy"&gt;EventProxy&lt;/a&gt;类库来尝试解决大量异步函数的嵌套问题。在首页上主要使用了以下这种模式：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(request, response, next) {

    &lt;span style="color: #006400"&gt;// 定义最终的回调函数&lt;/span&gt;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;render = &lt;span style="color: blue"&gt;function &lt;/span&gt;(tags, topics, hot_topics, stars, tops, no_reply_topics, pages){
        response.render(&lt;span style="color: maroon"&gt;'index'&lt;/span&gt;, {...});
    };

    &lt;span style="color: #006400"&gt;// 注册最终的回调函数&lt;/span&gt;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;proxy = &lt;span style="color: blue"&gt;new &lt;/span&gt;EventProxy();
    proxy.assign(&lt;span style="color: maroon"&gt;'tags'&lt;/span&gt;, &lt;span style="color: maroon"&gt;'topics'&lt;/span&gt;, &lt;span style="color: maroon"&gt;'hot_topics'&lt;/span&gt;, &lt;span style="color: maroon"&gt;'stars'&lt;/span&gt;, &lt;span style="color: maroon"&gt;'tops'&lt;/span&gt;, &lt;span style="color: maroon"&gt;'no_reply_topics'&lt;/span&gt;, &lt;span style="color: maroon"&gt;'pages'&lt;/span&gt;, render);
    
    tag_ctrl.get_all_tags(&lt;span style="color: blue"&gt;function &lt;/span&gt;(err, tags){
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        proxy.trigger(&lt;span style="color: maroon"&gt;'tags'&lt;/span&gt;, tags);
    });

    topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, topics) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        proxy.trigger(&lt;span style="color: maroon"&gt;'topics'&lt;/span&gt;, topics);
    });
    
    topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, hot_topics) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        proxy.trigger(&lt;span style="color: maroon"&gt;'hot_topics'&lt;/span&gt;, hot_topics);
    });
    
    user_ctrl.get_users_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, users) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        proxy.trigger(&lt;span style="color: maroon"&gt;'stars'&lt;/span&gt;, users);
    });
    
    user_ctrl.get_users_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, tops) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        proxy.trigger(&lt;span style="color: maroon"&gt;'tops'&lt;/span&gt;, tops);
    });
    
    topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, no_reply_topics) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        proxy.trigger(&lt;span style="color: maroon"&gt;'no_reply_topics'&lt;/span&gt;, no_reply_topics);
    });
    
    topic_ctrl.get_count_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, all_topics_count) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        &lt;span style="color: blue"&gt;var &lt;/span&gt;pages = Math.ceil(all_topics_count / limit);
        proxy.trigger(&lt;span style="color: maroon"&gt;'pages'&lt;/span&gt;, pages);
    });
};&lt;/pre&gt;

&lt;p&gt;这种模式可以简单概括为：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;准备一个最终的回调方法，在获取所有结果后执行，并使用assign方法注册给EventProxy对象。 &lt;/li&gt;

  &lt;li&gt;发起各异步操作，并将结果使用trigger方法提交至EventProxy。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当得到所有结果后，EventProxy自然会执行最终的回调方法，即完成最终的任务。&lt;/p&gt;

&lt;h1&gt;实现分析&lt;/h1&gt;

&lt;p&gt;从表面上看来，似乎EventProxy避免了回调函数的层层嵌套，但它的做法只是将每个异步调用分离出来（当然，的确会清晰一些）。如果您把现在的代码与之前“传统”代码相比，会发现两者的差距似乎只是——Tab的数量，或者说是缩进数量。真实的工作，例如每步操作的错误处理代码，还必须完全保留。简单地说，使用EventProxy其实并没有节省什么工作。&lt;/p&gt;

&lt;p&gt;而且，我们完全无需使用EventProxy，也可以轻松写出类似的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(request, response, next) {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;data = { };
    &lt;span style="color: blue"&gt;var &lt;/span&gt;steps = [&lt;span style="color: maroon"&gt;&amp;quot;tags&amp;quot;&lt;/span&gt;, &lt;span style="color: maroon"&gt;&amp;quot;topics&amp;quot;&lt;/span&gt;, ...];

    &lt;span style="color: blue"&gt;var &lt;/span&gt;done = &lt;span style="color: blue"&gt;function &lt;/span&gt;(name, value) {
        data[name] = value;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(steps.remove(name).length &amp;gt; 0) &lt;span style="color: blue"&gt;return&lt;/span&gt;;

        response.render(&lt;span style="color: maroon"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;, {...});
    }

    tag_ctrl.get_all_tags(&lt;span style="color: blue"&gt;function &lt;/span&gt;(err, tags) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        done(&lt;span style="color: maroon"&gt;&amp;quot;tags&amp;quot;&lt;/span&gt;, tags);
    });

    topic_ctrl.get_topics_by_query(..., &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, topics) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        done(&lt;span style="color: maroon"&gt;&amp;quot;topics&amp;quot;&lt;/span&gt;, topics);
    });

    ...
};&lt;/pre&gt;

&lt;p&gt;我们只要使用一个steps数组来准备所有的“数据”，每次完成后剔除一个，直到全部剔除为止即可。这么做还有个好处便是无需关注顺序，在使用EventProxy的时候，我们必须将注册时的使用的名称，和回调函数的参数保持顺序一致。试想，如果要增加一个步骤或是改变一些顺序，我们则必须加倍小心了。所以在我看来，在这里使用EventProxy并没有带来太多的益处，从简化编程的角度来说，效果十分有限。&lt;/p&gt;

&lt;p&gt;要简化编程体验，还是得看Jscex的，下次我们便来改造Node Club的首页。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2012/02/jscexify-nodeclub-1-prepare-nodeclub-website.html"&gt;使用Jscex改进Node Club（1）：运行Node Club网站&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2012/02/jscexify-nodeclub-2-import-jscex.html"&gt;使用Jscex改进Node Club（2）：引入Jscex类库&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;使用Jscex改进Node Club（3）：分析首页实现&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2012/02/jscexify-nodeclub-3-home-page-implementation.html#comments</comments>
      <pubDate>Wed, 29 Feb 2012 15:44:54 GMT</pubDate>
      <lastBuildDate>Sat, 03 Mar 2012 08:57:04 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>使用Jscex改进Node Club（2）：引入Jscex类库</title>
      <link>http://blog.zhaojie.me/2012/02/jscexify-nodeclub-2-import-jscex.html</link>
      <guid>http://blog.zhaojie.me/2012/02/jscexify-nodeclub-2-import-jscex.html</guid>
      <description>&lt;p&gt;之前我们已经将Node Club在本地运行起来了，接着我们便来引入&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;类库，为常用异步方法扩展出Jscex版本，并试着编写一些最简单的Jscex代码。&lt;/p&gt;

&lt;h1&gt;安装Jscex包&lt;/h1&gt;

&lt;p&gt;为项目中安装Jscex十分容易，因为Jscex已经发布在官方NPM源中，我们只需&lt;a href="https://github.com/JeffreyZhao/nodeclub/commit/81e04cea4c279efe275bb74660dfe41954a714e0"&gt;修改package.json文件&lt;/a&gt;即可：&lt;/p&gt;

&lt;pre class="code"&gt;{
    &lt;span style="color: maroon"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;NodeClub&amp;quot;
  &lt;/span&gt;, &lt;span style="color: maroon"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;0.0.1&amp;quot;
  &lt;/span&gt;, &lt;span style="color: maroon"&gt;&amp;quot;main&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;./app.js&amp;quot;
  &lt;/span&gt;, &lt;span style="color: maroon"&gt;&amp;quot;private&amp;quot;&lt;/span&gt;: &lt;span style="color: blue"&gt;true
  &lt;/span&gt;, &lt;span style="color: maroon"&gt;&amp;quot;dependencies&amp;quot;&lt;/span&gt;: {
      &lt;span style="color: maroon"&gt;&amp;quot;express&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;2.5.1&amp;quot;&lt;/span&gt;,
      &lt;span style="color: maroon"&gt;&amp;quot;ejs&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;0.5.0&amp;quot;&lt;/span&gt;,
      &lt;span style="color: maroon"&gt;&amp;quot;eventproxy&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;0.1.0&amp;quot;&lt;/span&gt;,
      &lt;span style="color: maroon"&gt;&amp;quot;mongoose&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;2.4.1&amp;quot;&lt;/span&gt;,
      &lt;span style="color: maroon"&gt;&amp;quot;node-markdown&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;0.1.0&amp;quot;&lt;/span&gt;,
      &lt;span style="color: maroon"&gt;&amp;quot;validator&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;0.3.7&amp;quot;&lt;/span&gt;,
      &lt;span style="color: maroon"&gt;&amp;quot;nodemailer&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;0.3.1&amp;quot;&lt;/span&gt;,
      &lt;span style="color: maroon"&gt;&amp;quot;jscex&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;0.6.x&amp;quot;&lt;/span&gt;,
      &lt;span style="color: maroon"&gt;&amp;quot;jscex-jit&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;0.6.x&amp;quot;&lt;/span&gt;,
      &lt;span style="color: maroon"&gt;&amp;quot;jscex-async&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;0.6.x&amp;quot;&lt;/span&gt;,
      &lt;span style="color: maroon"&gt;&amp;quot;jscex-async-powerpack&amp;quot;&lt;/span&gt;: &lt;span style="color: maroon"&gt;&amp;quot;0.6.x&amp;quot;
  &lt;/span&gt;}
}&lt;/pre&gt;

&lt;p&gt;在此我们引入四个和Jscex相关的包，并指定为0.6.x版本，以便与NPM上的Jscex同步更新。修改之后，便可以使用npm install安装新增的Jscex包：&lt;/p&gt;

&lt;pre class="code"&gt;$ &lt;strong&gt;npm install&lt;/strong&gt;
jscex-async@0.6.0 ./node_modules/jscex-async 
jscex-async-powerpack@0.6.0 ./node_modules/jscex-async-powerpack 
jscex@0.6.0 ./node_modules/jscex 
jscex-jit@0.6.0 ./node_modules/jscex-jit&lt;/pre&gt;

&lt;p&gt;有了NPM之后，每次为项目添加依赖库也只需编辑package.json再npm install就行了。如果需要将本地版本与NPM源保持同步更新，也只需一句npm update命令。&lt;/p&gt;

&lt;h1&gt;Node Club中的异步方法&lt;/h1&gt;

&lt;p&gt;在JavaScript有各种各样的异步方法，要配合Jscex使用的话，则必须使用能与Jscex适配的异步方法，简称Jscex异步方法。在Node Club项目中出现最多的异步方法便是使用&lt;a href="https://github.com/LearnBoost/mongoose"&gt;mongoose&lt;/a&gt;访问MongoDB。mongoose不仅仅提供了MongoDB的访问能力，它还提供相当的ORM功能，可以让我们快速的定义模型，并与MongoDB数据库里的集合映射起来，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;mongoose = require(&lt;span style="color: maroon"&gt;&amp;quot;mongoose&amp;quot;&lt;/span&gt;),
    Schema = mongoose.Schema,
    ObjectId = Schema.ObjectId;

&lt;span style="color: blue"&gt;var &lt;/span&gt;UserSchema = &lt;span style="color: blue"&gt;new &lt;/span&gt;Schema({
    name: String,
    password: String,
    createAt: Date
});

&lt;span style="color: blue"&gt;var &lt;/span&gt;User = mongoose.model(&lt;span style="color: maroon"&gt;'User'&lt;/span&gt;, UserSchema);&lt;/pre&gt;

&lt;p&gt;此时User类型便已经和数据库建立了映射关系，例如我们可以操作数据：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;updatePassword(id, password, cb) {
    User.findOne({ _id: id }, &lt;span style="color: blue"&gt;function &lt;/span&gt;(error, user) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(error) &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(error);

        user.password = password;
        user.save(&lt;span style="color: blue"&gt;function &lt;/span&gt;(error) {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(error) &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(error);

            cb(&lt;span style="color: blue"&gt;null&lt;/span&gt;);
        });
    });
}&lt;/pre&gt;

&lt;p&gt;以上代码的作用是更新一个用户的密码，我们先使用User.findOne获得用户对象，修改后再使用save方法存回数据库。这里用到的都是一种“标准”的异步方法模式，即使用回调函数获得（或返回）结果，其第一个参数为错误对象，如果不为空，则表示出错了。因此每次调用一个异步方法的时候，我们都需要在回调方法里判断是否出错，这也是异步编程的麻烦之一。&lt;/p&gt;

&lt;h1&gt;加载Jscex类库&lt;/h1&gt;

&lt;p&gt;如果要在项目里加载Jscex，我会建议使用一个简单的模块来存放与Jscex初始化相关的代码，&lt;a href="https://github.com/JeffreyZhao/nodeclub/commit/dc1d5d7fe5305d9cd0428e767745d500d5a70f3d"&gt;例如libs/jscex.js&lt;/a&gt;：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;Jscex = require(&lt;span style="color: maroon"&gt;&amp;quot;jscex&amp;quot;&lt;/span&gt;);
require(&lt;span style="color: maroon"&gt;&amp;quot;jscex-jit&amp;quot;&lt;/span&gt;).init(Jscex);
require(&lt;span style="color: maroon"&gt;&amp;quot;jscex-async&amp;quot;&lt;/span&gt;).init(Jscex);
require(&lt;span style="color: maroon"&gt;&amp;quot;jscex-async-powerpack&amp;quot;&lt;/span&gt;).init(Jscex);

&lt;span style="color: blue"&gt;var &lt;/span&gt;Jscexify = Jscex.Async.Jscexify;

&lt;span style="color: blue"&gt;var &lt;/span&gt;mongoose = require(&lt;span style="color: maroon"&gt;&amp;quot;mongoose&amp;quot;&lt;/span&gt;);

&lt;span style="color: blue"&gt;var &lt;/span&gt;mp = mongoose.Model.prototype;
mp.saveAsync = Jscexify.fromStandard(mp.save);
mp.removeAsync = Jscexify.fromStandard(mp.remove);

&lt;span style="color: blue"&gt;var &lt;/span&gt;m = mongoose.Model;
m.findByIdAsync = Jscexify.fromStandard(m.findById);
m.findOneAsync = Jscexify.fromStandard(m.findOne);
m.findAsync = Jscexify.fromStandard(m.find);
m.countAsync = Jscexify.fromStandard(m.count);

exports.Jscex = Jscex;&lt;/pre&gt;

&lt;p&gt;前几行代码是标准的Jscex引入方式。而从mongoose相关的代码开始，便是在为其扩展Jscex异步方法。mongoose对扩展十分友好，它把内部的元数据暴露出来，让我们进行统一扩展。例如为Model扩展一些静态或是实例方法，便相当于为所有的模型及其它们的实例添加了方法。这种做法充分利用了JavaScript的灵活性，假如它把所有的元数据都隐藏起来，那我们没法如此简单直接地引入Jscex扩展了——当然总是有办法的，只是麻烦一些。&lt;/p&gt;

&lt;p&gt;这里我强烈建议每个JavaScript类库或框架的开发者都能实现这种方式，给使用者充分的扩展途径。以Jscex自身为例，它的每个异步任务对象都是Jscex.Async.Task类型的实例，这样jscex-async-powerpack模块便可以轻易补充各种强大的辅助方法。&lt;/p&gt;

&lt;p&gt;此外，由于mongoose遵守异步方法“标准模式”（即使用回调函数传回结果，其第一个参数为错误对象），因此只要一个fromStandard辅助函数便可全部应对。在以后的代码中，我们会大量使用这些扩展后的Jscex异步方法。&lt;/p&gt;

&lt;h1&gt;编写简单的Jscex异步函数&lt;/h1&gt;

&lt;p&gt;现在我们就直接写几行Jscex代码吧。例如在controllers/user.js文件里定义了以下几个方法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;get_user_by_id(id, cb) {
    User.findOne({ _id: id }, &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, user) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(err, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(err, user);
    });
}

&lt;span style="color: blue"&gt;function &lt;/span&gt;get_user_by_name(name, cb) {
    User.findOne({ name: name }, &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, user) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(err, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(err, user);
    });
}

&lt;span style="color: blue"&gt;function &lt;/span&gt;get_user_by_loginname(name, cb) {
    User.findOne({ loginname: name }, &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, user) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(err, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(err, user);
    });
}

&lt;span style="color: blue"&gt;function &lt;/span&gt;get_users_by_ids(ids, cb) {
    User.find({ &lt;span style="color: maroon"&gt;'_id'&lt;/span&gt;: { &lt;span style="color: maroon"&gt;'$in'&lt;/span&gt;: ids } }, &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, users) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(err, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(err, users);
    });
}

&lt;span style="color: blue"&gt;function &lt;/span&gt;get_users_by_query(query, opt, cb) {
    User.find(query, [], opt, &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, users) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(err, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;cb(err, users);
    });
}&lt;/pre&gt;

&lt;p&gt;以上几个方法都有相同的特征：它们都是简单的调用一个mongoose的异步方法，判断是否出错，并返回结果。正如之前提过的那样，编写异步代码的麻烦之一，便是在每次回调时都要判断是否出错，因此在实际项目中的异步代码都会比各种“演示”用的玩具代码更麻烦一些。不过我们可以&lt;a href="https://github.com/JeffreyZhao/nodeclub/commit/9dcbb432bc2909e7e098a8eee3eba9981ca9da4c"&gt;使用Jscex来改写这些代码&lt;/a&gt;：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;Jscex = require(&lt;span style="color: maroon"&gt;&amp;quot;../libs/jscex&amp;quot;&lt;/span&gt;).Jscex;

&lt;span style="color: blue"&gt;var &lt;/span&gt;get_user_by_id_async = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(id) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$await(User.findOneAsync({ _id: id }));
}));

&lt;span style="color: blue"&gt;var &lt;/span&gt;get_user_by_name_async = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(name) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$await(User.findOneAsync({ name: name }));
}));

&lt;span style="color: blue"&gt;var &lt;/span&gt;get_user_by_loginname_async = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(name) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$await(User.findOneAsync({ loginname: name }));
}));

&lt;span style="color: blue"&gt;var &lt;/span&gt;get_users_by_ids_async = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(ids) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$await(User.findAsync({ &lt;span style="color: maroon"&gt;'_id'&lt;/span&gt;: { &lt;span style="color: maroon"&gt;'$in'&lt;/span&gt;: ids } }));
}));

&lt;span style="color: blue"&gt;var &lt;/span&gt;get_users_by_query_async = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(query, opt) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$await(User.findAsync(query, [], opt));
}));&lt;/pre&gt;

&lt;p&gt;在编写Jscex方法中，我们无需操作回调函数，只要在异步点上使用$await进行“等待”即可。我们也无需显式地处理错误，因为一旦出现错误便会抛出异常，异常如果没有被某个try…catch捕获到，则会顺着调用路径一路向上传递，直到被我们的代码或是系统捕获为止。Jscex将简单易用的传统编程模式与实践重新带回异步编程中，做到“同步编写，异步执行”的效果。这就是Jscex诞生的意义。&lt;/p&gt;

&lt;p&gt;从下一篇文章开始，我们将逐步改造Node Club网站中现有的代码。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2012/02/jscexify-nodeclub-1-prepare-nodeclub-website.html"&gt;使用Jscex改进Node Club（1）：运行Node Club网站&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;使用Jscex改进Node Club（2）：引入Jscex类库&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2012/02/jscexify-nodeclub-2-import-jscex.html#comments</comments>
      <pubDate>Mon, 20 Feb 2012 13:57:45 GMT</pubDate>
      <lastBuildDate>Mon, 20 Feb 2012 13:57:45 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>使用Jscex改进Node Club（1）：运行Node Club网站</title>
      <link>http://blog.zhaojie.me/2012/02/jscexify-nodeclub-1-prepare-nodeclub-website.html</link>
      <guid>http://blog.zhaojie.me/2012/02/jscexify-nodeclub-1-prepare-nodeclub-website.html</guid>
      <description>&lt;p&gt;一直想做个相对完整的项目来演示下&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;的使用，可惜缺少创意和精力，一直没能实现。前几天看到&lt;a href="http://club.cnodejs.org/"&gt;Node Club&lt;/a&gt;将其网站开源了，不禁让我十分欢喜。Node Club网站是个真实案例，复杂度适中，既是Jscex的典型使用场景，又能避开我不擅长的网页样式设计和制作，简直是一个再合适不过的基础样板了。周末我大致看了下代码，也试着将几个部分使用Jscex改进了一下，效果也十分显著，于是打算写作一个系列指引，希望可以对Jscex类库的推广有所帮助。在此第一篇，自然是最基本的环境建设开始说起。&lt;/p&gt;

&lt;h1&gt;配置系统环境&lt;/h1&gt;

&lt;p&gt;首先，我强烈建议您可以在GitHub上Fork一下&lt;a href="https://github.com/muyuan/nodeclub"&gt;Node Club网站的项目&lt;/a&gt;，有个版本管理环境做后盾，做什么都会放心许多。我也将所有修改存放在GitHub上，其&lt;a href="https://github.com/JeffreyZhao/nodeclub"&gt;master分支&lt;/a&gt;是我个人不断改进的版本，您可以时刻关注其最新发展。除此之外，我还创建了一个&lt;a href="https://github.com/JeffreyZhao/nodeclub/tree/tutorial"&gt;tutorial分支&lt;/a&gt;，专门为这个系列文章存放代码，保持两者进度一致，同时尽量将修改过程和版本提交对应起来。&lt;/p&gt;

&lt;p&gt;要运行Node Code，首先您得安装必要的环境：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://nodejs.org/"&gt;Node.js&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://npmjs.org/"&gt;Node Package Manager&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://mongodb.org/"&gt;MongoDB&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;无论您使用的是Windows，Linux还是OS X，以上都有对应的安装方式，在此就不详细描述了。文章中会出现一些控制台脚本，是我在OS X上运行的命令和结果。如果没有特殊说明，则路径是nodeclub项目的根目录。如果你使用的是Windows，则需要进行一些修改，其实最常见的修改就是分割路径的斜线还有可执行文件的使用。&lt;/p&gt;

&lt;p&gt;这里假设您对以上三个工具，最好还包括Git有了一定了解。其实目前也无需十分熟悉，看看官方文档，搜几篇文章瞅瞅，就应该差不多了。&lt;/p&gt;

&lt;h1&gt;配置Node Club环境&lt;/h1&gt;

&lt;p&gt;假设您已经下载了Node Club代码（当然最好您是git clone一份自己fork出来项目），并将其解压缩至nodeclub目录中。现在您应该可以使用npm install命令安装所有依赖的包：&lt;/p&gt;

&lt;pre class="code"&gt;$ &lt;strong&gt;npm install&lt;/strong&gt;
eventproxy@0.1.0 ./node_modules/eventproxy 
ejs@0.5.0 ./node_modules/ejs 
validator@0.3.7 ./node_modules/validator 
node-markdown@0.1.0 ./node_modules/node-markdown 
mongoose@2.4.1 ./node_modules/mongoose 
├── colors@0.5.1
├── hooks@0.1.9
└── mongodb@0.9.7-1.4
express@2.5.1 ./node_modules/express 
├── mime@1.2.5
├── qs@0.4.2
├── mkdirp@0.0.7
└── connect@1.8.5
nodemailer@0.3.1 ./node_modules/nodemailer 
├── mailcomposer@0.1.4 (mimelib-noiconv@0.1.6)
└── simplesmtp@0.1.12&lt;/pre&gt;

&lt;p&gt;要运行网站，则还需要准备一份配置文件。您可以将config.default.js复制一份至config.js文件，并建议修改一下一些配置：&lt;/p&gt;

&lt;pre class="code"&gt;exports.config = {
    &lt;span style="color: #006400"&gt;// 网站端口号，默认为80，可能会有冲突，建议改成其他值
    &lt;/span&gt;port: 8080,

    &lt;span style="color: #006400"&gt;// 发系统邮件时使用的用户名
    &lt;/span&gt;mail_user: &lt;span style="color: maroon"&gt;'xxxxx@gmail.com'&lt;/span&gt;,
    &lt;span style="color: #006400"&gt;// 发系统邮件时使用的密码
    &lt;/span&gt;mail_pass: &lt;span style="color: maroon"&gt;'xxxxx'&lt;/span&gt;,
    &lt;span style="color: #006400"&gt;// SMTP服务器地址
    &lt;/span&gt;mail_host: &lt;span style="color: maroon"&gt;'smtp.gmail.com'&lt;/span&gt;,
    &lt;span style="color: #006400"&gt;// 系统邮件发信人
    &lt;/span&gt;mail_sender: &lt;span style="color: maroon"&gt;'xxxxx@gmail.com'&lt;/span&gt;,
    &lt;span style="color: #006400"&gt;// 根据需求配置是否验证
    &lt;/span&gt;mail_use_authentication: &lt;span style="color: blue"&gt;true&lt;/span&gt;,
};&lt;/pre&gt;

&lt;p&gt;其中大部分的设置是在配置系统邮件的SMTP服务器，您可以像我一样使用Gmail，或是跟Node Club原项目一样使用126的邮箱。&lt;/p&gt;

&lt;h1&gt;运行Node Club网站&lt;/h1&gt;

&lt;p&gt;要运行Node Club网站，则需要启动MongoDB数据库，例如：&lt;/p&gt;

&lt;pre class="code"&gt;mongodb-osx-x86_64-2.0.2/bin$ &lt;strong&gt;mkdir data&lt;/strong&gt;
mongodb-osx-x86_64-2.0.2/bin$ &lt;strong&gt;./mongodb --dbpath data&lt;/strong&gt;&lt;/pre&gt;

&lt;p&gt;此时您就在本地启动了一个MongoDB进程，使用默认端口27017，这与网站的默认配置相符。此时您就可以执行app.js来启动网站：&lt;/p&gt;

&lt;pre class="code"&gt;$ &lt;strong&gt;node app.js&lt;/strong&gt;
NodeClub listening on port 8080 in development mode
God bless love....&lt;/pre&gt;

&lt;p&gt;在浏览器里访问http://127.0.0.1:8080，您应该就能看到一个空白的Node Club站点：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/jscexify-nodeclub/1.png"&gt;&lt;img alt="Node Club空白首页" src="http://img.zhaojie.me/blog/jscexify-nodeclub/1.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;此时您可以点击右上角的“注册”链接，注册一个名为admin的用户，部分操作（例如标签管理）需要使用该账号才能进行。注册时会要求填一个邮箱，提交后会收到一封激活邮件，但其中的链接可能不能直接访问（遗漏了端口号，应该是个bug），您可以将其地址复制到浏览器里修改并访问。激活成功后便可登录，登陆后会进入空白的后台页面：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/jscexify-nodeclub/2.png"&gt;&lt;img alt="Node Club空白后台" src="http://img.zhaojie.me/blog/jscexify-nodeclub/2.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;至此万事俱备，任何时候您想重启Node Club网站，只需ctrl+c中断node app.js命令再重新运行即可。从下一篇文章开始，我们将正式开始Jscex之旅。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;使用Jscex改进Node Club（1）：运行Node Club网站&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2012/02/jscexify-nodeclub-1-prepare-nodeclub-website.html#comments</comments>
      <pubDate>Mon, 20 Feb 2012 06:24:33 GMT</pubDate>
      <lastBuildDate>Mon, 20 Feb 2012 12:49:15 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>一份用于学习单元测试的案例需求（实现）</title>
      <link>http://blog.zhaojie.me/2012/02/a-case-requirement-to-practice-unit-testing-or-tdd-implementation.html</link>
      <guid>http://blog.zhaojie.me/2012/02/a-case-requirement-to-practice-unit-testing-or-tdd-implementation.html</guid>
      <description>&lt;p&gt;终于把这份实现写完了，比想象中要花时间，尤其是为了可测试性而增加的代码结构。我并没有使用TDD来开发这个类库，依然是先写代码，再写单元测试，测试代码也只关注了代码主体，没有刻意去测试边界情况。一部分原因是其中都是内部实现，可以把握住输入，令一部分原因是这段实现主要是各种交互，而没有复杂的业务逻辑。我个人满足于单元测试而不是测试驱动开发，但如果您是使用测试驱动开发（TDD）甚至传说中的BDD来实现这个方案，那就更好不过了。&lt;/p&gt;

&lt;h1&gt;引入接口与工厂&lt;/h1&gt;

&lt;p&gt;我经常嚷嚷说，为了增加代码的可测试性，我必须在项目里不断引入各种抽象。例如写一个用于连接的MyConnector类，它包含一个构造函数和三个成员，原本我只需要这么写：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyConnector&lt;/span&gt;&lt;span style="color: #2b91af"&gt;
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;MyConnector(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer) { ... }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;Client { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Connect() { ... }
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;CloseClient() { ...}
}&lt;/pre&gt;

&lt;p&gt;但是，假如有其他类使用了MyConnector，会发现MyConnector是没法Mock的（除非使用一些基于二进制修改的Mock类库）。例如，它的所有成员都是非虚（non-virtual）的。有人说，把它全部标为virtual不就行了嘛，像Java里面默认就是virtual的。但我不喜欢这样，因为这个类本来就没打算有扩展的场景，也不想为了单元测试而去改变代码实现。因此，为了可测试性，代码便会变成这个样子：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyConnectorFactory
&lt;/span&gt;{
    &lt;span style="color: #2b91af"&gt;IMyConnector &lt;/span&gt;Create(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer);
}

&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyConnector
&lt;/span&gt;{
    &lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;Client { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;void &lt;/span&gt;Connect();
    &lt;span style="color: blue"&gt;void &lt;/span&gt;CloseClient();
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyConnector &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IMyConnector
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Factory &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IMyConnectorFactory
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyConnector &lt;/span&gt;Create(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer)
        {
            &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyConnector&lt;/span&gt;(uris, eventFirer);
        }
    }

    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyConnectorFactory &lt;/span&gt;DefaultFactory = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Factory&lt;/span&gt;();

    &lt;span style="color: blue"&gt;public &lt;/span&gt;MyConnector(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer) { ... }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;Client { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Connect() { ... }
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;CloseClient() { ...}
}&lt;/pre&gt;

&lt;p&gt;为了可测试性，我们在具体的实现类以外还增加了：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;接口：使用者通过接口访问该类，便于Mock类的成员。 &lt;/li&gt;

  &lt;li&gt;工厂接口：注入使用者，便于创建实例，相当于Mock构造函数。 &lt;/li&gt;

  &lt;li&gt;具体类的默认工厂：定义在具体类内部，用于创建一个具体类的实例。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当然，&lt;a href="http://blog.zhaojie.me/2012/01/make-things-mockable-with-mono-cecil.html"&gt;使用我之前提过的方法&lt;/a&gt;便可以省去两个接口，只留两个具体类就行了。&lt;/p&gt;

&lt;h1&gt;模拟MyDriver代码&lt;/h1&gt;

&lt;p&gt;单元测试离不开Mock，但是我发布示例代码之后，有几个人（都是Java同学）向我反映说MyDriver项目里的MyDriverClient类为什么是final的，能否将其去除以便Mock？我说不行，这里我是故意设置的障碍，因为实际情况下第三方的类库可能也是这种情况，因此需要自己在MyClient项目里解决这个问题。&lt;/p&gt;

&lt;p&gt;当然解决方法并不难，只需要为MyDriverClient写一个接口及封装类即可，自然还有对应的工厂类型：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IDisposable
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Connect();
    &lt;span style="color: blue"&gt;void &lt;/span&gt;AddQuery(&lt;span style="color: blue"&gt;int &lt;/span&gt;queryId);
    &lt;span style="color: blue"&gt;void &lt;/span&gt;RemoveQuery(&lt;span style="color: blue"&gt;int &lt;/span&gt;queryId);
    &lt;span style="color: #2b91af"&gt;MyData &lt;/span&gt;Receive();
}

&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClientFactory
&lt;/span&gt;{
    &lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;Create(&lt;span style="color: blue"&gt;string &lt;/span&gt;uri);
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyDriverClientWrapper &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IMyDriverClient
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Factory &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IMyDriverClientFactory
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;Create(&lt;span style="color: blue"&gt;string &lt;/span&gt;uri)
        {
            &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyDriverClientWrapper&lt;/span&gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyDriverClient&lt;/span&gt;(uri));
        }
    }

    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClientFactory &lt;/span&gt;DefaultFactory = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Factory&lt;/span&gt;();

    &lt;span style="color: blue"&gt;private readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyDriverClient &lt;/span&gt;_client;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;MyDriverClientWrapper(&lt;span style="color: #2b91af"&gt;MyDriverClient &lt;/span&gt;client)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._client = client;
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Connect()
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._client.Connect();
    }

    ...
}&lt;/pre&gt;

&lt;p&gt;这么做除了便于单元测试以外，还可以形成一个窄接口，避免在使用的时候迷失在繁复的成员里。&lt;/p&gt;

&lt;h1&gt;抽象多线程操作&lt;/h1&gt;

&lt;p&gt;多线程操作会直接用到Thread类以及相关静态方法，这些也是不利于单元测试的地方，为此我抽象出了一个IThreadUtils接口，以及一个默认实现：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IThreadUtils
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Sleep(&lt;span style="color: blue"&gt;int &lt;/span&gt;millisecondsTimeout);

    &lt;span style="color: blue"&gt;void &lt;/span&gt;StartNew(&lt;span style="color: blue"&gt;string &lt;/span&gt;name, &lt;span style="color: #2b91af"&gt;ThreadStart &lt;/span&gt;start);
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadUtils &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IThreadUtils
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadUtils &lt;/span&gt;Instance = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadUtils&lt;/span&gt;();

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Sleep(&lt;span style="color: blue"&gt;int &lt;/span&gt;millisecondsTimeout)
    {
        &lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;.Sleep(millisecondsTimeout);
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;StartNew(&lt;span style="color: blue"&gt;string &lt;/span&gt;name, &lt;span style="color: #2b91af"&gt;ThreadStart &lt;/span&gt;start)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;thread = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;(start);
        thread.Name = name;
        thread.Start();
    }
}&lt;/pre&gt;

&lt;p&gt;在代码里所有的线程操作都会使用IThreadUtils完成，便于模拟。不过，在单元测试的时候，我们还必须真正去检查“新线程”有没有执行正确的代码。为此，我还实现了一个DelayThreadUtils类，专供单元测试使用：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DelayThreadUtils &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IThreadUtils
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;gt; _actionsToExecute = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;gt;();

    &lt;span style="color: blue"&gt;public virtual void &lt;/span&gt;StartNew(&lt;span style="color: blue"&gt;string &lt;/span&gt;name, &lt;span style="color: #2b91af"&gt;ThreadStart &lt;/span&gt;start)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._actionsToExecute.Add(() =&amp;gt; start());
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Sleep(&lt;span style="color: blue"&gt;int &lt;/span&gt;millisecondsTimeout) { }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Execute()
    {
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;action &lt;span style="color: blue"&gt;in this&lt;/span&gt;._actionsToExecute) action();
    }
}&lt;/pre&gt;

&lt;p&gt;在DelayThreadUtils中，所有的StartNew调用都只是“收集”操作，并不执行，一切都延迟到Execute方法调用时才真正执行。在单元测试里使用DelayThreadUtils的模式大约为（基于&lt;a href="http://code.google.com/p/moq"&gt;Moq&lt;/a&gt;类库）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// 1. 准备Mock对象
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;threadUtilsMock = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Mock&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;DelayThreadUtils&lt;/span&gt;&amp;gt; { CallBase = &lt;span style="color: blue"&gt;true &lt;/span&gt;};

&lt;span style="color: green"&gt;// 2. 使用threadUtilsMock.Object

// 3. 确认ThreadUtils上的相关方法已经正确调用
&lt;/span&gt;threadUtilsMock.Verify(tu =&amp;gt; tu.StartNew(&lt;span style="color: #a31515"&gt;&amp;quot;Some Name&amp;quot;&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;It&lt;/span&gt;.IsAny&amp;lt;&lt;span style="color: #2b91af"&gt;ThreadStart&lt;/span&gt;&amp;gt;()));

&lt;span style="color: green"&gt;// 4. 确认线程里的操作没有执行&lt;/span&gt;

&lt;span style="color: green"&gt;// 5. 执行线程里的操作
&lt;/span&gt;threadUtilsMock.Object.Execute();

&lt;span style="color: green"&gt;// 6. 确认线程里的操作已经正确执行&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;总而言之，我们只是想要确认目标代码的确是在新线程里执行。&lt;/p&gt;

&lt;h1&gt;构造函数&lt;/h1&gt;

&lt;p&gt;还是拿MyConnector为例，它在实际使用时其实只需要这样一个构造函数：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;MyConnector(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer) { ... }&lt;/pre&gt;

&lt;p&gt;但在内部实现的时候，我们还需要线程操作，也需要创建MyDriverClient对象，都是涉及单元测试的依赖。因此，我们也会准备另一个“完整”的构造函数，用于注入所有需要的依赖，而真正使用的构造函数则委托至“完整”的构造函数上：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal &lt;/span&gt;MyConnector(
    &lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris,
    &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer,
    &lt;span style="color: #2b91af"&gt;IThreadUtils &lt;/span&gt;threadUtils,
    &lt;span style="color: #2b91af"&gt;IMyDriverClientFactory &lt;/span&gt;clientFactory) { ... }

&lt;span style="color: blue"&gt;public &lt;/span&gt;MyConnector(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer)
    : &lt;span style="color: blue"&gt;this&lt;/span&gt;(uris, eventFirer, &lt;span style="color: #2b91af"&gt;ThreadUtils&lt;/span&gt;.Instance, &lt;span style="color: #2b91af"&gt;MyDriverClientWrapper&lt;/span&gt;.DefaultFactory) { }&lt;/pre&gt;

&lt;p&gt;为了区分“实际使用”的构造函数和“用于测试”的构造函数，我的规则是使用public和internal进行区分。由于它们大都是定义在内部类里，因此两者效果其实没有什么不同，只是为了“看上去”能分清而已。&lt;/p&gt;

&lt;h1&gt;MyConnection实现简要描述&lt;/h1&gt;

&lt;p&gt;MyClient项目唯一暴露的类型便是MyConnection。MyConnection的绝大部分操作都会委托给MySubscriptionManager，其完整功能被拆分成了数个小部分，每个小部分都能独立的实现和测试，代码不多，属于可测试的范围内。&lt;/p&gt;

&lt;p&gt;MyConnector类封装了与服务器连接相关的逻辑，包括失败后的重试：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Connect方法：尝试连接服务器，直至成功。连接失败时会发起ConnectFailed事件，成功后发起Connected事件，并在Client属性里暴露出可用的MyDriverClient对象。&lt;/li&gt;

  &lt;li&gt;CloseClient方法：关闭已经打开的MyDriverClient对象，并发起Disconnect事件。如正在尝试连接但还未完成，则停止尝试。该方法调用后可以重新调用Connect方法。&lt;/li&gt;

  &lt;li&gt;Client属性：可用的MyDriverClient对象，如果尚未成功连接，则该属性为null。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MyConnector的Connect方法总是在一个新线程里执行，连接成功后会触发Connected事件，由MySubscriptionManager的OnConnected方法响应，并开启三个工作线程，它们分别是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;MyRequestSender：从一个BlockingCollection&amp;lt;MyRequest&amp;gt;集合中不断获取MyRequest请求（Subscribe或Unsubscribe），并使用MyConnector.Client上的AddQuery或RemoveQuery方法。假如从集合中获取请求时操作被取消，则退出该任务。假如AddQuery或RemoveQuery时抛出异常，则关闭MyConnector对象。&lt;/li&gt;

  &lt;li&gt;MyDataReceiver：使用MyConnector.Client上的Receive方法不断获取MyData数据，并放入DataProduced集合。假如Receive方法抛出异常，则关闭MyConnector对象。MyDataReceiver同时还暴露出一个CancellationToken表明操作是否已经取消。假如Receive方法返回null（意味着了MyConnector已在其他地方关闭）或抛出异常，则都会将这个CancellationToken取消，并退出任务。简单地说，MyDataReceiver就是“生产者”。&lt;/li&gt;

  &lt;li&gt;MyDataDispatcher：从一个BlockingCollection&amp;lt;MyData&amp;gt;集合中不断获取MyData对象，并分派至对应的IMySubscriber对象中。假如从集合中获取对象时操作被取消，则退出该任务。简单地说，MyDataDispatcher是一个“消费者”，消费MyDataReceiver生产出来的MyData对象。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;剩下的便是MySubscriptionManager内部的协调工作了，它也会监听Disconnected事件，并重新调用MyConnector.Connect方法，后者会触发Connected事件，并重新开启三个新的MyRequestSender、MyDataReceiver，MyDataDispatcher任务。简单的说：旧的任务会在任意环节出错时停止，而每次重新连接之后，都会开启新的任务。&lt;/p&gt;

&lt;p&gt;MyClient的Program.cs项目中包含了简单的使用案例。&lt;/p&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;所有的代码都可以在GitHub项目里的&lt;a href="https://github.com/JeffreyZhao/unit-test-practices/tree/master/csharp/Practice01-End"&gt;csharp/Practice01-End目录里&lt;/a&gt;找到，包括实现以及单元测试。Java项目我就没有精力再做一份了，但是我想这不会影响交流，使用Java的同学肯定也可以理解C#代码，我也会继续关注一些Java同学所实现的代码，需要的时候也会将其移植为C#代码。&lt;/p&gt;

&lt;p&gt;单元测试的组织方式我尝试了使用&lt;a href="http://haacked.com/archive/2012/01/02/structuring-unit-tests.aspx"&gt;Phil Haacked之前提到的做法&lt;/a&gt;，感觉不错。我使用VS 2010编写代码，但没有使用VS集成的单元测试框架，这方面我使用的是&lt;a href="http://xunit.codeplex.com/"&gt;xUnit&lt;/a&gt;，一方面原因是由于xUnit更符合我的审美（这点以后再说），另一方面原因是我不想让示例与开发环境产生依赖，现在VS 2010 Express甚至Mono下都能运行这些代码。Mock类库使用的是&lt;a href="http://code.google.com/p/moq"&gt;Moq&lt;/a&gt;，这应该也是目前最流行的C#标准Mock类库了吧。所有依赖的类库我都用&lt;a href="http://nuget.codeplex.com/"&gt;NuGet&lt;/a&gt;进行管理，您也可以通过NuGet来安装这些类库（执行Practice01-End目录下的install-packages.cmd文件即可）。&lt;/p&gt;

&lt;p&gt;这只是个练习，因此我也有一些问题还没有完全想明白：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;设计上有没有什么问题？以上为了可测试性引入的代码结构是否必要？如果不必要，可以怎么做？&lt;/li&gt;

  &lt;li&gt;所有代码依旧是设计先行，然后实现，最后再单元测试，而不是使用TDD进行开发。从最后的结果来看，似乎我更多是在测试“交互行为”而不是输入输出，是否合适？这是否是因为没有TDD的缘故？&lt;/li&gt;

  &lt;li&gt;但是，对于此类非“逻辑验证”类型的代码，直接让我用TDD来开发，我真心感到无从下手。因为如果不是事件划分好职责，我很难获得很好的可测试性，不知如何单元测试，更不论测试驱动开发了。&lt;/li&gt;

  &lt;li&gt;并发方面的代码能否进行单元测试？例如MyConnector里就要考虑在Connect方法还没有返回的时候，使用CloseClient进行中断。这部分能用TDD实现吗？&lt;/li&gt;

  &lt;li&gt;能否用BDD开发这个案例吗？只要开个头就行，让我明白，剩下的我自己来。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果我有更多问题也会不断列出，欢迎大家一起来讨论。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2012/02/a-case-requirement-to-practice-unit-testing-or-tdd-implementation.html#comments</comments>
      <pubDate>Fri, 03 Feb 2012 10:44:23 GMT</pubDate>
      <lastBuildDate>Sat, 04 Feb 2012 10:38:28 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>使用Mono.Cecil解决无法Mock非虚方法和密闭类的问题</title>
      <link>http://blog.zhaojie.me/2012/01/make-things-mockable-with-mono-cecil.html</link>
      <guid>http://blog.zhaojie.me/2012/01/make-things-mockable-with-mono-cecil.html</guid>
      <description>&lt;p&gt;终于把&lt;a href="http://blog.zhaojie.me/2012/01/a-case-requirement-to-practice-unit-testing-or-tdd.html"&gt;上次那份需求&lt;/a&gt;的实现写完了，比想象中要花时间，尤其是为了可测试性而增加的代码结构。我并没有使用TDD来开发这个类库，依然是先写代码，再写单元测试，测试代码也只关注了代码主体，没有刻意去测试边界情况。一部分原因是其中都是内部实现，可以把握住输入，令一部分原因是这段实现主要是各种交互，而没有复杂的业务逻辑。我个人满足于单元测试而不是测试驱动开发，但如果您是使用测试驱动开发来实现这个方案，那就更好不过了。&lt;/p&gt;

&lt;h1&gt;引入接口与工厂&lt;/h1&gt;

&lt;p&gt;我经常嚷嚷说，为了增加代码的可测试性，我必须在项目里不断引入各种抽象。例如写一个用于连接的MyConnector类，它包含一个构造函数和三个成员，原本我只需要这么写：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyConnector&lt;/span&gt;&lt;span style="color: #2b91af"&gt;
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;MyConnector(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer) { ... }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;Client { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Connect() { ... }
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;CloseClient() { ...}
}&lt;/pre&gt;

&lt;p&gt;但是，假如有其他类使用了MyConnector，会发现MyConnector是没法Mock的。例如，它的所有成员都是非虚（non-virtual）的。有人说，把它全部标为virtual不就行了嘛，像Java里面默认就是virtual的。但我不喜欢这样，因为这个类本来就没打算有扩展的场景，也不想为了单元测试而去改变代码实现。因此，为了可测试性，代码便会变成这个样子：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyConnectorFactory
&lt;/span&gt;{
    &lt;span style="color: #2b91af"&gt;IMyConnector &lt;/span&gt;Create(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer);
}

&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyConnector
&lt;/span&gt;{
    &lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;Client { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;void &lt;/span&gt;Connect();
    &lt;span style="color: blue"&gt;void &lt;/span&gt;CloseClient();
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyConnector &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IMyConnector
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Factory &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IMyConnectorFactory
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyConnector &lt;/span&gt;Create(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer)
        {
            &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyConnector&lt;/span&gt;(uris, eventFirer);
        }
    }

    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyConnectorFactory &lt;/span&gt;DefaultFactory = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Factory&lt;/span&gt;();

    &lt;span style="color: blue"&gt;public &lt;/span&gt;MyConnector(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer) { ... }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;Client { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Connect() { ... }
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;CloseClient() { ...}
}&lt;/pre&gt;

&lt;p&gt;为了可测试性，我们在具体的实现类以外还增加了：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;接口：使用者通过接口访问该类，便于Mock类的成员。 &lt;/li&gt;

  &lt;li&gt;工厂接口：注入使用者，便于创建实例，相当于Mock构造函数。 &lt;/li&gt;

  &lt;li&gt;具体类的默认工厂：定义在具体类内部，用于创建一个具体类的实例。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当然，&lt;a href="http://blog.zhaojie.me/2012/01/make-things-mockable-with-mono-cecil.html"&gt;使用我之前提过的方法&lt;/a&gt;便可以省去两个接口，只留两个具体类就行了。&lt;/p&gt;

&lt;h1&gt;模拟MyDriver代码&lt;/h1&gt;

&lt;p&gt;单元测试离不开Mock，但是我发布示例代码之后，有几个人（都是Java同学）向我反映说MyDriver项目里的MyDriverClient类为什么是final的，能否将其去除以便Mock？我说不行，这里我是故意设置的障碍，因为实际情况下第三方的类库可能也是这种情况，因此需要自己在MyClient项目里解决这个问题。&lt;/p&gt;

&lt;p&gt;当然解决方法并不难，只需要为MyDriverClient写一个接口及封装类即可，自然还有对应的工厂类型：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IDisposable
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Connect();
    &lt;span style="color: blue"&gt;void &lt;/span&gt;AddQuery(&lt;span style="color: blue"&gt;int &lt;/span&gt;queryId);
    &lt;span style="color: blue"&gt;void &lt;/span&gt;RemoveQuery(&lt;span style="color: blue"&gt;int &lt;/span&gt;queryId);
    &lt;span style="color: #2b91af"&gt;MyData &lt;/span&gt;Receive();
}

&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClientFactory
&lt;/span&gt;{
    &lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;Create(&lt;span style="color: blue"&gt;string &lt;/span&gt;uri);
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyDriverClientWrapper &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IMyDriverClient
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Factory &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IMyDriverClientFactory
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClient &lt;/span&gt;Create(&lt;span style="color: blue"&gt;string &lt;/span&gt;uri)
        {
            &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyDriverClientWrapper&lt;/span&gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyDriverClient&lt;/span&gt;(uri));
        }
    }

    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IMyDriverClientFactory &lt;/span&gt;DefaultFactory = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Factory&lt;/span&gt;();

    &lt;span style="color: blue"&gt;private readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyDriverClient &lt;/span&gt;_client;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;MyDriverClientWrapper(&lt;span style="color: #2b91af"&gt;MyDriverClient &lt;/span&gt;client)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._client = client;
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Connect()
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._client.Connect();
    }

    ...
}&lt;/pre&gt;

&lt;p&gt;这么做除了便于单元测试以外，还可以形成一个窄接口，避免在使用的时候迷失在繁复的成员里。&lt;/p&gt;

&lt;h1&gt;抽象多线程操作&lt;/h1&gt;

&lt;p&gt;多线程操作会直接用到Thread类以及相关静态方法，这些也是不利于单元测试的地方，为此我抽象出了一个IThreadUtils接口，以及一个默认实现：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IThreadUtils
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Sleep(&lt;span style="color: blue"&gt;int &lt;/span&gt;millisecondsTimeout);

    &lt;span style="color: blue"&gt;void &lt;/span&gt;StartNew(&lt;span style="color: blue"&gt;string &lt;/span&gt;name, &lt;span style="color: #2b91af"&gt;ThreadStart &lt;/span&gt;start);
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadUtils &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IThreadUtils
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadUtils &lt;/span&gt;Instance = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadUtils&lt;/span&gt;();

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Sleep(&lt;span style="color: blue"&gt;int &lt;/span&gt;millisecondsTimeout)
    {
        &lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;.Sleep(millisecondsTimeout);
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;StartNew(&lt;span style="color: blue"&gt;string &lt;/span&gt;name, &lt;span style="color: #2b91af"&gt;ThreadStart &lt;/span&gt;start)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;thread = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;(start);
        thread.Name = name;
        thread.Start();
    }
}&lt;/pre&gt;

&lt;p&gt;在代码里所有的线程操作都会使用IThreadUtils完成，便于模拟。不过，在单元测试的时候，我们还必须真正去检查“新线程”有没有执行正确的代码。为此，我还实现了一个DelayThreadUtils类，专供单元测试使用：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DelayThreadUtils &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IThreadUtils
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;gt; _actionsToExecute = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;gt;();

    &lt;span style="color: blue"&gt;public virtual void &lt;/span&gt;StartNew(&lt;span style="color: blue"&gt;string &lt;/span&gt;name, &lt;span style="color: #2b91af"&gt;ThreadStart &lt;/span&gt;start)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._actionsToExecute.Add(() =&amp;gt; start());
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Sleep(&lt;span style="color: blue"&gt;int &lt;/span&gt;millisecondsTimeout) { }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Execute()
    {
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;action &lt;span style="color: blue"&gt;in this&lt;/span&gt;._actionsToExecute) action();
    }
}&lt;/pre&gt;

&lt;p&gt;在DelayThreadUtils中，所有的StartNew调用都只是“收集”操作，并不执行，一切都延迟到Execute方法调用时才真正执行。在单元测试里使用DelayThreadUtils的模式大约为（基于&lt;a href="http://code.google.com/p/moq"&gt;Moq&lt;/a&gt;类库）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// 1. 准备Mock对象
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;threadUtilsMock = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Mock&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;DelayThreadUtils&lt;/span&gt;&amp;gt; { CallBase = &lt;span style="color: blue"&gt;true &lt;/span&gt;};

&lt;span style="color: green"&gt;// 2. 使用threadUtilsMock.Object

// 3. 确认ThreadUtils上的相关方法已经正确调用
&lt;/span&gt;threadUtilsMock.Verify(tu =&amp;gt; tu.StartNew(&lt;span style="color: #a31515"&gt;&amp;quot;Some Name&amp;quot;&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;It&lt;/span&gt;.IsAny&amp;lt;&lt;span style="color: #2b91af"&gt;ThreadStart&lt;/span&gt;&amp;gt;()));

&lt;span style="color: green"&gt;// 4. 确认线程里的操作没有执行&lt;/span&gt;

&lt;span style="color: green"&gt;// 5. 执行线程里的操作
&lt;/span&gt;threadUtilsMock.Object.Execute();

&lt;span style="color: green"&gt;// 6. 确认线程里的操作已经正确执行&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;总而言之，我们只是想要确认目标代码的确是在新线程里执行。&lt;/p&gt;

&lt;h1&gt;构造函数&lt;/h1&gt;

&lt;p&gt;还是拿MyConnector为例，它在实际使用时其实只需要这样一个构造函数：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;MyConnector(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer) { ... }&lt;/pre&gt;

&lt;p&gt;但在内部实现的时候，我们还需要线程操作，也需要创建MyDriverClient对象，都是涉及单元测试的依赖。因此，我们也会准备另一个“完整”的构造函数，用于注入所有需要的依赖，而真正使用的构造函数则委托至“完整”的构造函数上：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal &lt;/span&gt;MyConnector(
    &lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris,
    &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer,
    &lt;span style="color: #2b91af"&gt;IThreadUtils &lt;/span&gt;threadUtils,
    &lt;span style="color: #2b91af"&gt;IMyDriverClientFactory &lt;/span&gt;clientFactory) { ... }

&lt;span style="color: blue"&gt;public &lt;/span&gt;MyConnector(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] uris, &lt;span style="color: #2b91af"&gt;IConnectionEventFirer &lt;/span&gt;eventFirer)
    : &lt;span style="color: blue"&gt;this&lt;/span&gt;(uris, eventFirer, &lt;span style="color: #2b91af"&gt;ThreadUtils&lt;/span&gt;.Instance, &lt;span style="color: #2b91af"&gt;MyDriverClientWrapper&lt;/span&gt;.DefaultFactory) { }&lt;/pre&gt;

&lt;p&gt;为了区分“实际使用”的构造函数和“用于测试”的构造函数，我的规则是使用public和internal进行区分。由于它们大都是定义在内部类里，因此两者效果其实没有什么不同，只是为了“看上去”能分清而已。&lt;/p&gt;

&lt;h1&gt;MyConnection实现简要描述&lt;/h1&gt;

&lt;p&gt;MyClient项目唯一暴露的类型便是MyConnection。MyConnection的绝大部分操作都会委托给MySubscriptionManager，其完整功能被拆分成了数个小部分，每个小部分都能独立的实现和测试，代码不多，属于可测试的范围内。&lt;/p&gt;

&lt;p&gt;MyConnector类封装了与服务器连接相关的逻辑，包括失败后的重试：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Connect方法：尝试连接服务器，直至成功。连接失败时会发起ConnectFailed事件，成功后发起Connected事件，并在Client属性里暴露出可用的MyDriverClient对象。 &lt;/li&gt;

  &lt;li&gt;CloseClient方法：关闭已经打开的MyDriverClient对象，并发起Disconnect事件。如正在尝试连接但还未完成，则停止尝试。该方法调用后可以重新调用Connect方法。 &lt;/li&gt;

  &lt;li&gt;Client属性：可用的MyDriverClient对象，如果尚未成功连接，则该属性为null。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MyConnector的Connect方法总是在一个新线程里执行，连接成功后会触发Connected事件，由MySubscriptionManager的OnConnected方法响应，并开启三个工作线程，它们分别是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;MyRequestSender：从一个BlockingCollection&amp;lt;MyRequest&amp;gt;集合中不断获取MyRequest请求（Subscribe或Unsubscribe），并使用MyConnector.Client上的AddQuery或RemoveQuery方法。假如从集合中获取请求时操作被取消，则退出该任务。假如AddQuery或RemoveQuery时抛出异常，则关闭MyConnector对象。 &lt;/li&gt;

  &lt;li&gt;MyDataReceiver：使用MyConnector.Client上的Receive方法不断获取MyData数据，并放入DataProduced集合。假如Receive方法抛出异常，则关闭MyConnector对象。MyDataReceiver同时还暴露出一个CancellationToken表明操作是否已经取消。假如Receive方法返回null（意味着了MyConnector已在其他地方关闭）或抛出异常，则都会将这个CancellationToken取消，并退出任务。简单地说，MyDataReceiver就是“生产者”。 &lt;/li&gt;

  &lt;li&gt;MyDataDispatcher：从一个BlockingCollection&amp;lt;MyData&amp;gt;集合中不断获取MyData对象，并分派至对应的IMySubscriber对象中。假如从集合中获取对象时操作被取消，则退出该任务。简单地说，MyDataDispatcher是一个“消费者”，消费MyDataReceiver生产出来的MyData对象。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;剩下的便是MySubscriptionManager内部的协调工作了，它也会监听Disconnected事件，并重新调用MyConnector.Connect方法，后者会触发Connected事件，并重新开启三个新的MyRequestSender、MyDataReceiver，MyDataDispatcher任务。简单的说：旧的任务会在任意环节出错时停止，而每次重新连接之后，都会开启新的任务。&lt;/p&gt;

&lt;p&gt;MyClient的Program.cs项目中包含了简单的使用案例。&lt;/p&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;所有的代码都可以在GitHub项目里的&lt;a href="https://github.com/JeffreyZhao/unit-test-practices/tree/master/csharp/Practice01-End"&gt;csharp/Practice01-End目录里&lt;/a&gt;找到，包括实现以及单元测试。Java项目我就没有精力再做一份了，但是我想这不会影响交流，使用Java的同学肯定也可以理解C#代码，我也会继续关注一些Java同学所实现的代码，需要的时候也会将其移植为C#代码。&lt;/p&gt;

&lt;p&gt;我使用VS 2010编写代码，但没有使用VS集成的单元测试框架。我使用的是&lt;a href="http://xunit.codeplex.com/"&gt;xUnit&lt;/a&gt;，一方面原因是由于xUnit更符合我的审美（这点以后再说），另一方面原因是我不想让示例与开发环境产生依赖，现在VS 2010 Express甚至Mono下都能运行这些代码。Mock类库使用的是&lt;a href="http://code.google.com/p/moq"&gt;Moq&lt;/a&gt;，这应该也是目前最流行的C#标准Mock类库了吧。所有依赖的类库我都用&lt;a href="http://nuget.codeplex.com/"&gt;NuGet&lt;/a&gt;进行管理，您也可以通过NuGet来安装这些类库。&lt;/p&gt;

&lt;p&gt;这只是个练习，因此我也有一些问题还没有完全想明白：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;设计上有没有什么问题？以上为了可测试性引入的代码结构是否必要？如果不必要，可以怎么做？ &lt;/li&gt;

  &lt;li&gt;所有代码依旧是设计先行，然后实现，最后再单元测试，而不是使用TDD进行开发。从最后的结果来看，似乎我更多是在测试“交互行为”而不是输入输出，是否合适？这是否是因为没有TDD的缘故？ &lt;/li&gt;

  &lt;li&gt;但是，对于此类非“逻辑验证”类型的代码，直接让我用TDD来开发，我真心感到无从下手。因为如果不是事件划分好职责，我很难获得很好的可测试性，不知如何单元测试，更不论测试驱动开发了。 &lt;/li&gt;

  &lt;li&gt;并发方面的代码能否进行单元测试？例如MyConnector里就要考虑在Connect方法还没有返回的时候，使用CloseClient进行中断。这部分能用TDD实现吗？ &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果我有更多问题也会不断列出，欢迎大家一起来讨论。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2012/01/make-things-mockable-with-mono-cecil.html#comments</comments>
      <pubDate>Thu, 12 Jan 2012 14:50:26 GMT</pubDate>
      <lastBuildDate>Fri, 03 Feb 2012 11:11:34 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>求助：一份用于学习单元测试的案例需求</title>
      <link>http://blog.zhaojie.me/2012/01/a-case-requirement-to-practice-unit-testing-or-tdd.html</link>
      <guid>http://blog.zhaojie.me/2012/01/a-case-requirement-to-practice-unit-testing-or-tdd.html</guid>
      <description>&lt;p&gt;一直熟知单元测试的重要性，也算是了看了几本这方面的经典书籍，但是真开始上手的时候总会遇到各种各样的坎。例如，为什么总感觉自己的单元测试之间有较多的重合，为什么每个单元测试都要准备那么多依赖？有的说法是，这意味着代码设计不够好，单元测试也有问题，或者说没有使用TDD的缘故，等等。但是，现实开发过程中在这方面也颇感无力。前段时间在微博上咨询了几个问题，感觉收获不大，这次干脆整理一份需求，仔细认真向高手学习一下代码设计，单元测试，甚至测试驱动开发的方式吧。我也会准备一些礼物来感谢一部分同学的帮助。&lt;/p&gt;

&lt;h1&gt;MyDriver项目描述&lt;/h1&gt;

&lt;p&gt;我准备了一个简单而完整的需求，使用C#和Java各实现了一份，以便更多同学可以加入到这次活动中来。不过下面的说明暂时围绕C#代码开展，后文也会提到Java实现与C#的区别，不过两者基本完全一致，只有些微不同，例如命名方式。&lt;/p&gt;

&lt;p&gt;首先，我们有一个MyDriver项目，您可以将其认为是一个网络服务的驱动程序。在实际情况下，这个驱动程序不会公开源代码，因此您不能修改这个项目里的任何代码，而只能在MyClient项目里使用它们。&lt;/p&gt;

&lt;p&gt;MyDriver项目里唯一的核心类型便是MyDriverClient类，它有几个主要成员：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;MyDriverClient(string uri)：构造函数，创建一个MyDriverClient类型的实例。uri为服务器地址，在这里只是个摆设。 &lt;/li&gt;

  &lt;li&gt;void Connect()：连接服务器，连接后则可以进行后续操作。&lt;/li&gt;

  &lt;li&gt;void AddQuery(int queryId)：向服务器端发起一个查询，标识为queryId。使用相同queryId多次调用这个方法与调用一次无异。 &lt;/li&gt;

  &lt;li&gt;void RemoveQuery(int queryId)：向服务器端取消一个查询，标识为queryId。如果这个查询本身并不存在，则不会发生任何事情。 &lt;/li&gt;

  &lt;li&gt;void Dispose()：关闭与服务器端的连接。 &lt;/li&gt;

  &lt;li&gt;MyData Receive()：接受一条服务器端返回的查询数据，如果没有数据，则会一直阻塞。如果连接关闭，则会返回null。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其中Connect，AddQuery、RemoveQuery和Receive由于都要和服务器端进行通信，因此都有可能会抛出MyDriverException。这些方法一旦抛出异常之后，该MyDriverClient对象则需要被视为不可用，但我们依然需要调用Dispose方法将其关闭。&lt;/p&gt;

&lt;p&gt;MyData有两个字段，int类型的QueryID和字符串类型的Value。前者标识这条数据对应于哪条查询，而Value则是查询得到的数据，服务器端会将查询得到的数据不断推送到客户端。值得注意的是，即使使用了RemoveQuery取消了某个id的查询，但服务器端也可能已经向客户端推送了与这个id有关的数据，因此在一定时间内，Receive方法还是会得到这个id有关的MyData对象。这个时间虽然会很短，但并非不可能发生。&lt;/p&gt;

&lt;p&gt;在调用AddQuery方法添加某个id的查询之后，服务器端首先会推送一条{ QueryID: id, Value: &amp;quot;begin&amp;quot; }这样的MyData对象，表示查询已经生效，此后再会源源不断地推送与该查询相关的数据。&lt;/p&gt;

&lt;p&gt;在Program类型中有可执行的Main方法，可能会帮助您理解以上这些描述：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;driver = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyDriverClient&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;jeffz://server:12345&amp;quot;&lt;/span&gt;);

    &lt;span style="color: blue"&gt;try
    &lt;/span&gt;{
        driver.Connect();
        driver.AddQuery(1);
        driver.AddQuery(2);
        driver.AddQuery(3);
    }
    &lt;span style="color: blue"&gt;catch
    &lt;/span&gt;{
        driver.Dispose();
        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;&amp;quot;Error occurred when connect or add query.&amp;quot;&lt;/span&gt;);
        &lt;span style="color: #2b91af"&gt;Environment&lt;/span&gt;.Exit(1);
    }

    &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;(() =&amp;gt; ReceiveData(driver)).Start();
}

&lt;span style="color: blue"&gt;private static void &lt;/span&gt;ReceiveData(&lt;span style="color: #2b91af"&gt;MyDriverClient&lt;/span&gt; driver)
{
    &lt;span style="color: blue"&gt;try
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;data = driver.Receive();
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(data == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
            {
                &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;&amp;quot;Closed&amp;quot;&lt;/span&gt;);
                &lt;span style="color: blue"&gt;break&lt;/span&gt;;
            }
            &lt;span style="color: blue"&gt;else
            &lt;/span&gt;{
                &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(data);
            }
        }
    }
    &lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;MyDriverException&lt;/span&gt;)
    {
        driver.Close();
        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;&amp;quot;Error occurred when receive data.&amp;quot;&lt;/span&gt;);
    }
}&lt;/pre&gt;

&lt;p&gt;在Main方法中，我们先创建一个MyDriverClient对象，并添加三个查询。如果添加查询时抛出了异常，则关闭MyDriverClient并退出，否则便启动一个额外的线程轮询Receive方法，并将数据打印在屏幕上。Receive方法如果返回null，则表明连接已经断开（其他某处调用了Dispose方法）。如果Receive方法抛出异常，则将MyDriverClient对象关闭。这段程序将会输出与下面类似的内容：&lt;/p&gt;

&lt;pre class="code"&gt;1, begin
2, begin
3, begin
1, 572224
2, 64186468
3, 9448434
1, 568828
2, 94581343
3, 7291394
...
1, 84165615
2, 26815943
3, 237878844
1, 44716345
Error occurred when receive data.&lt;/pre&gt;

&lt;p&gt;Java程序的结构与C#完全相同，区别只是命名方式有所不同。此外C#里的MyDriver项目在Java项目里则变成了myDriver包，自然您也不能修改包里的代码。&lt;/p&gt;

&lt;h1&gt;MyClient项目描述&lt;/h1&gt;

&lt;p&gt;与MyDriver对应，MyClient则是您要实现的代码（及其单元测试）。其中我已经定义了一些需要对外暴露的类型和成员，您自然也可以添加更多私有或是内部的类型和成员，但一定要保持这些公开的接口不变。&lt;/p&gt;

&lt;p&gt;MyConnection类型是MyClient的核心部分，其中封装了MyDriverClient类的使用。MyConnection的成员如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ReconnectInterval：一个静态常量，两次尝试之间的间隔。&lt;/li&gt;

  &lt;li&gt;MyConnection(string[] uris)：构造函数，一个MyConnection保存多个服务器的uri。&lt;/li&gt;

  &lt;li&gt;void Open()：开始连接服务器。Open方法则会在额外的线程连接服务器，其调用本身会立即返回。&lt;/li&gt;

  &lt;li&gt;int Subscribe(IMySubscriber subscriber)：使用一个新的QueryID订阅数据，subscriber会开始收到与此次订阅相关的所有数据。该方法返回一个int值，作为订阅的ID，可以使用Unsubscribe方法取消订阅。取消订阅后subscriber不会继续收到数据。&lt;/li&gt;

  &lt;li&gt;bool Unsubscribe(int subscriptionId)：取消数据订阅，如果传入的subscriptionId有效，则返回true，否则返回false。&lt;/li&gt;

  &lt;li&gt;void Dispose()：断开与服务器的连接（即调用MyDriverClient的Dispose方法）。该方法“不需要”清除所有订阅，换言之可以再次Open，每个已注册的subscriber会重新开始接受数据。&lt;/li&gt;

  &lt;li&gt;Connected事件：在每次连接成功时触发。&lt;/li&gt;

  &lt;li&gt;ConnectFailed事件：在每次连接失败时触发。&lt;/li&gt;

  &lt;li&gt;Disconnected事件：连接断开时触发。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;与MyDriverClient的“抛出异常”的错误反馈方式不同，MyConnection一旦遇到各种服务器通信的失败时则会选择“重试”。例如：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;调用Open方法之后，会在另一个的线程里从第一个uri开始连接。如果连接失败，则尝试连接下一个uri，如果最后一个uri也失败则重新尝试第一个uri，直至成功。两次尝试之间都需要等待一段时间（ReconnectInterval）。&lt;/li&gt;

  &lt;li&gt;MyConnection会在另一个线程里轮询MyDriverClient的Receive方法，如果抛出了MyDriverException异常，则关闭当前的MyDriverClient对象，并“立即”重新开始连接下一个uri，如果失败，则再尝试下一个（此时需要有一定间隔），连接成功后重新向各subscriber发送数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IMySubscriber是订阅器的接口，它有两个方法：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;void OnBegin()：当MyDriverClient的Receive方法收到begin数据时，则调用这个方法。&lt;/li&gt;

  &lt;li&gt;void OnMessage(string message)：当MyDriverClient的Receive方法收到其他数据时，调用这个方法。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;简单地说，MyConnection便是在一个额外的线程中轮询MyDriverClient的Receive方法，并将接收到的MyData对象，根据QueryID分派到对应的subscriber中。接收和分派需要使用一个缓冲区，不能阻塞。值得注意的是，如果调用Subscribe方法的时候尚未连接成功，则相当于只是注册了一个subscriber。假如已经连接了服务器，则在额外的线程里调用AddQuery方法，而Subscribe方法使用要立即返回。同理，取消订阅时使用的Unsubscribe方法也要立即返回，不能被MyDriverClient的RemoveQuery方法阻塞。&lt;/p&gt;

&lt;p&gt;Java项目与C#类似，一些区别则是为了遵循Java的习惯，例如命名以及事件的定义方式，以及使用Closeable接口代替了IDisposable接口。&lt;/p&gt;

&lt;h1&gt;实现要求&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/unit-test-practices"&gt;代码已经存放在GitHub中，项目Practices01&lt;/a&gt;，C#和Java都有。您可以Fork项目，也可以下载到本地。最好您也可以把代码存放到某个在线的Repository中，这样我可以随时查看到您的进展，我也可以立即给您反馈。&lt;/p&gt;

&lt;p&gt;MyClient实现中最重要的部分其实是单元测试，如果您使用测试驱动开发（TDD）亦可，但不做强求，唯一的要求便是有“足够”的单元测试。为了简化问题，您也不需要过于纠缠于一些没有描述到的边界情况，例如还没有调用MyDriverClient的Connect方法便去AddQuery会怎样，或是反复调用Open等等。我们假设所有的使用方式都是“正确”的。但是，对于描述过的情况，例如必须使用“额外的线程”，或是出错之后的重新连接，则必须正确地实现，并包含足够的单元测试。&lt;/p&gt;

&lt;p&gt;在编写单元测试的时候，您可以使用您最喜欢的Mock类库，但请尽量使用普通的Mock类库，而不要使用基于二进制改写或其他方式实现的高级类库，如&lt;a href="http://www.typemock.com/"&gt;TypeMock&lt;/a&gt;，&lt;a href="http://research.microsoft.com/en-us/projects/moles/"&gt;Moles&lt;/a&gt;或是&lt;a href="http://code.google.com/p/powermock/"&gt;PowerMock&lt;/a&gt;。我知道它们可以Mock静态成员或是final成员，的确会有很大帮助，但是我还是希望可以了解如何通过优秀的代码设计来辅助单元测试。像&lt;a href="http://code.google.com/p/moq/"&gt;Moq&lt;/a&gt;或是&lt;a href="http://easymock.org/"&gt;EasyMock&lt;/a&gt;都是不错的选择。&lt;/p&gt;

&lt;p&gt;如果您有任何反馈，例如上面的需求有任何不清楚的地方，也请立即提出，我会做出改进。这次我打算给三位积极参与并提供较好实现的同学分别送上一本书（或是其他您想要的礼物）作为感谢。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2012/01/a-case-requirement-to-practice-unit-testing-or-tdd.html#comments</comments>
      <pubDate>Sat, 07 Jan 2012 07:23:32 GMT</pubDate>
      <lastBuildDate>Thu, 02 Feb 2012 13:10:57 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>使用Mono.Cecil辅助ASP.NET MVC使用dynamic类型Model</title>
      <link>http://blog.zhaojie.me/2011/09/aspnet-mvc-dynamic-model-mono-cecil.html</link>
      <guid>http://blog.zhaojie.me/2011/09/aspnet-mvc-dynamic-model-mono-cecil.html</guid>
      <description>&lt;p&gt;这也是之前在珠三角技术沙龙上的示例之一，解决的是在ASP.NET MVC使用dynamic类型Model时遇到的一个真实问题。C# 4编译器支持dynamic类型，因此在编写页面模板的时候自然就可以把它作为视图的Model类型。表现层的需求很容易改变，因此dynamic类型的Model可以减少我们反复修改强类型Model的麻烦，再配合匿名类型的使用，可谓是动静相宜，如鱼得水。不过，如果把一个匿名类型直接作为Model交给视图去使用，在默认情况下会抛出异常。我们可以用Mono.Cecil来改变这一情况。&lt;/p&gt;

&lt;h1&gt;在视图中使用dynamic类型Model&lt;/h1&gt;

&lt;p&gt;我们先来重现这个问题。创建一个使用C# 4的ASP.NET MVC网站，添加如下的Controller，其中把匿名类型作为视图Model：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HomeController &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Controller
&lt;/span&gt;{&lt;span style="color: green"&gt;
    &lt;/span&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Index(&lt;span style="color: blue"&gt;string &lt;/span&gt;title = &lt;span style="color: #a31515"&gt;&amp;quot;&amp;lt;&amp;lt;Default&amp;gt;&amp;gt;&amp;quot;&lt;/span&gt;)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;View(&lt;span style="color: blue"&gt;new &lt;/span&gt;{ Title = title });
    }
}&lt;/pre&gt;

&lt;p&gt;并定义一个Index.aspx作为视图模板，Model类型作为dynamic，并用到Title：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="background: yellow"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;@ &lt;/span&gt;&lt;span style="color: maroon"&gt;Page &lt;/span&gt;&lt;span style="color: red"&gt;Language&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;C#&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;Inherits&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;System.Web.Mvc.ViewPage&amp;lt;dynamic&amp;gt;&amp;quot; &lt;/span&gt;&lt;span style="background: yellow"&gt;%&amp;gt;

&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;!&lt;/span&gt;&lt;span style="color: maroon"&gt;DOCTYPE &lt;/span&gt;&lt;span style="color: red"&gt;html&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;html&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;head &lt;/span&gt;&lt;span style="color: red"&gt;runat&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;server&amp;quot;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;title&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;Index&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;title&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;head&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;body&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;h1&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: yellow"&gt;&amp;lt;%&lt;/span&gt;&lt;span style="color: blue"&gt;: &lt;/span&gt;Model.Title &lt;span style="background: yellow"&gt;%&amp;gt;&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;h1&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;body&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;html&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;按理来说，这么做应该一切正常，但是运行之后便会提示说Model上找不到Title成员：&lt;/p&gt;
&lt;img alt="dynamic model load failed" src="http://img.zhaojie.me/blog/aspnet-mvc-dynamic-model-mono-cecil/0.png" /&gt; 

&lt;p&gt;这又是什么原因呢？&lt;/p&gt;

&lt;h1&gt;访问级别与成员&lt;/h1&gt;

&lt;p&gt;在C# 4出现之前，我们也完全可以构造一个Model类型作为视图的模型，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IndexModel
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Title { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;

&lt;p&gt;使用这种做法便完全可以正常运行通过了。那么为什么具体类型能够正常工作，而匿名类型却失败了呢？“按常理推断”它们不都是普通的类型，然后访问它们的属性吗？我们用ILSpy查看使用匿名类型编译后的结果，可以发现匿名类型与上面的IndexModel有一个重要的不同之处：&lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/aspnet-mvc-dynamic-model-mono-cecil/1.png" target="_blank"&gt;&lt;img alt="internal anonymous types" src="http://img.zhaojie.me/blog/aspnet-mvc-dynamic-model-mono-cecil/1.png"  width="450px"/&gt;&lt;/a&gt;

&lt;p&gt;由于是“匿名类型”，显然它的访问级别应该是internal的，这样它就能对外“隐藏”起来了。但是这就给ASP.NET MVC的视图带来了麻烦。因为ASP.NET MVC的视图会在运行时动态地编译aspx为额外的dll，因此它是无法访问到Controller所在程序集的internal成员的。经试验，如果我们将之前的IndexModel的访问级别修改为internal便会得到相同的结果。&lt;/p&gt;

&lt;p&gt;额外提一句，类似的代码在Mono下却可以运行通过。这意味着在动态访问对象成员的时候，Mono和.NET在访问级别方面的检查是有所不同的。虽然在这个情景里Mono更方便，但理论上说，.NET的做法实则更合理。&lt;/p&gt;

&lt;h1&gt;使用NuGet安装Mono.Cecil&lt;/h1&gt;

&lt;p&gt;&lt;a href="http://www.mono-project.com/Cecil"&gt;Mono.Cecil&lt;/a&gt;是Mono的组件之一，用来编辑.NET程序集文件。我们可以用它来打探一个.NET程序集内部的结构，就像反射那样，只不过并不需要将程序集加载进来，Mono.Cecil只是读取文件物理内容而已。例如，上图所用的ILSpy便用到了Mono.Cecil。更重要的是，Mono.Cecil可以修改并保存程序集，这便可以让我们实现各种奇形怪状的要求。像这篇文章所提到的，只不过是小试牛刀而已。&lt;/p&gt;

&lt;p&gt;Mono和.NET是二进制兼容的，因此我们可以直接把Mono下的Mono.Cecil.dll复制并引用到.NET程序里。不过这么做还是麻烦了，如今在.NET平台上使用各种组件已经有更方便的做法：使用包管理器。.NET平台下的包管理器叫做&lt;a href="http://nuget.org/"&gt;NuGet&lt;/a&gt;，是由SubText的作者，后来被微软聘用作ASP.NET MVC程序经理的Phil Haack带头开发的开源项目。NuGet提供了Visual Studio的扩展，同时也有基于PowerShell的命令行。这里我们就从Visual Studio的扩展开始使用吧。&lt;/p&gt;

&lt;p&gt;创建一个名为PublicAnonymous的控制台项目，并选择Reference - Manage NuGet Packages：&lt;/p&gt;
&lt;img alt="manage nuget packages" src="http://img.zhaojie.me/blog/aspnet-mvc-dynamic-model-mono-cecil/2.png" /&gt; 

&lt;p&gt;搜索Mono.Cecil，并安装即可：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/aspnet-mvc-dynamic-model-mono-cecil/3.png" target="_blank"&gt;&lt;img alt="install mono.cecil via nuget" src="http://img.zhaojie.me/blog/aspnet-mvc-dynamic-model-mono-cecil/3.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;NuGet会自动处理组件之间的依赖及项目的配置，您也可以自己把玩一番。&lt;/p&gt;

&lt;h1&gt;使用Mono.Cecil修改程序集&lt;/h1&gt;

&lt;p&gt;有了Mono.Cecil我们便可以修改程序集了，只需数行代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;asmFile = args[0];
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;&amp;quot;Making anonymous types public for '{0}'.&amp;quot;&lt;/span&gt;, asmFile);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;asmDef = &lt;span style="color: #2b91af"&gt;AssemblyDefinition&lt;/span&gt;.ReadAssembly(asmFile, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ReaderParameters
    &lt;/span&gt;{
        ReadSymbols = &lt;span style="color: blue"&gt;true
    &lt;/span&gt;});

    &lt;span style="color: blue"&gt;var &lt;/span&gt;anonymousTypes = asmDef.Modules
        .SelectMany(m =&amp;gt; m.Types)
        .Where(t =&amp;gt; t.Name.Contains(&lt;span style="color: #a31515"&gt;&amp;quot;&amp;lt;&amp;gt;f__AnonymousType&amp;quot;&lt;/span&gt;));

    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;type &lt;span style="color: blue"&gt;in &lt;/span&gt;anonymousTypes)
    {
        type.IsPublic = &lt;span style="color: blue"&gt;true&lt;/span&gt;;
    }

    asmDef.Write(asmFile, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WriterParameters
    &lt;/span&gt;{
        WriteSymbols = &lt;span style="color: blue"&gt;true
    &lt;/span&gt;});
}&lt;/pre&gt;

&lt;p&gt;首先，从参数中获取需要修改的程序集名称，找到所有的匿名类型，并将其访问级别设为Public后保存。保存的时候将WriteSymbols参数设为true，这样它也会同时修改pdb文件——这很重要，否则修改后的程序集无法和pdb文件内容相对应，便无法调试了。换句话说，Mono.Cecil也能正确处理pdb文件。&lt;/p&gt;

&lt;p&gt;最后，只要在ASP.NET MVC网站编译时使用这个项目即可，只需配置一下它的Post Build事件：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/aspnet-mvc-dynamic-model-mono-cecil/4.png" target="_blank"&gt;&lt;img alt="post build scripts" src="http://img.zhaojie.me/blog/aspnet-mvc-dynamic-model-mono-cecil/4.png" width="450" /&gt;&lt;/a&gt;

&lt;p&gt;再次编译并运行程序，即可得到正确结果。再拿ILSpy来检查一番：&lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/aspnet-mvc-dynamic-model-mono-cecil/5.png" target="_blank"&gt;&lt;img alt="public anonymous types" src="http://img.zhaojie.me/blog/aspnet-mvc-dynamic-model-mono-cecil/5.png"  width="450px" /&gt;&lt;/a&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;在沙龙上，有朋友问我怎么样可以成为一个高级.NET技术人员。我不知道“如何成为”，但我想，了解整个生态环境的发展情况，了解.NET的优势及不足，甚至能够了解相关领域其他技术方向的发展态势，应该是优秀.NET程序员的特质之一吧。&lt;/p&gt;

&lt;p&gt;而Mono便是.NET生态环境的重要组成部分。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/09/aspnet-mvc-dynamic-model-mono-cecil.html#comments</comments>
      <pubDate>Mon, 05 Sep 2011 16:21:25 GMT</pubDate>
      <lastBuildDate>Mon, 05 Sep 2011 17:40:10 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <title>在.NET平台下使用C#交互式控制台（上）：简介</title>
      <link>http://blog.zhaojie.me/2011/08/port-csharp-repl-from-mono-1-introduction.html</link>
      <guid>http://blog.zhaojie.me/2011/08/port-csharp-repl-from-mono-1-introduction.html</guid>
      <description>&lt;p&gt;上周日在广州的珠三角技术沙龙上，我的演讲题目是“Mono之于.NET程序员”。Mono一直是我十分喜爱的产品，我也一直关注它的发展，总有很多人用各种方式对它进行FUD，甚至是.NET程序员自己。这其实跟程序员使用盗版一样，自掘坟墓，是种无比愚蠢的行为。在演讲中，我提到.NET程序员可以如何从Mono项目中得到帮助，现在便以C#交互式控制台为例，演示下在.NET平台下使用Mono项目的常见方式。&lt;/p&gt;

&lt;p&gt;Mono和.NET都是&lt;a href="http://www.ecma-international.org/publications/standards/Ecma-335.htm"&gt;CLI（ECMA 335）&lt;/a&gt;的实现，包括C#语言编译器，运行时和类库等等。与微软实现的.NET不同，Mono是个开源项目，我们可以在授权允许的范围内任意折腾。例如，微软在Silverlight里提供了一个JSON类库，但我们无法将其用于普通的.NET项目，于是我们就可以&lt;a href="http://blog.zhaojie.me/2010/10/use-silverlight-system-json-in-normal-application.html"&gt;从Mono里剥离相关代码出来&lt;/a&gt;。再比如，Windows Phone的SDK中没有提供像.NET 4.0里面一样的任务并行库，于是也有开发人员&lt;a href="http://www.infoq.com/cn/news/2011/08/Silverlight-TPL"&gt;将Mono里面的实现移植了过来&lt;/a&gt;。社区成员也十分喜欢开源，因此无论是Mono团队还是其他开发人员都会很乐意捣鼓各种东西，并且也搞出许多有意思的东西来。&lt;a href="http://www.mono-project.com/CsharpRepl"&gt;C#交互式控制台&lt;/a&gt;（有时也将其称为&lt;a href="http://en.wikipedia.org/wiki/Read-eval-print_loop"&gt;REPL&lt;/a&gt;，即Read-Eval-Print-Loop）就是其中一个。&lt;/p&gt;

&lt;p&gt;之前Anders Hejlsberg在PDC 2010中提到“C#与Visual Basic的未来”，其中一项功能就是“&lt;a href="http://blog.zhaojie.me/2010/11/pdc2010-the-future-of-csharp-and-vb-by-anders-hejlsberg-3.html"&gt;编译器即服务（Compiler as a Service）&lt;/a&gt;”。这个功能是指，把编译器的功能向普通用户开放出来，这样便可以实现更多的东西，例如代码解释执行，或是把代码变成语法树，让程序理解其语义等等（这便是&lt;a href="http://www.infoq.com/cn/articles/jscex-javascript-asynchronous-programming"&gt;Jscex的根本所在&lt;/a&gt;）。其实几年前就已经在Mono有类似的实现了。Mono的C#编译器mcs本身是由C#实现的，因此它直接就包含了编译器的完整功能，API好用与否暂且不论，但的确提供了这方面的能力，需要的同学完全可以自行获取。&lt;/p&gt;

&lt;p&gt;Mono编译器功能暴露在Mono.CSharp类库中，而C#的交互式控制台便是“编译器即服务”顺理成章的衍生品。我想你一定遇到过这样的情况，例如，知道DateTime类型的ToString在接受某些参数会输出什么样的效果，却有些记不清，查文档自然会有答案但是太麻烦，其实一试便知。还有比如字符串解析，正则表达式匹配，临时计算等等。我见过很多同学在遇到这种情况的时候，都会直接把代码写在程序里，然后设置断点，然后运行程序观察执行效果。经验丰富一点的朋友则会时刻准备着一个控制台项目或是用&lt;a href="http://www.sliver.com/dotnet/SnippetCompiler/"&gt;Snippit Compiler&lt;/a&gt;，写点试验代码运行一下。不过，最理想的方式其实是使用F#，Scala，Ruby，Python等语言中都有的交互式控制台。例如以下便是在VS 2010中自带的F#交互式控制台：&lt;/p&gt;
&lt;img alt="F# REPL" src="http://img.zhaojie.me/blog/csharp-repl/fsharp-repl.png" /&gt; 

&lt;p&gt;我几乎天天都会用到F#交互式控制台，用来验证一些做法和猜想是否正确，确定之后再写成C#代码。我会F#，使用F#交互控制台自然不在话下。如果您懂得Python或Ruby，则使用IronPython或IronRuby的交互式控制台也是个不错的办法。但如果您只会用C#，那似乎就没辙了。幸运的是，Mono为C#程序员打开了这扇窗，我们也完全可以使用C#的交互式控制台来辅助工作，如下图：&lt;/p&gt;
&lt;img alt="C# REPL" src="http://img.zhaojie.me/blog/csharp-repl/csharp-repl-0.png" /&gt; 

&lt;p&gt;在下一篇文章里，我们便来一起看一下，如何让Mono提供的C#交互式控制台运行在Windows和.NET下。这是个不错的例子，略有障碍，也没有过于复杂或取巧的地方，可以作为.NET程序员利用Mono组件的一个典型示例。&lt;/p&gt;

&lt;p&gt;最后再附上上周日的演讲幻灯片，大伙后睹为快：&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_9054321"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="Mono for .NET Developers" href="http://www.slideshare.net/jeffz/mono-for-dotnet-developers" target="_blank"&gt;Mono for .NET Developers&lt;/a&gt;&lt;/strong&gt; &lt;iframe height="355" marginheight="0" src="http://www.slideshare.net/slideshow/embed_code/9054321" frameborder="0" width="425" marginwidth="0" scrolling="no"&gt;&lt;/iframe&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/" target="_blank"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz" target="_blank"&gt;jeffz&lt;/a&gt; &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;总之一句话：作为.NET程序员，如果您忽视或排斥Mono的话，损失的将会是你自己。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/08/port-csharp-repl-from-mono-1-introduction.html#comments</comments>
      <pubDate>Wed, 31 Aug 2011 16:25:50 GMT</pubDate>
      <lastBuildDate>Thu, 01 Sep 2011 01:25:29 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>谈谈年度最佳代码“不管你们信不信，反正我信了”</title>
      <link>http://blog.zhaojie.me/2011/08/from-the-code-of-no-matter-you-believe-it-or-not.html</link>
      <guid>http://blog.zhaojie.me/2011/08/from-the-code-of-no-matter-you-believe-it-or-not.html</guid>
      <description>&lt;p&gt;最近有段十分流行的代码，是从江湖传闻“身怀八蛋”的铁道部发言人王勇平同志的一句名言：“不管你们信不信，反正我信了……这是生命的奇迹……它就是发生了”所引申出来的。这段代码虽然只是在调侃，但是围绕这段代码也产生了一些讨论（如代码风格，编程规范等等），在此顺手记录一下，就当无聊罢。&lt;/p&gt;

&lt;p&gt;这段代码是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;try
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(you.believe(it) == &lt;span style="color: blue"&gt;true &lt;/span&gt;|| you.believe(it) == &lt;span style="color: blue"&gt;false&lt;/span&gt;)
    {
        I.believe(it);
    }
}
&lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
{
    &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Exception&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;It's a miracle!&amp;quot;&lt;/span&gt;);
}
&lt;span style="color: blue"&gt;finally
&lt;/span&gt;{
    it.justHappened();
}&lt;/pre&gt;

&lt;p&gt;代码与原文的对应关系不言自明，从命名风格上看，我们默认其为Java代码。话题主要是围绕在if条件的写法上。&lt;/p&gt;

&lt;h1&gt;书写风格&lt;/h1&gt;

&lt;p&gt;先来看看它的书写风格问题。我说这段代码不是老鸟写的，因为老鸟不会把一个布尔表达式跟true和false直接判断，而会写成：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(you.believe(it) || !you.believe(it))&lt;/pre&gt;

&lt;p&gt;于是有朋友提出，把布尔表达式跟true或false相比较来的更清晰一些，我表示这话并没有什么道理，因为这种读代码的方式是把视角停留在“数据”层面上：一个布尔表达式返回了布尔型的“数据”，于是把它和另外一个“数据”进行比较。如今的编程都在不断强调“语义”，“语义”的清晰才是真的清晰。我说Java是一门糟糕的语言，主要原因就是指它的表达能力太差，导致写出来的代码体现不出问题的解决方式，让人们把目光都集中在具体每条语句上了，所谓“见木不见林”。C#等现代语言都在强调“做什么”而不是“怎么做”，语义上就有很大提高了。&lt;/p&gt;

&lt;p&gt;回到目前这个具体问题上，if里面的语义是“you.believe(it)”的返回结果，而不是它的值与另外一个布尔常量的比较结果。其实这个观点我从初中搞信息学竞赛时就被老师不断强调，今天我同样咨询了同事，他也赞同我的观点。如果您还继续坚持这种写法不太清晰的话，我只能说“这只是不适应而已，要让自己适应这类写法”，很多人还觉得LINQ不清晰呢，小学生还觉得高中数学的解法不清晰呢。&lt;/p&gt;

&lt;p&gt;还有朋友认为，作为编码规范，应该要求这么写，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(10&lt;span style="color: blue"&gt; &lt;/span&gt;== i)&lt;/pre&gt;

&lt;p&gt;就是说，把常量写在比较操作的左边，并认为“这样更有普遍意义”。其实这也没有必要，这个习惯是从C语言时代遗传下来的“陋习”。在C语言里，如果把常量写在比较右侧，并且一不小心把“比较”操作符（两个等号）写成“赋值”操作符（一个等号），也可以编译通过，但是结果却大不相同，这给错误排查也会带来许多麻烦。但是，在如今的语言里已经比C语言做的安全多了，所以没必要制定这种规范。把一种语言的标准带入另一种语言不叫做“有普遍意义”，只是多余。&lt;/p&gt;

&lt;h1&gt;代码含义&lt;/h1&gt;

&lt;p&gt;然后要谈的便是代码与那句话的“映射”关系了，再来仔细读一下这个if子句：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(you.believe(it) || !you.believe(it))
{
    I.believe(it);
}&lt;/pre&gt;

&lt;p&gt;从“需求”上来理解，我认为代码应该保证if内部的代码一定会执行。那么现在这个需求肯定会满足吗？不一定，因为you.believe方法可能是有副作用的：如果它第一次调用返回false，而第二次调用时返回true，则if内部的代码就会整段略过，这显然不是铁道部王发言人的意图。因此，有同学提议代码应该是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;true &lt;/span&gt;|| you.believe(it))&lt;/pre&gt;

&lt;p&gt;这么做的确可以忽略you.believe(it)的结果，因为它已经被短路了根本不会执行。可能它也能满足需求，但我想更合理的做法可能应该是：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(you.believe(it) || &lt;span style="color: blue"&gt;true&lt;/span&gt;)&lt;/pre&gt;

&lt;p&gt;这段代码与之前的区别就在于you.believe(it)一定会被调用一次，但是无所谓其结果是如何，这充分符合天朝&lt;strike&gt;某些&lt;/strike&gt;部门喜欢装摸作样“咨询民意”的状况。&lt;/p&gt;

&lt;h1&gt;扩展思考&lt;/h1&gt;

&lt;p&gt;最后再来一道扩展思考题吧：有人把“你爱，或者不爱我，爱就在那里，不增不减”写成了一段C#代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(you.Love(me) || !you.Love(me))
{
    love++;
    love--;
}&lt;/pre&gt;

&lt;p&gt;有人说，这段代码的if条件本身应该被编译器优化掉，因此会直接执行if内部的代码。还有人说，if内部的代码也会被编译器优化掉。您怎么看，为什么呢？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/08/from-the-code-of-no-matter-you-believe-it-or-not.html#comments</comments>
      <pubDate>Fri, 05 Aug 2011 15:15:39 GMT</pubDate>
      <lastBuildDate>Sat, 06 Aug 2011 05:58:05 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/discussion/">思考讨论</category>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>IBM面试记</title>
      <link>http://blog.zhaojie.me/2011/07/ibm-interview.html</link>
      <guid>http://blog.zhaojie.me/2011/07/ibm-interview.html</guid>
      <description>&lt;p&gt;话说其实我很久没有被正经面试过了。一开始去微软实习自然经过了经典的笔试和几轮面试，然后去了朋友的创业公司并立即被激动集团收编——没有面试，接着从激动集团去合伙创业——没有面试，然后被朋友推荐去盛大创新院——面试更像是讨论及聊天。由于长久缺乏职场磨练，我虽然对自己能力有一定信心，但也怀疑自己如果通过“正经渠道”去面试的话能有多少机会成功。而这次面试IBM终于算是过足了面试瘾，记录一下。&lt;/p&gt;

&lt;p&gt;大约一两个月前，我收到一封邮件，某同学忽悠我去尝试下&lt;a href="http://blog.zhaojie.me/2011/06/two-job-descriptions.html"&gt;在IBM的.NET工作机会&lt;/a&gt;。我感觉这机会似乎还不错，也正好想考察一下自己的面试水平，于是表示说愿意尝试一下。很快收到HR的邮件，让我做一套在线的笔试题。&lt;/p&gt;

&lt;p&gt;笔试题分两部分，一部分是.NET测试，另一部分是英语测试，分别有几十道选择题。HR在电话里说，.NET测试会偏向理论一些，有些难，可以用搜索引擎找下答案。被她那么一说我也不知道该如何是好，更不知该如何准备，于是就打算死马当活马医，霸王硬上弓吧。当天晚上直接打开链接做题，做了以后才发现，所谓“偏理论”估计是HR从以往的被面试者那里得到的反馈，事实上这套题目考的其实就是我一直强调的.NET基础，例如C#语言的特性（从面向对象到LINQ里的种种），CLR的一些表现，BCL内常用类库的实现细节，还有就是代码阅读题了。总体而言其中相当部分也是我&lt;a href="http://blog.zhaojie.me/2011/03/my-interview-questions-for-dotnet-programmers.html"&gt;常用的面试题&lt;/a&gt;，自然正中下怀。&lt;/p&gt;

&lt;p&gt;.NET笔试十分顺利，但英语就麻烦许多了。要知道自从大学前两年的英语课以外，我已经好多年没有正经地学过、考过、用过英语了，什么GRE，托福，雅思，甚至六级我都没接触过。英语测试就跟传统的测试一样，例如给你一大段文字，提出一个问题，并选择一个正确答案，还有选出出现语法错误的某句话。测试的内容涉及政治、金融、文化、生活等方面，唯独没有我最熟悉的——技术领域的内容。尽管有Google和Bing翻译，但是从实际效果来看，自动翻译技术要达到“可用”还有很长的路要走。&lt;/p&gt;

&lt;p&gt;第二天一大早就我收到的结果，.NET测试96%，这意味着我的成绩在“做过这套测试”的人里面排在前5%。为此我也暗自得意了一番，因为从某同学那里了解到，在之前应聘这个职位的人里面，笔试成绩无一超过50%。此外我的英语是71%，而“及格线”似乎是30%。总体而言，我的笔试成绩应该还是比较令人满意的。于是，HR跟我预约了第二天的电话面试，由J.P.Morgan的人来面试，全程英语。&lt;/p&gt;

&lt;p&gt;于是我立马找出机器里的一堆技术视频来看，希望能让耳朵适应一下英语内容。听下来感觉倒还算不错，基本没有大大障碍。但是在第二天电话面试的实际过程中，我发现一旦隔了电话，导致背景里出现了很多噪音之后，听力立马会打一个折扣（适应以后会好一些）。面试我的人是两个老外，基本上没有口音（或者说就是美剧里常见的标准口音）。他们问了我很多东西，有部分和笔试差不多，例如最典型的：某对象分配在堆还是栈上——有趣的是，在他们的追问中，我也发现原来这个问题在流传甚广的“标准答案”以外还有一个变数，不过我略加思考应该也回答地没有问题。还有印象比较深刻的便是让我解释一下LINQ的相关内容（例如LINQ Provider的实现方式），GC的工作方式及特点，多线程开发会遇到的问题等等。就我个人感觉来说，这些问题我都应该回答地不错，用“对答如流”来形容似乎也不太过分。与其形成极端对比的是，对于WPF和WCF方面的问题，我也没有作任何“挣扎”，基本都是直接回答“不好意思，没有接触过，不太清楚”。电话面试耗时大约50分钟。&lt;/p&gt;

&lt;p&gt;比较有趣的事情是，我在简历和自我介绍里提到我平时接触过及感兴趣的技术（基本就是我博客右边写的那样），可能他们也正巧对Scala感兴趣（毕竟是要用Java的），于是就提出让我向他们解释下Scala这门语言。解释地过程没有什么值得一提的，不过我也适时地表达了我对Java一贯的厌恶态度，我的原话是：Java is a dead language, it sucks，然后就围绕Java语言展开了简单地讨论，主要还是我以前谈过的那些。例如我解释道，我很喜欢Java平台、类库、框架、运行时等等，讨厌的只是Java语言；我很了解和关注Java语言，说它dead是因为它死不肯演化或者演化地很奇怪（Java 7和8）；还有Java表达能力太差，虽然看上去简单，但是需要太多代码，写完了以后还看不懂。前段时间有人问我说去了IBM以后还会不会骂Java？我说我就是骂着Java进IBM的，所以绝对会继续骂不停口。而且我很希望到时候我在骂得时候，某些弟兄不要仅仅看到我是微软MVP的身份，也可以顺便一提IBM员工这茬。&lt;/p&gt;

&lt;p&gt;电话面试后的第三天，HR又给我来电话说通过了，说接下来便是一场“面对面”的面试，地点在深圳。会有两个团队的人从香港过来与我面试，一个是做.NET的，一个.NET和Java均有涉及——同时还发给我一份Java的在线笔试题，说希望我能在面试前完成“以供参考”。那天正好是个周五，而面试安排在周一，与.NET的裸考不同，我有一个周末的准备时间。有了.NET笔试的经验，我估摸着Java笔试应该也是类似的题目，于是找了本一千多页的SCJP的辅导教材，认真地啃了一天半。期间也弥补了我对Java语言认识的一点缺失，因为我实在被Java的&lt;a href="http://blog.zhaojie.me/2011/06/java-anonymous-method-closure-scope-this.html"&gt;内嵌类、匿名类、泛型的复杂度给恶心到了&lt;/a&gt;。后来再有人跟我说Java语言简单，我就会回应说其实“它比你想象地要复杂很多”，要不我们可以来谈谈某些话题——更重要地是，复杂而不好用，导致人们会刻意规避这些复杂度，因此“连你也不知道Java语言原来这么复杂”。&lt;/p&gt;

&lt;p&gt;不过Java笔试其实比.NET要简单不少，完全没有涉及Java语言的复杂部分，更像是考一些代码阅读题以及基础类库，对于后者我只能根据一些“常识”和“经验”来猜测结果了。最后我的Java笔试成绩是93%，也算是不错的样子，面试时老外跟我的开场白便是说我.NET和Java成绩都很好——他的组会同时使用Java和.NET。倒是原本提到的用.NET那组的面试，由于签证没有过关，只是在电话里简单地聊了几句。&lt;/p&gt;

&lt;p&gt;面试房间有白板，因此面试的方式也有所不同。首先他提出一些业务上的场景，给我一些可用的组件（例如持久化队列），让我在白板上画出解决方案的设计图。然后他会继续做出一些假设，例如某一个服务的压力提高，成为了性能瓶颈，那么可以如何改进这个服务。不断涂涂改改最终也差不多画满了整面墙。此外还有各种关于性能诊断和优化的问题，涉及到日志记录的设计，CLR（主要还是GC部分），WinDBG，数据结构（线性表，哈西表，优先队列，平衡二叉树）等等。这些大都是开放题，因此可以谈得东西不少，我也不太担心因为不了解而无言以对。而且因为可以使用手势和示意图来辅助我那半吊子英语，也比纯粹在电话里的交流来的清楚。此外对方也谈到他对JVM的了解多过CLR，所以很多时候我也更像是在“解释”CLR的行为，目的是能够让对方理解，而不是去“迎合”对方心里的正确答案。总体而言，面试后的感觉其实比电话面试更有自信一些。&lt;/p&gt;

&lt;p&gt;回到上海后，我也很快收到了回应：我通过了J.P.Mongan方面的面试，也是第一个通过的.NET技术人员。剩下的便是令人烦躁的Offer商讨问题，略过不谈。值得一提的是，在正式发Offer之前，IBM还让我做了一套智力题（就是那种根据规律选则下一个数或下一幅图），据说是每个IBM员工都需要经历的测试——这是我整个过程中经历的最困难，也是最没有信心的环节了。最后我得了72分，堪堪超过65分的及格线，幸好终究没有在阴沟里翻船。&lt;/p&gt;

&lt;p&gt;以上便是我这次整体的经历。我也不想总结出什么理论地哲学化的大道理，就这样完整地记叙一遍吧。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/07/ibm-interview.html#comments</comments>
      <pubDate>Sat, 02 Jul 2011 15:29:37 GMT</pubDate>
      <lastBuildDate>Sun, 03 Jul 2011 08:57:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>Jscex项目现状：UglifyJS解析器及AOT编译器</title>
      <link>http://blog.zhaojie.me/2011/04/jscex-status-uglifyjs-parser-and-aot-compiler.html</link>
      <guid>http://blog.zhaojie.me/2011/04/jscex-status-uglifyjs-parser-and-aot-compiler.html</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;项目是我为了简化JavaScript异步的一个类库，支持任意JavaScript（ECMASCript 3）引擎。Jscex小巧而强大，可以极大地改善前端的AJAX及动画等场景的编程体验，同样也可以用在&lt;a href="http://nodejs.org/"&gt;node.js&lt;/a&gt;进行服务器开发。从产生Jscex的想法到现在也有几个月的时间了，也一直想设法进行推广。在思考过程也发现了它在实际生产中可能会遇到的问题，于是前两个星期的主要工作，便是针对这些问题进行优化。首先我将Jscex的JavaScript分析器从&lt;a href="https://github.com/mozilla/narcissus"&gt;Narcissus&lt;/a&gt;换成了&lt;a href="https://github.com/mishoo/UglifyJS"&gt;UglifyJS&lt;/a&gt;，并基于node.js开发了一个简单的AOT编译器。接下来我也打算写个稍微详细一点的介绍，然后在国外社区看看反响如何。&lt;/p&gt;

&lt;p&gt;Jscex的本质是一个用JavaScript编写的JavaScript编译器，因此我需要一个JavaScript实现的JavaScript解析器。我起初&lt;a href="http://blog.zhaojie.me/2010/11/narcissus-javascript-parser.html"&gt;选择了著名的Narcissus项目&lt;/a&gt;，但由于它用到了&lt;a href="http://en.wikipedia.org/wiki/SpiderMonkey_(JavaScript_engine)"&gt;SpiderMonkey&lt;/a&gt;的一些扩展，最终我使用的其实是&lt;a href="http://www.neilmix.com/narrativejs/doc/"&gt;NarrativeJS&lt;/a&gt;中旧版的Narcissus代码。我一直在设法减小Jscex核心的体积及执行速度（毕竟一个重要的场景是浏览器端），再加上不是很喜欢旧版Narcissus代码的解析结果，于是我也在不断寻找它的替代品。前段时间我发现了UglifyJS这个JavaScript压缩器，它的解析器移植于&lt;a href="http://marijn.haverbeke.nl/parse-js/"&gt;parse-js&lt;/a&gt;项目，后者是一个用Common Lisp实现的类库，因此输出结构也十分简单，一个“表”而已，&lt;a href="http://blog.zhaojie.me/2011/04/uglifyjs-has-a-good-javascript-parser.html"&gt;执行速度也大大领先于Narcissus&lt;/a&gt;，体积也更小。于是我花了一个周末的时间将Jscex编译器改写为基于UglifyJS的实现。&lt;/p&gt;

&lt;p&gt;在改写过程中，我也同样考虑了目标代码在压缩后的体积。我使用&lt;a href="http://code.google.com/closure/compiler/"&gt;Closure Compiler&lt;/a&gt;的“高级”模式压缩代码，一般来说Closure Compiler的高级模式很破坏代码，我&lt;a href="http://blog.zhaojie.me/2011/04/compress-javascript-with-google-closure-compiler-in-advance-mode.html"&gt;使用了各种方式&lt;/a&gt;来保证压缩后的代码能够正确执行。目前，如果您要在项目中使用Jscex编写异步程序，需要依次加载以下三个文件（它们都在项目源码的bin目录中）：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;uglifyjs-parser.min.js：UglifyJS解析器，大小20K，gzip后8K。 &lt;/li&gt;

  &lt;li&gt;jscex.min.js：Jscex核心编译器，大小5.5K，gzip后1.8K。 &lt;/li&gt;

  &lt;li&gt;jscex.async.min.js：Jscex异步核心类库，大小2K，gzip后0.9K。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果您觉得gzip后10K左右的体积还是有些大，那么也可以使用目前已经提供的AOT编译器——虽然AOT编译器的原始目的并不是为了减小体积。&lt;/p&gt;

&lt;p&gt;Jscex改善异步编程的原理，在于让程序员直接编写代码，使用普通的编程思路来实现算法，包括是用try...catch来捕获异常等等，而不会因为异步所需要的回调将代码拆得支离破碎。例如我们要实现冒泡排序算法的动画演示，也只需要使用传统编码方式实现算法即可：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// 标准算法&lt;/span&gt;
&lt;span style="color: blue"&gt;var &lt;/span&gt;bubbleSort = &lt;span style="color: blue"&gt;function &lt;/span&gt;(array) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;x = 0; x &amp;lt; array.length; x++) {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;y = 0; y &amp;lt; array.length - x; y++) {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(array[y] &amp;gt; array[y + 1]) {
                swap(array, y, y + 1);
            }
        }
    }
}

&lt;span style="color: green"&gt;// 演示动画&lt;/span&gt;
&lt;span style="color: blue"&gt;var &lt;/span&gt;bubbleSortAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(array) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;x = 0; x &amp;lt; array.length; x++) {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;y = 0; y &amp;lt; array.length - x; y++) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;r = $await(compareAsync(array[y], array[y + 1]));
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;gt; 0) {
                $await(swapAsync(array, y, y + 1));
            }
        }
    }
}));&lt;/pre&gt;

&lt;p&gt;Jscex.compile会解析代码，并生成异步代码，并交给eval来解释执行。bubbleSortAsync和其中调用的compareAsync（比较两个元素大小，并暂停10毫秒）和swapAsync（交换两个元素，绘图，并暂停20毫秒）都是异步方法。但是无论在编写和使用上，异步方法和同步算法几乎没有区别——唯一的区别便是$await语句必须单起一行。这个限制一是为了保证开发人员可以明确分清普通的JavaScript代码及异步方法调用，二便是为了简化编译器的实现。例如，“理想情况”下类似以下的代码也需要支持：&lt;/p&gt;

&lt;pre class="code"&gt;f(g(1), $await(...))

&lt;span style="color: blue"&gt;if &lt;/span&gt;(x &amp;gt; y &amp;amp;&amp;amp; $await(...)) { ... }&lt;/pre&gt;

&lt;p&gt;尤其是第二行代码，$await可能由于短路而根本不会执行。为此，Jscex要求开发人员明确编写这样的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;a1 = g(1);
&lt;span style="color: blue"&gt;var &lt;/span&gt;a2 = $await(...);
f(a1, a2);

&lt;span style="color: blue"&gt;if &lt;/span&gt;(x &amp;gt; y) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;flag = $await(...);
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(flag) { ... }
}&lt;/pre&gt;

&lt;p&gt;我并不担心这会让开发人员编写代码时有所不便，事实上F#的&lt;a href="http://msdn.microsoft.com/en-us/library/dd233250.aspx"&gt;Async Workflow&lt;/a&gt;是有这般要求，我甚至敢保证&lt;a href="http://blog.zhaojie.me/2010/10/pdc2010-the-future-of-csharp-and-vb-by-anders-hejlsberg-1.html"&gt;未来C#的异步特性&lt;/a&gt;也是类似的设计。但是，JavaScript有个重要的特点：它在实际使用时往往会被压缩。如果仅仅是去除空白字符，那么Jscex自然还可以正常工作。但事实上现代的JavaScript压缩工具都会分析代码的语义，并重新生成体积更小的代码。例如之前的bubbleSortAsync经过压缩便会成为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;bubbleSortAsync=eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;,&lt;span style="color: blue"&gt;function&lt;/span&gt;(a){&lt;span style="color: blue"&gt;for&lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;b=0;b&amp;lt;a.length;b++)&lt;span style="color: blue"&gt;for&lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;c=0;c&amp;lt;a.length-b;c++){&lt;span style="color: blue"&gt;var &lt;/span&gt;d=$await(compareAsync(a[c],a[c+1]));d&amp;gt;0&amp;amp;&amp;amp;$await(swapAsync(a,c,c+1))}}))&lt;/pre&gt;

&lt;p&gt;试看d&amp;gt;0&amp;amp;&amp;amp;$wait(...)这段代码，完全就让Jscex无法工作了。为此，我为Jscex开发了AOT编译器（scripts目录下的jscexc.js及JscexExtractor.js文件），即在部署前便对代码进行编译并生成目标代码（之前是在运行时生成代码，即JIT编译）。AOT编译器同样使用JavaScript编写，使用node.js运行，这样便可以直接使用Jscex的编译器实现。与编译器核心不同，AOT编译器使用了最新版的Narcissus来解析代码，这是因为Narcissus能够提供更丰富的解析结果，我可以直接获得整个目标方法的起始和结束地址（不过有bug，我使用时绕开了），自然还包括原始代码，用起来十分方便。至于之前提到的依赖于SpiderMonkey扩展，体积较大，执行速度慢等缺点，对于AOT编译器来说便完全不是问题了。&lt;/p&gt;

&lt;p&gt;Jscex的AOT编译器使用起来十分简单：&lt;/p&gt;

&lt;pre class="code"&gt;node jscexc.js --input input_file --output output_file&lt;/pre&gt;

&lt;p&gt;例如，如果一个文件包含之前的bubbleSortAsync方法，那么经过AOT编译器之后，它的代码便会被替换成为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;bubbleSortAsync = (&lt;span style="color: blue"&gt;function &lt;/span&gt;(array) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$_builder_$ = Jscex.builders[&lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;];
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;x = 0;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Loop(
                &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;x &amp;lt; array.length;
                },
                &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    x++;
                },
                $_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                        &lt;span style="color: blue"&gt;var &lt;/span&gt;y = 0;
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Loop(
                            &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                &lt;span style="color: blue"&gt;return &lt;/span&gt;y &amp;lt; (array.length - x);
                            },
                            &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                y++;
                            },
                            $_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Bind(compareAsync(...), &lt;span style="color: blue"&gt;function &lt;/span&gt;(r) {
                                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                        &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;gt; 0) {
                                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Bind(swapAsync(...), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                                                &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Normal();
                                            });
                                        } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
                                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Normal();
                                        }
                                    });
                                });
                            }),
                            &lt;span style="color: blue"&gt;false
                        &lt;/span&gt;);
                    });
                }),
                &lt;span style="color: blue"&gt;false
            &lt;/span&gt;);
        });
    });
})&lt;/pre&gt;

&lt;p&gt;再进行压缩，便不会产生任何问题了。从表面看起来，编译后的Jscex代码体积大了不少，但是其中大部分为重复架子代码，压缩比例一般也会比较大。使用AOT编译后的代码有以下几个好处：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;经过JavaScript压缩器处理后也能正确执行。 &lt;/li&gt;

  &lt;li&gt;运行时只需要加载一个极小的jscex.async.min.js文件（异步核心类库），gzip后大小不到1K。 &lt;/li&gt;

  &lt;li&gt;由于代码在发布前生成，节省了JIT编译的开销。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在我个人看来，目前的Jscex已经可以在一些比较正式的场合中使用了。Jscex功能强大，实现小巧，能够与其它类库同时使用（它只会在全局对象上产生一个Jscex对象），接下来我也会为jQuery或MooTools等著名JavaScript框架/类库提供Jscex的绑定。在此也希望您可以实际使用一下Jscex项目，如果遇到问题请及时与我联系，我会给予您必要的支持。&lt;/p&gt;

&lt;p&gt;&lt;span style="color: red"&gt;广告时间：&lt;/span&gt;&lt;a href="http://nbazaar.org/"&gt;第四届nBazaar技术交流会&lt;/a&gt;将于2011年4月23日举行。第四届交流会的形式将略作改变：除了三场演讲（Windows Phone 7、IDE插件开发、单点登陆解决方案设计与实现）之外，本次活动设有嘉宾互动环节，您将有机会和嘉宾就某些话题进行探讨。我们正在收集话题，也希望大家踊跃提问，具体信息详见&lt;a href="http://nbazaar.org/"&gt;http://nbazaar.org/&lt;/a&gt;。此外，nBazaar技术沙龙的邮件列表已经正式启用，所有用户也已添加完成（之前报名或参加过技术会议）。如果有任何疑问，请邮件至&lt;img class="embed" src="http://services.nexodyne.com/email/customicon/9xaGAYDnFdYrRqAdl1tTvrs%3D/Y4fIT8s%3D/000000/ffffff/000000/0/image.png" /&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/jscex-status-uglifyjs-parser-and-aot-compiler.html#comments</comments>
      <pubDate>Thu, 14 Apr 2011 18:04:38 GMT</pubDate>
      <lastBuildDate>Thu, 14 Apr 2011 18:09:53 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>通过定义常量控制Closure Compiler的行为</title>
      <link>http://blog.zhaojie.me/2011/04/use-define-annotation-in-closure-compiler.html</link>
      <guid>http://blog.zhaojie.me/2011/04/use-define-annotation-in-closure-compiler.html</guid>
      <description>&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2011/04/compress-javascript-with-google-closure-compiler-in-advance-mode.html"&gt;上一篇文章&lt;/a&gt;里我提到，在进行&lt;a href="http://code.google.com/closure/compiler/"&gt;Closure Compiler&lt;/a&gt;压缩之前可以对代码进行一些预处理，这样可以得到更好的效果。在回复中有朋友提到可以&lt;a href="http://code.google.com/closure/compiler/docs/js-for-compiler.html"&gt;使用一些Annotation（标记）&lt;/a&gt;，例如加上@export，然后使用--generate_exports，便可以保留需要的那些变量名。不过经过实验还是没有得到预期的效果，所以使用标记来“指导”高级压缩行为依旧是一个不太可行的做法。不过有个标记与我的设想一直，那就是使用@define来“定义一个常量”，然后在编译（压缩）时对其进行覆盖。这为一些压缩需求提供一种更直接的控制方式。&lt;/p&gt;

&lt;p&gt;还是先谈一下@export，当时看了文档的说明，我一度也觉得这能满足我的要求（即保留变量名不被压缩）：&lt;/p&gt;

&lt;pre class="code"&gt;$ java -jar compiler.jar --help
...
--generate_exports         : Generates export code for those marked
                              with @export
...&lt;/pre&gt;

&lt;p&gt;但事实上，实验的结果令人大失所望。例如下面的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;/**
 * @export
 */
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;Jscex = (&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: #006400"&gt;/**
     * @constructor
     */
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;CodeGenerator = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: #006400"&gt;/**
         * @export
         */
        &lt;/span&gt;&lt;span style="color: blue"&gt;this&lt;/span&gt;.normalMode = &lt;span style="color: blue"&gt;false&lt;/span&gt;;
    }
    &lt;span style="color: #006400"&gt;/**
     * @export
     */
    &lt;/span&gt;CodeGenerator.prototype.generate = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        alert(&lt;span style="color: blue"&gt;this&lt;/span&gt;.normalMode);
    }

    &lt;span style="color: blue"&gt;function &lt;/span&gt;compile() { &lt;span style="color: blue"&gt;return new &lt;/span&gt;CodeGenerator(); };

    &lt;span style="color: blue"&gt;return &lt;/span&gt;{ compile: compile };

})();&lt;/pre&gt;

&lt;p&gt;首先，Closure Compiler只允许在Global范围内使用@export标记，因此以上几个@export只有Jscex上的那个才能保留下来。其次，当去除其他@export标记之后，Closure Compiler生成的结果却是：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;b=&lt;span style="color: blue"&gt;function&lt;/span&gt;(){...};goog.a(&lt;span style="color: maroon"&gt;&amp;quot;Jscex&amp;quot;&lt;/span&gt;,b);&lt;/pre&gt;

&lt;p&gt;Closure Compiler很神奇地为脚本添加了&lt;a href="http://code.google.com/closure/library/"&gt;Closure Libraries&lt;/a&gt;这个依赖，这显然无法令人接受。&lt;/p&gt;

&lt;p&gt;失望之余，我观察并尝试了其他一些标记，发现@define还是不错的。就拿前文来说，我希望&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;能够在Debug模式下避免生成不必要的括号，原来的做法是编写这样的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: maroon"&gt;&amp;quot;dot&amp;quot;&lt;/span&gt;: &lt;span style="color: blue"&gt;function &lt;/span&gt;(ast) {
    &lt;span style="color: blue"&gt;function &lt;/span&gt;needBracket() { &lt;span style="color: #006400"&gt;/* ... */ &lt;/span&gt;}

    &lt;span style="color: blue"&gt;var &lt;/span&gt;nb = needBracket();
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(nb) {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._write(&lt;span style="color: maroon"&gt;&amp;quot;(&amp;quot;&lt;/span&gt;)
            ._visit(ast[1])
            ._write(&lt;span style="color: maroon"&gt;&amp;quot;).&amp;quot;&lt;/span&gt;)
            ._write(ast[2]);
    } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._visit(ast[1])
            ._write(&lt;span style="color: maroon"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;)
            ._write(ast[2]);
    }
},&lt;/pre&gt;

&lt;p&gt;然后在使用Closure Compiler压缩之前将needBracket方法的调用替换成true，这样Closure Compiler就会发现if的第二个分支永远不会执行，因此直接从最后的结果中去除了；更近一步，由于needBracket方法没有其他地方调用过，因此它也会被完整地删除，这就得到了体积更小的代码。不过这个方法多少有点trick的意味，它需要代码编写者与压缩脚本之间进行约定，其实我们可以使用一种更直接的方式，那就是使用@define标记来定义一个常量：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;/** @define {boolean} */
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;JSCEX_DEBUG = &lt;span style="color: blue"&gt;true&lt;/span&gt;;
...

&lt;span style="color: maroon"&gt;&amp;quot;dot&amp;quot;&lt;/span&gt;: &lt;span style="color: blue"&gt;function &lt;/span&gt;(ast) {
    &lt;span style="color: blue"&gt;function &lt;/span&gt;needBracket() { &lt;span style="color: #006400"&gt;/* ... */ &lt;/span&gt;}

    &lt;span style="color: blue"&gt;var &lt;/span&gt;nb = (!JSCEX_DEBUG) || needBracket();
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(nb) {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._write(&lt;span style="color: maroon"&gt;&amp;quot;(&amp;quot;&lt;/span&gt;)
            ._visit(ast[1])
            ._write(&lt;span style="color: maroon"&gt;&amp;quot;).&amp;quot;&lt;/span&gt;)
            ._write(ast[2]);
    } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._visit(ast[1])
            ._write(&lt;span style="color: maroon"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;)
            ._write(ast[2]);
    }
},&lt;/pre&gt;

&lt;p&gt;在代码中JSCEX_DEBU为true，因此nb的值会是needBracket方法调用后的结果。但如果我们在使用Closure Compiler压缩代码添加--define参数，便可以将JSCEX_DEBUG的值修改为false：&lt;/p&gt;

&lt;pre class="code"&gt;java \
    -jar compiler.jar \
    --js jscex.js \
    --js_output_file jscex.min.js \
    --define 'JSCEX_DEBUG=false' \
    --compilation_level ADVANCED_OPTIMIZATIONS&lt;/pre&gt;

&lt;p&gt;于是乎——就不多做解释了。Closure Compiler提供的标记有很多，但实验下来行为大都和想象中不符，这倒也是件很奇怪的事情。可能还是要去邮件列表之类的地方问问吧。&lt;/p&gt;

&lt;p&gt;&lt;span style="color: red"&gt;广告时间：&lt;/span&gt;&lt;a href="http://nbazaar.org/"&gt;第四届nBazaar技术交流会&lt;/a&gt;将于2011年4月23日举行。根据部分用户反馈，第四届交流会的形式将略作改变：除了三场演讲之外，本次活动设有嘉宾互动环节，您将有机会和嘉宾就某些话题进行探讨。我们将在会前收集部分话题，也希望大家踊跃提问，具体方式详见 &lt;a href="http://nbazaar.org/"&gt;http://nbazaar.org/&lt;/a&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/use-define-annotation-in-closure-compiler.html#comments</comments>
      <pubDate>Sun, 10 Apr 2011 17:12:09 GMT</pubDate>
      <lastBuildDate>Sun, 10 Apr 2011 17:12:41 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>使用Google Closure Compiler全力压缩代码</title>
      <link>http://blog.zhaojie.me/2011/04/compress-javascript-with-google-closure-compiler-in-advance-mode.html</link>
      <guid>http://blog.zhaojie.me/2011/04/compress-javascript-with-google-closure-compiler-in-advance-mode.html</guid>
      <description>&lt;p&gt;JavaScript压缩代码的重要性不言而喻，如今的压缩工具也有不少，例如&lt;a href="http://developer.yahoo.com/yui/compressor/"&gt;YUI Compressor&lt;/a&gt;，Google &lt;a href="http://code.google.com/closure/compiler/"&gt;Closure Compiler&lt;/a&gt;，以及现在比较红火的&lt;a href="https://github.com/mishoo/UglifyJS"&gt;UglifyJS&lt;/a&gt;。UglifyJS的出名是由于它代替Closure Compiler成为jQuery项目的压缩工具。根据我的实测，&lt;a href="http://code.jquery.com/jquery-1.5.2.js"&gt;jQuery Core的代码&lt;/a&gt;使用&lt;a href="http://marijnhaverbeke.nl/uglifyjs"&gt;UglifyJS压缩&lt;/a&gt;后（节省62.5%）的确要比&lt;a href="http://closure-compiler.appspot.com/home"&gt;Closure Compiler压缩&lt;/a&gt;后（节省57.53%）更小一些。很显然，这是因为UglifyJS的压缩策略比Closure Compiler更“聪明”一些。我这里用了“聪明”而不是“激进”，是因为“激进”带上了一丝负面的意味——就好比Closure Compiler的“高级”优化方式。之前与UglifyJS相比的是Closure Compiler的“简单”优化方式，它们都是“安全”的，而Closure Compiler的“高级”优化几乎100%会破坏您的代码，因此它提出了各种“激进”的手段去“破坏”您的代码，以此达到压缩的目的。这种手段是把双刃剑，如果您能掌控它的压缩规则，则代码便可以压缩至极小。&lt;/p&gt;

&lt;p&gt;我们先来看看的Closure Compiler的威力。例如我有这样一段代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;Jscex = (&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: #006400"&gt;/**
    * @constructor
    */
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;CodeGenerator = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.normalMode = &lt;span style="color: blue"&gt;false&lt;/span&gt;;
    }
    CodeGenerator.prototype.generate = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        alert(&lt;span style="color: maroon"&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;function &lt;/span&gt;compile() {
        &lt;span style="color: blue"&gt;return&lt;/span&gt; &lt;span style="color: blue"&gt;new &lt;/span&gt;CodeGenerator();
    };

    &lt;span style="color: blue"&gt;return &lt;/span&gt;{ compile: compile };
})();&lt;/pre&gt;

&lt;p&gt;猜猜看，如果使用Closure Compiler的高级优化方式来压缩代码，会是什么情况呢？结果如下：&lt;/p&gt;

&lt;pre class="code"&gt;(&lt;span style="color: blue"&gt;function&lt;/span&gt;(){&lt;span style="color: blue"&gt;function &lt;/span&gt;a(){&lt;span style="color: blue"&gt;this&lt;/span&gt;.a=!1}&lt;span style="color: blue"&gt;return&lt;/span&gt;{compile:&lt;span style="color: blue"&gt;function&lt;/span&gt;(){&lt;span style="color: blue"&gt;return new &lt;/span&gt;a}}})();&lt;/pre&gt;

&lt;p&gt;目标代码很短，硬着头皮看看也无妨。首先，Jscex对象消失了，因为Closure Compiler认为其他地方并没有使用这个对象。其次，CodeGenerator的normalMode字段也被改名为a，因为这个名字更省空间。最后，generate方法也不见了，理由同第一项。您瞅瞅，这样的代码还能执行吗？这就是Closure Compiler激进的地方，它把输入文件作为一个完整的单元，并不会考虑对外的“接口”是否会变化。我读了Closure Compiler的文档，发现了它支持&lt;a href="http://code.google.com/closure/compiler/docs/js-for-compiler.html"&gt;对源代码做标记&lt;/a&gt;。但是经过实验，这些标记似乎并不能影响编译后的结果，只是让编译器工作时多做一些“静态检查”。&lt;/p&gt;

&lt;p&gt;当然，理论上说Closure Compiler提供了保持成员名称的机制，例如&lt;a href="http://code.google.com/closure/compiler/docs/api-tutorial3.html"&gt;exports和extern&lt;/a&gt;。假设我要保持之前的Jscex对象，那么就必须这么做：&lt;/p&gt;

&lt;pre class="code"&gt;window[&lt;span style="color: maroon"&gt;&amp;quot;Jscex&amp;quot;&lt;/span&gt;] = (&lt;span style="color: blue"&gt;function &lt;/span&gt;() { ... &lt;/span&gt;})();&lt;/pre&gt;

&lt;p&gt;这样Closure Compiler生成的代码就会变成：&lt;/p&gt;

&lt;pre class="code"&gt;window.Jscex=(&lt;span style="color: blue"&gt;function&lt;/span&gt;(){ ... &lt;/span&gt;})();&lt;/pre&gt;

&lt;p&gt;为了“节省”空间，它把“索引”的访问方式又切换回“字段”的访问方式，何等蛋疼！此外，原本我以为Closure Compiler强迫我依赖浏览器环境，后来发现其实window也可以替换为其他名称，例如：&lt;/p&gt;

&lt;pre class="code"&gt;my_root[&lt;span style="color: maroon"&gt;&amp;quot;Jscex&amp;quot;&lt;/span&gt;] = (&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: #006400"&gt;/**
    * @constructor
    */
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;CodeGenerator = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;[&lt;span style="color: maroon"&gt;&amp;quot;normalMode&amp;quot;&lt;/span&gt;] = &lt;span style="color: blue"&gt;false&lt;/span&gt;;
    }
    CodeGenerator.prototype[&lt;span style="color: maroon"&gt;&amp;quot;generate&amp;quot;&lt;/span&gt;] = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        alert(&lt;span style="color: maroon"&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;function &lt;/span&gt;compile() {
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;CodeGenerator();
    };

    &lt;span style="color: blue"&gt;return &lt;/span&gt;{ compile: compile };
})();&lt;/pre&gt;

&lt;p&gt;虽然从理论上说，使用这种方式可以告诉Closure Compiler哪些成员名称是可以压缩的而哪些不行，但我真心难以接受这种四处使用“索引”的写法。不过，其实这对我造成的影响其实不大，因为我很少使用那种“面向对象”的方式来对外公开接口，我一般也就是用“对象”加上“方法”的形式，例如上面的Jscex.compile方法，至于内部类型，如CodeGenerator，就随Closure Compiler压缩去吧。&lt;/p&gt;

&lt;p&gt;话又说回来，其实如果您是从头开始编写JavaScript代码，并且遵守一定规则，那么Closure Compiler的确可以把您的代码压缩地很小。甚至您可以多写一点调试用的代码，但在最终压缩后的代码中去掉它们。这里最基本的原则可以归纳为：将压缩后不需要的代码抽取为独立的方法，然后在预处理阶段去除这些方法的调用代码，于是Closure Compiler便会将这些方法的定义一并删除，节省了相当多的空间。&lt;/p&gt;

&lt;p&gt;以&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;项目为例加以说明：Jscex的核心之一是根据AST生成JavaScript代码，在“调试”版本的实现中，我希望生成的代码能够美观、易读；而在“发布”版本中，我希望需要代码的体积越少越好。于是，对于某个表达式“是否需要添加括号”这样的场景，便需要详细斟酌了。我的策略是，在“调试”代码中，将判断是否需要增加括号的逻辑放置到needBracket方法中，然后编写这样的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: maroon"&gt;&amp;quot;dot&amp;quot;&lt;/span&gt;: &lt;span style="color: blue"&gt;function &lt;/span&gt;(ast) {
    &lt;span style="color: blue"&gt;function &lt;/span&gt;needBracket() { &lt;span style="color: #006400"&gt;/* ... */ &lt;/span&gt;}

    &lt;span style="color: blue"&gt;var &lt;/span&gt;nb = needBracket();
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(nb) {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._write(&lt;span style="color: maroon"&gt;&amp;quot;(&amp;quot;&lt;/span&gt;)
            ._visit(ast[1])
            ._write(&lt;span style="color: maroon"&gt;&amp;quot;).&amp;quot;&lt;/span&gt;)
            ._write(ast[2]);
    } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._visit(ast[1])
            ._write(&lt;span style="color: maroon"&gt;&amp;quot;.&amp;quot;&lt;/span&gt;)
            ._write(ast[2]);
    }
},&lt;/pre&gt;

&lt;p&gt;上面这个方法的作用是生成一个dot表达式的代码，其中定义了needBracket方法，我们可以在其中放置复杂而低效的逻辑，用来判断dot的左侧表达式是否需要添加括号。如果needBracket返回true，则生成括号，例如(&amp;quot;abc&amp;quot; + &amp;quot;def&amp;quot;).length；否则，便会生成更为简洁易读的代码，例如Jscex.Async.start，而不会是((Jscex).Async).start。但是在最终“发布”版本的代码中，nb变量被直接设置为true，于是Closure Compiler则会发现if的一个分支永远不会执行，则将其完全去除。在压缩后的代码中，以上方法只会是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;dot:&lt;span style="color: blue"&gt;function&lt;/span&gt;(a){&lt;span style="color: blue"&gt;this&lt;/span&gt;.a(&lt;span style="color: maroon"&gt;&amp;quot;(&amp;quot;&lt;/span&gt;).b(a[1]).a(&lt;span style="color: maroon"&gt;&amp;quot;).&amp;quot;&lt;/span&gt;).a(a[2])},&lt;/pre&gt;

&lt;p&gt;可以看出，这段实现无论如何都会生成带括号的JavaScript代码，丑则丑矣，但对JavaScript引擎来说没有丝毫区别。目前jscex.js的压缩脚本其实是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;# pre-processing for Closure Compiler
sed \
    -e 's/var Jscex =/my_temp_root[&amp;quot;Jscex&amp;quot;] =/' \
    -e 's/\._writeLine(/._write(/g' \
    -e 's/this\._write();//g' \
    -e 's/\._write()//g' \
    -e 's/this\._writeIndents();//g' \
    -e 's/\._writeIndents()//g' \
    -e 's/this\._indentLevel = 0;//g' \
    -e 's/this\._indentLevel++;//g' \
    -e 's/this\._indentLevel--;//g' \
    -e 's/checkBindArgs([^;]*;//g' \
    -e 's/needBracket([^;]*;/true;/g' \
    -e 's/throwUnsupportedError();//g' \
    -e 's/_log([^;]*;//g' \
    ../src/jscex.js &amp;gt; ../bin/jscex.tmp.js


# use Closure Compiler to compress
java \
    -jar ../tools/compiler.jar \
    --js ../bin/jscex.tmp.js \
    --js_output_file ../bin/jscex.tmp.min.js \
    --compilation_level ADVANCED_OPTIMIZATIONS

# post-processing
sed 's/my_temp_root\.Jscex=/var Jscex=/' ../bin/jscex.tmp.min.js &amp;gt; ../bin/jscex.min.js

# remove temp files
rm ../bin/jscex.tmp*.js&lt;/pre&gt;

&lt;p&gt;我在使用Closure Compiler压缩代码之前，会先对脚本进行一下“预处理”，暂时为如下几项：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;为了避免Jscex对象丢失，先将var Jscex替换成my_temp_root[&amp;quot;Jscex&amp;quot;]，压缩之后再将其替换回来。 &lt;/li&gt;

  &lt;li&gt;将所有的writeLine方法调用替换成write，这样代码里便不会用到writeLine方法，Closure Compiler会去除该方法定义。 &lt;/li&gt;

  &lt;li&gt;去除空的write方法调用，这一般是由writeLine替换为write而引起的。 &lt;/li&gt;

  &lt;li&gt;去除与“缩进（indent）”相关的所有属性和方法，这样相关定义在压缩后也会一并消失。 &lt;/li&gt;

  &lt;li&gt;去除各种错误检查，如checkBindArgs，throwUnsupportedError方法的调用。 &lt;/li&gt;

  &lt;li&gt;去除日志输出，即_log方法调用，则_log方法本身也会消失不见。 &lt;/li&gt;

  &lt;li&gt;将needBracket方法调用直接替换为true，强制输出带括号的代码。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用这样的做法，我们可以充分利用Closure Compiler在“高级”优化级别下的激进压缩方式，同时得到正确、高效、体积还很小的代码（补充：后来发现其实某些情况下可以&lt;a href="http://blog.zhaojie.me/2011/04/use-define-annotation-in-closure-compiler.html"&gt;使用定义常量的方式来简化预处理的步骤&lt;/a&gt;）。就拿jscex.js和jQuery Core进行比较（事先都去除注释及空白字符）：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“简单”压缩的jQuery Core（安全压缩）：减小30.83%体积（120.18KB =&amp;gt; 83.13KB）。 &lt;/li&gt;

  &lt;li&gt;“高级”压缩的jQuery Core（非安全压缩，不可用）：减小37.91%体积（120.18KB =&amp;gt; 74.62KB）。 &lt;/li&gt;

  &lt;li&gt;“高级”压缩的Jscex.js（非安全压缩，可用）：节省55.02%体积（12.14KB =&amp;gt; 5.46KB）。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以上数据是从&lt;a href="http://closure-compiler.appspot.com/home"&gt;在线的Closure Compiler&lt;/a&gt;得出的结果，我也不知道为何效果不如本地明显。从本地压缩来看，jscex.js是25812字节，而jscex.min.js只有5585字节，有将近5倍的差距。&lt;/p&gt;

&lt;p&gt;可惜的是，如果不是从代码编写及压缩一开始就考虑到Closure Compiler的诸多行为，我们只能使用“简单”的压缩方式来确保代码的正确性。如果要让一个现有的大段代码（例如jQuery）安全通过Closure Compiler的“高级”考验，这几乎是一件不可能的事情。&lt;/p&gt;

&lt;p&gt;&lt;span style="color: red"&gt;广告时间：&lt;/span&gt;&lt;a href="http://nbazaar.org/"&gt;第四届nBazaar技术交流会&lt;/a&gt;将于2011年4月23日举行。根据部分用户反馈，第四届交流会的形式将略作改变：除了三场演讲之外，本次活动设有嘉宾互动环节，您将有机会和嘉宾就某些话题进行探讨。我们将在会前收集部分话题，也希望大家踊跃提问，具体方式详见 &lt;a href="http://nbazaar.org/"&gt;http://nbazaar.org/&lt;/a&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/compress-javascript-with-google-closure-compiler-in-advance-mode.html#comments</comments>
      <pubDate>Thu, 07 Apr 2011 01:30:56 GMT</pubDate>
      <lastBuildDate>Thu, 14 Apr 2011 16:49:26 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>UglifyJS有个不错的JavaScript解析器</title>
      <link>http://blog.zhaojie.me/2011/04/uglifyjs-has-a-good-javascript-parser.html</link>
      <guid>http://blog.zhaojie.me/2011/04/uglifyjs-has-a-good-javascript-parser.html</guid>
      <description>&lt;p&gt;我一直在为&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;寻找好用的JavaScript解析器，之前我用的是&lt;a href="https://github.com/mozilla/narcissus"&gt;Narcissus&lt;/a&gt;，也&lt;a href="http://blog.zhaojie.me/2010/11/narcissus-javascript-parser.html"&gt;写过相关文章&lt;/a&gt;。不过可惜的是，Narcissus使用了SpiderMonkey的扩展，因此它并不是用ECMAScript 3实现的，无法在IE 8等浏览器中使用。目前Jscex使用的是&lt;a href="http://www.neilmix.com/narrativejs/doc/"&gt;NarrativeJS&lt;/a&gt;中旧版的Narcissus，但是我并不喜欢它输出的AST结构，使用中也发现高级功能里的一些bug，有些食之无味弃之可惜的感觉，而改写新版Narcissus又必须大动干戈。最近我接触到了&lt;a href="https://github.com/mishoo/UglifyJS"&gt;UglifyJS&lt;/a&gt;，发现它的解析器相当不错，性能也比Narcissus高出许多，在此介绍给大家。&lt;/p&gt;

&lt;h1&gt;介绍&lt;/h1&gt;

&lt;p&gt;UglifyJS是个JavaScript压缩器，效果和&lt;a href="http://code.google.com/closure/compiler/"&gt;Google Closure Compiler&lt;/a&gt;相比有过之而无不及。对于现代化的JavaScript压缩器来说，简单的去除空白和压缩局部变量是远远不够的，同时需要理解代码的语义，将其替换成提及更小的形式（Uglify的说明页上有许多描述）。这显然需要一个JavaScript解析器。UglifyJS基于NodeJS开发，不过可以在各种支持CommonJS模块系统的JavaScript引擎/平台上运行。如果没有CommonJS，也只需将exports相关的代码去掉即可。&lt;/p&gt;

&lt;p&gt;JavaScript解析器的作用自然是将JavaScript代码分解成AST，然后根据AST便可以做到许多有趣的事情。相同的AST可以在内存中有不同的表现形式，例如之前提到我不太喜欢Jscex目前使用的旧版Narcissus，一个重要的原因便是它的AST结构不够友好（最新的Narcissus倒不错）。此外，虽然它提供了一些高级功能，例如标注了每个元素在源代码中的位置，这样使用者就可以直接根据getSource方法获得它对应的源代码——只可惜经试验这个功能有bug，这迫使我还得遍历完整的AST。&lt;/p&gt;

&lt;p&gt;UglifyJS的JavaScript分词器和解析器存放在源代码的parse-js.js文件中，移植于&lt;a href="http://marijn.haverbeke.nl/parse-js/"&gt;parse-js&lt;/a&gt;项目，后者是一个用Common Lisp实现的类库。现在您应该可以猜到它输出的AST是什么表现形式了吧。没错，就是个“表”，用JavaScript来表示，就是个数组套数组。我写了点简单的代码对其进行格式化输出，您可以&lt;a href="http://files.zhaojie.me/demos/js-parsers/uglifyjs-parser.html"&gt;在这里简单尝试一下UglifyJS的解析器&lt;/a&gt;。这个输出虽然简单，但对于Jscex来说也已经完全够用了。&lt;/p&gt;

&lt;h1&gt;使用&lt;/h1&gt;

&lt;p&gt;打开parse-js.js文件，您会看到这样一些代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;/* -----[ Tokenizer (constants) ]----- */

&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;KEYWORDS = array_to_hash([
    ...
]);

&lt;span style="color: blue"&gt;var &lt;/span&gt;RESERVED_WORDS = array_to_hash([
    ...
]);

...

&lt;span style="color: blue"&gt;function &lt;/span&gt;parse($TEXT, exigent_mode, embed_tokens) {
    ...
}

...

&lt;span style="color: #006400"&gt;/* -----[ Exports ]----- */&lt;/span&gt;

exports.tokenizer = tokenizer;
exports.parse = parse;
exports.slice = slice;
exports.curry = curry;
exports.member = member;
exports.array_to_hash = array_to_hash;
exports.PRECEDENCE = PRECEDENCE;
exports.KEYWORDS_ATOM = KEYWORDS_ATOM;
exports.RESERVED_WORDS = RESERVED_WORDS;
exports.KEYWORDS = KEYWORDS;
exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN;
exports.OPERATORS = OPERATORS;
exports.is_alphanumeric_char = is_alphanumeric_char;
exports.set_logger = &lt;span style="color: blue"&gt;function&lt;/span&gt;(logger) {
        warn = logger;
};&lt;/pre&gt;

&lt;p&gt;UglifyJS是基于CommonJS模块机制编写的，这一个文件其实就是个模块，它对外的方法通过exports暴露出来。如果我们将其作为普通的JavaScript文件引入到浏览器中，显然会报“export未定义”异常。理论上说，如果定义一个exports对象，甚至去除和exports有关的代码就能正常使用parse方法了。不过这么做也有个严重的问题，那就是对根对象的“污染”实在是太严重了，例如在浏览器中所有的函数，变量都出现在window上，再引入一些其他类库，造成冲突的可能性相当高。&lt;/p&gt;

&lt;p&gt;因此，我们必须对代码进行一些修改。幸运的是，在JavaScript中解决这类“作用域”问题十分容易，例如我这样将parse-js.js的代码包围了起来：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;UglifyJS = {};

(&lt;span style="color: blue"&gt;function &lt;/span&gt;(exports) {

&lt;span style="color: #006400"&gt;/* original code here */

&lt;/span&gt;})(UglifyJS);&lt;/pre&gt;

&lt;p&gt;这样就解决了作用域问题，如今我们就能访问UglifyJS对象上的KEYWORDS集合以及parse等成员了。&lt;/p&gt;

&lt;h1&gt;性能&lt;/h1&gt;

&lt;p&gt;然后再说说性能。JavaScript一直被认为是一门执行效率低下的语言——这其实是个错误的观点。其实从语言设计上说，JavaScript比Python和Ruby都要快，只不过由于历史原因各大浏览器对它都不太重视而已。不过如今情况早就有所改变，在V8的带领下，现代的JavaScript引擎执行速度都已经超过了目前最快的Python和Ruby实现。话不多说，现在我们就来比较一下UglifyJS的解析器与Narcissus在各浏览器下的表现吧。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://files.zhaojie.me/demos/js-parsers/benchmark.html"&gt;测试页面在此&lt;/a&gt;，您也可以自行尝试，测试场景是使用两者分别解析十次Narcissus的源代码——大约1500行未压缩的JavaScript代码（值得一提的是，我试了许多压缩后的代码，如jquery-min.js，它们用UgilifyJS可以正常解析，而Narcissus却解析失败）。我使用两台公司配置的标准工作机，测试了IE、Chrome和Firefox各两个版本共6种浏览器。每个浏览器我都会运行多遍测试，去除偏差大的结果，取中游数值。遗憾的是，由于条件所限，两台机器的操作系统有所不同，虽然我认为并不会对结果有什么影响，但如果您足够顶真，也不妨再自行评测一把。&lt;/p&gt;

&lt;p&gt;首先我在Win 7下测试了Chrome 10、FireFox 3和IE9，结果如下：&lt;/p&gt;

&lt;iframe width='100%' height='520' frameborder='0' src='https://spreadsheets.google.com/pub?hl=en&amp;hl=en&amp;key=0AuVfB8TyGeJ1dFRLOHlOMXNFRnVXYkdTNkYxTWMtUVE&amp;single=true&amp;gid=1&amp;output=html&amp;widget=true'&gt;&lt;/iframe&gt;

&lt;p&gt;对于UglifyJS来说，Chrome 10的表现最好，IE 9相比略慢少许，而Firefox 3耗时则是前两者的数倍。对于Narcissus来说，则是IE 9表现最好，仅为Chrome 10的五分之一，和Firefox 3相比更是数量级上的领先。有趣的是，Chrome 10和Firefox 3下两个解析器的耗时都是一比十左右，而IE 9下则相差无几。&lt;/p&gt;

&lt;p&gt;然后是Win XP下Chromium 12、Firefox 4及IE 8，结果如下：&lt;/p&gt;

&lt;iframe width='100%' height='520' frameborder='0' src='https://spreadsheets.google.com/pub?hl=en&amp;hl=en&amp;key=0AuVfB8TyGeJ1dFRLOHlOMXNFRnVXYkdTNkYxTWMtUVE&amp;single=true&amp;gid=2&amp;output=html&amp;widget=true'&gt;&lt;/iframe&gt;

&lt;p&gt;对于UglifyJS来说，Chromium 12的表现依旧抢眼，胜过Firefox 4不少，不过使用Narcissus的情况则正好相反。同样可以看出，IE 8的JavaScript引擎性能全面不敌其他浏览器，不过它和IE 9、Firefox 4（以及后面的Safari）的情况类似，即UglifyJS和Narcissus的耗时并没有太大差别。&lt;/p&gt;

&lt;p&gt;为了便于观察，我将两次测试的结果放在一起（除了&lt;a href="http://www.chromium.org/developers/calendar"&gt;非正式版本&lt;/a&gt;的Chromium 12）：&lt;/p&gt;

&lt;iframe width='100%' height='560' frameborder='0' src='https://spreadsheets.google.com/pub?hl=en&amp;hl=en&amp;key=0AuVfB8TyGeJ1dFRLOHlOMXNFRnVXYkdTNkYxTWMtUVE&amp;single=true&amp;gid=4&amp;output=html&amp;widget=true'&gt;&lt;/iframe&gt;

&lt;p&gt;总体而言，Chrome 10、IE 9和Firefox 4为第一军团。IE 9在UglifyJS上小负于Chrome 10，但在Narcissus上优势明显；Chrome 10在UglifyJS上表现最佳，但在Narcissus却落后IE 9和Firefox 4较多；Firefox 4虽然都不是“最佳”，但差距也并不太大。至于IE 8和Firefox 3，在JavaScript的执行效率方面的确已经落后于这个时代了。必须承认，如今的浏览器大战的确大大提高了各方的质量。&lt;/p&gt;

&lt;p&gt;此外我还测试了公司iMac上的Chrome 10、Firefox 3以及Safari 5，在此列出结果：&lt;/p&gt;

&lt;iframe width='100%' height='520' frameborder='0' src='https://spreadsheets.google.com/pub?hl=en&amp;hl=en&amp;key=0AuVfB8TyGeJ1dFRLOHlOMXNFRnVXYkdTNkYxTWMtUVE&amp;single=true&amp;gid=0&amp;output=html&amp;widget=true'&gt;&lt;/iframe&gt;

&lt;p&gt;虽然浏览器的表现各有高低，差距也有所不同，但可以确定的是，UglifyJS解析器的性能的确比Narcissus要高。因此，我打算在接下来几天里用UglifyJS替换掉目前Jscex里使用的Narcissus。&lt;/p&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;由于前端开发和JavaScirpt的流行，越来越多的人开始用JavaScript做一些有趣的事情。我很不喜欢如今许多所谓的前端实践，纠缠于大量的hack以及各种浏览器的表现，甚至是JavaScript里某种特定写法的性能更高——例如，居然有消息称，对于字符串连接操作来说，a += b的性能比a = a + b要高（或反之）。在我看来这些东西是最无用的，知道了又如何？随着浏览器更新换代，这些“经验”瞬间就毫无作用了。&lt;/p&gt;

&lt;p&gt;这也是我为什么喜欢玩JavaScript，却死也不愿去做前端开发，尤其是HTML、CSS。同样，如IE 6这种浏览器在我眼中也是必须消灭的东西。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/uglifyjs-has-a-good-javascript-parser.html#comments</comments>
      <pubDate>Fri, 01 Apr 2011 07:40:06 GMT</pubDate>
      <lastBuildDate>Sat, 02 Apr 2011 03:37:57 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>模拟HTML表单上传文件（RFC 1867）</title>
      <link>http://blog.zhaojie.me/2011/03/html-form-file-uploading-programming.html</link>
      <guid>http://blog.zhaojie.me/2011/03/html-form-file-uploading-programming.html</guid>
      <description>&lt;p&gt;如今使用HTTP协议定制API已经是十分常见的事情，在普通的GET和POST请求中传递些参数估计人人都会，但是如果我们需要上传文件呢？如果只是传递单个文件，那么将数据流POST给服务器端即可。但如果需要上传多个文件，或是在文件之外需要附带一些信息，那么又该怎么做呢？之前我遇到过一些朋友是这么打算的，他们说，不如就把文件流转化为文本，然后把它当作一个普通的字段传递。这么做自然可以“实现功能”，但缺点也很多。首先，将二进制流转化为文本会增大体积（例如最常见的BASE64编码会增大1/3的数据量）；其次，既然互联网上存在相关的协议，又为何要自定义一套规则呢？其实这便是《&lt;a href="http://www.faqs.org/rfcs/rfc1867.html"&gt;RFC 1867 - Form-based File Upload in HTML&lt;/a&gt;》，它是我们用HTML表单上传文件时使用的传输协议，虽然十分常用，但似乎了解它的人并不多。&lt;/p&gt;

&lt;h1&gt;普通POST操作&lt;/h1&gt;

&lt;p&gt;说起HTML表单，大家绝对不会陌生。例如下面这样的HTML表单：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;form &lt;/span&gt;&lt;span style="color: red"&gt;action&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;http://www.baidu.com/&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;method&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;post&amp;quot;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;text&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;myText1&amp;quot; /&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;br &lt;/span&gt;&lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;text&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;myText2&amp;quot; /&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;br &lt;/span&gt;&lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;submit&amp;quot; /&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;form&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;提交时会向服务器端发出这样的数据（已经去除部分不相关的头信息）：&lt;/p&gt;

&lt;pre class="code"&gt;POST http://www.baidu.com/ HTTP/1.1
Host: www.baidu.com
Content-Length: 74
Content-Type: application/x-www-form-urlencoded

myText1=hello+world&amp;amp;myText2=%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C&lt;/pre&gt;

&lt;p&gt;对于普通的HTML POST表单，它会在头信息里使用Content-Length注明内容长度。头信息每行一条，空行之后便是Body，即“内容”。此外，我们可以发现它的Content-Type是application/x-www-form-urlencoded，这意味着消息内容会经过URL编码，就像在GET请求时URL里的Query String那样。在上面的例子中，myText1里的空格被编码为加号，而myText2，您看得出这是“你好世界”这四个汉字吗？&lt;/p&gt;

&lt;h1&gt;使用POST上传文件&lt;/h1&gt;

&lt;p&gt;不过之前的HTML表单是无法上传文件的，因此&lt;a href="http://www.faqs.org/rfcs/rfc1867.html"&gt;RFC 1867&lt;/a&gt;应运而生，它的目的便是让HTML表单可以提交文件。它对HTML表单的扩展主要是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;为input标记的type属性增加一个file选项。 &lt;/li&gt;

  &lt;li&gt;在POST情况下，为form标记的enctype属性定义默认值为application/x-www-form-urlencoded。 &lt;/li&gt;

  &lt;li&gt;为form标记的enctype属性增加multipart/form-data选项。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;于是，如果我们要使用HTML表单提交文件，则可以使用如下定义：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;form &lt;/span&gt;&lt;span style="color: red"&gt;action&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;http://www.baidu.com/&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;method&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;post&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;enctype&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;multipart/form-data&amp;quot;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;text&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;myText&amp;quot; /&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;br &lt;/span&gt;&lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;file&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;upload1&amp;quot; /&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;br &lt;/span&gt;&lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;file&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;upload2&amp;quot; /&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;br &lt;/span&gt;&lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;input &lt;/span&gt;&lt;span style="color: red"&gt;type&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;submit&amp;quot; /&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;form&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;为了实验所需，我们创建两个文件file1.txt和file2.txt，内容分别为“This is file1.”及“This is file2, it's bigger.”。在文本框里写上“hello world”，并选择这两个文件，提交，则会看到浏览器传递了如下数据：&lt;/p&gt;

&lt;pre class="code"&gt;POST http://www.baidu.com/ HTTP/1.1
Host: www.baidu.com
Content-Length: 495
Content-Type: multipart/form-data; boundary=---------------------------7db2d1bcc50e6e

-----------------------------7db2d1bcc50e6e
Content-Disposition: form-data; name=&amp;quot;myText&amp;quot;

hello world
-----------------------------7db2d1bcc50e6e
Content-Disposition: form-data; name=&amp;quot;upload1&amp;quot;; filename=&amp;quot;C:\file1.txt&amp;quot;
Content-Type: text/plain

This is file1.
-----------------------------7db2d1bcc50e6e
Content-Disposition: form-data; name=&amp;quot;upload2&amp;quot;; filename=&amp;quot;C:\file2.txt&amp;quot;
Content-Type: text/plain

This is file2, it's longer.
-----------------------------7db2d1bcc50e6e--
&lt;/pre&gt;

&lt;p&gt;这段内容比较有趣，值得细细观察。首先，第一个空行之前自然还是HTTP头，之后则是Body，而此时的Body也比之前要复杂一些。根据RFC 1867定义，我们需要选择一段数据作为“分割边界”，这个“边界数据”不能在内容其他地方出现，一般来说使用一段从概率上说“几乎不可能”的数据即可。例如，上面这段数据使用的是IE 9，而我在Chrome下则是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;POST http://www.baidu.com/ HTTP/1.1
Host: www.baidu.com
Content-Length: 473
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryW49oa00LU29E4c5U

------WebKitFormBoundaryW49oa00LU29E4c5U
Content-Disposition: form-data; name=&amp;quot;myText&amp;quot;

hello world
------WebKitFormBoundaryW49oa00LU29E4c5U
Content-Disposition: form-data; name=&amp;quot;upload1&amp;quot;; filename=&amp;quot;file1.txt&amp;quot;
Content-Type: text/plain

This is file1.
------WebKitFormBoundaryW49oa00LU29E4c5U
Content-Disposition: form-data; name=&amp;quot;upload2&amp;quot;; filename=&amp;quot;file2.txt&amp;quot;
Content-Type: text/plain

This is file2, it's bigger.
------WebKitFormBoundaryW49oa00LU29E4c5U--
&lt;/pre&gt;

&lt;p&gt;很显然它们两个选择了不同的数据“模式”作为边界——事实上，浏览器提交两次数据时，使用的边界也可能不会相同，这都没有问题。&lt;/p&gt;

&lt;p&gt;选择了边界之后，便会将它放在头部的Content-Type里传递给服务器端，实际需要传递的数据便可以分割为“段”，每段便是“一项”数据。从上面的内容中大家应该都能看出数据传输的规范，因此便不做细谈了。只强调几点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;数据均无需额外编码，直接传递即可，例如您可以看出上面的示例中的“空格”均没有变成加号。至于这里您可以看到清晰地文字内容，是因为我们上传了仅仅包含可视ASCII码的文本文件，如果您上传一个普通的文件，例如图片，捕获到的数据则几乎完全不可读了。 &lt;/li&gt;

  &lt;li&gt;IE和Chrome在filename的选择策略上有所不同，前者是文件的完整路径，而后者则仅仅是文件名。 &lt;/li&gt;

  &lt;li&gt;数据内容以两条横线结尾，并同样以一个换行结束。在网络协议中一般都以连续的CR、LF（即\r、\n，或0x0D、Ox0A）字符作为换行，这与Windows的标准一致。如果您使用其他操作系统，则需要考虑它们的&lt;a href="http://en.wikipedia.org/wiki/Newline"&gt;换行符&lt;/a&gt;。 &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;实现&lt;/h1&gt;

&lt;p&gt;了解上述策略之后，使用编程来实现文件上传也是顺理成章的事情，例如我这里便编写了一段简单的代码实现这一功能。&lt;/p&gt;

&lt;p&gt;首先，我们定义一个Part类，表示每“段”，它的Write方法会写入整段数据。每段数据分为Header和Body两部分，使用WriteHeader和WriteBody两个抽象方法写入：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Part
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected abstract void &lt;/span&gt;WriteHeader(&lt;span style="color: #2b91af"&gt;StreamWriter &lt;/span&gt;writer);
    &lt;span style="color: blue"&gt;protected abstract void &lt;/span&gt;WriteBody(&lt;span style="color: #2b91af"&gt;StreamWriter &lt;/span&gt;writer);

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Write(&lt;span style="color: #2b91af"&gt;StreamWriter &lt;/span&gt;writer)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.WriteHeader(writer);
        writer.WriteLine();
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.WriteBody(writer);
    }
}&lt;/pre&gt;

&lt;p&gt;接着便是表示普通字段的NormalPart和文件上传得FilePart：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NormalPart &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Part
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Name { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Value { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;WriteHeader(&lt;span style="color: #2b91af"&gt;StreamWriter &lt;/span&gt;writer)
    {
        writer.WriteLine(&lt;span style="color: #a31515"&gt;&amp;quot;Content-Disposition: form-data; name=\&amp;quot;{0}\&amp;quot;&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;this&lt;/span&gt;.Name);
    }

    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;WriteBody(&lt;span style="color: #2b91af"&gt;StreamWriter &lt;/span&gt;writer)
    {
        writer.WriteLine(&lt;span style="color: blue"&gt;this&lt;/span&gt;.Value);
    }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;FilePart &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Part
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Name { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;FilePath { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;WriteHeader(&lt;span style="color: #2b91af"&gt;StreamWriter &lt;/span&gt;writer)
    {
        writer.WriteLine(
            &lt;span style="color: #a31515"&gt;&amp;quot;Content-Disposition: form-data; name=\&amp;quot;{0}\&amp;quot;; filename=\&amp;quot;{1}\&amp;quot;&amp;quot;&lt;/span&gt;,
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.Name,
            &lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.GetFileName(&lt;span style="color: blue"&gt;this&lt;/span&gt;.FilePath));

        writer.WriteLine(&lt;span style="color: #a31515"&gt;&amp;quot;Content-Type: application/octet-stream&amp;quot;&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;WriteBody(&lt;span style="color: #2b91af"&gt;StreamWriter &lt;/span&gt;writer)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;data = &lt;span style="color: #2b91af"&gt;File&lt;/span&gt;.ReadAllBytes(&lt;span style="color: blue"&gt;this&lt;/span&gt;.FilePath);
        writer.Flush();
        writer.BaseStream.Write(data, 0, data.Length);
        writer.WriteLine();
    }
}&lt;/pre&gt;

&lt;p&gt;最后便是统一写入各段的Write方法，我在这里使用新建的GUID作为“边界”：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Write(&lt;span style="color: #2b91af"&gt;StreamWriter &lt;/span&gt;writer, &lt;span style="color: #2b91af"&gt;IEnumerable&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Part&lt;/span&gt;&amp;gt; parts)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;guidBytes = &lt;span style="color: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid().ToByteArray();
    &lt;span style="color: blue"&gt;var &lt;/span&gt;boundary = &lt;span style="color: #a31515"&gt;&amp;quot;----------------&amp;quot; &lt;/span&gt;+ &lt;span style="color: #2b91af"&gt;Convert&lt;/span&gt;.ToBase64String(guidBytes);

    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;p &lt;span style="color: blue"&gt;in &lt;/span&gt;parts)
    {
        writer.WriteLine(boundary);
        p.Write(writer);
    }

    writer.WriteLine(boundary + &lt;span style="color: #a31515"&gt;&amp;quot;--&amp;quot;&lt;/span&gt;);
}&lt;/pre&gt;

&lt;p&gt;其实就是这么简单。不过在实际情况中可能会复杂一些。例如，由于HTTP协议需要先发送头信息，因此我们需要提前计算出Content-Length再传输所有内容，不过我相信这对您来说也不会是件难事。&lt;/p&gt;

&lt;h1&gt;其他&lt;/h1&gt;

&lt;p&gt;世界上已经有了足够多的协议，在我看来在绝大部分情况下都无所谓使用自定义的协议。协议在制定时，往往也会考虑到安全、性能等诸多方面，有时候我们自己所谓的“顾虑”其理由也并不充分。更重要的是，使用现成的协议，我们往往都有现成的实现，对于开发和测试都会有很大帮助。&lt;/p&gt;

&lt;p&gt;RFC 1867是一个很简单的协议，当然再简单也不是我这短短一篇文章可以完整描述的，其中很多细节（例如在同一个“段”中上传多个文件）就要靠您自己去挖掘了。&lt;/p&gt;

&lt;p&gt;&lt;span style="color:red;"&gt;广告时间：&lt;/span&gt;nBazaar技术会议的邮件列表已经正式启用，所有用户也已添加完成。目前已经发送了第一封邮件，建议您检查一下自己的收件箱或垃圾箱，确保可以收到未来的邮件。如有任何疑问，请发邮件至&lt;img class="embed" src="http://services.nexodyne.com/email/customicon/9xaGAYDnFdYrRqAdl1tTvrs%3D/Y4fIT8s%3D/000000/ffffff/000000/0/image.png" /&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/03/html-form-file-uploading-programming.html#comments</comments>
      <pubDate>Sun, 27 Mar 2011 10:59:11 GMT</pubDate>
      <lastBuildDate>Tue, 29 Mar 2011 14:00:32 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>在MongoDB中实现乐观并发控制</title>
      <link>http://blog.zhaojie.me/2011/02/optimistic-concurrency-control-in-mongodb.html</link>
      <guid>http://blog.zhaojie.me/2011/02/optimistic-concurrency-control-in-mongodb.html</guid>
      <description>&lt;p&gt;说起来，自从接触了&lt;a href="http://www.mongodb.org/"&gt;MongoDB&lt;/a&gt;以后，我在大小项目中就再也没有接触过关系型数据库了。性能倒不是什么主要问题，主要是方便，例如我可以在MongoDB中直接保存数组，然后把其中的元素当作查询条件，而在关系型数据库中，则需要使用额外的表格，然后再JOIN等等。当然，在MongoDB中很难进行JOIN，于是对于某些场景下会略显麻烦，但在记忆中我似乎真没什么束手束脚的情况。这方面我还没有仔细分析，可能MongoDB支持保存复杂对象会有所帮助吧。以上都是废话，这里我简单谈一下如何在MongoDB中实现&lt;a href="http://en.wikipedia.org/wiki/Optimistic_concurrency_control"&gt;乐观并发控制&lt;/a&gt;。当然加入您对MongoDB的功能都有所了解，那么这种做法也是十分显而易见的。&lt;/p&gt;

&lt;p&gt;简单地说，“并发控制”便是避免在并发环境下某条记录被错误地覆盖。例如在一次“读取”、“修改”、“提交”的事务中，除非进行合理控制，否则可能其中某次提交的数据就遗失了。所谓“悲观”并发控制，则意味着在某次事务的“开始”和“提交”之间不会出现任何“读取”操作（即这条记录被锁定了），这自然不会有问题。而乐观并发控制，则保证的是在某次“读取”和“提交”之间没有进行任何“提交”操作，否则便会提交失败，于是当前事务便会重新从“读取”这个最早的步骤开始。此类概念（或者说并发处理方式）在许多地方都有体现，例如在普通的并发编程中，lock就近似于“悲观”并发控制，而“&lt;a href="http://en.wikipedia.org/wiki/Software_transactional_memory"&gt;软件事务内存&lt;/a&gt;”则类似于“乐观”并发控制。&lt;/p&gt;

&lt;p&gt;如果要在普通的关系型数据库里实现乐观并发控制，我们一般需要为其加上一个额外的Version字段，它是整型，也可能是个时间戳。在更新某条记录时，我们将这个字段的“旧值”作为UPDATE语句的条件之一，同时这个字段也会写入新的值。如果这次更新影响了某条记录，那么表示更新成功，反之则表示这条记录已经被删除，或是在“读取”和“提交”之间遇到了其他提交操作。在SQL Server中存在一个Timestamp类型，这个类型的字段会在记录修改时自动更新。&lt;/p&gt;

&lt;p&gt;在MongoDB中的做法也没有太大区别，只是它的update语句并不会返回它所影响的记录数，于是我们必须额外进行一次查询，例如&lt;a href="http://www.mongodb.org/display/DOCS/Atomic+Operations"&gt;文档上所记载&lt;/a&gt;：&lt;/p&gt;

&lt;pre class="code"&gt;&amp;gt; t.update({_id: 1, version: 3}}, {$set: {Content: &lt;span style="color: maroon"&gt;&amp;quot;New Content&amp;quot;&lt;/span&gt;, version: 4}});
&amp;gt; db.$cmd.findOne({getlasterror: 1});
{&lt;span style="color: maroon"&gt;&amp;quot;err&amp;quot;&lt;/span&gt;:, &lt;span style="color: maroon"&gt;&amp;quot;updatedExisting&amp;quot;&lt;/span&gt;: &lt;span style="color: blue"&gt;true&lt;/span&gt;, &lt;span style="color: maroon"&gt;&amp;quot;n&amp;quot;&lt;/span&gt;: 1 , &lt;span style="color: maroon"&gt;&amp;quot;ok&amp;quot;&lt;/span&gt;: 1} &lt;span style="color: #006400"&gt;// it worked

&lt;/span&gt;&amp;gt; t.update({_id: 1, version: 3}}, {$set: {Content: &lt;span style="color: maroon"&gt;&amp;quot;New Content&amp;quot;&lt;/span&gt;, version: 4}}); 
&amp;gt; db.$cmd.findOne({getlasterror: 1});
{&lt;span style="color: maroon"&gt;&amp;quot;err&amp;quot;&lt;/span&gt;:, &lt;span style="color: maroon"&gt;&amp;quot;updatedExisting&amp;quot;&lt;/span&gt;: &lt;span style="color: blue"&gt;false&lt;/span&gt;, &lt;span style="color: maroon"&gt;&amp;quot;n&amp;quot;&lt;/span&gt;: 0 , &lt;span style="color: maroon"&gt;&amp;quot;ok&amp;quot;&lt;/span&gt;: 1} &lt;span style="color: #006400"&gt;// did not work&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;我们可以在update语句后面跟上一句db.$cmd查询，如果它返回updatedExisting为true，则表示更新成功了。我一开始担心db.$cmd查询的结果是否准确：如果在update语句和db.$cmd查询之间，另外一个连接恰好也执行了一次update操作，那么db.$cmd返回的是哪次更新的结果？从后来从邮件列表中得知，db.$cmd查询是与连接相关，这便不会有问题了。不过值得注意的是，如果您使用的的驱动程序是“自动管理连接”的，则可能您在程序中发起的两次查询会使用两个不同的链接。不过我猜成熟的驱动应该都有办法解决这个问题，例如&lt;a href="http://www.mongodb.org/display/DOCS/CSharp+Driver+Tutorial"&gt;MongoDB的官方.NET驱动&lt;/a&gt;便可以要求直接返回db.$cmd查询的结果，或者在代码里显式“固定”某个链接。如今MongoDB的官方驱动已经十分完善，将MongoDB的功能体现地淋漓尽致，我也正在它的基础上更新&lt;a href="http://github.com/JeffreyZhao/EasyMongo"&gt;EasyMongo&lt;/a&gt;（经过几个项目使用，感想不错），和之前的“民间驱动”相比省了不少心——顺便一提，官方驱动其实也借用了民间驱动的不少代码，即便它们之间的API有许多差异。&lt;/p&gt;

&lt;p&gt;除此之外，您也可以使用基于runCommand的&lt;a href="http://www.mongodb.org/display/DOCS/findAndModify+Command"&gt;findAndModify&lt;/a&gt;命令进行更新，更新条件自然同样需要包括版本号。如果更新成功，那么findAndModify命令则会返回“更新前”的数据，否则则返回空文档。一般来说，MongoDB的驱动也已经包含了runCommand命令，甚至对findAndModify的直接支持（例如官方的.NET驱动）。&lt;/p&gt;

&lt;p&gt;正如我一开始所说的那样，其实如果您了解MongoDB的功能，本文内容其实是十分显而易见的，的确就是这么简单。&lt;/p&gt;

&lt;p&gt;最后顺手发布两条“招聘”消息（地点都是上海）：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;钢琴入门老师（1名）：指导我进行钢琴基础技术训练及乐理（如24个大小调音阶，快速识谱方法）。本人小时候学过点钢琴，但基础没有打好，现在打算认真来过。 &lt;/li&gt;

  &lt;li&gt;优秀.NET程序员（1~2名）：加入盛大创新院工作，要求“自认为对.NET有较好了解”，有一定编程基础，热爱技术学习。热情参与社区活动者为佳。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;欢迎各位推荐或自荐，联系方式（邮件）：&lt;img src="http://services.nexodyne.com/email/icon/T3pYH0sugOKOkgan4fyp/GoPfEek%3D/R01haWw%3D/0/image.png" class="embed" /&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/02/optimistic-concurrency-control-in-mongodb.html#comments</comments>
      <pubDate>Tue, 22 Feb 2011 01:39:51 GMT</pubDate>
      <lastBuildDate>Thu, 03 Mar 2011 08:41:24 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/discussion/">思考讨论</category>
      <title>分清“语言/规范”以及“平台/实现”，以及跨平台.NET开发</title>
      <link>http://blog.zhaojie.me/2011/01/be-clear-with-language-spec-and-platform-implementation-dotnet-cross-platform.html</link>
      <guid>http://blog.zhaojie.me/2011/01/be-clear-with-language-spec-and-platform-implementation-dotnet-cross-platform.html</guid>
      <description>&lt;p&gt;在许多年前，“语言”就等同于“平台”，例如C，C++以及最早的Ruby和Python等等。但是随着技术发展，出现了一些通用的平台，例如.NET和Java，逐渐这些平台上的语言也越来越多。再后来，某些语言在不同平台上的实现也越来越多，事情也变得有些复杂。技术在发展，但是从目前社区的讨论中，我发现许多朋友的观念还没有跟上。简单地说，如今的观念，一定要从“语言即平台”切换成“语言及平台”，当分清“语言”和“平台”这两个不同事物之后，许多问题才能讨论地清楚。&lt;/p&gt;

&lt;p&gt;例如我写过一个太监系列《&lt;a href="http://blog.zhaojie.me/2010/04/why-java-sucks-and-csharp-rocks-1-thoughts-and-goals.html"&gt;Why Java Sucks and C# Rocks&lt;/a&gt;》，其中谈的是C#和Java两个“语言”而不是两者的“平台”。编程“语言”其实是一种“规范”，它涉及了程序员在使用这门语言时的文本表现形式（这里暂不考虑其他形式的语言），而“平台”则包括对这个规范的“实现”（广义的“平台”还包括整个生态环境等等）。C#和Java分别处在各自的平台上，但许多语言其实是跨多种平台的。例如Python，Ruby，Scala，Clojure，JavaScript等等，数不胜数。同样，一个平台上也会出现多种语言。而且事实上，由于.NET和Java这样的平台越来越成熟，语言的设计及实现者也都越来越倾向于让语言运行在“某个平台”上。这么做可以尽可能地利用前人的成果，而不是什么都要自己从头做起。&lt;/p&gt;

&lt;p&gt;其实基本的原则就是这么简单，但是真正在考虑问题的时候，可能就不是那么容易了，我们必须时刻保持清晰地头脑。&lt;/p&gt;

&lt;p&gt;例如有个人说“C#比Java执行效率高（或低）”，这个说法是否正确？其实这种说法有很大问题。因为我们知道，在这里C#和Java都是“语言”，它们的执行环境CLI及JVM一样都是“规范”，但“执行效率”是一种表现，这和“实现”得如何有很大关系。例如，C#是运行在.NET平台还是Mono上（它们都是CLI规范的具体实现），Java是运行在JRockit还是Hotspot（前者是Oracle的JVM商业实现，后者是Sun的开源实现——当然现在也是Oracle的），亦或是Android的Dalvik上？很显然，不同实现之间的表现会有区别，不可一概而论，否则也不会出现JavaScript引擎的效率之争了。同理，有些人使用Hotspot上的Java性能来说明Java在Android上运行时的表现，这也是不对的——要知道Google在和Oracle的Java专利官司中不断强调Dalvik不是“Oracle那种Java”。作为结论，Java在Android上的表现的确不错，但论证方式也必须正确才行。&lt;/p&gt;

&lt;p&gt;当然，有时候“规范”也会影响到“实现”，例如一个动态分发的语言，其性能基本百分百不如在编译期绑定的静态语言。所以事情原本就是这么复杂，做一个思路清晰的程序员并不是件容易的事情。顺便一提，女人在这方面的头脑一般都比较清楚，她们一般都知道骑白马的不一定是王子，也有可能是唐僧。&lt;/p&gt;

&lt;p&gt;对于俗称“.NET程序员”的那一批人来说，分清“语言”和“平台”更是一件十分重要的事情，因为C#语言可以说是目前“平台”、“实现”最为广泛的“语言”之一了。之前我为InfoQ写过一篇文章，其中提到Mono的创始人&lt;a href="http://tirania.org/blog/"&gt;Miguel de Icaza&lt;/a&gt;给出的&lt;a href="http://www.infoq.com/cn/news/2010/11/mono-cross-platform"&gt;目前C#语言可执行平台的“不完全”列表&lt;/a&gt;，几乎覆盖了各种流行的操作系统及设备等等，例如：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Windows &lt;/li&gt;

  &lt;li&gt;Mac OS &lt;/li&gt;

  &lt;li&gt;Linux / BSD / Solaris &lt;/li&gt;

  &lt;li&gt;Windows Phone，Android，iOS &lt;/li&gt;

  &lt;li&gt;XBox 360，Wii，PS3 &lt;/li&gt;

  &lt;li&gt;…… &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此就拿C#这一种语言来说，“实现”也会各自略有不同，这便是所谓的“配置（Profile）”。目前至少已经有这么多配置了：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;.NET 4.0配置 &lt;/li&gt;

  &lt;li&gt;Silverlight配置 &lt;/li&gt;

  &lt;li&gt;Windows Phone 7配置 &lt;/li&gt;

  &lt;li&gt;XBox360配置 &lt;/li&gt;

  &lt;li&gt;Mono核心配置：与.NET配置相同，可以在Linux，MacOS X，Solaris，Windows和BSD里使用。 &lt;/li&gt;

  &lt;li&gt;.NET Micro Framework &lt;/li&gt;

  &lt;li&gt;Mono的iOS配置 &lt;/li&gt;

  &lt;li&gt;Mono的Android配置 &lt;/li&gt;

  &lt;li&gt;Mono的PS3配置 &lt;/li&gt;

  &lt;li&gt;Mono的Wii配置 &lt;/li&gt;

  &lt;li&gt;Moonlight配置（与Silverlight兼容） &lt;/li&gt;

  &lt;li&gt;Moonlight扩展配置（Silverlight和完整的.NET 4 API） &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“配置”之间的区别主要体现在执行环境的能力（例如iOS不支持运行时代码生成，因此支持AOT但不能JIT）以及类库的覆盖面上（例如XNA类库只存在于Windows Phone及XBox 360等游戏平台），不过它们终究实现了一个核心规范，因此我们可以说在不同平台上都可以“使用.NET进行开发”。&lt;/p&gt;

&lt;p&gt;Mono实在是一个了不得的作品，它让我知道了“跨平台原来可以这么做”。之前我也写过有关&lt;a href="http://blog.zhaojie.me/2010/06/is-cross-platform-a-lie-or-not.html"&gt;跨平台的问题&lt;/a&gt;，其中谈到在“客户端的跨平台一般都很难得到最佳的体验”，这个论点的最佳证明便是Java。但Mono走的却是另一条跨平台的道路，它在各平台上实现了核心的执行引擎和类库之外，解决“体验”的方式便是在各个平台上提供原生平台的绑定。这样无论是在Mac OS，iOS，Android上都可以得到原生应用的体验。&lt;/p&gt;

&lt;p&gt;我很奇怪为什么有些搞.NET的人一边说.NET的适用面太小，一边却忽视Mono的成果，在我看来这完全是“自作孽不可活”，我愈发觉得是否接受Mono是判断一个.NET程序员是否优秀的重要准则。其实Mono实在很火，因为他为广大.NET程序员扩展了工作领域，使用现有的知识来开发iOS等平台的应用程序，还可以共享代码，何乐而不为？前不久苹果发布了Mac上的App Store，于是MonoMac也立即推出了&lt;a href="http://tirania.org/monomac/archive/2011/Jan-10-1.html"&gt;面向AppStore的打包器&lt;/a&gt;，&lt;a href="http://twitter.com/#!/praeclarum"&gt;Frank Krueger&lt;/a&gt;也开始着手移植它的作品&lt;a href="http://icircuitapp.com/"&gt;iCircuit&lt;/a&gt;，&lt;a href="http://vimeo.com/18651634"&gt;成果显著&lt;/a&gt;。因此在我看来，这才是一个现代.NET程序员该有的工作台：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/morden-dotnet-prog-workbench.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/morden-dotnet-prog-workbench.jpg" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;对于MonoTouch这样的新思路，带有疑惑是正常的。我也知道还有许多聪明人可以找到各种反对的理由。不管怎样，我现在这里随意列上几条吧：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;有人说，用MonoTouch等.NET实现来做iOS开发“不正式”；我说，这个说法颇有“血统论”的意味，不过既然在Windows上用C++和Delphi都很正式，那么为什么在iOS上使用Objective-C才是正途？&lt;/li&gt;

  &lt;li&gt;有人说，MonoTouch性能一定不如Objective-C好；我说，这是猜测，即使性能不如Objective-C，看看各种案例也知道这在实践中并不是问题（事实上MonoTouch的前身便是Unity3D对Mono的使用，而iOS上实在有太多游戏在使用Unity3D了）。&lt;/li&gt;

  &lt;li&gt;有人说，MonoTouch或MonoDroid没有大公司支持，不靠谱；我说，您之前不是经常鄙视类似“开源没有微软靠谱”或是“微软开发人员只知道微软技术”这种说法的吗？&lt;/li&gt;

  &lt;li&gt;有人说，用MonoTouch等于抛弃了CocoaTouch社区，出了问题都没人问；我说，MonoTouch的问题基本就是CocoaTouch的问题，MonoTouch的UI层就是CocoaTouch，有问题直接去CocoaTouch社区或CocoaTouch程序员，代码直接映射，类库直接使用。&lt;/li&gt;

  &lt;li&gt;有人说，用MonoTouch的人不好招；我说，用C#、.NET的人比用Objective-C、Cocoa多太多了。给我一个熟练使用.NET和C#的人，三天上手，一周成为能够开发出成品的iOS开发者。&lt;/li&gt;

  &lt;li&gt;有人说，难道就是为了用.NET所以用MonoTouch？我说，用MonoTouch/MonoDroid的好处很多，例如我可以在iOS、Android、Windows Phone甚至更多平台上共享UI以外的代码，并可以直接使用大量.NET上的类库——这点实在太方便了。不要问我为什么Android上不能使用Java类库，我只知道开发Andorid的同事发现SOAP访问类库没有，REST找不到好的，JSON支持也只有最原始的支持，于是痛苦万分。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我还知道，这些说法依旧挡不住出现基于MonoDroid的&lt;a href="http://deltaengine.net/"&gt;DeltaEngine&lt;/a&gt;，这是个跨平台的游戏引擎，在Mono的支持下可以运行在Linux，MacOS X，iOS和Android上，在微软.NET支持下可以运行在XBox 360，Windows Phone 7自然还有普通的Windows系统上。在CES 2011上&lt;a href="http://blogs.nvidia.com/2011/01/nvidia-press-conference-ces-2011/"&gt;NVidia演示了一个游戏&lt;/a&gt;，&lt;a href="http://mobilebits.de/Blog/post/2011/01/05/Delayed-blogging-of-building-the-first-SoulCraft-Tech-Demo-version.aspx"&gt;Soul Craft&lt;/a&gt;，它运行在&lt;a href="http://blogs.nvidia.com/2011/01/lg-launches-optmus-2x-dual-core-superphone-powered-by-tegra-2/"&gt;LG Optimus 2X&lt;/a&gt;，这个游戏正是使用了DeltaEngine。&lt;/p&gt;

&lt;p&gt;对于我们来说，最大的限制其实还是眼界和思维，突破这一屏障也是我组织&lt;a href="http://nbazaar.org/"&gt;nBazaar技术沙龙&lt;/a&gt;的目的之一。本周六将会举办第三届nBazaar技术交流会，具体信息请访问&lt;a href="http://nbazaar.org/"&gt;http://nbazaar.org/&lt;/a&gt;。如果您还没有报名，也可以直接前来，也欢迎带上感兴趣的朋友或同事。根据以往的经验，场地就像乳沟，挤挤总是有的……&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/01/be-clear-with-language-spec-and-platform-implementation-dotnet-cross-platform.html#comments</comments>
      <pubDate>Thu, 13 Jan 2011 15:35:21 GMT</pubDate>
      <lastBuildDate>Fri, 14 Jan 2011 02:29:51 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/news/">新闻信息</category>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <title>第三届nBazaar技术交流会开始报名</title>
      <link>http://blog.zhaojie.me/2010/12/3rd-nbazaar-meeting-sign-up.html</link>
      <guid>http://blog.zhaojie.me/2010/12/3rd-nbazaar-meeting-sign-up.html</guid>
      <description>&lt;p&gt;为了错开年底密集的技术会议，第三届&lt;a href="http://nbazaar.org/"&gt;nBazaar技术交流会&lt;/a&gt;（即前“盛大创新院赞助的.NET技术交流会”）将于2011年1月15日举行。第三届的交流会将继续以往四场高质量的演讲，这也是确定nBazaar名称之后的第一次活动，希望nBazaar能够真正&lt;a href="http://blog.zhaojie.me/2010/10/status-of-iron-languages-and-nbazaar.html"&gt;成为“集市”般热闹的社区活动&lt;/a&gt;。从现在开始，nBazaar技术沙龙的相关信息将逐渐集中至独立域名中，欢迎关注。&lt;/p&gt;

&lt;h1&gt;志愿者招募&lt;/h1&gt;

&lt;p&gt;为了留下每次的活动资料，我们希望为每场演讲进行拍摄。如果您有这方面的志愿请发邮件至&lt;a href="mailto:jeffz@nbazaar.org"&gt;jeffz@nbazaar.org&lt;/a&gt;与我联系。&lt;/p&gt;

&lt;h1&gt;时间及议程安排&lt;/h1&gt;

&lt;p&gt;本次交流会首次向社区征集议题，我们在回复中挑选了三场：面向iPad平台的网站架构、面向企业应用语言ABAP、基于.NET的轻量级分布式框架，以及在创新院内部分享会上倍受好评的“分布式版本管理”话题。四位演讲者都是业界一线技术高手，四场话题都是他们的实战心得，希望能够让您满意。&lt;/p&gt;

&lt;table style="text-align: center" border="1" cellspacing="0" cellpadding="5"&gt;&lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;时间&lt;/th&gt;

      &lt;th&gt;议程&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;&lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;12:30 ~ 13:00&lt;/td&gt;

      &lt;td&gt;签到&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;13:00 ~ 14:00&lt;/td&gt;

      &lt;td&gt;针对iPad平台的高性能网站架构&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;14:00 ~ 14:10&lt;/td&gt;

      &lt;td&gt;短休&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;14:10 ~ 15:10&lt;/td&gt;

      &lt;td&gt;分布式版本管理&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;15:10 ~ 15:40&lt;/td&gt;

      &lt;td&gt;茶歇&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;15:40 ~ 16:40&lt;/td&gt;

      &lt;td&gt;企业开发领域的语言特性&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;16:40 ~ 16:50&lt;/td&gt;

      &lt;td&gt;短休&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;16:50 ~ 17: 50&lt;/td&gt;

      &lt;td&gt;使用.NET构建轻量级分布式框架&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;

&lt;h1&gt;演讲内容&lt;/h1&gt;

&lt;p&gt;以下为是四场演讲的信息：&lt;/p&gt;
&lt;a href="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/mashijie-450x600.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/mashijie-450x600.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;针对iPad平台的高性能网站架构&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;马士杰，EF英孚教育，Tech Leader。关注高性能网站架构和前沿技术在线教育领域的应用创新。曾经重点关注的技术领域包括ORM，AOP和SOA。近期的关注重点是针对移动平台的高性能网站架构。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;iPad的横空出世，几乎使得2010年成为全球IT领域的平板电脑之年。摩根士丹利发布的《移动互联网报告》认为移动互联网周期是50年来的第5个新技术周期，以Apple的iPad平板电脑和手机上网为代表的移动互联网的增长势头将超过电脑上网。本演讲的目的是和听众分享本人近一年在针对iPad平台的高性能网站架构方面的一些经验，包括兼容不同平台桌面和移动浏览器的表现层设计模式，针对iPad Mobile Safari浏览器的Web页面性能优化和iPad本地程序和在线网站的无缝整合等。&lt;/p&gt;
&lt;a href="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/lijun-400x600.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/lijun-400x600.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;分布式版本管理&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;李骏，中国第一批J2EE开发者，大中华区第一个中间件和SOA领域的Oracle ACE Director。有着10年以上企业级应用系统咨询/设计/实施以及软件公司管理经验的行业老兵，因为喜欢创造能影响人们日常生活的东西，所以来到盛大创新院，梦想能找到“正确的把软件作成业务”的方法。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;演讲：&lt;/strong&gt;源代码和其它软件工程产物的版本管理，是每个开发者每天都会碰到的问题，经过数十年的发展，CVS、SVN等上一代版本管理系统中的一些问题，催生着新的技术及其应用模式。在过去几年中以Git和Mercurial为代表的分布式版本管理工具有了较大的发展，已经基本具备了普及应用的基础。这里将介绍分布式版本管理欲解决的问题及其关键价值，并以Mercurial为例介绍具体使用的方法和流程。&lt;/p&gt;
&lt;a href="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/shijianzhuo-576x432.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/shijianzhuo-576x432.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;企业开发领域的语言特性&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;师建茁（Amos Shi），GRC软件工程师，SAP Labs China。中学时代在 286、486 系统上学会了 12个DOS内部命令，若干外部命令；使用QBasic开始程序生涯，半夜起来在图画本上设计流程图。后来跟着谭浩强的C语言课本和Borland的TC 2.0进入 Windows CMD 时代，后来有了 C++、Delphi、Python、.NET、Java，排名不分先后，意识到语言并非那么重要； 直到有一天，遇到了 ABAP，以及 Web Dynpro，意识到，对于优秀的软件，高级语言特性还是有所帮助的。2007年加入SAP Labs China，从事GRC软件产品的研发。日常工作涉及GRC 的多个部分。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;演讲：&lt;/strong&gt;作为一个程序员，在加入SAP之前用过一些各种各样流行的技术来写软件产品，到了Labs之后开始使用一种不广为所知的ABAP语言开发GRC软件产品。在学习和使用ABAP语言的过程中，经常会有类似“啊！对了，以前我就想过应该是这样的”、“是的！就是应该这样集成！”、“本来嘛，已经有足够的信息在那里了，她就是应该自动生成，一行代码都不应该写！”这样的感叹，对很多特性总是有相见恨晚的感觉。回想到以前的产品开发中遇到的种种问题，觉得如果这些特性已经有了的话，可以大大提高效率，降低错误。后来逐渐接触到其后面的NetWeaver平台，和Web Dynpro之后，更觉得这一整套东西为企业应用进行了精心设计。又回想起了“程序员如何在非洲捕捉大象？”这个经典笑话，哈哈哈哈。如果你要开发企业应用的话，确实有很多东西可以从这里借鉴，希望对你能有所裨益。&lt;/p&gt;
&lt;a href="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/qiaojie-400x622.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/qiaojie-400x622.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;使用.NET构建轻量级分布式框架&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;乔捷，瓦格纳比罗舞台系统公司，技术主管。热爱技术，对多种技术领域都有涉猎，目前主要从事剧院舞台控制系统和虚拟舞台系统的设计和研发工作，主要涉及到分布式系统、实时控制系统、虚拟现实系统、3D实时/离线渲染等技术领域。主要的编程语言为C++/C#/JavaScript，喜爱.NET技术，最近比较关注分布式计算和并行计算技术，并已在实际的项目应用中取得了初步的成功。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;演讲：&lt;/strong&gt;分布式系统的设计和构建是一项复杂多变而很具有挑战性的任务，设计的目标包括服务的伸缩性、可靠性、安全性、实时性、性能、容错等多个方面，同时还可能需要面对各种异构平台的集成和整合。目前市面上已有的分布式框架包括.NET提供的Remoting和WCF，都不能很好的满足上述所有的这些需求。因此，打造一套轻量级的、高度可定制的、符合自身项目需求的分布式框架变得很有现实意义。本演讲就来讨论如何应用.NET技术构建这样一套轻量级的分布式框架。&lt;/p&gt;

&lt;h1&gt;地点&lt;/h1&gt;

&lt;p&gt;本次交流会举办地为&lt;strong style="color: red"&gt;上海市浦东新区碧波路888号畅星大厦&lt;/strong&gt;（地铁二号线张江高科站下，步行10分钟可达）3楼会议厅，地图如下：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-map.png"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-map.png" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;鸟瞰图：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-hybrid.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-hybrid.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;畅星大厦外观：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-building.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-building.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;会场实景照片：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-room.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-room.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;会场容量可以容纳超过200人，希望到时候不会显得太过空旷。:)&lt;/p&gt;

&lt;h1&gt;报名信息&lt;/h1&gt;
&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/sign-up-now.jpg" /&gt; 

&lt;p&gt;本次交流会&lt;a href="http://diaochapai.com/survey518744"&gt;现已开始报名，请填写报名表&lt;/a&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/12/3rd-nbazaar-meeting-sign-up.html#comments</comments>
      <pubDate>Mon, 20 Dec 2010 16:45:46 GMT</pubDate>
      <lastBuildDate>Mon, 20 Dec 2010 16:45:46 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>抗拒“组合”的UITabBarController</title>
      <link>http://blog.zhaojie.me/2010/12/iphone-composition-resistant-uitabbarcontroller.html</link>
      <guid>http://blog.zhaojie.me/2010/12/iphone-composition-resistant-uitabbarcontroller.html</guid>
      <description>&lt;p&gt;最近在写一个iPhone应用程序，基于MonoTouch，所以在开发方面的问题，基本都是在界面元素的搭建上。这个程序界面相对比较复杂，于是我根据自己的想法来进行组合，结果发现UITabBarController不能放入其他的视图内，而只能直接放在Window上（或Window里的UINavigationController里），否则就会出现界面向下偏移的情况。现在虽然有workaround，但是对于UITabBarController抗拒组合的情况，只能深表叹息了。&lt;/p&gt;

&lt;p&gt;先来看一下这个界面的最终效果吧：&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjI5MzQxMjUy/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;这里再简单描述一下效果。打开程序以后看到的是一个标准的带有两个标签界面，目前选中第一个，界面上有一个按钮。点击按钮，整个界面（连同下方的标签栏）都会切换至新界面，并可以退回。在第二个标签内则有另一个按钮，点击则会切换至新一级，注意此时下方的标签栏会保持不变。不过这个程序最关键的一点在于，标签栏上放浮动着一个文字区域，它独立于标签栏中的各个视图。&lt;/p&gt;

&lt;p&gt;显然这里会需要一个UINavigationController作为根元素，其中放入一个UITabBarController和文字区域。在UITabBarController的第二个标签中，则放入另一个UINavigationController。由于“根导航”在切换时，UITabBarController和文字区域要同时显示和消失，于是我很自然地打算将UITabBarController和一个UILabel组合成一个自定义的UIViewController。这样的界面本该很容易，因为控件的组合是编写界面的常用手段。&lt;/p&gt;

&lt;p&gt;于是我新建了一个CustomController.xib文件，放入了一个UITabBarController和一个UILabel，并补充了一些操作逻辑（显示拖动时的坐标）。这些文字显示的逻辑本就属于CustomController，在实际应用中它也会和UITabBarController内部的视图产生交互，因此我把这些逻辑都隐藏在CustomController中。我认为这个组合方式十分合理。不过，在界面上显示MyTabBarController后则出现了奇怪的状况：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/iphone-tabbar-wrong-offset.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/iphone-tabbar-wrong-offset.png" height="300" /&gt;&lt;/a&gt; 

&lt;p&gt;当然，上面这个只是我直接把一个UITabBarController的View放入Window里的UIView控件之后出现的情况：向下整体偏移了。这个偏移量是最上方状态栏的高度，这让我很摸不着头脑。经过了一整下午的纠结试验，我的结论是：似乎UITabBarController抗拒组合。说地具体一些：UITabBarController会把视图中的UITabBar控件定位在屏幕下方，但是在计算位置的时候，它不会关注自己父容器，而是茫然地认为自己一定是在根窗体上，于是会把顶部状态栏的高度考虑进去。于是，如果它的父视图不是从整个界面（包括状态栏）的顶部算起，UITabBar的位置便会出现偏移了。&lt;/p&gt;

&lt;p&gt;有资深iOS开发者告诉我，我把UITabBarController组合到另一个UIViewController里不是UITabBarController的标准用法，如果我要组合，就应该使用UITabBar控件自己搭配，自己处理切换逻辑。不过我始终认为UI元素（不单指控件）应该意识到自己会参与组合。例如UITableViewController，UIImagePickerController，组合使用是它们的天然职责。在我看来，如其他（我用过的）UI库一般，只要为每个控件设好Dock（类似iOS里的Auto Resizing），组合起来应该是随意自然的。&lt;/p&gt;

&lt;p&gt;既然不能组合，那么扩展的方法似乎只有继承了——这样我便不能使用Interface Builder绘制界面，麻烦了不少。这里我创建一个MyTabBarController继承UITabBarController，并补充一些逻辑：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyTabBarController &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;UITabBarController
&lt;/span&gt;{
    &lt;span style="color: green"&gt;/* Constructors */

    &lt;/span&gt;&lt;span style="color: blue"&gt;public override void &lt;/span&gt;ViewDidLoad()
    {
        &lt;span style="color: blue"&gt;base&lt;/span&gt;.ViewDidLoad();

        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_label = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UILabel&lt;/span&gt;()
        {
            Text = &lt;span style="color: #a31515"&gt;&amp;quot;Hello MonoTouch&amp;quot;&lt;/span&gt;,
            TextAlignment = &lt;span style="color: #2b91af"&gt;UITextAlignment&lt;/span&gt;.Center,
            Frame = &lt;span style="color: blue"&gt;new &lt;/span&gt;System.Drawing.&lt;span style="color: #2b91af"&gt;RectangleF&lt;/span&gt;(0, 400, 320, 20),
            AutoresizingMask = &lt;span style="color: #2b91af"&gt;UIViewAutoresizing&lt;/span&gt;.FlexibleTopMargin
        };

        &lt;span style="color: blue"&gt;this&lt;/span&gt;.View.AddSubview(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_label);
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.View.BringSubviewToFront(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_label);
    }

    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UILabel &lt;/span&gt;m_label;

    &lt;span style="color: blue"&gt;public override void &lt;/span&gt;TouchesMoved(&lt;span style="color: #2b91af"&gt;NSSet &lt;/span&gt;touches, &lt;span style="color: #2b91af"&gt;UIEvent &lt;/span&gt;evt)
    {
        &lt;span style="color: blue"&gt;base&lt;/span&gt;.TouchesMoved(touches, evt);

        &lt;span style="color: blue"&gt;var &lt;/span&gt;touch = (&lt;span style="color: #2b91af"&gt;UITouch&lt;/span&gt;)touches.AnyObject;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;location = touch.LocationInView(&lt;span style="color: blue"&gt;this&lt;/span&gt;.View);
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_label.Text = location.ToString();
    }
}&lt;/pre&gt;

&lt;p&gt;这样，就差不多了，剩下的就是简单地嵌套关系，以及在切换到第二个Tab的时候隐藏“根导航”的导航栏。&lt;/p&gt;

&lt;p&gt;如果您有更好的做法，请务必告诉我。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/12/iphone-composition-resistant-uitabbarcontroller.html#comments</comments>
      <pubDate>Mon, 13 Dec 2010 05:38:38 GMT</pubDate>
      <lastBuildDate>Mon, 13 Dec 2010 05:38:38 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <title>服务器端执行JavaScript代码</title>
      <link>http://blog.zhaojie.me/2010/11/execute-javascript-at-server.html</link>
      <guid>http://blog.zhaojie.me/2010/11/execute-javascript-at-server.html</guid>
      <description>&lt;p&gt;话说，如今不在客户端使用JavaScript代码才是稀奇事儿。由于Web应用的体验越来越丰富，客户端用JavaScript实现的逻辑也越来越多，这造成的结果就是某些几乎一致的逻辑需要在客户端和服务器端各实现一遍。这违反了DRY原则，不容易维护。幸运的是，我们可以在服务器端执行JavaScript代码，谁让JavaScript傍上了这无比霸道的浏览器平台呢？&lt;/p&gt;

&lt;p&gt;例如，如今在客户端使用JavaScript进行验证已经是个标准，它可以有效避免用户在正常情况下提交错误的数据，增强用户体验。当然，服务器端的验证是必不可少的，因为这才是“安全性”的体现。有些解决方案，会在服务器端提供有限的验证种类，然后在客户端生成JavaScript代码，并辅以服务器端的验证框架。这种做法可以追溯到ASP.NET 1.x上的Validator控件，但这显然会有扩展性，灵活性上的限制，因此我更倾向于在服务器端执行JavaScript代码。&lt;/p&gt;

&lt;p&gt;例如，要检查用户名是否合法，我们可能会写这样的JavaScript代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;checkName = &lt;span style="color: blue"&gt;function &lt;/span&gt;(name) { &lt;span style="color: blue"&gt;return &lt;/span&gt;/^\w{3,10}$/.test(name); }&lt;/pre&gt;

&lt;p&gt;这在客户端验证自然没有任何问题，服务器端就要借助一些JavaScript执行引擎了。在.NET平台上有例如比较新的&lt;a href="https://github.com/fholm/IronJS"&gt;IronJS&lt;/a&gt;项目，这是个基于DLR的JavaScript执行引擎，十分重视性能，&lt;a href="http://ironjs.com/blog/"&gt;从作者博客&lt;/a&gt;上的评测结果来看，甚至领先于以速度见长的V8。可惜的是，IronJS还没有完整实现ECMAScript 3.0，还缺少一些重要功能，例如正则表达式。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://jint.codeplex.com/"&gt;Jint&lt;/a&gt;是一个.NET平台上较早的JavaScript执行引擎，因此与DLR关系不大，因此可能不太容易与IronPython，IronRuby等语言进行互操作。用它来执行一些简单的JavaScript脚本不成问题，例如上面的代码： &lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;jint = &lt;span style="color: blue"&gt;new&lt;/span&gt; Jint.&lt;span style="color: #2b91af"&gt;JintEngine&lt;/span&gt;();
jint.Run(&lt;span style="color: #a31515"&gt;@&amp;quot;var checkName = function(name) { return /^\w{3,10}$/.test(name); }&amp;quot;&lt;/span&gt;);

&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(jint.CallFunction(&lt;span style="color: #a31515"&gt;&amp;quot;checkName&amp;quot;&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;jeffz&amp;quot;&lt;/span&gt;)); &lt;span style="color: green"&gt;// True
&lt;/span&gt;&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(jint.CallFunction(&lt;span style="color: #a31515"&gt;&amp;quot;checkName&amp;quot;&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;hello world&amp;quot;&lt;/span&gt;)); &lt;span style="color: green"&gt;// False&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;只可惜，在实际使用中，Jint不支持多线程的环境，即我们无法在多个线程下同时调用jint的CallFunction方法，但是如果每次都重新Run一遍JavaScript代码，也会带来较多的性能开销。其实要解决这个问题也并不困难，构造一个对象池即可，.NET 4中提供了并行容器（如ConcurrentStack，ConcurrentQueue），实现一个简单的对象池可谓不费吹灰之力。&lt;/p&gt;

&lt;p&gt;这方面&lt;a href="http://jurassic.codeplex.com/"&gt;Jurassic&lt;/a&gt;的表现要好的多，这是一个构建于.NET 4.0的JavaScript执行引擎：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;engine = &lt;span style="color: blue"&gt;new &lt;/span&gt;Jurassic.&lt;span style="color: #2b91af"&gt;ScriptEngine&lt;/span&gt;();
engine.Evaluate(&lt;span style="color: #a31515"&gt;@&amp;quot;var checkName = function(name) { return /^\w{3,10}$/.test(name); }&amp;quot;&lt;/span&gt;);

&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(engine.CallGlobalFunction&amp;lt;&lt;span style="color: blue"&gt;bool&lt;/span&gt;&amp;gt;(&lt;span style="color: #a31515"&gt;&amp;quot;checkName&amp;quot;&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;jeffz&amp;quot;&lt;/span&gt;));
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(engine.CallGlobalFunction&amp;lt;&lt;span style="color: blue"&gt;bool&lt;/span&gt;&amp;gt;(&lt;span style="color: #a31515"&gt;&amp;quot;checkName&amp;quot;&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;hello world&amp;quot;&lt;/span&gt;));&lt;/pre&gt;

&lt;p&gt;此外，&lt;a href="http://jurassic.codeplex.com/wikipage?title=Benchmarks&amp;amp;referringTitle=Home"&gt;从Benchmark上来看&lt;/a&gt;，Jurassic性能也比Jint有所提高，但还是远远落后于V8，甚至IE 8里的JavaScript引擎。而且，它还提供了一个基于Silverlight控制台，您可以在浏览器里把玩一番。&lt;/p&gt;

&lt;p&gt;令人感到意外的是，Jint和Jurassic作为JavaScript执行引擎都有一些严重的问题，那便是不能正确运行&lt;a href="http://attacklab.net/showdown/"&gt;showdown.js&lt;/a&gt;（JavaScript实现的Markdown转化器）——虽然我并没有发现showdown.js中有过于复杂的内容，基本就是些字符串操作吧。原本我还想把它们用在mono中，既然如此也就不做进一步尝试了。不过，经过简单的实验，Jurassic似乎使用了mono 2.8中尚不支持的接口，但也有可能只是Jurassic控制台中的问题。&lt;/p&gt;

&lt;p&gt;有趣的是，.NET平台下最靠谱的JavaScript执行引擎居然是&lt;a href="http://www.mozilla.org/rhino/"&gt;Rhino JavaScript&lt;/a&gt;，最近一次发布是在2009年3月，不过实现的十分完整。要说缺点，可能就是使用起来比较麻烦，还有，这是个Java项目。&lt;/p&gt;

&lt;p&gt;嗯，我没有开玩笑，我们完全可以在.NET平台下使用Rhino JavaScript：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;cx = &lt;span style="color: #2b91af"&gt;Context&lt;/span&gt;.enter();
&lt;span style="color: blue"&gt;try
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;scope = cx.initStandardObjects();
    cx.evaluateString(scope, &lt;span style="color: #a31515"&gt;@&amp;quot;var checkName = function(name) { return /^\w{3,10}$/.test(name); }&amp;quot;&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;checkName.js&amp;quot;&lt;/span&gt;, 1, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;func = (&lt;span style="color: #2b91af"&gt;Function&lt;/span&gt;)scope.get(&lt;span style="color: #a31515"&gt;&amp;quot;checkName&amp;quot;&lt;/span&gt;, scope);

    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #2b91af"&gt;Context&lt;/span&gt;.toString(func.call(cx, scope, scope, &lt;span style="color: #a31515"&gt;&amp;quot;jeffz&amp;quot;&lt;/span&gt;)));
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #2b91af"&gt;Context&lt;/span&gt;.toString(func.call(cx, scope, scope, &lt;span style="color: #a31515"&gt;&amp;quot;hello world&amp;quot;&lt;/span&gt;));
}
&lt;span style="color: blue"&gt;finally &lt;/span&gt;
{
    &lt;span style="color: #2b91af"&gt;Context&lt;/span&gt;.exit();
}&lt;/pre&gt;

&lt;p&gt;因为我们有&lt;a href="http://www.ikvm.net/"&gt;IKVM.NET&lt;/a&gt;。mono等.NET开源社区上有大量宝藏，就看您能利用多少了。我用ikvmc把js.jar转化为RhinoJs.dll之后就可以直接使用，效果很好，对调试也有很好的支持（如果JavaScript执行时出现了错误，则VS会直接带您至出错的那行）。性能也是比较令人满意的，在我的Mac OSX上安装的Ubuntu Server 10.10虚拟机，单线程转化并过滤博客上最近的3800条评论，大约耗时20秒。试验时Host上还开着一个Windows 7虚拟机，还有大量浏览器等应用程序，并不十分空闲。&lt;/p&gt;

&lt;p&gt;您可能知道，我的博客目前是基于mono 2.6的，其中比较有特色的地方便是评论功能了，我使用Markdown标记，并提供了实时的预览功能，这自然需要在客户端解释Markdown标记，并进行过滤。目前，我还在服务器使用了C#实现的Markdown转化器及过滤逻辑，但在某些特殊情况下结果会有所不同，且需要维护两套代码。不久以后，我会将把博客升级为ASP.NET 4.0及mono 2.8（C# 4.0的dynamic特性在某些情况下的确比较方便），并且在服务器端使用IKVM.NET + Rhino JavaScript执行相同转化代码。从效果上来看还是十分令人满意的。&lt;/p&gt;

&lt;p&gt;值得一提的是，其实在.NET平台上还有一个基于DLR的JavaScript执行引擎，是为&lt;a href="http://www.remobjects.com/script.aspx"&gt;RemObjects Script for .NET&lt;/a&gt;，据称也支持mono。只可惜它并不是开源产品（不过公开了源代码），且&lt;a href="http://code.remobjects.com/p/roscript/page/License/"&gt;授权协议&lt;/a&gt;要求我们最多在5台机器上安装代码，且只供我们自己使用，于是我就没有对它有关注太多了。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/11/execute-javascript-at-server.html#comments</comments>
      <pubDate>Tue, 09 Nov 2010 10:11:28 GMT</pubDate>
      <lastBuildDate>Tue, 09 Nov 2010 10:11:28 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>对HTML做白名单过滤</title>
      <link>http://blog.zhaojie.me/2010/10/html-xss-filter-with-whilte-list.html</link>
      <guid>http://blog.zhaojie.me/2010/10/html-xss-filter-with-whilte-list.html</guid>
      <description>&lt;p&gt;让用户输入HTML的内容是很常见的需求，但是这有一定危险性，可能会带来XSS等问题，因此一般大家都要对HTML进行一定过滤。这个过滤并不容易，如&amp;lt;script /&amp;gt;元素自不必说，其他还有如onload或onclick事件，甚至一个普通的&amp;lt;a /&amp;gt;元素，它的href中也可以执行JavaScript代码。以前我一直有一段用于过滤的C#实现，一直没有出篓子，似乎也挺靠谱，但最近不知怎么的却发现了问题，可能是C &amp;amp; P出错，也可能原本就有问题，我没有太去关心。但问题总需要解决，于是我想，不如换个角度，基于白名单进行过滤吧。&lt;/p&gt;

&lt;p&gt;以前HTML过滤的方式往往是基于黑名单的，例如去除&amp;lt;script /&amp;gt;元素及onload事件等等，万一有所遗漏，便会造成安全上的隐患。而白名单策略则正好相反，我们只列出合法的HTML元素及其属性，如果有所遗漏，则最多导致HTML上无法使用某些元素，但不会有安全问题。具体采用哪种方式，就要看您自己的决策了。在我看来，我并不在意进行白名单过滤，因为从我写博客及做项目的经验上来看，用户根本不需要如此广泛的HTML元素以及完整的属性支持，而且这反而会造成样式上的混乱。甚至说，我们只是需要简单的几种元素，如p、a、h1~h6、strong、ul、ol或是li等等，就足够了。样式问题？&lt;a href="http://blog.zhaojie.me/2010/08/my-view-of-good-blogging-theme.html"&gt;由上下文环境来统一控制，这才能得到良好的浏览体验&lt;/a&gt;。&lt;/p&gt;

&lt;h1&gt;配置&lt;/h1&gt;

&lt;p&gt;首先，我们要准备一份白名单的配置，其中表明哪些HTML元素，以及其中哪些属性，还有属性的哪些形式是合法的。按照传统，配置往往会采用XML格式。不过我觉得XML虽然还算便于表达，但是冗余信息还是太多，且对于“&amp;amp;”等字符还需要转义，因此有时候并不是配置的理想方式。目前常用的配置格式还有JSON，它不像XML那样冗余，也能较好的表现结构化数据，不过由于我们需要表达正则表达式，使用JSON的话在字符串里的转义就麻烦了。至于其他格式，如ini文件，可能并不容易表示带层级的关系 ，或者需要自己写解析方式，于是我最终还是决定使用XML作为配置形式，如下： &lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;?&lt;/span&gt;&lt;span style="color: #a31515"&gt;xml &lt;/span&gt;&lt;span style="color: red"&gt;version&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;1.0&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;encoding&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;utf-8&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;?&amp;gt;
&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;config&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
  &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;tag &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;*&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;attr &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;style&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;^((font|color)\s*:\s*[^;]+;\s*)*$&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;attr&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
  &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;tag&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
  &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;tag &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;a&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;attr &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;href&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;^[a-zA-Z]+://.+$&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;attr&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;attr &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;title&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;.+&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;attr&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
  &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;tag&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
  &lt;/span&gt;...
&lt;span style="color: blue"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;config&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;根元素下的每个tag元素表示一个合法的HTML元素，其中星号表示对所有元素的统一设置，例如上面的配置便开放了所有元素的style属性中的font和color两个设置，以及a元素中的href和title属性，其中href还必须是“{scheme}://xxx”的形式，这样就避免了“javascript:alert(1)”这样的XSS问题。&lt;/p&gt;

&lt;p&gt;有了XML格式，则代码自然也是一蹴而就的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TagConfig &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;&amp;gt;
{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;TagConfig()
        : &lt;span style="color: blue"&gt;base&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;StringComparer&lt;/span&gt;.OrdinalIgnoreCase)
    { }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;TagConfig(&lt;span style="color: #2b91af"&gt;XElement &lt;/span&gt;config)
    {
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;ele &lt;span style="color: blue"&gt;in &lt;/span&gt;config.Elements(&lt;span style="color: #a31515"&gt;&amp;quot;attr&amp;quot;&lt;/span&gt;))
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.Add(ele.Attribute(&lt;span style="color: #a31515"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;).Value, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;(ele.Value));
        }
    }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;FilterConfig &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;TagConfig&lt;/span&gt;&amp;gt;
{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;FilterConfig()
        : &lt;span style="color: blue"&gt;base&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;StringComparer&lt;/span&gt;.OrdinalIgnoreCase)
    { }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;FilterConfig(&lt;span style="color: #2b91af"&gt;XElement &lt;/span&gt;config)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;wildcardElement = config
            .Elements(&lt;span style="color: #a31515"&gt;&amp;quot;tag&amp;quot;&lt;/span&gt;)
            .SingleOrDefault(e =&amp;gt; e.Attribute(&lt;span style="color: #a31515"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;).Value == &lt;span style="color: #a31515"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;);

        &lt;span style="color: blue"&gt;var &lt;/span&gt;wildcardConfig = wildcardElement == &lt;span style="color: blue"&gt;null &lt;/span&gt;? &lt;span style="color: blue"&gt;null &lt;/span&gt;:
            &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TagConfig&lt;/span&gt;(wildcardElement);

        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;ele &lt;span style="color: blue"&gt;in &lt;/span&gt;config
            .Elements(&lt;span style="color: #a31515"&gt;&amp;quot;tag&amp;quot;&lt;/span&gt;)
            .Where(e =&amp;gt; e.Attribute(&lt;span style="color: #a31515"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;).Value != &lt;span style="color: #a31515"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;))
        {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;name = ele.Attribute(&lt;span style="color: #a31515"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;).Value;
            &lt;span style="color: blue"&gt;var &lt;/span&gt;tagConfig = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TagConfig&lt;/span&gt;(ele);

            &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;pair &lt;span style="color: blue"&gt;in &lt;/span&gt;wildcardConfig)
            {
                &lt;span style="color: blue"&gt;if &lt;/span&gt;(!tagConfig.ContainsKey(pair.Key))
                {
                    tagConfig.Add(pair.Key, pair.Value);
                }
            }

            &lt;span style="color: blue"&gt;this&lt;/span&gt;.Add(name, tagConfig);
        }
    }
}&lt;/pre&gt;

&lt;p&gt;在操作时，我将星号中的配置加到每个元素中，这是为了简化操作。如果您愿意，自然也可以独立为星号中的配置再过滤一遍。&lt;/p&gt;

&lt;h1&gt;过滤策略&lt;/h1&gt;

&lt;p&gt;这里我不打算对HTML进行合法性验证，例如是否匹配等等，我的目的只是保留合法的HTML元素。因此我的策略很简单，使用几个简单的正则表达式就行了。&lt;/p&gt;

&lt;p&gt;首先，我使用下面的正则表达式找出所有的HTML元素：&lt;/p&gt;

&lt;pre class="code"&gt;正则：&amp;lt;[^&amp;gt;]*&amp;gt;
匹配：&lt;span style="background-color: yellow"&gt;&amp;lt;a href=&amp;quot;http://blog.zhaojie.me/&amp;quot;&amp;gt;123321&amp;lt;/a&amp;gt;&lt;/span&gt;bbc&lt;span style="background-color: yellow"&gt;&amp;lt;hello /&amp;gt;&lt;/span&gt;dde&lt;/pre&gt;

&lt;p&gt;对于每个HTML元素，我则依次捕获出begin、tag、attr及end四个部分：&lt;/p&gt;

&lt;pre class="code"&gt;正则：^(?&amp;lt;begin&amp;gt;&amp;lt;/?)(?&amp;lt;tag&amp;gt;[a-zA-z]+)\s*(?&amp;lt;attr&amp;gt;[^&amp;gt;]*?)(?&amp;lt;end&amp;gt;/?&amp;gt;)$
匹配：&lt;span style="background-color: yellow"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background-color: #80c0ff"&gt;a&lt;/span&gt; &lt;span style="background-color: yellow"&gt;href=&amp;quot;http://blog.zhaojie.me&amp;quot;&lt;/span&gt;&lt;span style="background-color: #80c0ff"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;最后，从上面得到的attr中，再次捕获到属性的键值对：&lt;/p&gt;

&lt;pre class="code"&gt;正则：(?&amp;lt;name&amp;gt;[a-zA-Z]+)\s*=\s*&amp;quot;(?&amp;lt;value&amp;gt;[^&amp;quot;]*)&amp;quot;
匹配：&lt;span style="background-color: yellow"&gt;href&lt;/span&gt;=&amp;quot;&lt;span style="background-color: #80c0ff"&gt;http://blog.zhaojie.me/&lt;/span&gt;&amp;quot; &lt;span style="background-color: yellow"&gt;title&lt;/span&gt;=&amp;quot;&lt;span style="background-color: #80c0ff"&gt;老赵点滴&lt;/span&gt;&amp;quot;&lt;/pre&gt;

&lt;p&gt;最后，再对捕获到的属性及其值进行过滤即可。简单起见，我在这里只考虑由双引号包含的属性值，因为客户端的富文本编辑器可以保证正规方式提交的HTML格式，至于一些Hacker的做法，我只要保证它不会破坏系统就足够了。总体说来，这样的过滤策略并不严谨，但简单粗暴，还算有效好用。&lt;/p&gt;

&lt;h1&gt;过滤实现&lt;/h1&gt;

&lt;p&gt;之前描述的策略，使用C#实现只需短短数十行代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HtmlFilter
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RegexOptions &lt;/span&gt;REGEX_OPTIONS =
        &lt;span style="color: #2b91af"&gt;RegexOptions&lt;/span&gt;.Compiled | 
        &lt;span style="color: #2b91af"&gt;RegexOptions&lt;/span&gt;.IgnoreCase | 
        &lt;span style="color: #2b91af"&gt;RegexOptions&lt;/span&gt;.Singleline;

    &lt;span style="color: green"&gt;// 依次填入上文中三个正则表达式
    &lt;/span&gt;&lt;span style="color: blue"&gt;private static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex &lt;/span&gt;TAG_REGEX = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;(..., REGEX_OPTIONS);
    &lt;span style="color: blue"&gt;private static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex &lt;/span&gt;VALID_TAG_REGEX = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;(..., REGEX_OPTIONS);
    &lt;span style="color: blue"&gt;private static readonly &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex &lt;/span&gt;ATTRIBUTE_REGEX = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;(..., REGEX_OPTIONS);

    &lt;span style="color: blue"&gt;public &lt;/span&gt;HtmlFilter() : &lt;span style="color: blue"&gt;this&lt;/span&gt;(&lt;span style="color: blue"&gt;null&lt;/span&gt;) { }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;HtmlFilter(&lt;span style="color: #2b91af"&gt;FilterConfig &lt;/span&gt;config)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Config = config ?? &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;FilterConfig&lt;/span&gt;();
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;FilterConfig &lt;/span&gt;Config { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Filter(&lt;span style="color: blue"&gt;string &lt;/span&gt;html)
    {
        &lt;span style="color: green"&gt;// 对每个HTML标记进行替换?
        &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;TAG_REGEX.Replace(html, GetTag);
    }

    &lt;span style="color: blue"&gt;private string &lt;/span&gt;GetTag(&lt;span style="color: #2b91af"&gt;Match &lt;/span&gt;match)
    {
        &lt;span style="color: green"&gt;// 如果不是合法的HTML标记形式，则替换为空字符串
        &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;validTagMatch = VALID_TAG_REGEX.Match(match.Value);
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(!validTagMatch.Success) &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;&amp;quot;&lt;/span&gt;;

        &lt;span style="color: blue"&gt;var &lt;/span&gt;tag = validTagMatch.Groups[&lt;span style="color: #a31515"&gt;&amp;quot;tag&amp;quot;&lt;/span&gt;].Value;

        &lt;span style="color: green"&gt;// 如果这个标记不在白名单中，则替换为空字符串
        &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TagConfig &lt;/span&gt;tagConfig;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(!&lt;span style="color: blue"&gt;this&lt;/span&gt;.Config.TryGetValue(tag, &lt;span style="color: blue"&gt;out &lt;/span&gt;tagConfig)) &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;&amp;quot;&lt;/span&gt;;

        &lt;span style="color: blue"&gt;var &lt;/span&gt;begin = validTagMatch.Groups[&lt;span style="color: #a31515"&gt;&amp;quot;begin&amp;quot;&lt;/span&gt;].Value;
        &lt;span style="color: green"&gt;// 如果是闭合标记，则直接构造并返回
        &lt;/span&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(begin == &lt;span style="color: #a31515"&gt;&amp;quot;&amp;lt;/&amp;quot;&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;&amp;quot;&amp;lt;/{0}&amp;gt;&amp;quot;&lt;/span&gt;, tag.ToLower());
        }

        &lt;span style="color: green"&gt;// 过滤出合法的属性键值对
        &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;attrText = validTagMatch.Groups[&lt;span style="color: #a31515"&gt;&amp;quot;attr&amp;quot;&lt;/span&gt;].Value;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;attrMatches = ATTRIBUTE_REGEX.Matches(attrText).Cast&amp;lt;&lt;span style="color: #2b91af"&gt;Match&lt;/span&gt;&amp;gt;();
        &lt;span style="color: blue"&gt;var &lt;/span&gt;validAttributes = attrMatches
            .Select(m =&amp;gt; GetAttribute(m, tagConfig))
            .Where(s =&amp;gt; !&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.IsNullOrEmpty(s)).ToArray();

        &lt;span style="color: blue"&gt;var &lt;/span&gt;end = validTagMatch.Groups[&lt;span style="color: #a31515"&gt;&amp;quot;end&amp;quot;&lt;/span&gt;].Value;
        &lt;span style="color: green"&gt;// 如果没有合法的属性，则直接构造返回
        &lt;/span&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(validAttributes.Length == 0)
        {
            &lt;span style="color: blue"&gt;return &lt;/span&gt;begin + tag + end;
        }
        &lt;span style="color: blue"&gt;else &lt;/span&gt;&lt;span style="color: green"&gt;// 否则返回带属性的HTML标记
        &lt;/span&gt;{ 
            &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Format(
                &lt;span style="color: #a31515"&gt;&amp;quot;{0}{1} {2}{3}&amp;quot;&lt;/span&gt;,
                begin,
                tag,
                &lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Join(&lt;span style="color: #a31515"&gt;&amp;quot; &amp;quot;&lt;/span&gt;, validAttributes),
                end);
        }
    }

    &lt;span style="color: blue"&gt;private static string &lt;/span&gt;GetAttribute(&lt;span style="color: #2b91af"&gt;Match &lt;/span&gt;attrMatch, &lt;span style="color: #2b91af"&gt;TagConfig &lt;/span&gt;tagConfig)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;name = attrMatch.Groups[&lt;span style="color: #a31515"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;].Value;

        &lt;span style="color: #2b91af"&gt;Regex &lt;/span&gt;regex;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(!tagConfig.TryGetValue(name, &lt;span style="color: blue"&gt;out &lt;/span&gt;regex)) &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;&amp;quot;&lt;/span&gt;;
        
        &lt;span style="color: blue"&gt;var &lt;/span&gt;value = attrMatch.Groups[&lt;span style="color: #a31515"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;].Value;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(regex.IsMatch(value))
        {
            &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;&amp;quot;{0}=\&amp;quot;{1}\&amp;quot;&amp;quot;&lt;/span&gt;, name, value);
        }
        &lt;span style="color: blue"&gt;else
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;&amp;quot;&lt;/span&gt;;
        }
    }
}&lt;/pre&gt;

&lt;p&gt;您也可以编写一段与之对应的JavaScript代码，在客户端实现实时过滤（预览）。事实上，我这个博客的评论系统也是用类似的方式实现的。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/10/html-xss-filter-with-whilte-list.html#comments</comments>
      <pubDate>Tue, 19 Oct 2010 07:43:30 GMT</pubDate>
      <lastBuildDate>Tue, 19 Oct 2010 07:43:30 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>当类型转换表达式遇上自定义转换操作</title>
      <link>http://blog.zhaojie.me/2010/10/when-conversion-expression-meet-customized-cast-operator.html</link>
      <guid>http://blog.zhaojie.me/2010/10/when-conversion-expression-meet-customized-cast-operator.html</guid>
      <description>&lt;p&gt;之前我提到说System.Json是一个十分不好用的类库，其中一点就是在于，我没法将一个JsonValue转化为范型类型——它只为Int32，String等几种特定类型定义了隐式转换，又无法得到以object类型所引用的值。不过这也难不到拥有“在运行时创建自定义表达式树并编译成动态代码”的.NET程序员。例如我们可以写这样一个辅助类进行JsonValue至任意类型的转化操作，.NET类库会负责为我们选择合适的转换方式：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;JsonConverter&lt;/span&gt;&amp;lt;T&amp;gt;
{
    &lt;span style="color: blue"&gt;static &lt;/span&gt;JsonConverter()
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;jsonValueExpr = &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;.Parameter(&lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;JsonValue&lt;/span&gt;), &lt;span style="color: #a31515"&gt;&amp;quot;jsonValue&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;var &lt;/span&gt;convertExpr = &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;.Convert(jsonValueExpr, &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(T));
        &lt;span style="color: blue"&gt;var &lt;/span&gt;lambdaExpr = &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;.Lambda&amp;lt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;JsonValue&lt;/span&gt;, T&amp;gt;&amp;gt;(convertExpr, jsonValueExpr);
        s_convert = lambdaExpr.Compile();
    }

    &lt;span style="color: blue"&gt;private static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;JsonValue&lt;/span&gt;, T&amp;gt; s_convert;

    &lt;span style="color: blue"&gt;public static &lt;/span&gt;T Convert(&lt;span style="color: #2b91af"&gt;JsonValue &lt;/span&gt;jsonValue)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;s_convert(jsonValue);
    }
}&lt;/pre&gt;

&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2009/11/cache-and-object-creation-benchmark.html"&gt;泛型参数字典&lt;/a&gt;和动态代码生成都是性能的有效保证，而构造一颗我们需要的表达式树也十分容易。这个方法&lt;a href="http://blog.zhaojie.me/2009/11/general-add-operation.html"&gt;我以前也谈起过&lt;/a&gt;，我认为每个称职的.NET程序员都应该熟练掌握这种“通用操作”的实现方式。不过我最近在构造这种类型转换表达式的时候遇到了一些问题。例如，如果我们需要构造一个表达式，将一个JsonPrimitive（而不是先前的JsonValue）转化为int，这便会抛出一个异常，提示我们“没有合适的强制转化方式”。&lt;/p&gt;

&lt;p&gt;我猜，可能是因为那些隐式转化操作定义在JsonValue上吧。那么这算不算一个Bug？我认为是，因为与它等价的代码是没有任何问题的，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;value = (&lt;span style="color: blue"&gt;int&lt;/span&gt;)(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;JsonPrimitive&lt;/span&gt;(10));&lt;/pre&gt;

&lt;p&gt;C#编译器会为我们找到JsonValue上定义的op_Implict方法并生成调用代码，而.NET类库却不会这么做，双方配合不够默契。不过其实这很容易绕开，只是我们要将这样的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;convertExpr = &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;.Convert(instanceExpr, targetType);&lt;/pre&gt;

&lt;p&gt;修改为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Expression &lt;/span&gt;GetConvertExpression(&lt;span style="color: #2b91af"&gt;Expression &lt;/span&gt;instanceExpr, &lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;targetType)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;mediateType = instanceExpr.Type;

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(mediateType == &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;object&lt;/span&gt;))
    {
        &lt;span style="color: green"&gt;// (TargetType)instance
        &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;.Convert(instanceExpr, targetType);
    }

    &lt;span style="color: blue"&gt;while &lt;/span&gt;(mediateType != &lt;span style="color: blue"&gt;typeof&lt;/span&gt;(&lt;span style="color: blue"&gt;object&lt;/span&gt;))
    {
        &lt;span style="color: blue"&gt;try
        &lt;/span&gt;{
            &lt;span style="color: green"&gt;// (MediateType)instace
            &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;mediateExpr = &lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;.Convert(instanceExpr, mediateType);
            &lt;span style="color: green"&gt;// (TargetType)(MediateType)instance
            &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Expression&lt;/span&gt;.Convert(mediateExpr, targetType);
        }
        &lt;span style="color: blue"&gt;catch
        &lt;/span&gt;{
            mediateType = mediateType.BaseType;
        }
    }

    &lt;span style="color: blue"&gt;throw new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Exception&lt;/span&gt;(...);
}&lt;/pre&gt;

&lt;p&gt;我们选择的策略很简单，提供一个“过渡类型”，先将instance转换为过渡类型，再转换成目标类型。如果发生转换错误，则会继续尝试其父类，直至object类型为止，则抛出异常表示转换失败。目前看来这个做法并没有什么问题。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/10/when-conversion-expression-meet-customized-cast-operator.html#comments</comments>
      <pubDate>Fri, 15 Oct 2010 05:50:22 GMT</pubDate>
      <lastBuildDate>Fri, 15 Oct 2010 05:50:22 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>在传统.NET程序中使用Silverlight SDK里的JSON类库</title>
      <link>http://blog.zhaojie.me/2010/10/use-silverlight-system-json-in-normal-application.html</link>
      <guid>http://blog.zhaojie.me/2010/10/use-silverlight-system-json-in-normal-application.html</guid>
      <description>&lt;p&gt;话说在Silverlight SDK中提供了一套JSON类库，叫做&lt;a href="http://msdn.microsoft.com/en-us/library/system.json(VS.95).aspx"&gt;System.Json&lt;/a&gt;。这个类库功能很简单，就是使用.NET来表示JSON格式的“结构”。换句话说，就是我之前&lt;a href="http://blog.zhaojie.me/2010/10/jsonme-a-type-contract-separated-lightweight-json-mapping-library.html"&gt;在JsonMe中所提到的&lt;/a&gt;JsonObject，JsonArray之类的东西，但完全不包括JSON结构和实际类型之间的转化。虽然这个类库很不好用（谁用谁知道），但至少是一个可以通用于Silverlight和MonoTouch的类库，因此我决定将JsonMe基于它进行构建。为此，我&lt;a href="http://github.com/JeffreyZhao/System.Json"&gt;对mono中的开源实现进行了移植&lt;/a&gt;，使它仅仅依赖于功能最基本的.NET Framework 3.5 Client Profile，并修改了其中的一些明显的Bug。&lt;/p&gt;

&lt;p&gt;Mono的项目源码可以&lt;a href="http://github.com/mono/mono"&gt;从github里获得&lt;/a&gt;，或者您直接&lt;a href="http://ftp.novell.com/pub/mono/sources-stable/"&gt;下载mono 2.8的源码tar包&lt;/a&gt;，不过现在我们&lt;a href="http://github.com/mono/mono/tree/master/mcs/class/System.Json/System.Json/"&gt;只需要其中的System.Json&lt;/a&gt;。System.Json，只有五个文件，不过它依赖了mono的&lt;a href="http://github.com/mono/mono/tree/master/mcs/class/System.ServiceModel.Web/System.Runtime.Serialization.Json/"&gt;System.ServiceModel.Web.dll里定义&lt;/a&gt;的JavaScriptReader和JavaScriptObjectDeserializer类，因此我也将这两个内部类一并移植到System.Json项目中。因此，最终项目的组织结构如下：&lt;/p&gt;
&lt;img src="http://img.zhaojie.me/blog/system-json-solution.png" /&gt; 

&lt;p&gt;从上图中可以看出，如今的System.Json项目只依赖了极少的系统类库，事实上它可以在.NET Framework 3.5 Client Profile下运行，这样无论是Silverlight还是普通的客户端都可以使用它的功能，这是依赖System.Web.Extensions.dll的时候所无法做到的。同样，JsonMe也已经进行了相应的改写，现在它已经能够用于Silverlight，MonoTouch，甚至未来的MonoDroid中。不过基于System.Json之后JsonMe反而因此变得复杂了，这是因为System.Json实在不是一个好用的类库。&lt;/p&gt;

&lt;p&gt;mono的System.Json还存在一个bug：JsonObject的Add方法不允许value为null，这是个十足的问题，使得类库无法支持null值。我使用.NET Reflector检查了Silverlight中的实现，它并没有这样的约束，这才符合我的预期。将这个约束去除以后，序列化成JSON字符串的逻辑便会引发NullReferenceException，因此我也修改了对应的序列化逻辑。此外，如果您用DateTime创建一个JsonPrimitive类型，序列化时也会出现问题。我还不太清楚Silverlight SDK中是如何处理这个情况的，再我看来在JSON中放置DateTime也是一个很奇怪的行为，还是先转化为字符串吧。杯具的是，MonoTouch SDK中也有这些bug，不幸中的万幸，只要在MonoTouch重新编译&lt;a href="http://github.com/JeffreyZhao/System.Json"&gt;我移植的代码&lt;/a&gt;就行了。&lt;/p&gt;

&lt;p&gt;如果您对此感兴趣，也不妨&lt;a href="http://github.com/JeffreyZhao/System.Json"&gt;获取这些代码&lt;/a&gt;捣鼓捣鼓。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/10/use-silverlight-system-json-in-normal-application.html#comments</comments>
      <pubDate>Tue, 12 Oct 2010 09:34:41 GMT</pubDate>
      <lastBuildDate>Tue, 12 Oct 2010 09:34:41 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>JsonMe - 合约与类型分离的轻量级JSON映射类库</title>
      <link>http://blog.zhaojie.me/2010/10/jsonme-a-type-contract-separated-lightweight-json-mapping-library.html</link>
      <guid>http://blog.zhaojie.me/2010/10/jsonme-a-type-contract-separated-lightweight-json-mapping-library.html</guid>
      <description>&lt;p&gt;JSON全称为JavaScript Object Notation，原本作为JavaScript语言中用于表示对象结构的文本形式。不过目前JSON成功地脱离了JavaScript语言，它已经成为一种运用十分广泛的数据交换格式。从表面看来，目前用于某个对象与JSON格式之间相互转化的解决方案已经有了许多种，例如在.NET平台上，我们可以使用ASP.NET AJAX中引入的JavaScriptSerializer，WCF中引入的DataContractJsonSerializer，亦或是&lt;a href="http://json.codeplex.com/"&gt;Json.NET&lt;/a&gt;。但是，最近我忽然发现这些类库都无法满足我的要求，因此，我今天花了一点时间，写了一个非常简单的对象与JSON格式相互转化的类库，是为&lt;a href="http://github.com/JeffreyZhao/JsonMe"&gt;JsonMe&lt;/a&gt;。&lt;/p&gt;

&lt;h1&gt;现有解决方案的不足&lt;/h1&gt;

&lt;p&gt;您可能会感到疑惑，难道现有的解决方案都不够好吗？又搞一个新的实现出来，这不是重复造轮子嘛。但事实上，它们在我眼里，都有一些难以逾越的障碍。就拿JavaScriptSerializer来说，它使用起来十分简单，配合C#的匿名对象特性，输出一个JSON格式可谓无比直接：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;value = &lt;span style="color: blue"&gt;new
&lt;/span&gt;{
    hello = &lt;span style="color: #a31515"&gt;&amp;quot;world&amp;quot;&lt;/span&gt;,
    array = &lt;span style="color: blue"&gt;new object&lt;/span&gt;[] { 1, 2, 3, &lt;span style="color: #a31515"&gt;&amp;quot;jeffz&amp;quot; &lt;/span&gt;}
};

&lt;span style="color: blue"&gt;var &lt;/span&gt;json = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;JavaScriptSerializer&lt;/span&gt;().Serialize(value);&lt;/pre&gt;

&lt;p&gt;JavaScriptSerializer也支持JSON格式与某种类型的对象相互转化，甚至可以加上ScriptIgnoreAttribute标记来忽略某个属性，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Title { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    [&lt;span style="color: #2b91af"&gt;ScriptIgnore&lt;/span&gt;]
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Content { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime &lt;/span&gt;CreateTime { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;

&lt;p&gt;如果我想要改变某个属性在JSON中的字段名呢？JavaScriptSerializer应该也做得到，但我现在我一时想不起来了，也懒得去查。我知道DataContractJsonSerializer一定支持，事实上它是.NET中用于代替JavaScriptSerializer的JSON序列化解决方案，为此如今的JavaScriptSerializer已经标记了ObsolateAttribute，编译时您应该可以看到warning。&lt;/p&gt;

&lt;p&gt;那么我再提一个要求：&lt;font color="#ff0000"&gt;在序列化某个字段的时候，对它的值进行一个简单的转化&lt;/font&gt;。例如，我希望将DateTime对象在JSON中表现为字符串，这对于JavaScriptSerializer和DataContractJsonSerializer来说似乎就做不到了，至少会十分麻烦。但是这对于Json.NET来说不是个问题，相比于前两者来说，Json.NET可谓是JSON解决方案中的Word和Excel，经过了许多版本的积极开发，如今的Json.NET已经实现了大量的功能，几乎在每一处都提供了扩展点。我对Json.NET的了解不多，不过在简单浏览代码以后，我发现它的复杂度已经能和如今的ASP.NET MVC比肩了，是不是显得有些可怕？我也这么觉得。&lt;/p&gt;

&lt;p&gt;但事实上，让我重新写一个JsonMe的关键，是因为我希望&lt;font color="#ff0000"&gt;同一种类型能够与不同的JSON格式相互转化&lt;/font&gt;。试想，我有一个User类型，但是我却有两个不同格式的JSON数据源。或者说，相同的User对象，需要根据环境得到两种不同格式的JSON输出。这样JavaScriptSerializer或DataContractJsonSerializer就无法满足我的要求了，因为它们使用自定义特性来控制JSON的格式，但一个类型只能应用一种JSON策略，这又让我如何是好？&lt;/p&gt;

&lt;p&gt;当然，这点对于Json.NET应该不是问题，但是它实在太复杂了，如果我能把它研究透彻并加以扩展，还不如自己重新去写一个简单的类库呢。于是，一个秋高气爽的周日下午，JsonMe就此诞生了。&lt;/p&gt;

&lt;h1&gt;JsonMe使用方式&lt;/h1&gt;

&lt;p&gt;为了实现以上的要求，在JsonMe中我将JSON转化的“合约”与类型本身分离。我很喜欢这样的策略，正如&lt;a href="http://github.com/JeffreyZhao/EasyMongo"&gt;EasyMongo&lt;/a&gt;那样，除了一个类型可以对应多种映射策略之外，我还能将一种类型与它的映射策略解耦。例如，现在有一个User对象：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;User
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;UserName { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;Age { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public bool &lt;/span&gt;IsAdult { &lt;span style="color: blue"&gt;get &lt;/span&gt;{ &lt;span style="color: blue"&gt;return this&lt;/span&gt;.Age &amp;gt;= 18; } }
}&lt;/pre&gt;

&lt;p&gt;在进行序列化之前，需要先定义这样的“合约”（即映射策略）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var&lt;/span&gt; userContract = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;JsonContract&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;User&lt;/span&gt;&amp;gt;();
userContract.SimpleProperty(u =&amp;gt; u.UserName).Name(&lt;span style="color: #a31515"&gt;&amp;quot;Name&amp;quot;&lt;/span&gt;);
userContract.SimpleProperty(u =&amp;gt; u.Age);&lt;/pre&gt;

&lt;p&gt;User类型中定义了三个属性，其中我们只采纳了两个：Age和UserName（并改名为Name）。利用这个“合约”我们可以进行JSON转化：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;user = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;User &lt;/span&gt;{ UserName = &lt;span style="color: #a31515"&gt;&amp;quot;Tom&amp;quot;&lt;/span&gt;, Age = 20 };
&lt;span style="color: blue"&gt;var &lt;/span&gt;jsonUser = &lt;span style="color: #2b91af"&gt;JsonSerializer&lt;/span&gt;.SerializeObject(user, userContract);
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(jsonUser);&lt;/pre&gt;

&lt;p&gt;这将会输出（已经过手动格式化）：&lt;/p&gt;

&lt;pre class="code"&gt;{
    &amp;quot;Name&amp;quot; : &amp;quot;Tom&amp;quot;,
    &amp;quot;Age&amp;quot; : 20
}&lt;/pre&gt;

&lt;p&gt;同样，我们可以为某个属性运用转化规则，只要提供一个IJsonConverter对象即可：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Title { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Content { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime &lt;/span&gt;CreateTime { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DataTimeConverter &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IJsonConverter
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public object &lt;/span&gt;ToJsonValue(&lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;type, &lt;span style="color: blue"&gt;object &lt;/span&gt;value)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;((&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;)value).ToString(&lt;span style="color: #a31515"&gt;&amp;quot;R&amp;quot;&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;public object &lt;/span&gt;FromJsonValue(&lt;span style="color: #2b91af"&gt;Type &lt;/span&gt;type, &lt;span style="color: blue"&gt;object &lt;/span&gt;value)
    {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.ParseExact((&lt;span style="color: blue"&gt;string&lt;/span&gt;)value, &lt;span style="color: #a31515"&gt;&amp;quot;R&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
    }
}&lt;/pre&gt;

&lt;p&gt;定义合约：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var&lt;/span&gt; postContract = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;JsonContract&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Post&lt;/span&gt;&amp;gt;();
postContract.SimpleProperty(p =&amp;gt; p.Title);
postContract.SimpleProperty(p =&amp;gt; p.CreateTime).Converter(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DataTimeConverter&lt;/span&gt;());&lt;/pre&gt;

&lt;p&gt;在这里，DataTimeConverter会将CreateTime属性的时间日期转化为字符串以后再输出。例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;post = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;{ Title = &lt;span style="color: #a31515"&gt;&amp;quot;Good day today.&amp;quot;&lt;/span&gt;, CreateTime = &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now };
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #2b91af"&gt;JsonSerializer&lt;/span&gt;.SerializeObject(post, postContract));
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine();&lt;/pre&gt;

&lt;p&gt;便会得到：&lt;/p&gt;

&lt;pre class="code"&gt;{
    &amp;quot;Title&amp;quot; : &amp;quot;Good day today.&amp;quot;,
    &amp;quot;CreateTime&amp;quot; : &amp;quot;Sun, 10 Oct 2010 23:12:53 GMT&amp;quot;
}&lt;/pre&gt;

&lt;p&gt;我们可以做得还有更多，例如这里有一个包含复杂属性的Category对象：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public string &lt;/span&gt;Name { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;User &lt;/span&gt;Author { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Post&lt;/span&gt;&amp;gt; Posts { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}&lt;/pre&gt;

&lt;p&gt;我们在定义Category的合约时，则可以用到之前的合约：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var&lt;/span&gt; categoryContract = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;JsonContract&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;();
categoryContract.SimpleProperty(p =&amp;gt; p.Name);
categoryContract.ComplexProperty(p =&amp;gt; p.Author).Contract(userContract);
categoryContract.ArrayProperty(p =&amp;gt; p.Posts).ElementContract(postContract);&lt;/pre&gt;

&lt;p&gt;那么下面这段代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;category = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category
&lt;/span&gt;{
    Name = &lt;span style="color: #a31515"&gt;&amp;quot;Default&amp;quot;&lt;/span&gt;,
    Author = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;User &lt;/span&gt;{ UserName = &lt;span style="color: #a31515"&gt;&amp;quot;Jerry&amp;quot;&lt;/span&gt;, Age = 15 },
    Posts = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Post&lt;/span&gt;&amp;gt;
    {
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;{ Title = &lt;span style="color: #a31515"&gt;&amp;quot;Post 1&amp;quot;&lt;/span&gt;, CreateTime = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;(2010, 1, 1) },
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;{ Title = &lt;span style="color: #a31515"&gt;&amp;quot;Post 2&amp;quot;&lt;/span&gt;, CreateTime = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;(2010, 2, 1) },
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Post &lt;/span&gt;{ Title = &lt;span style="color: #a31515"&gt;&amp;quot;Post 3&amp;quot;&lt;/span&gt;, CreateTime = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;(2010, 3, 1) }
    }
};
&lt;span style="color: blue"&gt;var &lt;/span&gt;jsonCategory = &lt;span style="color: #2b91af"&gt;JsonSerializer&lt;/span&gt;.SerializeObject(category, s_categoryContract);
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(jsonCategory);&lt;/pre&gt;

&lt;p&gt;将会输出：&lt;/p&gt;

&lt;pre class="code"&gt;{
    &amp;quot;Name&amp;quot; : &amp;quot;Default&amp;quot;,
    &amp;quot;Author&amp;quot; :
    {
        &amp;quot;Name&amp;quot; : &amp;quot;Jerry&amp;quot;,
        &amp;quot;Age&amp;quot; : 15
    },
    &amp;quot;Posts&amp;quot; :
    [
        {
            &amp;quot;Title&amp;quot; : &amp;quot;Post 1&amp;quot;,
            &amp;quot;CreateTime&amp;quot; : &amp;quot;Fri, 01 Jan 2010 00:00:00 GMT&amp;quot;
        },
        {
            &amp;quot;Title&amp;quot; : &amp;quot;Post 2&amp;quot;,
            &amp;quot;CreateTime&amp;quot; : &amp;quot;Mon, 01 Feb 2010 00:00:00 GMT&amp;quot;
        },
        {
            &amp;quot;Title&amp;quot; : &amp;quot;Post 3&amp;quot;,
            &amp;quot;CreateTime&amp;quot; : &amp;quot;Mon, 01 Mar 2010 00:00:00 GMT&amp;quot;
        }
    ]
}&lt;/pre&gt;

&lt;p&gt;当然，您同样可以定义一个匿名对象作为JSON输出：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var&lt;/span&gt; value = &lt;span style="color: blue"&gt;new &lt;/span&gt;{ v = &lt;span style="color: #2b91af"&gt;JsonSerializer&lt;/span&gt;.SerializeObject(category, categoryContract) };
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #2b91af"&gt;JsonSerializer&lt;/span&gt;.Serialize(value));&lt;/pre&gt;

&lt;p&gt;是不是很简单？&lt;/p&gt;

&lt;h1&gt;更多讨论&lt;/h1&gt;

&lt;p&gt;以上代码只是演示了序列化成JSON的功能，但是您应该也可以了解反序列化的使用方式。目前JsonMe的功能就只有这么多，可以说非常简单，但是我认为已经基本够用，甚至在大部分情况下完整代替JavaScriptSerializer和DataContractJsonSerialzier是没有什么问题的。&lt;/p&gt;

&lt;p&gt;开发JsonMe恰好花了我五个小时（下午2点到7点），只有几百行代码（当然还很粗糙），大部分还是用于合约配置的“骨架”，真正进行对象属性的赋值和转化的代码只有一百多行。JsonMe能够如此简约的原因，是因为站在JavaScriptSerializer的肩膀上。说实话，JavaScriptSerializer其实提供了一个很好的基础，因为它可以将一段JSON字符串转化为Dictionary&amp;lt;string, object&amp;gt;与object数组间的嵌套，这正是JSON格式的本质。当然，为了实现简单，我在JsonMe中创建了JsonObject和JsonArray两个对象，分别继承Dictionary&amp;lt;string, dynamic&amp;gt;和List&amp;lt;dynamic&amp;gt;，它们便作为JSON结构的表现形式。&lt;/p&gt;

&lt;p&gt;不过我也意识到，JavaScriptSerializer可能并不是一个合适的选择，因为这会让我们依赖System.Web.Extensions.dll。事实上在.NET平台上有一个更独立，更简单的JSON结构实现，那就是&lt;a href="http://msdn.microsoft.com/en-us/library/system.json(VS.95).aspx"&gt;Silverlight中的System.Json.dll&lt;/a&gt;。只可惜我们只能用它开发Silverlight程序。我打算在合适的时候，将mono中的System.Json.dll实现移植到.NET 3.5中，这样JsonMe就可以摆脱对System.Web.Extensions.dll的依赖，并摆脱自定义的JsonObject和JsonArray，可以直接使用System.Json里的结构（&lt;a href="http://blog.zhaojie.me/2010/10/use-silverlight-system-json-in-normal-application.html"&gt;已完成&lt;/a&gt;）。更重要的是，这可以让JsonMe作用在Silverlight，甚至是&lt;a href="http://blog.zhaojie.me/2010/09/develop-ios-app-with-monotouch-in-visual-studio-1.html"&gt;基于MonoTouch的iOS开发&lt;/a&gt;中（很有可能还包括未来的&lt;a href="http://monodroid.net/"&gt;MonoDroid&lt;/a&gt;）。&lt;/p&gt;

&lt;p&gt;如果您感兴趣的话，也不妨&lt;a href="http://github.com/JeffreyZhao/JsonMe"&gt;获取JsonMe的源代码和简单示例&lt;/a&gt;，修改修改，尝试尝试。我认为它还是相当实用的。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/10/jsonme-a-type-contract-separated-lightweight-json-mapping-library.html#comments</comments>
      <pubDate>Sun, 10 Oct 2010 16:29:24 GMT</pubDate>
      <lastBuildDate>Sun, 10 Oct 2010 16:29:24 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/translation/">翻译引进</category>
      <title>Padding Oracle Attack实例分析</title>
      <link>http://blog.zhaojie.me/2010/10/padding-oracle-attack-in-detail.html</link>
      <guid>http://blog.zhaojie.me/2010/10/padding-oracle-attack-in-detail.html</guid>
      <description>&lt;p&gt;在之前的《&lt;a href="http://blog.zhaojie.me/2010/09/things-about-padding-oracle-vulnerability-in-asp-net.html"&gt;浅谈&lt;/a&gt;》一文中，我提到《&lt;a href="http://www.gdssecurity.com/l/b/2010/09/14/automated-padding-oracle-attacks-with-padbuster/"&gt;Automated Padding Oracle Attacks with PadBuster&lt;/a&gt;》一文对理解Padding Oracle Attack非常有帮助，并打算将其翻译出来。现在我便来实现承诺了。《Automated》一文其实是在介绍PadBuster这个自动攻击工具，不过其中也通过实例加配图详细介绍了Padding Oracle Attack的原理——这也是我会翻译的部分。这篇文章写的非常通俗易懂，您只需要了解一点点关于加密的基础概念即可，不需要对加密算法或其证明有任何了解。我想只要配合些许Wikipedia上的定义，大部分朋友应该都能顺利地理解这篇文章。&lt;/p&gt;

&lt;p&gt;以下为翻译内容。&lt;/p&gt;

&lt;p&gt;最近出现了许多有关Padding Oracle Attack的声音，在今年夏天早些时候的BlakHat Europe会议上，&lt;a href="http://netifera.com/research/"&gt;Juliano Rizzo和Thai Duong&lt;/a&gt;在他们的演讲中演示了这种攻击方式。虽然Padding Oracle是种相对容易的攻击方式，但如果您还没有对它的自动攻击原理有一定了解，那么利用它进行攻击还是需要不少时间的。由于缺少好用的工具以识别及利用Padding Oracles，我们开发了一个基于Padding Oracle的内部脚本，PadBuster，现在我们打算将它与社区分享。&lt;a href="https://www.gdssecurity.com/l/t/d.php?k=PadBuster"&gt;您可以在这里下载工具&lt;/a&gt;，现在我们也会花些时间来讨论这个工具的工作方式，以及它所支持的几种场景。&lt;/p&gt;

&lt;h1&gt;一些背景知识&lt;/h1&gt;

&lt;p&gt;在讨论PadBuster之前，我们先来简单讨论一下典型的Padding Oracle Attack基础。故名思义，Padding Oracle Attack背后的关键性概念便是加/解密时的填充（Padding）。明文信息可以是任意长度，但是块状加密算法需要所有的信息都由一定数量的数据块组成。为了满足这样的需求，便需要对明文进行填充，这样便可以将它分割为完整的数据块。&lt;/p&gt;

&lt;p&gt;加密时可以使用多种填充规则，但最常见的填充方式之一是在PKCS#5标准中定义的规则。PCKS#5的填充方式为：明文的最后一个数据块包含N个字节的填充数据（N取决于明文最后一块的数据长度）。下图是一些示例，展示了不同长度的单词（FIG、BANANA、AVOCADO、PLANTAIN、PASSIONFRUIT）以及它们使用PKCS#5填充后的结果（每个数据块为8字节长）。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig1.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig1.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;请注意，每个字符串都至少有1个字节的填充数据，因此7字节的值（如AVOCADO）则使用0x01进行填充，而8字节的值（如PLANTAIN）则会填充一个额外的数据块。填充字节的值也说明了填充的字节数，因此待加密数据的最后几个字节必须是以下几种情况之一：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一个0x01（0x01） &lt;/li&gt;

  &lt;li&gt;两个0x02（0x02，0x02） &lt;/li&gt;

  &lt;li&gt;三个0x03（0x03，0x03，0x03） &lt;/li&gt;

  &lt;li&gt;四个0x04（0x04，0x04，0x04，0x04） &lt;/li&gt;

  &lt;li&gt;…… &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果解密后的最后一个数据块末尾并非这些合法的字节序列，大部分加/解密程序都会抛出一个填充异常。这个异常对于攻击者尤为关键，它是Padding Oracle Attack的基础。&lt;/p&gt;

&lt;h1&gt;一个基本的Padding Oracle Attack场景&lt;/h1&gt;

&lt;p&gt;作为一个具体例子，请考虑以下场景：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;某个应用程序使用Query String参数来传递一个用户加密后的用户名，公司ID及角色ID。参数使用CBC模式加密，每次都使用不同的初始化向量（IV，Initialization Vector）并添加在密文前段。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;当应用程序接受到加密后的值以后，它将返回三种情况：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;接受到正确的密文之后（填充正确且包含合法的值），应用程序正常返回（200 - OK）。 &lt;/li&gt;

  &lt;li&gt;接受到非法的密文之后（解密后发现填充不正确），应用程序抛出一个解密异常（500 - Internal Server Error）。 &lt;/li&gt;

  &lt;li&gt;接受到合法的密文（填充正确）但解密后得到一个非法的值，应用程序显示自定义错误消息（200 - OK）。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上述的场景体现了一个典型的Padding Oracle（填充提示），我们可以利用应用程序的行为轻易了解某个加密的值是否填充正确。这里的单词Oracle代表了一种机制，用于了解某个测试是否通过。&lt;/p&gt;

&lt;p&gt;既然已经给出了场景，那么我们便来查看应用程序所使用的一个加密后的参数。这个参数保存了使用分号隔离的一系列值，在我们的示例中，则是用户名（BRIAN），公司ID（12）及角色ID（12）：因此这里的明文是“&lt;strong&gt;BRIAN;12;2;&lt;/strong&gt;”。以下则是经过加密的Query String实例，请注意加密后的UID参数使用了ASCII十六进制表示法。&lt;/p&gt;

&lt;pre class="code"&gt;http://sampleapp/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6&lt;/pre&gt;

&lt;p&gt;在实际情况中，攻击者并不会知道这里所对应的明文是多少，不过作为示例，我们已经知道了明文、填充、以及加密后的值（如下表）。正如之前所提到的那样，IV添加在密文的前段，即最前面8个字节。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig2.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig2.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;攻击者可以根据加密后值的长度来推测出数据块的大小。由于长度（这里是24）能被8整除但不能被16整除，因此可以得知数据块的大小是8个字节。现在我们来观察下加密和解密的内部实现，下图便展示了字节级别的运算方式，这对以后攻击方式的讨论很有帮助。请注意，其中带圆圈的加号表示XOR（异或）操作。&lt;/p&gt;

&lt;p&gt;加密过程：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig3.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig3.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;解密过程：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig4.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig4.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;同样值得指出的是，解密之后的最后一个数据块，其结尾应该包含正确的填充序列。如果这点没有满足，那么加/解密程序就会抛出一个填充异常。&lt;/p&gt;

&lt;h1&gt;利用Padding Oracle进行解密&lt;/h1&gt;

&lt;p&gt;我们现在来关注一下如何利用Padding Oracle Attack进行解密。我们将每次操作一个单独的加密块，因此我们可以独立出第一块密文（IV后的那块），在前面加上全为NULL的IV值，并发送至应用程序。以下是URL极其相关回复：&lt;/p&gt;

&lt;pre class="code"&gt;Request: http://sampleapp/home.jsp?UID=0000000000000000F851D6CC68FC9537
Response: 500 - Internal Server Error&lt;/pre&gt;

&lt;p&gt;回复的500错误是意料之中的，因为这个值在解密后完全非法。下图展示了应用程序在尝试解密的时候究竟做了哪些事情。您会发现，因为我们只处理单个数据块，因此它的结尾必须包含正确的填充字节，才能避免出现非法填充异常。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig5.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig5.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;如上图所示，在解密之后，数据块的末尾并没有包含正确的填充序列，因此出现了异常。现在我们将IV加一，并发送同样的密文，看看会发生什么：&lt;/p&gt;

&lt;pre class="code"&gt;Request: http://sampleapp/home.jsp?UID=0000000000000001F851D6CC68FC9537
Response: 500 - Internal Server Error&lt;/pre&gt;

&lt;p&gt;与之前一样，我们得到了500异常。这是因为在解密后我们还是没有获得合法的填充序列。稍有不同的是，我们在深入内部之后会发现，最后一个字节的值会有所变化（变成了0x3C而不是0x3D）。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig6.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig6.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;如果我们重复发送这样的请求，每次将IV的最后一个字节加一（直至0xFF），那么最终我们将会产生一个合法的单字节填充序列（0x01）。对于可能的256个值中，只有一个值会产生正确的填充字节0x01。遇上这个值的时候，你应该得到一个不同于其他255个请求的回复结果：&lt;/p&gt;

&lt;pre class="code"&gt;Request: http://sampleapp/home.jsp?UID=000000000000003CF851D6CC68FC9537
Response: 200 OK&lt;/pre&gt;

&lt;p&gt;同样，我们从示意图中了解一下此时发生了什么：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig7.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig7.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;在这个情况下，我们便可以推断出中间值（Intermediary Value）的最后一个字节，因为我们知道它和0x3C异或后的结果为0x01，于是：&lt;/p&gt;

&lt;pre class="code"&gt;因为 [Intermediary Byte] ^ 0×3C == 0×01, 
得到 [Intermediary Byte] == 0×3C ^ 0×01, 
所以 [Intermediary Byte] == 0×3D&lt;/pre&gt;

&lt;p&gt;现在我们可以更进一步。我们已经知道了中间值的最后一个字节，于是我们可以推断出解密后的值是多少。您可以回忆一下，在解密的过程中，中间值的每个字节都会与密文中的前一个数据块（对于第一个数据块来说便是IV）的对应字节进行异或操作，于是我们使用之前示例中原来的IV中的最后一个字节（0x0F），与中间值异或一下便可以得到明文。不出意料，我们会得到0x32，这表示数字“2”（明文中第一个数据块的最后一个字节）。&lt;/p&gt;

&lt;p&gt;我们现在已经破解了示例数据块中的第8个字节，是时候关注第7个字节了。在破解第8个字节时，我们使用暴力枚举IV，让解密后的最后一个字节成为0x01（合法填充）。在破解第7个字节的时候，我们要做的事情也差不多，不过此时要求第7个字节与第8个字节都为0x02（再重复一遍，这表示合法的填充）。我们已经知道，中间值的最后一个字节是0x3D，因此我们可以将IV中的第8个字节设为0x3F（这会产生0x02）并暴力枚举IV的第七个字节（从0x00开始，直至0xFF）。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig8.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig8.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;我们再次遭遇填充异常，直至遇上某个值，它使得解密后的第7个字节成为0x02（正确填充），此时IV中的字节为0x24：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig9.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig9.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;使用这种技巧，我们可以从后往前破解中间值里的每个字节，最终得到解密后的值（尽管每次一个字节）。下图展示了完全破解后的IV值，此时整个数据块都为填充值（0x08）：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig10.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig10.png" width="450" /&gt;&lt;/a&gt; 

&lt;h1&gt;使用PadBuster进行解密&lt;/h1&gt;

&lt;p&gt;（译注：这段内容为PadBuster的使用指南，在此略过，如果您对这部分内容感兴趣可以阅读原文。）&lt;/p&gt;

&lt;h1&gt;加密任意的值&lt;/h1&gt;

&lt;p&gt;我们已经知道如何利用Padding Oracle和PadBuster来依次破解每个加密的数据块。现在，我们就来观察下如何使用同样的漏洞来加密任意数据。&lt;/p&gt;

&lt;p&gt;可能您已经发现，一旦我们可以推断出密文数据块的中间值，我们便能通过操作IV的值来完全控制解密所得到的结果。例如，在前面的示例中，如果想要将密文中第一个数据块解密为“TEST”这个值，您可以计算出它所需要的IV值，只要将目标明文与中间值进行异或操作即可。因此，只要您将字符串“TEST”（自然，还包括四个0x04字节作为填充）与中间值异或之后，便可以得到最终的IV，即0×6D，0×36，0×70，0×76，0×03，0×6E，0×22，0×39：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig11.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig11.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;这种做法对于单个数据块来说自然没有问题，但如果我们想要用它来生成长度超过一个数据块的值又该怎么办呢？我们来看一个简单通俗的实际案例。这次我们要生成一个加密的字符串“ENCRYPT TEST”而不仅仅是“TEST”。第一步，还是将文本分拆成数据块，并补上必须的填充字节，如下图：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig12.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/padding-oracle-attack-in-detail/po_fig12.png" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;在构造超过一个数据块的值时，我们实际上是从最后一个数据块开始，向前依次生成所需的密文。在这里，最后的数据块与之前的相同，因此我们已经知道以下的IV和密文能够生成字符串“TEST”：&lt;/p&gt;

&lt;pre class="code"&gt;Request: http://sampleapp/home.jsp?UID=6D367076036E2239F851D6CC68FC9537&lt;/pre&gt;

&lt;p&gt;接下来，我们需要弄明白中间值6D367076036E2239在作为密文，而不是IV传递至应用程序时会被如何解密。在这里只要使用与破解过程相同的技巧就行了，我们把它作为密文传递给应用程序，并从全部为NULL的IV开始进行暴力破解：&lt;/p&gt;

&lt;pre class="code"&gt;Request: http://sampleapp/home.jsp?UID=00000000000000006D367076036E2239&lt;/pre&gt;

&lt;p&gt;一旦我们通过暴力破解得到中间值之后，IV便可以用来生成我们想要的任意值。新的IV可以被放在前一个示例的前面，这样便可以得到一个符合我们要求的，包含两个数据块的密文了。这个过程可以不断重复，这样便能生成任意长度的数据了。&lt;/p&gt;

&lt;h1&gt;使用PadBuster加密任意的值&lt;/h1&gt;

&lt;p&gt;（译注：这段内容为PadBuster的使用指南，在此略过，如果您对这部分内容感兴趣可以阅读原文。）&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/10/padding-oracle-attack-in-detail.html#comments</comments>
      <pubDate>Fri, 08 Oct 2010 16:20:05 GMT</pubDate>
      <lastBuildDate>Fri, 08 Oct 2010 16:20:05 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <title>浅谈这次ASP.NET的Padding Oracle Attack相关内容</title>
      <link>http://blog.zhaojie.me/2010/09/things-about-padding-oracle-vulnerability-in-asp-net.html</link>
      <guid>http://blog.zhaojie.me/2010/09/things-about-padding-oracle-vulnerability-in-asp-net.html</guid>
      <description>&lt;p&gt;上一周爆出了一个关于ASP.NET的安全漏洞，有关这个漏洞的第一篇文章应该是&lt;a href="http://weblogs.asp.net/scottgu/archive/2010/09/18/important-asp-net-security-vulnerability.aspx"&gt;ScottGu的说明&lt;/a&gt;，但是其中各方面谈的也是语焉不详。由于这个漏洞关系到“安全”这样敏感的话题，其中又涉及到密码学这样常人看不明白的技术，于是导致了各种猜测和推测，其中甚至与我对ASP.NET的了解所有矛盾，因此我觉得也大都不靠谱。中秋休息在家，我简单地了解了一下与这个漏洞有关的内容，总结出了一些“能够说服自己”的内容，在此记录下来。因此，这篇文章的面向读者是那些和我差不多的同学：对ASP.NET有所了解，但对密码学知之甚少。&lt;/p&gt;

&lt;h1&gt;什么是Padding和Oracle&lt;/h1&gt;

&lt;p&gt;要谈这个问题，先要了解什么是Padding Oracle Attack。有些文章把Padding和Oracle，与CSS样式表或是那个收购了Sun的甲骨文公司联系起来，这就驴唇不对马嘴了。&lt;/p&gt;

&lt;p&gt;Padding在这里的含义是“填充”，因为对于加密算法来说，它们是基于等长的“数据块”进行操作的（如对于RC2，DES或TripleDES算法来说这个长度是8字节，而对于Rijndael算法来说则是16、24或32字节）。但是，我们的输入数据长度是不规则的，因此必然需要进行“填充”才能形成完整的“块”。“填充”时比较常用的是&lt;a href="http://tools.ietf.org/html/rfc2898"&gt;PKCS #5&lt;/a&gt;规则，简单地说，便是根据最后一个数据块所缺少的长度来选择填充的内容。&lt;/p&gt;

&lt;p&gt;例如，数据块长度要求是8字节，如果输入的最后一个数据块只有5个字节的数据，那么则在最后补充三个字节的0x3。如果输入的最后一个数据块正好为8字节长，则在最后补充一个完整的长为8字节的数据块，每个字节填0x8。使用这个规则，我们便可以根据填充的内容来得知填充的长度，以便在解密后去除填充的字节。&lt;/p&gt;

&lt;p&gt;在解密时，如果算法发现解密后得到的结果，它的填充方式不符合规则，那么表示输入数据有问题，对于解密的类库来说，往往便会抛出一个异常，提示Padding不正确。Oracle在这里便是“提示”的意思，和甲骨文公司没有任何关系。&lt;/p&gt;

&lt;h1&gt;如何进行Padding Oracle Attack&lt;/h1&gt;

&lt;p&gt;刚才已经提到，如果输入的密文不合法，类库则会抛出异常，这便是一种提示。攻击者可以不断地提供密文，让解密程序给出提示，不断修正，最终得到的所需要的结果。这里的一个关键在于，攻击者所需要的提示仅仅是“解密成功与否”这样一个二元信息，例如它在一个Web程序中可能只是“200 - OK”及“500 - Internal Server Error”这样的表现形式，而不需要其他任何详细信息。&lt;/p&gt;

&lt;p&gt;例如，现代流行的Web框架大都是开源的，因此它的加密方式完全透明（当然这点其实并不是必须的，只是大有帮助而已），对于攻击者来说唯一不知道的便是密钥。于是攻击者便可以根据这个加密方式设计有针对性的密文，最终得到密钥（及&lt;a href="http://en.wikipedia.org/wiki/Initialization_vector"&gt;IV&lt;/a&gt;等信息）。在很多时候，一个网站都会使用同样的密钥和IV，于是只需从一个漏洞，便可以在网站的其他方面进行破坏，或解密信息，或绕开验证。&lt;/p&gt;

&lt;p&gt;在具体操作上还可以有许多方式进行辅助，在&lt;a href="http://netifera.com/research/"&gt;Juliano Rizzo和Thai Duong&lt;/a&gt;的《&lt;a href="http://usenix.org/events/woot10/tech/full_papers/Rizzo.pdf"&gt;Practical Padding Oracle Attacks&lt;/a&gt;》（&lt;a href="https://media.blackhat.com/bh-eu-10/whitepapers/Duong_Rizzo/BlackHat-EU-2010-Duong-Rizzo-Padding-Oracle-wp.pdf"&gt;及此&lt;/a&gt;）论文（下文称PPOA）中便提到了很多方式，例如使用Google搜索异常的关键字（这说明许多站点都把异常信息输出在页面上），检查代码，从外表检查一些BASE64形式的字符串，猜测常见的分割符，如“--”，“|”或是“:”等等。PPOA认为，如今Padding Oracle漏洞与SQL注入，脚本注入等漏洞一样无处不在，论文中还详细讨论了利用这个漏洞来攻击eBay拉丁美洲站点，CAPTCHA等应用，以及在JSF（包括&lt;a href="http://myfaces.apache.org/"&gt;Apache MyFaces&lt;/a&gt;和&lt;a href="https://javaserverfaces.dev.java.net/"&gt;Sun Mojarra&lt;/a&gt;实现），Ruby on Rails等Web框架中的漏洞——奇怪的是其中反而没有提到ASP.NET。&lt;/p&gt;

&lt;p&gt;关于Padding Oracle Attack的具体细节，您可以从《&lt;a href="http://www.gdssecurity.com/l/b/2010/09/14/automated-padding-oracle-attacks-with-padbuster/"&gt;Automated Padding Oracle Attacks with PadBuster&lt;/a&gt;》及《&lt;a href="http://www.isg.rhul.ac.uk/~kp/secretIV.pdf"&gt;Padding Oracle Attacks on CBC-mode Encryption with Secret and Random IVs&lt;/a&gt;》两篇文章中得到更详细的信息，它们似乎并不像表面那样高深莫测，尤其是前者，有机会我也打算将它翻译一下。&lt;/p&gt;

&lt;h1&gt;针对ASP.NET的攻击及其危害&lt;/h1&gt;

&lt;p&gt;那么，这次又是如何对ASP.NET站点进行攻击的呢？方式有不少，例如攻击者可以为一个需要认证的请求发送自定义的cookie值，如果没有通过认证，则会得到一个转向登陆页面的302跳转。一个更为直观和通用的作法来自于PPOA论文的作者&lt;a href="http://www.youtube.com/watch?v=yghiC_U2RaM"&gt;所提供的一段视频&lt;/a&gt;，其中使用了WebResources.axd?d=xyz进行探测工作。WebResource.axd有一个特点，便是会对错误的密文（即d=xyz中的xyz）产生500错误，而对正确的密文产生404错误，这便形成了足够的提示。&lt;/p&gt;

&lt;p&gt;好，那么假设攻击者已经得到了站点的Machine Key，也就是网站所使用的密钥，那么它又能造成什么危害呢？&lt;/p&gt;

&lt;p&gt;一些危害是很容易理解的，例如解密（或注入）ViewState，或是如视频里那样设置一个管理员的cookie。在ScottGu等文章中描述这个漏洞的危害时还提到，这个漏洞可以用来下载web.config等私密文件，这又是如何办到的呢？要知道web.config文件的下载是被IIS和ASP.NET所禁止的，它似乎和加密解密或是Machine Key无关。不过您是否意识到，在ASP.NET 3.5 SP1以后，我们可以利用ScriptManager来打包输出本地的脚本文件？例如： &lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;asp&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: maroon"&gt;ScriptManager &lt;/span&gt;&lt;span style="color: red"&gt;ID&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;sm&amp;quot; &lt;/span&gt;&lt;span style="color: red"&gt;runat&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;server&amp;quot;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;CompositeScript&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
        &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;Scripts&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;asp&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: maroon"&gt;ScriptReference &lt;/span&gt;&lt;span style="color: red"&gt;Path&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;~/scripts/core.js&amp;quot; /&amp;gt;
            &amp;lt;&lt;/span&gt;&lt;span style="color: maroon"&gt;asp&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: maroon"&gt;ScriptReference &lt;/span&gt;&lt;span style="color: red"&gt;Path&lt;/span&gt;&lt;span style="color: blue"&gt;=&amp;quot;~/scripts/lib.js&amp;quot; /&amp;gt;
        &amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;Scripts&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;CompositeScript&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: maroon"&gt;asp&lt;/span&gt;&lt;span style="color: blue"&gt;:&lt;/span&gt;&lt;span style="color: maroon"&gt;ScriptManager&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;这段内容会在页面上放置一段ScriptResource.axd的引用，它的Query String便包含了需要输出的文件路径，它是与ScriptManager等组件完全独立的。那么，如果攻击者告诉它输出“~/web.config”的时候……&lt;/p&gt;

&lt;p&gt;有趣的是，PPOA论文作者同时还在今年两月和六月分别提供了&lt;a href="http://www.youtube.com/watch?v=e46A-PUpDvk"&gt;攻击CAPTCHA&lt;/a&gt;和&lt;a href="http://www.youtube.com/watch?v=euujmKDxmC4"&gt;攻击Apache MyFaces&lt;/a&gt;的视频，同时也提供了一个&lt;a href="http://netifera.com/research/"&gt;针对JSF的自动攻击工具&lt;/a&gt;，不过它们并没有形成微软对ASP.NET的漏洞那样强烈反应。&lt;/p&gt;

&lt;h1&gt;防止攻击&lt;/h1&gt;

&lt;p&gt;目前ScottGu给出了多个workaround，归根结底便是消除“Oracle”，也就是提示信息。例如他强调要为404和500错误提供完全相同的反馈——不止是输出的错误页面，也包括所有的头信息（如Server Time等自然除外），这种做法会让攻击者无法得到提示信息，自然也就无法解密了。此外，ScottGu的一些代码同时让错误页面Sleep一小段时间，这也是种常用的混淆手段，让攻击者无法从响应时间长短上来了解这个请求“性质”如何。&lt;/p&gt;

&lt;p&gt;从上面的分析中我们可以知道，这种统一错误信息的作法似乎是针对WebResource.axd和ScriptResource.axd的。由于我们知道了攻击的手段，便也可以采取其他作法。例如对于不需要这两个Handler的站点，就把它们从ASP.NET或IIS里直接摘除吧。还有，如果在日志中发现太多CryptographicException异常，便要关注站点是否遭受的攻击。&lt;/p&gt;

&lt;p&gt;但是，Padding Oracle Attack的危害之处在于它所需要的信息实在太少，攻击者只需分辨两种状态便可以进行攻击，即便WebResource.axd的攻击被您防止了，那么之前提到的用户认证所带来的302跳转又如何？对于我们独立编写的应用程序来说，要绕开这个问题可以使用各种技巧。但对于微软来说可能就不容易了，因为ASP.NET作为一个框架，它提供的是一种统一的，普适的机制，这也是为什么这个漏洞会影响几乎所有微软ASP.NET产品的缘故。&lt;/p&gt;

&lt;p&gt;此外还有一些做法也是可取的。例如：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;避免在ViewState和Cookie中存放敏感数据。 &lt;/li&gt;

  &lt;li&gt;不要过度依赖Machine Key。&lt;/li&gt;

  &lt;li&gt;在认证cookie里保存的不只是用户名，而是外界无法得知的ID，或是同时保存checksum等额外的验证信息。 &lt;/li&gt;

  &lt;li&gt;为ScriptResource.axd写一个Wrapper，只让它输出扩展名为js的内容。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些做法的目的是：即使攻击者得到了Machine Key，也无法对站点造成破坏。&lt;/p&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;安全性漏洞总是不令人愉快的，但是在遇到这种状况的同时，我们也要努力得知问题的真实情况。在如今信息爆炸的时代，产生和获取一条没有多大价值甚至是错误的信息，可谓是非常容易的。排除干扰寻求真相，即便只是种态度和意愿，也是一名技术人员的基本素质。因此在这个问题上，我最反感的便是“微软的产品就是不安全”，“反正我不会被攻击”这样的态度。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/09/things-about-padding-oracle-vulnerability-in-asp-net.html#comments</comments>
      <pubDate>Fri, 24 Sep 2010 18:25:07 GMT</pubDate>
      <lastBuildDate>Fri, 24 Sep 2010 18:25:07 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助第二届.NET技术交流会 - 演讲录像及下载</title>
      <link>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html</link>
      <guid>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html</guid>
      <description>&lt;p&gt;经过一个多星期的努力，我们在此为大家奉上&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;第二届.NET技术交流会&lt;/a&gt;的演讲录像。由于录像过程中的一些失误，我们在在讲师录像方面存在着很大问题，经过补救，也只能得到后两场演讲中使用手持设备拍摄下来的录像。在此向大家表示深深的歉意，有了这次的教训，我们以后会更加重视每一个环节的预防及补救措施，尽力避免如现在这样无可挽回的结果。&lt;/p&gt;

&lt;h1&gt;响应式编程与响应式框架&lt;/h1&gt;

&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：赵劼，盛大创新院，研究员。关注前沿技术，并致力于开源社区与微软平台的组合优化。对函数式编程，并行程序开发，代码之美以及程序员能力与修养等相关问题也有着浓厚的兴趣，同时非常希望能够写程序到60岁。最近致力于F#，Scala语言及mono平台在社区中的推广。&lt;/p&gt;

&lt;p&gt;简介：异步编程改变了我们的编程方式，也为我们带来的许多挑战，同时让一些编程模型重新焕发了生机。与传统的“拉”模型不同，响应式编程将异步事件流视为可观察的集合，这是一种“推”模型。微软为了提高云时代的编程体验而设计了响应式框架，其目的是为了简化复杂事件处理之间混合操作。从中我们了解到一些异步编程的模式与LINQ使用技巧，并可以将这种编程模型普及到JavaScript等其他平台上去。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjA3OTIzNzI0/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100928/1/rx.mov"&gt;高清格式下载&lt;/a&gt;（mov格式，1280 * 720，265M）&lt;/p&gt;

&lt;h1&gt;大话程序员可用的算法&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：程劭非，盛大创新院，研究员。网名winter，无忧脚本版主。Web前端技术的积极倡导者。学生时代曾经热衷于参加ACM/ICPC。目前工作在Bambook电子书项目，主要负责文字排版和浏览器引擎WebKit相关。之前曾负责在Windows CE系统上的IE开发。&lt;/p&gt;

&lt;p&gt;简介：俗话说“数据结构+算法=程序”，算法是什么？算法书里满篇是看不懂的形式化推导，网上一些&amp;quot;高人&amp;quot;写的关于算法文章高深莫测，大公司面 试最让人讨厌的就是考算法题，“我做了这么多年，跟本在实际开发中就没用过算法！”，算法真的是距离我们如此遥远的东西吗？且听这回演讲， 算法究竟如何影响我们的开发。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjA3OTI3MjAw/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100928/1/algorithms.mov"&gt;高清格式下载&lt;/a&gt;（mov格式，1280 * 720，128M）&lt;/p&gt;

&lt;h1&gt;Windows内核技术介绍&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：潘爱民，盛大创新院专家，微软学者，集团COO专家顾问。长期从事软件和系统技术的研究和开发工作，撰写了大量软件技术文章，并著译了多部经典计算机图书。在MSR/清华等从事多年科研工作，在北大和清华多年执教经验。数学学士学位和计算机科学博士，主要研究领域包括软件设计、信息安全、操作系统和Internet技术。&lt;/p&gt;

&lt;p&gt;简介：Windows操作系统经过二十年的发展，已臻成熟。Microsoft在推动Windows内核方面做了大量工作，譬如于2006年夏季向教育界开放了当时最为先进的内核源代码（Windows Research Kernel）。主讲者在这次讲座中，结合这些可利用的资源，分享对Windows内核研究的体会，尤其将重点讨论Windows中的I/O模型和环境子系统。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjEyOTkyMzgw/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100928/1/win-kernal.mov"&gt;高清格式下载&lt;/a&gt;（mov格式，1280 * 720，480M）&lt;/p&gt;

&lt;h1&gt;面向对象与生活&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：高翔，5173.com&amp;#160; 项目经理。关注前沿技术和技术人员的非技术生活。对面向对象、模式和建模技术有浓厚兴趣，并对游戏设计和图形学方面也比较感兴趣。最近在学习F#，Lua以及关注一些关于职业生涯规划方面的话题。&lt;/p&gt;

&lt;p&gt;简介：面向对象这个话题虽然很热，但与哲学一样，很难给其一个很准确的定义。也因为如此，每个人对它都有自己的理解。本次演讲将从一个实际的例子出发，逐步引入面向对象的三个特征，结合对象的生命周期，以及基于事件的对象扩展方式等方面，探讨其与设计模式，与生活之间的联系。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjA2NjIxMzky/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100928/1/ooad.mov"&gt;高清格式下载&lt;/a&gt;（mov格式，1280 * 720，320M）&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助第二届.NET技术交流会开始报名了！&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助第二届.NET技术交流会即将召开&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助第二届.NET技术交流会 - 演讲录像及下载 &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会：&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;报名&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;预告&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;幻灯片&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;演讲录象及下载&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html#comments</comments>
      <pubDate>Tue, 21 Sep 2010 03:27:11 GMT</pubDate>
      <lastBuildDate>Tue, 21 Sep 2010 03:27:11 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助第二届.NET技术交流会 - 各场演讲幻灯片</title>
      <link>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html</link>
      <guid>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html</guid>
      <description>&lt;p&gt;昨天有160多位朋友参加了&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助的&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;第二届.NET技术交流会&lt;/a&gt;，再次感谢各位对我们的支持。比较遗憾的是，这次的讲师录像方面有着很大问题，我们正在想办法进行修补，希望可以有“差强人意”的结果。现在，大家请在第一时间浏览本次活动新鲜出炉的幻灯片。&lt;/p&gt;

&lt;h1&gt;响应式编程与响应式框架&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：赵劼，盛大创新院，研究员。关注前沿技术，并致力于开源社区与微软平台的组合优化。对函数式编程，并行程序开发，代码之美以及程序员能力与修养等相关问题也有着浓厚的兴趣，同时非常希望能够写程序到60岁。最近致力于F#，Scala语言及mono平台在社区中的推广。&lt;/p&gt;

&lt;p&gt;简介：异步编程改变了我们的编程方式，也为我们带来的许多挑战，同时让一些编程模型重新焕发了生机。与传统的“拉”模型不同，响应式编程将异步事件流视为可观察的集合，这是一种“推”模型。微软为了提高云时代的编程体验而设计了响应式框架，其目的是为了简化复杂事件处理之间混合操作。从中我们了解到一些异步编程的模式与LINQ使用技巧，并可以将这种编程模型普及到JavaScript等其他平台上去。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_5183111"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="响应式编程及框架" href="http://www.slideshare.net/jeffz/reactive-programming-and-framework"&gt;响应式编程及框架&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse5183111" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=rx-100912025913-phpapp01&amp;stripped_title=reactive-programming-and-framework" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse5183111" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=rx-100912025913-phpapp01&amp;stripped_title=reactive-programming-and-framework" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;相关下载：&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/rx-zhaojie.pdf"&gt;幻灯片&lt;/a&gt;、&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/rx-demo-zhaojie.zip"&gt;示例程序&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;大话程序员可用的算法&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：程劭非，盛大创新院，研究员。网名winter，无忧脚本版主。Web前端技术的积极倡导者。学生时代曾经热衷于参加ACM/ICPC。目前工作在Bambook电子书项目，主要负责文字排版和浏览器引擎WebKit相关。之前曾负责在Windows CE系统上的IE开发。&lt;/p&gt;

&lt;p&gt;简介：俗话说“数据结构+算法=程序”，算法是什么？算法书里满篇是看不懂的形式化推导，网上一些&amp;quot;高人&amp;quot;写的关于算法文章高深莫测，大公司面 试最让人讨厌的就是考算法题，“我做了这么多年，跟本在实际开发中就没用过算法！”，算法真的是距离我们如此遥远的东西吗？且听这回演讲， 算法究竟如何影响我们的开发。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_5183325"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="大话程序员可用的算法" href="http://www.slideshare.net/jeffz/programmers-and-algorithms"&gt;大话程序员可用的算法&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse5183325" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100912045100-phpapp01&amp;stripped_title=programmers-and-algorithms" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse5183325" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100912045100-phpapp01&amp;stripped_title=programmers-and-algorithms" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;相关下载：&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/algorithms-chengshaofei.pdf"&gt;幻灯片&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;Windows内核技术介绍&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：潘爱民，盛大创新院专家，微软学者，集团COO专家顾问。长期从事软件和系统技术的研究和开发工作，撰写了大量软件技术文章，并著译了多部经典计算机图书。在MSR/清华等从事多年科研工作，在北大和清华多年执教经验。数学学士学位和计算机科学博士，主要研究领域包括软件设计、信息安全、操作系统和Internet技术。&lt;/p&gt;

&lt;p&gt;简介：Windows操作系统经过二十年的发展，已臻成熟。Microsoft在推动Windows内核方面做了大量工作，譬如于2006年夏季向教育界开放了当时最为先进的内核源代码（Windows Research Kernel）。主讲者在这次讲座中，结合这些可利用的资源，分享对Windows内核研究的体会，尤其将重点讨论Windows中的I/O模型和环境子系统。&lt;/p&gt;

&lt;div style="width:425px" id="__ss_5183269"&gt;&lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/jeffz/win-os-kernel-tech" title="Windows内核技术介绍"&gt;Windows内核技术介绍&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse5183269" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=winos-kernel-techforsnda2010-9-100912042602-phpapp01&amp;stripped_title=win-os-kernel-tech&amp;userName=jeffz" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse5183269" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=winos-kernel-techforsnda2010-9-100912042602-phpapp01&amp;stripped_title=win-os-kernel-tech&amp;userName=jeffz" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="padding:5px 0 12px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;相关下载：&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/win-kernel-panaimin.pdf"&gt;幻灯片&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;面向对象与生活&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：高翔，5173.com&amp;#160; 项目经理。关注前沿技术和技术人员的非技术生活。对面向对象、模式和建模技术有浓厚兴趣，并对游戏设计和图形学方面也比较感兴趣。最近在学习F#，Lua以及关注一些关于职业生涯规划方面的话题。&lt;/p&gt;

&lt;p&gt;简介：面向对象这个话题虽然很热，但与哲学一样，很难给其一个很准确的定义。也因为如此，每个人对它都有自己的理解。本次演讲将从一个实际的例子出发，逐步引入面向对象的三个特征，结合对象的生命周期，以及基于事件的对象扩展方式等方面，探讨其与设计模式，与生活之间的联系。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_5183310"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="面向对象与生活" href="http://www.slideshare.net/jeffz/object-orientation-and-life"&gt;面向对象与生活&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse5183310" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100912044502-phpapp01&amp;stripped_title=object-orientation-and-life" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse5183310" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100912044502-phpapp01&amp;stripped_title=object-orientation-and-life" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;相关下载：&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/ooad-gaoxiang.pdf"&gt;幻灯片&lt;/a&gt;、附加资料“&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/%e4%b8%96%e7%95%8c%e7%9a%84%e5%ae%8f%e8%a7%82%e5%92%8c%e5%be%ae%e8%a7%82.pptx"&gt;世界的宏观和微观&lt;/a&gt;”&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助第二届.NET技术交流会开始报名了！&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助第二届.NET技术交流会即将召开&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助第二届.NET技术交流会 - 各场演讲幻灯片 &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会：&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;报名&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;预告&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;幻灯片&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;演讲录象及下载&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html#comments</comments>
      <pubDate>Sun, 12 Sep 2010 14:32:31 GMT</pubDate>
      <lastBuildDate>Sun, 19 Dec 2010 17:32:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助第二届.NET技术交流会即将召开</title>
      <link>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html</link>
      <guid>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html</guid>
      <description>&lt;p&gt;由&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助的第二届.NET技术大会将于9月11号下午1点召开，本次交流会请到了四位讲师，议题覆盖了响应式编程、算法、面向对象设计及Windows内核等多个方面，其中最为突出的莫过于由潘爱民老师为大家带来的Windows内核方面的话题。我已经看过了各场演讲的幻灯片终稿，也很期待各位讲师在正式演讲中的表现。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/conf-2-600x850.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/conf-2-600x850.jpg" width="300" /&gt;&lt;/a&gt; 

&lt;p&gt;本次大会中，我们还获得了人民邮电出版社&lt;a href="http://www.turingbook.com/Homepage/Default.aspx"&gt;图灵教育&lt;/a&gt;赠送的15册图书、数枚书签、几件T恤，再加上由会务经费购买的两本潘老师的著作，将会作为各场次的奖品，赠送给在交流会中表现积极的听众。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/turingbook.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/turingbook.jpg" width="300" /&gt;&lt;/a&gt; 

&lt;p&gt;同样，我们还请到了&lt;a href="http://www.ku6.com/"&gt;酷六网&lt;/a&gt;的专业摄影师对演讲过程进行全程拍摄，并配合各位讲师自身的屏幕录像，将在后期合成为适合独立观看的演讲视频（&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;效果&lt;/a&gt;），让不能到场的朋友在线或是下载后观看。&lt;/p&gt;
&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/ku6-logo.jpg" /&gt; 

&lt;p&gt;最后，本次活动中我们还邀请了&lt;a href="http://renjian.com/"&gt;人间网&lt;/a&gt;为我们进行线上直播，我们会有专人负责直播信息的管理和发布。您也可以在活动现场，甚至在场外通过会场的无线网络或者短信发布消息。这些信息都会显示在会场的大屏幕上。&lt;/p&gt;
&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/renjian-logo.jpg" /&gt; 

&lt;p&gt;以下是直播效果：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/renjian-live-demo.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/renjian-live-demo.jpg" width="300" /&gt;&lt;/a&gt; 

&lt;p&gt;本次会议的邀请函已经发给各位报名者，请携带邀请函至会议现场签到，没有报名的朋友可以在现场直接报名。&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;关于会议的时间、地点、交通、议程等更多信息，请关注会议的报名信息&lt;/a&gt;。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助第二届.NET技术交流会开始报名了！&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;盛大创新院赞助第二届.NET技术交流会即将召开&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会：&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;报名&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;预告&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;幻灯片&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;演讲录象及下载&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html#comments</comments>
      <pubDate>Thu, 09 Sep 2010 03:14:43 GMT</pubDate>
      <lastBuildDate>Thu, 09 Sep 2010 03:14:43 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>关于静态资源打包后的相对路径问题</title>
      <link>http://blog.zhaojie.me/2010/09/relative-path-in-packed-static-resources.html</link>
      <guid>http://blog.zhaojie.me/2010/09/relative-path-in-packed-static-resources.html</guid>
      <description>&lt;p&gt;将多个静态资源打包为单个资源以减少请求数目，是提高页面加载速度的常用手段。于是上个星期，我就在实现网站静态资源的自动打包功能，原以为是个比较简单的问题，实现起来也没有遇到什么障碍，不过在开发完毕投入使用的时候却让我跌了下眼镜。由于静态资源在打包以后，它们的访问路径势必会改变，这样其他一些依赖于原有路径的资源就访问不到了。这方面最常见的例子，便是CSS样式表中引用的图片路径是相对于CSS文件路径的。当意识到这个问题以后，还真是让人手忙脚乱了一把。&lt;/p&gt;

&lt;p&gt;例如，有一个描述对话框组件的CSS文件，它的原有访问路径为/styles/dialog/core.css，其中有行样式表为url(header.png)，这意味这幅图片的访问路径为/styles/dialog/header.png。同样，在另一个文件/styles/menu/core.css中也有url(bg.png)这样的代码。那么好，如果我们将这两个文件打包在一起，叫做/packed/styles/dialog-menu.css，那么浏览器就会去加载/packed/styles下的header.png和bg.png，为什么？因为url(…)这段代码，在打包后出现的位置就不同了。&lt;/p&gt;

&lt;p&gt;解决这个问题似乎有多种办法，例如将资源文件复制到/packed/styles下面，但这就要处理文件重名的问题。如果使用组件化开发的方式，两个组件之间应该不去考虑资源的重名，否则就产生了依赖。另一种方式，是在CSS内部使用绝对路径，例如在/styles/menu/core.css中使用url(/styles/menu/bg.png)而不是url(bg.png)。这么做可以解决打包上的问题，但是却破坏了组件化的开发方式。例如此时dialog组件就不是独立的了，它的图片路径是死的。而在理想情况下，一个组件的CSS文件和图片等资源应该是统一的，并独立与其他条件，这也是CSS中url(…)含义的设计目的。&lt;/p&gt;

&lt;p&gt;当然，我并不是说这种“组件化”的实践是必须遵守的，如果只是在开发一个独立的项目，便可以将“绝对路径”作为项目中的约定。但是在实际情况下，这种组件化及相对路径的使用是客观存在的。例如jQuery成百上千的插件，它们的CSS样式中一定不会使用绝对路径。那么我们又该如何对它进行打包呢？&lt;/p&gt;

&lt;p&gt;其实路径问题也很容易解决，只要在“打包”的时候修改掉CSS文件内容里的路径即可。例如我们可以知道，如果浏览器是在/packed/styles/dialog-menu.css文件中，访问到原本在/styles/dialog/core.css里的内容，那么原本的url(bg.png)就必须改写成url(/styles/dialog/bg.png)这样的绝对路径，或者是url(../…/styles/dialog/bg.png)这样相对于新地址的“相对路径”。&lt;/p&gt;

&lt;p&gt;这样的修改其实并不复杂，例如在CSS中，似乎需要替换的也只有url(…)这样的地址了，一个普通的正则表达式便可以解决，而比较麻烦的可能是JavaScript文件了。在某些组件化的JavaScript中，它需要的一些资源也是根据脚本文件的地址相对计算出来的。此时，其中的路径可能只是一些普通的字符串而已，没有规律保险的替换规则。如果要解决这个问题，我们可以在项目中形成某种约定，例如，计算相对路径时都不是简单的字符串拼接，而是使用一个函数调用。而这个函数调用，在进行“打包”时便可以看作是一个待替换的标志。&lt;/p&gt;

&lt;p&gt;相对路径的问题似乎就这么解决了，我想前面这段描述也已经足够清楚，比写代码还要清楚，所以暂时就先不提供我在项目里使用的动态打包机制了。不过这里还有个小小的花絮可以一谈。&lt;/p&gt;

&lt;p&gt;负责前端开发的同事很重视静态资源的打包问题，因此也就一直催促我实现这方面的功能。之前他说，他需要一个工具，输入一个配置文件，便可以将指定文件打包，然后在正式站点发布时修改页面上引用的地址就行了。我说这样不行，会死人的。例如，一个组件会在多个页面上使用，那么它的更新会导致多次打包，还要修改多个页面文件，这对于站点的快速升级更新也是一个灾难。此外，在页面上所使用的静态资源信息，还需要在打包工具的配置文件中重复，&lt;a href="http://blog.zhaojie.me/2010/07/dry-principle-everywhere.html"&gt;这也是一种违反DRY的情况&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;我理想中的打包机制要有以下几个条件：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;遵守DRY原则，例如，一张页面需要加载哪些脚本，此类信息只会出现在对应页面中。&lt;/li&gt;

  &lt;li&gt;在开发系统和生产系统上部署同一套代码，而不会为了生产系统修改代码。&lt;/li&gt;

  &lt;li&gt;自动打包，自动版本升级或回退。打包结果易于开发人员进行识别、分析及调试。&lt;/li&gt;

  &lt;li&gt;简单，清晰，性能高。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上周实现的解决方案似乎基本符合这些条件，有机会我再来分享吧。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/09/relative-path-in-packed-static-resources.html#comments</comments>
      <pubDate>Sun, 05 Sep 2010 13:53:26 GMT</pubDate>
      <lastBuildDate>Sun, 05 Sep 2010 13:53:26 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助第二届.NET技术交流会开始报名了！</title>
      <link>http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html</link>
      <guid>http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html</guid>
      <description>&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-in.png" /&gt; 

&lt;p&gt;自上次&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;盛大创新院&lt;/a&gt;赞助的&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;首届.NET技术交流会&lt;/a&gt;到现在已经有两个月，这意味着按照原来的“一季一次”的计划也已经离第二次的活动不远了，考虑到9月份的中秋和国庆假期将工作日和休息日搞的支离破碎，于是交流会的时间会略微有些提前。第二届交流会的形式与上次相同，将为您献上四场高质量的技术演讲。当然这次在内容上有了新的尝试，除了引入了算法及面向对象设计的内容之外，这次更是请到了“传说中的大侠”为大家带来有关Windows内核的深度内容。&lt;font color="#ff0000"&gt;人数暂定为200人，事不宜迟，赶快报名吧&lt;/font&gt;。除了.NET社区的群众以外，也欢迎其他技术社区的朋友前来参与交流。事实上，我组织技术交流会的目的之一便是希望能够促进.NET社区与其他技术社区的交流及相互学习。&lt;/p&gt;

&lt;h1&gt;时间及议程安排&lt;/h1&gt;

&lt;p&gt;第二次交流会定于&lt;strong style="color: red"&gt;2010年9月11日&lt;/strong&gt;（周六）举行，具体时间及议程安排如下。本次依然安排了四场演讲，不过在内容上有了新的尝试：在与一些朋友和同事进行沟通之后，我决定在第二次交流会上引入与算法相关的议题，由创新院内部的ACM/ICPC达人来谈一下日常工作中的算法，如果您对于算法在工作的处境所有疑惑的话，这也是个共同探讨的好机会；此外，我也邀请了&lt;a href="http://www.5173.com/"&gt;5173.com&lt;/a&gt;的技术专家来讨论面向对象设计方面的问题，我看过他过去在内部演讲时使用的PPT，内容很充实；而这次的“重头戏”，便是请到了传说中的&lt;font color="#ff0000"&gt;潘爱民&lt;/font&gt;老师为大家演讲Windows内核方面的问题。潘老师是业界著名技术专家，不久前加入了盛大创新院，可谓“镇院之宝”。事实上，在9月初潘老师便会回到北京工作，而这次他也是为了交流会专门出差至上海，您怎能错过这次机会？&lt;/p&gt;

&lt;table style="text-align: center" border="1" cellspacing="0" cellpadding="5"&gt;&lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;时间&lt;/th&gt;

      &lt;th&gt;议程&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;&lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;12:30 ~ 13:00&lt;/td&gt;

      &lt;td&gt;签到&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;13:00 ~ 14:00&lt;/td&gt;

      &lt;td&gt;响应式编程与响应式框架&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;14:00 ~ 14:10&lt;/td&gt;

      &lt;td&gt;短休&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;14:10 ~ 15:10&lt;/td&gt;

      &lt;td&gt;大话程序员可用的算法&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;15:10 ~ 15:40&lt;/td&gt;

      &lt;td&gt;茶歇&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;15:40 ~ 16:40&lt;/td&gt;

      &lt;td&gt;面向对象与生活&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;16:40 ~ 16:50&lt;/td&gt;

      &lt;td&gt;短休&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;16:50 ~ 17: 50&lt;/td&gt;

      &lt;td&gt;Windows内核技术介绍&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;

&lt;h1&gt;演讲内容&lt;/h1&gt;

&lt;p&gt;以下是关于四场演讲的详细描述。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;响应式编程与响应式框架&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;赵劼，盛大创新院，研究员。关注前沿技术，并致力于开源社区与微软平台的组合优化。对函数式编程，并行程序开发，代码之美以及程序员能力与修养等相关问题也有着浓厚的兴趣，同时非常希望能够写程序到60岁。最近致力于F#，Scala语言及mono平台在社区中的推广。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;异步编程改变了我们的编程方式，也为我们带来的许多挑战，同时让一些编程模型重新焕发了生机。与传统的“拉”模型不同，响应式编程将异步事件流视为可观察的集合，这是一种“推”模型。微软为了提高云时代的编程体验而设计了响应式框架，其目的是为了简化复杂事件处理之间混合操作。从中我们了解到一些异步编程的模式与LINQ使用技巧，并可以将这种编程模型普及到JavaScript等其他平台上去。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;大话程序员可用的算法&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;程劭非，盛大创新院，研究员。网名winter，无忧脚本版主。Web前端技术的积极倡导者。学生时代曾经热衷于参加ACM/ICPC。目前工作在Bambook电子书项目，主要负责文字排版和浏览器引擎WebKit相关。之前曾负责在Windows CE系统上的IE开发。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;俗话说“数据结构+算法=程序”，算法是什么？算法书里满篇是看不懂的形式化推导，网上一些&amp;quot;高人&amp;quot;写的关于算法文章高深莫测，大公司面 试最让人讨厌的就是考算法题，“我做了这么多年，跟本在实际开发中就没用过算法！”，算法真的是距离我们如此遥远的东西吗？且听这回演讲， 算法究竟如何影响我们的开发。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;面向对象与生活&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;高翔，5173.com&amp;#160; 项目经理。关注前沿技术和技术人员的非技术生活。对面向对象、模式和建模技术有浓厚兴趣，并对游戏设计和图形学方面也比较感兴趣。最近在学习F#，Lua以及关注一些关于职业生涯规划方面的话题。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;面向对象这个话题虽然很热，但与哲学一样，很难给其一个很准确的定义。也因为如此，每个人对它都有自己的理解。本次演讲将从一个实际的例子出发，逐步引入面向对象的三个特征，结合对象的生命周期，以及基于事件的对象扩展方式等方面，探讨其与设计模式，与生活之间的联系。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;Windows内核技术介绍&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;潘爱民，盛大创新院专家，微软学者，集团COO专家顾问。长期从事软件和系统技术的研究和开发工作，撰写了大量软件技术文章，并著译了多部经典计算机图书。在MSR/清华等从事多年科研工作，在北大和清华多年执教经验。数学学士学位和计算机科学博士，主要研究领域包括软件设计、信息安全、操作系统和Internet技术。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;Windows操作系统经过二十年的发展，已臻成熟。Microsoft在推动Windows内核方面做了大量工作，譬如于2006年夏季向教育界开放了当时最为先进的内核源代码（Windows Research Kernel）。主讲者在这次讲座中，结合这些可利用的资源，分享对Windows内核研究的体会，尤其将重点讨论Windows中的I/O模型和环境子系统。&lt;/p&gt;

&lt;h1&gt;地点&lt;/h1&gt;

&lt;p&gt;本次交流会举办地为&lt;strong style="color: red"&gt;上海市浦东新区碧波路888号畅星大厦&lt;/strong&gt;（地铁二号线张江高科站下，步行10分钟可达）3楼会议厅，地图如下：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-map.png"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-map.png" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;鸟瞰图：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-hybrid.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-hybrid.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;畅星大厦外观：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-building.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-building.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;会场实景照片：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-room.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-room.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;会场容量可以容纳超过200人，希望到时候不会显得太过空旷。:)&lt;/p&gt;

&lt;h1&gt;报名信息&lt;/h1&gt;
&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/sign-up-now.jpg" /&gt; 

&lt;p&gt;本次交流会&lt;a href="http://www.diaochapai.com/survey504720"&gt;现已开始报名，请填写报名表&lt;/a&gt;，&lt;strike&gt;报名截止日期为2010年9月5日&lt;/strike&gt;人数已满，多谢大家支持。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;盛大创新院赞助第二届.NET技术交流会开始报名了！ &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助第二届.NET技术交流会即将召开&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会：&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;报名&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;预告&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;幻灯片&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;演讲录象及下载&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html#comments</comments>
      <pubDate>Mon, 16 Aug 2010 03:02:30 GMT</pubDate>
      <lastBuildDate>Mon, 16 Aug 2010 03:02:30 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>关于Windows频繁打开关闭端口时出现的问题</title>
      <link>http://blog.zhaojie.me/2010/08/lack-of-dynamic-ports-when-frequently-open-and-close-socket.html</link>
      <guid>http://blog.zhaojie.me/2010/08/lack-of-dynamic-ports-when-frequently-open-and-close-socket.html</guid>
      <description>&lt;p&gt;最近事情很多，人也懒，东西看了不少，也想到过一些东西，但就是懒得写。现在记录一下前两个星期做一个压力测试时出现的现象，希望重开一个好头。简单地说，这是个从Windows Server连接Linux下的MongoDB服务时出现的问题。MongoDB使用的是自定义的二进制协议，客户端使用普通的TCP连接进行连接后再读写数据。在以前的测试中，我使用的都是建立少量连接，每个连接进行多次操作，而这次则是对“应用程序”进行压力测试，因此需要不断地开启及关闭连接——频率大约是每秒4、500次吧。&lt;/p&gt;

&lt;p&gt;我使用的环境是Windows Web Server 2008 R2，MongoDB部署在Cent OS上，双方都是64位操作系统。压力测试刚开启时一切顺利，性能也比较令人满意，但是不久后便会抛出这样的异常：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;由于系统缓冲区空间不足或队列已满，不能执行套接字上的操作。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;一开始我以为是程序里有哪个地方没有释放连接，于是检查了程序代码，觉得没有问题；后来又直接使用&lt;a href="http://github.com/samus/mongodb-csharp"&gt;mongodb-csharp&lt;/a&gt;进行频繁连接关闭，结果还是出现了同样的错误，于是我又怀疑是驱动本身的问题，但是看了看讨论组中似乎又没有人汇报过这个问题；于是我又换了个思路，使用了Java平台上的驱动写了个简单的测试程序，居然还是得到了这个错误。由此我确定了两点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;这很可能不是mongodb-csharp这个驱动程序的问题。当然，要确定这一点还需要更多测试，例如在mono上使用这个驱动。&lt;/li&gt;

  &lt;li&gt;这是操作系统方面的问题，因为.NET和Java都给出了同样的错误信息，甚至和当前程序的语言文化设置无关。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;还有一个细节：在直接使用驱动进行插入操作的时候，发现无论使用多少线程同时进行，最终永远是在插入了16370-16380条记录之后停止，这意味着每次都是打开关闭了确定次数之后出现的错误，这很有可能是一个操作系统限制所致的结果。因此，我使用这段错误信息在网上寻找解决方案，原因有很多，大都不是我需要的。顺便一提，只有中文的错误信息真是很难找到合适的结果，因此我不得不通过几个关键字，再连蒙带猜地得到了错误的标准英文翻译：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;有了这段信息，找起答案来就简单多了，例如&lt;a href="http://support.microsoft.com/kb/929851/en-us"&gt;KB上找到了这样一条记录&lt;/a&gt;，说是在Windows Vista及2008中，Tcp/IP动态端口的范围调整到49152至65535，做一个简单的减法可以发现我们可以使用16384个接口，和我们之前看到的记录数量大致相同，基本可以确定是频繁地打开关闭操作造成客户端的动态端口用尽的问题。KB上也给出了解决方法，只要使用netsh命令便可以进行设置。&lt;/p&gt;

&lt;p&gt;不过有意思的是，我在此之前还找到了&lt;a href="http://support.microsoft.com/kb/196271/en-us/"&gt;另一条记录&lt;/a&gt;，说是在HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters下面可以增加一个MaxUserPort参数，指定程序可以使用的端口范围，它的默认值是5000，也就是端口范围是1025至5000，这也是上一条记录中说Windows之前的端口策略，它的“适用范围”已经不包括2008系统，但我鬼使神差地将MaxUserPort设置为65534（十进制）之后，原本只能插入16000多条记录的程序已经能够插入数万条，这意味着修改的确生效了。&lt;/p&gt;

&lt;p&gt;当然，这么做还是没有解决问题，总不见得插入这么多条记录之后还是失败吧。其实&lt;a href="http://support.microsoft.com/kb/196271/en-us/"&gt;第二条记录&lt;/a&gt;里还写到，有一个TcpTimedWaitDelay参数，表示一个关闭后的端口等待多久之后可以重新使用，顺着这个信息我找到了《&lt;a href="http://www.microsoft.com/downloads/details.aspx?displaylang=en&amp;amp;FamilyID=06c60bfe-4d37-4f50-8587-8b68d32fa6ee"&gt;TCP/IP Registry Values for Windows Server 2008&lt;/a&gt;》这样一篇文章，描述Vista与2008系统中各种TCP/IP相关的参数，其中自然包括了TcpTimedWaitDelay，它的默认值为120，表示端口关闭后120秒才能重新使用。&lt;/p&gt;

&lt;p&gt;于是我们来算一下，假设有60000个端口可用，如果在120秒内消耗完毕，则每秒最多使用500个端口，这远远低于&lt;a href="http://blog.zhaojie.me/2010/02/mongodb-tokyo-tyrant-benchmark-2-concurrent-insert.html"&gt;MongoDB的性能瓶颈&lt;/a&gt;，甚至接近了一个Web应用程序的需求——根据压力测试，我们单台Web服务器每秒可以处理接近200个动态请求，这意味着平均每个请求只能使用2.5个连接。根据文档，我将TcpTimedWaitDelay设成最短的30秒，这意味着我们可以每秒开启关闭2000个端口，平均每个请求使用10个连接。够了。&lt;/p&gt;

&lt;p&gt;这个问题就这样解决了，说实话很简单，也就是个“知道就能解决”的配置问题。当然现在这个方式并不算太理想，更好的方式应该是利用连接池，这样便不会开启/关闭大量的TCP/IP连接，默认的端口数量也已经足够了，更重要的是这也可以省下很大的开销——因为经过测试，即使是最复杂的ASP.NET页面，只要不涉及MongoDB，每秒也能处理6500多个请求，而目前每秒200个动态请求，从数字上看也远低于MongoDB的能力，我们有理由相信目前的性能似乎是卡在连接的打开/关闭上了。&lt;/p&gt;

&lt;p&gt;只可惜目前mongodb-csharp的连接池实现有bug，用于清理连接的维护进程居然会让造成明显的中断，甚至在频繁使用十几分钟后还抛出了异常。有机会的话我再看看吧，但我总觉得它目前的实现过于复杂了，我估计都可以说是面向对象的“经典”使用案例了。&lt;/p&gt;

&lt;p&gt;最后再来一提，话说我目前使用的是64位的&lt;a href="http://www.microsoft.com/windowsserver2008/en/us/2008-web.aspx"&gt;Windows Web Server 2008 R2&lt;/a&gt;系统，功能强大，&lt;a href="http://www.microsoft.com/windowsserver2008/en/us/pricing.aspx"&gt;价格便宜&lt;/a&gt;，&lt;a href="http://www.microsoft.com/windowsserver2008/en/us/licensing-web-server.aspx"&gt;授权宽松&lt;/a&gt;，最多允许使用到32GB内存，作为Web服务器我很满意。&lt;/p&gt;

&lt;h1&gt;相关阅读&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.cnblogs.com/shanyou/archive/2010/07/09/1774075.html"&gt;TCP - WAIT状态及其对繁忙的服务器的影响&lt;/a&gt;&lt;li&gt;
&lt;li&gt;&lt;a href="http://blog.csdn.net/zhengyun_ustc/archive/2010/08/10/5802025.aspx"&gt;Windows频繁打开和关闭端口可能引发的问题&lt;/a&gt;&lt;li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/08/lack-of-dynamic-ports-when-frequently-open-and-close-socket.html#comments</comments>
      <pubDate>Mon, 09 Aug 2010 10:03:20 GMT</pubDate>
      <lastBuildDate>Mon, 09 Aug 2010 10:03:20 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>无处不在的DRY原则</title>
      <link>http://blog.zhaojie.me/2010/07/dry-principle-everywhere.html</link>
      <guid>http://blog.zhaojie.me/2010/07/dry-principle-everywhere.html</guid>
      <description>&lt;p&gt;DRY（Don't Repeat Yourself）原则，一般是指在写代码的时候尽量避免重复的实现。违反DRY原则导致的坏处很容易理解，例如维护困难，修改时一旦遗漏就会产生不易察觉的问题。不过其实这个原则并非写代码的时候独有，其实在各处都有类似的情况。当然，现在还是不谈“生活”，而是简单谈谈我在工作中发生的一些事情。&lt;/p&gt;
&lt;img src="http://img.zhaojie.me/blog/tortoisesvn_logo_hor468x64.png" /&gt; 

&lt;p&gt;您认识这只小乌龟吗？相信大部分Windows下的程序员一定认识，这是个著名的SVN的客户端工具&lt;a href="http://tortoisesvn.tigris.org/"&gt;TortoiseSVN&lt;/a&gt;，与Windows下的资源管理器集成，提供了各种可视化的操作，深受广大人民群众喜爱。更重要的是，这个功能可谓十分强大的工具是免费的。&lt;/p&gt;
&lt;img src="http://img.zhaojie.me/blog/logo_visual_svn.png" /&gt; 

&lt;p&gt;那么，您用过&lt;a href="http://www.visualsvn.com/visualsvn/"&gt;VisualSVN&lt;/a&gt;吗？VisualSVN是另一个和SVN相关的工具，它的主要功能是与Visual Studio集成，可以让开发人员在Visual Studio里进行SVN控制。VisualSVN是收费工具，不过&lt;a href="http://www.visualsvn.com/visualsvn/purchase/mvp/"&gt;对Microsoft MVP提供免费的个人版&lt;/a&gt;——说到这点，其实不少收费工具对MVP有此类政策，如著名的单元测试工具&lt;a href="http://site.typemock.com/"&gt;TypeMock Isolator&lt;/a&gt;也&lt;a href="http://blog.typemock.com/2008/05/free-typemock-isolator-license-for.html"&gt;对MVP提供免费的License&lt;/a&gt;。MVP同学们可以多多挖掘这类福利，毕竟MVP在世界范围内还是十分有价值的荣誉。此外，VisualSVN还提供了&lt;a href="http://www.visualsvn.com/server/"&gt;VisualSVN Server&lt;/a&gt;（标准版免费），方便人们搭建和管理SVN服务器，不过这是题外话了。&lt;/p&gt;

&lt;p&gt;值得一提的是，VisualSVN是基于TortoiseSVN的工具，它与SVN的交互完全利用TortoiseSVN的功能，包括各式对话框。这么看来，VisualSVN的主要工作都是由一个免费工具全权完成，于是我就纳闷了，那么VisualSVN的这么点“附加”功能，就值得收取50美金的费用？当然收费与否，收费多少是软件开发者的自由，不过为什么会有那么多人去购买VisualSVN呢？在Visual Studio里操作SVN，和在资源管理器里操作SVN又有什么大不了的优势呢？VisualSVN值那么多钱吗？&lt;/p&gt;

&lt;p&gt;由于我近几年来一直遵守“软件无盗版”的原则，于是对于收费工具不是很上心，即便如VisualSVN这种可以免费使用的软件，我也倾向于使用其免费的替代品。总之，我没有使用VisualSVN，当然使用SVN时几乎还都是借助于TortoiseSVN的。于是，这样便造成了一些问题。例如，我时不时会遗漏一些新建的文件，没有将它们提交至SVN中。于是，另一台机器获取代码之后很显然就无法编译通过了。为什么会造成这种情况？因为我往往在Visual Studio里添加了一个文件，它会出现在解决方案树中，然后继续编辑修改测试，然后就用TortoiseSVN提交了。但是，我还没有把新文件添加到SVN索引里面去！于是我本地看上去完全正常，而SVN Repository里面的数据却是不完整的。&lt;/p&gt;

&lt;p&gt;这就是TortoiseSVN的局限性所在。TortoiseSVN只关心硬盘上的文件，而不会关注这些文件在逻辑上有什么关联。在上面的例子中，问题就是“新文件”与“项目文件”其实应该是同步的（如csproj里面的文件也应该在SVN中出现），但这样的重复就需要由我们手动维护了。于是，我们为项目添加一个文件时就要做两件事情：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;在Visual Studio添加文件 &lt;/li&gt;

  &lt;li&gt;在TortoiseSVN里添加（选择）文件 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在文件的改名或删除时也会出现类似的情况。其实我已经很小心了，但是这样的事情还是难以完全避免。一个文件添加（改名或删除）两次，这不也是违反了DRY原则吗？于是我才意识到VisualSVN的重要性，我认为它最关键的一点便是帮我们在Visual Studio与SVN索引之间进行同步，例如在解决方案树中添加文件会自动出现在SVN索引中（即下图的红点，这表明该文件“已经加入索引”但“尚未提交”）：&lt;/p&gt;
&lt;img src="http://img.zhaojie.me/blog/visualsvn-screenshot-small.png" /&gt; 

&lt;p&gt;在平时开发过程中和DRY相关的地方有很多，例如之前有同事说要将压缩后的JS和CSS文件一并提交到SVN中，我说这样会同步死的，于是最后在某个发布过程中添加了一个步骤对这些资源进行自动压缩。还有，一些程序集可能会用于多个应用程序，于是它们的配置文件也会随之出现在各处，给维护造成困难。最后还是使用了一些最简的做法：只在一处维护配置数据，然后再构建过程中复制到各个地方去。这一切都是不能松懈的。&lt;/p&gt;

&lt;p&gt;当然，现在我主要使用的是基于命令行的git（虽然有&lt;a href="http://code.google.com/p/tortoisegit/"&gt;TortoiseGIT&lt;/a&gt;），在每次提交前都会git status看看情况，这样遗漏文件的情况反而不多见了。虽然命令行的git可能不如TortoiseSVN这种GUI工具来的方便，但它可以较方便地暴露出潜在错误，这也是一种颇为可观的优势。&lt;/p&gt;

&lt;p&gt;PS：&lt;span style="color: red"&gt;如果您身处上海，熟悉git，有自信能表现出它的优点并愿意这么去做，请联系我&lt;/span&gt;（推特帐号：&lt;a href="https://twitter.com/jeffz_cn"&gt;@jeffz_cn&lt;/a&gt;）。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/07/dry-principle-everywhere.html#comments</comments>
      <pubDate>Thu, 22 Jul 2010 09:00:18 GMT</pubDate>
      <lastBuildDate>Thu, 22 Jul 2010 09:00:18 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>为什么我支持托管运行时（虚拟机）</title>
      <link>http://blog.zhaojie.me/2010/07/why-i-support-managed-runtime-virtual-machine-based-program.html</link>
      <guid>http://blog.zhaojie.me/2010/07/why-i-support-managed-runtime-virtual-machine-based-program.html</guid>
      <description>&lt;p&gt;最近博客园上在炒关于C#性能的问题，其实应该说是.NET性能的问题，其中某位仁兄提出，他希望C#能够直接编译为原生代码，而不是在CLR这样一个托管运行时上执行，因为虚拟机啊，JIT什么的性能差。后来发到TL上以后，&lt;a href="http://groups.google.com/group/pongba/msg/ea9169ac259c4ff4"&gt;也有朋友认为&lt;/a&gt;，“基于虚拟机的语言都是大公司为了利益在推动，说白了就是政治”，因此“对C#提高性能的建议感到可笑，因为它本来就不是用来开发高性能程序的”，&lt;a href="http://groups.google.com/group/pongba/msg/cec200f41555a0c1"&gt;再有&lt;/a&gt;，“C、C++已经明确不和这些后进争所谓的‘容易开发’的头衔”，那么其他语言为什么要和C++它们比较性能呢？我是托管运行时，或者虚拟机的忠实拥护者，这里谈一下我在这方面的看法。&lt;/p&gt;

&lt;p&gt;我并不反对编译为原生代码的语言，尤其是C语言，它的意义在于提供了一种对硬件完全控制的手段，对硬件提供了一种最直接的抽象，几乎可以映射到最终流程控制方式，因此无可替代。C++作为C语言的超集，提供了更丰富的抽象能力（如面向对象和模版化），只是语言本身过于复杂，超过了以我的智商可以承受的范围，因此我学了几次都没怎么学会，现在更是忘得差不多了。不过我认为，越来越多的语言会构建在托管平台上，而不是直接编译成原生代码。因为一个统一的托管运行时会带来很多好处。&lt;/p&gt;

&lt;p&gt;首先，统一的运行时提供了跨平台的能力，Java便是一例典型。.NET上有&lt;a href="http://www.mono-project.com/"&gt;mono&lt;/a&gt;，使用也很广泛，也有不少如&lt;a href="http://unity3d.com/"&gt;Unity3D&lt;/a&gt;，&lt;a href="http://do.davebsd.com/"&gt;Gnome DO&lt;/a&gt;等成功案例。Novell，包括其他一些公司也在销售基于mono的商业产品（如&lt;a href="http://monotouch.net/"&gt;MonoTouch&lt;/a&gt;及&lt;a href="http://www.infragistics.com/dotnet/netadvantage/aspnet.aspx"&gt;Infragistics的ASP.NET Controls组件&lt;/a&gt;），我本身也在两年多前就在生产环境上使用了mono，您现在看到的这个博客也是基于Ubuntu Server、mono 2.6 、Apache以及微软开源的ASP.NET MVC 2构建的。虽说从某些层面（如API兼容性）上说，mono的跨平台性远不如Java平台，但它也是一个比较成熟的执行环境，并具备相当程度的跨平台能力——尤其是在mono上开发，MS .NET上运行的时候（&lt;a href="http://blog.zhaojie.me/2010/06/is-cross-platform-a-lie-or-not.html"&gt;虽然我不建议这么做&lt;/a&gt;）。如今支持mono的产品、类库数不胜数，我时常调侃道，“如果您的产品不支持mono，还真不好意思和人打招呼”。只可惜，不少人都用一些“不是亲娘生的”类似的调调来否定mono，在我看来没有经过调查研究的看法只能属于“臆断”，而且更是一种FUD了。&lt;/p&gt;

&lt;p&gt;即便退一步来说，我们不“跨操作系统”吧。有人说，.NET就支持Windows么，何必搞个虚拟机，还JIT那么麻烦。但事实上，“跨平台”并非指的是简单的“跨操作系统”，而是“跨执行环境”，如Silverlight。事实上，跨计算机体系结构本身也是种跨平台（当然，操作系统其实已经进行了一定的统一抽象了）。因此，虚拟机的目的，是为上层执行体抽象出了统一的运行环境——这其实还是跨平台，这平台不仅仅是指操作系统，整体运行环境之间的差异也是运行时所“抽象”的一部分。比如在并发环境中，不同CPU架构的流水线上的乱序方式不一样，同样的代码执行的效果就可能不同。最典型的例子，便是&lt;a href="http://blog.zhaojie.me/2009/09/double-check-failure-answer.html"&gt;JVM之前的内存一致性模型控制的比较宽松，导致经典的double check模式在某些CPU上是会失败的&lt;/a&gt;。现在&lt;a href="http://blogs.sun.com/dave/entry/java_memory_model_concerns_on"&gt;Java标准也变得严格了&lt;/a&gt;，和.NET CLR一样避免了Store Reordering。这意味着在某些CPU上，会在特定的地方加上&lt;a href="http://en.wikipedia.org/wiki/Memory_barrier"&gt;Memory Barrier&lt;/a&gt;保证执行效果的一致性。在我看来这是更好的可移植性。C++或是C语言等实现“可移植性”的方式，往往是通过为不同环境提供不同的编译器，生成不同的结果，而且会使用“宏”等方式，在代码里写出有平台针对性的代码。&lt;/p&gt;

&lt;p&gt;有了统一的运行时，也可以让多语言互操作更为容易，如果没有JVM或CLR，就很难像现在这么轻松地在Scala/Java/Jython/JRuby，或是C#/F#/IronPython/IronRuby，甚至是未来的语言之间进行直接地互操作，更难做到“无缝集成”了。在合适的场景下选用合适的语言，是提高生产力的重要手段。如果没有JVM平台，就很难&lt;a href="http://blog.zhaojie.me/2010/04/why-java-sucks-and-csharp-rocks-slides-final-version.html"&gt;使用Scala来代替Java这样的劣质语言&lt;/a&gt;，而现在Scala便能够保证充分利用Java平台上类库等积淀。&lt;/p&gt;

&lt;p&gt;有了“多语言”，那么便会引出虚拟机的另一个作用：让语言实现者和虚拟机实现者的工作可以分离开来，各自优化。虚拟机的实现者可以尽力优化虚拟机的各种表现，为虚拟机加入各种优化措施，而无需让虚拟机的上层语言分别调整。例如JVM性能提高之后，Scala、Java、Jython、JRuby等语言的性能都可以提高，否则各种语言分别优化的代价太高了（当然某些情况还是需要特殊对待的）。要说这方面的实例，在.NET平台上有个开源的组件&lt;a href="http://dlr.codeplex.com/"&gt;DLR（动态语言运行时）&lt;/a&gt;，是在CLR上提供编写动态语言的统一辅助类库。以前它有个缺点，便是启动速度比较慢，因为动态代码的JIT耗时较长，而动态语言的很多场景是“即用即抛”的。后来，DLR增加了一个优化策略，便是一开始直接对抽象语法树直接解释执行，而执行多次之后才使用后台线程将代码JIT成原生代码。有了如此改进，基于DLR的动态语言，如IronRuby和IronPython的启动速度都提高了。&lt;/p&gt;

&lt;p&gt;虚拟机/运行时本身可以做的优化也很多，如果真觉得性能不够，那么完全可以在运行前在本地编译成native code，这和直接从源代码变成native code从结果上看没什么区别。但是现实是，很少有人去这么做，因为这么做往往只是节省了JIT的开销，对性能提高效果不大。在不同环境中，此外还有各种优化，比如使用解释执行，而不是JIT以次节省内存消耗，或是在运行时回收JIT的代码（印象中在.NET Compact Framework里有这样的策略，求详情），或是在运行时根据代码逻辑进行二次编译。下面会谈一个例子。另一种典型的优化，一直在研究却还没有真正实现的，虚拟机便是“自动并行”。关于这点，&lt;a href="http://blog.zhaojie.me/2010/05/trends-and-future-directions-in-programming-languages-by-anders-6-concurrency.html"&gt;Anders在上次的演讲中多次提到过&lt;/a&gt;，要实现这点还需要有各种支持，如声明式编程，提供“无副作用”标记，甚至在语言级别的支持等等。&lt;/p&gt;

&lt;p&gt;之前那位朋友提到，C/C++已经明确不与后进语言比生产力了，后进语言也没办法和它们比较性能。对这个观点我持保留意见，因为基于虚拟机的做法，其优化空间还有很多，理论上也可以做到更为彻底，在许多情况下性能完全有超越静态编译语言的可能。这是许多人的看法，而事实也是如此，&lt;a href="http://blog.zhaojie.me/2010/01/talk-about-code-performance-2-compiler.html"&gt;在某些场景下Java的性能也已经超过了C++&lt;/a&gt;。如回到上面的二次编译优化，对于性能优化也大有好处。举一个简单的例子，面向对象语言会出现很多虚方法调用（尤其是在符合一些关于设计的最佳实践时，如“基于抽象编程”），调用需方法需要查方法表找方法入口，最普通的做法就是必须每次根据对象的实际类型查找方法表，找到地址，然后调用。伪代码如下：&lt;/p&gt;

&lt;pre class="code"&gt;根据实际类型找到函数入口
调用&lt;/pre&gt;

&lt;p&gt;但是在实际执行过程中，可能有某个特定类型出现的次数特别多，甚至完全只会出现一种具体类型的实例（抽象只是为了扩展性或是单元测试等等），但因为虚方法的调用语义，我们就无法对这次调用进行内联优化等等。不过在托管的运行时中，如果发现某个特性类型出现次数特别多，则还是可能将那个类型的方法内联进来，然后只在“不是那个实例”的情况下才去查找方法入口。伪代码就是：&lt;/p&gt;

&lt;pre class="code"&gt;if (对象为不是类型A) then
    根据实际类型找到函数入口
    调用
else
    执行内联后的类型A的代码
end&lt;/pre&gt;

&lt;p&gt;类似这种的优化不是空中楼阁，它们在HotSpot（即&lt;strike&gt;Sun&lt;/strike&gt;Oracle的JVM）是确切实现的。Hotspot的JIT，尤其是加上-server开关时，它的优化会变得十分激进，而CLR的JIT与它相比就像是静态编译器了。C++在科学运算中性能高，往往是靠大量的inline和精细资源控制，但是这说实话都还是静态的“手动优化”，如果比实际工程的真实运行情况，比如上面说的虚方法跳转，还真不见得C++可以超过托管代码。虚拟机做的则是动态优化，对于长期执行的代码甚至可能执行的越来越高效，还能够针对环境改变作出调整。总而言之，托管代码可以运用的优化策略实在太多了。随着技术发展，托管代码的速度可以进一步提高，而静态编译代码的空间就小得多了。&lt;/p&gt;

&lt;p&gt;对此&lt;a href="http://www.cnblogs.com/MiloYip"&gt;Milo Yip&lt;/a&gt;老大&lt;a href="http://groups.google.com/group/pongba/msg/b7a92cedaeee721c"&gt;做过一些补充&lt;/a&gt;，他指出这方面不同的C++ compiler有不同優化方法，例如VC2008開始有的&lt;a href="http://msdn.microsoft.com/en-us/library/e7k32f4k.aspx"&gt;Profile-Guided Optimizations&lt;/a&gt;：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Virtual Call Speculation - If a virtual call, or other call through a function pointer, frequently targets a certain function, a profile-guided optimization can insert a conditionally-executed direct call to the frequently-targeted function, and the direct call can be inlined.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我简单地思考了一下，从理论上说，各种优化策略都可以通过静态编译的方式写入原生代码，因此JIT能做的事情，native code理论上都能做。不过，这就意味着要在每个程序中带上“负责优化”的代码，而不光是程序的“功能代码”。用虚拟机，就意味着所有的程序都共享了虚拟机的这么一套优化机制。如果虚拟机的优化手段改进了，那么所有基于虚拟机的程序都能获利，而静态编译的程序，往往只能靠“重新编译”来得到优化了。&lt;/p&gt;

&lt;p&gt;此外，对于面向对象语言来说，虚函数是很常见的，虚函数之间各种组合调用，分支、路径也特别多，到处都可能使用虚函数。我“猜想”，像VC这样的编译器，为每个虚函数调用之处都提供了基于profiling的内联优化是不太现实的。而且，为每个虚函数调用的地方都提供“内联”和“不内联”两种版本也不太可能。而基于虚拟机的话，它就可以动态的进行各种优化，每段JIT后的代码都可以回收再动态生成，这种优化是静态编译难以比拟的。&lt;/p&gt;

&lt;p&gt;诚然，就目前来说，就性能方面，许多情况下还是静态编译配合手动细节优化可能获得更好的效率，但利用托管运行时获得的好处也是非常可观的，而且我也一直没有遇到过这方面的效率问题。同时，我认为托管运行时的愿景也十分现实可靠，因此就我看来，托管代码是未来趋势，除了越来越小的特定领域，越来越多的程序和语言会构建于托管平台上。事实上，正因为上面这些好处（例如独立优化），越来越多的语言也开始加入虚拟机这样的策略了。例如&lt;a href="http://rubini.us/"&gt;Rubinius&lt;/a&gt;，&lt;a href="http://codespeak.net/pypy/dist/pypy/doc/"&gt;PyPy&lt;/a&gt;等新的Ruby和Python语言实现，其实都是有个虚拟机这样的机制存在。&lt;/p&gt;

&lt;p&gt;最后我再多说一句：我并不是说追求性能的做法不对，我也从来不会说追求性能本身没有意义。但是我不同意说“考察托管语言性能”没有意义，更是完全不同意说托管代码虚拟机“本身意义就不大”，“完全是大公司在推动”，或是“政治因素”云云。技术就是技术，这些技术上投入了无数天才的精力和创意，这不是什么“政治”之类说法就能带过的。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/07/why-i-support-managed-runtime-virtual-machine-based-program.html#comments</comments>
      <pubDate>Thu, 01 Jul 2010 03:37:23 GMT</pubDate>
      <lastBuildDate>Thu, 01 Jul 2010 03:37:23 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助首届.NET技术交流会 - 演讲录像及下载</title>
      <link>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html</link>
      <guid>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html</guid>
      <description>&lt;p&gt;经过几天的努力，终于将&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助的&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;首届.NET技术交流会&lt;/a&gt;的演讲录像制作完成了。本来在现在的高清视频以外，我还想像Channel 9一样提供一些低码率的格式下载，但多次尝试都以失败告终，各中滋味难以言喻。因此目前只能给大家提供mov格式的高清视频下载，对于Windows下各类强大的播放器都不成问题。您也可以在线观看这些视频，不过上传至优酷后，发现除了清晰度较低外，甚至还有音画不同步的问题。我正在联系&lt;a href="http://www.ku6.com/"&gt;酷六网&lt;/a&gt;，会尽快用上质量更好的视频。&lt;/p&gt;

&lt;h1&gt;F#语言对异步程序设计的支持&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;赵劼，盛大创新院，研究员&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;如今的Web应用、 Silverlight以及各种分布式系统让异步解决方案有了更进一步的需求。F#是微软.NET平台上的函数式编程语言，并添加了不少让并行及异步编程变得有趣且轻松的特性。本次演讲将讨论F#的核心概念，并探讨F#中的不可变性、函数式设计、异步工作流、代理等特性是如何应对真实应用中的异步挑战的。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjEzMDM1MDgw/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100624/1/fsharp-async-20100619-high.mov"&gt;高清视频下载&lt;/a&gt;（mov格式，1280 * 720，495M）&lt;/p&gt;

&lt;h1&gt;Rails: Better Framework, Better Life&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;吕国宁，Intridea.com，高级工程师&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;RoR是Ruby on Rails的缩写，是一个用于编写Web应用的框架。它基于Ruby语言，给开发人员提供了强大便利的框架支持。Ruby有很多优点，但是一直以来其流行范围仅局限于日本。2004年，当Rails框架横空出世，让人们认识到了一个更符合实际需要并且高效的web框架，在其出现不久就受到了业内的广泛关注。吕国宁将结合自己三年的Rails开发经验，给大家介绍一些Rails的优点，背后的设计文化，以及Rails的前景发展等内容。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMTg0MTI0NjU2/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100624/1/ror-20100619-high.mov"&gt;高清视频下载&lt;/a&gt;（mov格式，1280 * 720，432M）&lt;/p&gt;

&lt;h1&gt;大众点评网的技术变迁之路&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;王宏，大众点评网，架构师&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;大众点评网从2003年创建以来，已经经历了7个年头，在技术方面从最初构建时期的简单的、低成本的方案，到发展阶段不断“痛苦”的转型演变，到目前比较复杂的技术架构，大众点评网的技术团队一直在关注业界新技术，力求提高可用性、降低成本、优化用户体验，并针对“点评”这一第三方参与的特点，摸索出一些特有的解决方案，借此机会希望能够分享给大家。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMTg0MTA5MTY0/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100624/1/dianping-20100619-high.mov"&gt;高清视频下载&lt;/a&gt;（mov格式，1280 * 720，486M）&lt;/p&gt;

&lt;h1&gt;Q &amp;amp; A&lt;/h1&gt;

&lt;p&gt;按照计划，原本还会有一场关于C#的演讲，但该场的讲师由于突然有急事只得作罢。于是我在最后增加了“演讲嘉宾问答”的环节，您可以在&lt;a href="http://www.ku6.com/"&gt;酷六网&lt;/a&gt;上进行在线观看。&lt;/p&gt;

&lt;p&gt;第1段：&lt;/p&gt;
&lt;embed src="http://player.ku6.com/refer/QFX3kb6ZJ8Wqge-w/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="always" allowfullscreen="true" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;第2段：&lt;/p&gt;
&lt;embed src="http://player.ku6.com/refer/wmD_kenujbB6gxnM/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="always" allowfullscreen="true" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助首届.NET技术交流会开始报名了！&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助首届.NET技术交流会即将召开&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助首届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会 - 演讲录像及下载 &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html#comments</comments>
      <pubDate>Thu, 24 Jun 2010 06:40:51 GMT</pubDate>
      <lastBuildDate>Thu, 24 Jun 2010 06:40:51 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助首届.NET技术交流会 - 各场演讲幻灯片</title>
      <link>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html</link>
      <guid>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html</guid>
      <description>&lt;p&gt;今天是近期最热的一天，气温高达35度，异常闷热，但是依然有160多位朋友冒着酷暑参加了&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助的&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;首届.NET技术交流会&lt;/a&gt;，这让我感到很欣慰，因此这里首先要感谢大家的支持。我刚才浏览了一下三场演讲的桌面录像，可谓异常完美，现在只等酷六网的摄影师的讲师录像到手，便可以合成为最终的演讲视频了，希望能够尽快展示给大家。不过现在，大家可以在第一时间浏览本次活动新鲜出炉的幻灯片。&lt;/p&gt;

&lt;h1&gt;F#语言对异步程序设计的支持&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;赵劼，盛大创新院，研究员 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;如今的Web应用、 Silverlight以及各种分布式系统让异步解决方案有了更进一步的需求。F#是微软.NET平台上的函数式编程语言，并添加了不少让并行及异步编程变得有趣且轻松的特性。本次演讲将讨论F#的核心概念，并探讨F#中的不可变性、函数式设计、异步工作流、代理等特性是如何应对真实应用中的异步挑战的。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_4546454"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="F#语言对异步程序设计的支持" href="http://www.slideshare.net/jeffz/f-4546454"&gt;F#语言对异步程序设计的支持&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse4546454" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=f-100619111719-phpapp01&amp;stripped_title=f-4546454" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse4546454" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=f-100619111719-phpapp01&amp;stripped_title=f-4546454" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;h1&gt;Rails: Better Framework, Better Life&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;吕国宁，Intridea.com，高级工程师 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;RoR是Ruby on Rails的缩写，是一个用于编写Web应用的框架。它基于Ruby语言，给开发人员提供了强大便利的框架支持。Ruby有很多优点，但是一直以来其流行范围仅局限于日本。2004年，当Rails框架横空出世，让人们认识到了一个更符合实际需要并且高效的web框架，在其出现不久就受到了业内的广泛关注。吕国宁将结合自己三年的Rails开发经验，给大家介绍一些Rails的优点，背后的设计文化，以及Rails的前景发展等内容。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_4545483"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="Better Framework Better Life" href="http://www.slideshare.net/jeffz/better-framework-better-life"&gt;Better Framework Better Life&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse4545483" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=better-framework-better-life-100619101625-phpapp01&amp;stripped_title=better-framework-better-life" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse4545483" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=better-framework-better-life-100619101625-phpapp01&amp;stripped_title=better-framework-better-life" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;h1&gt;大众点评网的技术变迁之路&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;王宏，大众点评网，架构师 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;大众点评网从2003年创建以来，已经经历了7个年头，在技术方面从最初构建时期的简单的、低成本的方案，到发展阶段不断“痛苦”的转型演变，到目前比较复杂的技术架构，大众点评网的技术团队一直在关注业界新技术，力求提高可用性、降低成本、优化用户体验，并针对“点评”这一第三方参与的特点，摸索出一些特有的解决方案，借此机会希望能够分享给大家。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_4545503"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="大众点评网的技术变迁之路" href="http://www.slideshare.net/jeffz/ss-4545503"&gt;大众点评网的技术变迁之路&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse4545503" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100619101718-phpapp02&amp;stripped_title=ss-4545503" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse4545503" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100619101718-phpapp02&amp;stripped_title=ss-4545503" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;

&lt;/div&gt;&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助首届.NET技术交流会开始报名了！&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助首届.NET技术交流会即将召开&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会 - 各场演讲幻灯片 &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助首届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html#comments</comments>
      <pubDate>Sat, 19 Jun 2010 15:48:23 GMT</pubDate>
      <lastBuildDate>Sat, 19 Jun 2010 15:48:23 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助首届.NET技术交流会即将召开</title>
      <link>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html</link>
      <guid>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html</guid>
      <description>&lt;p&gt;由&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助的首届.NET技术大会将于6月19号下午1点召开，本次交流会请到了四位讲师，议题覆盖了F#、C#、Rails及架构等多个方面。我已经看过了各场演讲的幻灯片草稿，也很期待各位讲师在正式演讲中的表现。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/conf-1-600x850.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/conf-1-600x850.jpg" width="300" /&gt;&lt;/a&gt; 

&lt;p&gt;本次大会中，我们还获得了人民邮电出版社&lt;a href="http://www.turingbook.com/Homepage/Default.aspx"&gt;图灵教育&lt;/a&gt;赠送的20册图书，将会作为奖品赠送给在交流会中表现积极的听众。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/turingbook.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/turingbook.jpg" width="300" /&gt;&lt;/a&gt; 

&lt;p&gt;此外，我们还请到了&lt;a href="http://www.ku6.com/"&gt;酷六网&lt;/a&gt;的专业摄影师对演讲过程进行全程拍摄，并配合各位讲师自身的屏幕录像，将在后期合成为适合独立观看的演讲视频，让不能到场的朋友在线或是下载后观看。&lt;/p&gt;

&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/ku6-logo.jpg" /&gt; 

&lt;p&gt;本次会议的邀请函已经发给各位报名者，请携带邀请函至会议现场签到，&lt;span style="color:red;"&gt;没有报名的朋友可以在现场直接报名&lt;/span&gt;。&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;关于会议的时间、地点、交通、议程等更多信息，请关注会议的报名信息&lt;/a&gt;。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助首届.NET技术交流会开始报名了！&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会即将召开 &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助首届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助首届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html#comments</comments>
      <pubDate>Thu, 17 Jun 2010 03:45:06 GMT</pubDate>
      <lastBuildDate>Thu, 17 Jun 2010 03:45:06 GMT</lastBuildDate>
    </item>
  </channel>
</rss>
