<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <title>老赵点滴 - 追求编程之美</title>
    <link>http://blog.zhaojie.me/</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>Wed, 28 Dec 2011 04:45:46 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/cutting-edge/">技术尝鲜</category>
      <title>HTML Metro开发里的数据绑定（1）：WinJS.Binding.List</title>
      <link>http://blog.zhaojie.me/2012/05/metro-html-binding-1-winjs-binding-list.html</link>
      <guid>http://blog.zhaojie.me/2012/05/metro-html-binding-1-winjs-binding-list.html</guid>
      <description>&lt;p&gt;前段时间接触了一些WPF开发方面内容，了解如何使用MVVM模式将界面与模型几乎彻底分离开来，只通过其强大的绑定功能连接两者，让人叹为观止。Win8的Metro开发支持使用XAML（配合C#，C++等语言）或是HTML（配合JavaScript），前者的控件和数据的绑定已经相当完备，而后者一直没有一个标准的模型。之前我也简单了解过如&lt;a href="http://knockoutjs.com/"&gt;Knockout&lt;/a&gt;这样的绑定支持与MVVM模式实现，理论上说也完全可以在Metro开发里使用，但其实Metro开发本身也已经提供了一些内置的绑定支持，基本对应于XAML/C#开发里涉及到的ObservableCollection与INotifyPropertyChanged。&lt;/p&gt;

&lt;p&gt;ObservableCollection的功能是作为元素的集合，绑定到一个控件上去，当集合发生改变时，将会触发一个CollectionChanged事件，通知控件（或其他监听者）发生了什么样的变化：&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;ObservableCollection&lt;/span&gt;&amp;lt;T&amp;gt;
{
    &lt;span style="color: blue"&gt;public event &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotifyCollectionChangedEventHandler &lt;/span&gt;CollectionChanged;
}

&lt;span style="color: blue"&gt;public delegate void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotifyCollectionChangedEventHandler&lt;/span&gt;(&lt;span style="color: blue"&gt;object &lt;/span&gt;sender, &lt;span style="color: #2b91af"&gt;NotifyCollectionChangedEventArgs &lt;/span&gt;e);

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotifyCollectionChangedEventArgs &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;EventArgs
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotifyCollectionChangedAction &lt;/span&gt;Action { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IList &lt;/span&gt;NewItems { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;NewStartingIndex { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IList &lt;/span&gt;OldItems { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;OldStartingIndex { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }
}

&lt;span style="color: blue"&gt;public enum &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotifyCollectionChangedAction
&lt;/span&gt;{
    Add = 0,
    Remove = 1,
    Replace = 2,
    Move = 3,
    Reset = 4,
}&lt;/pre&gt;

&lt;p&gt;ObservableCollection在Metro开发中的JavaScript对应物为&lt;a href="http://msdn.microsoft.com/en-us/library/windows/apps/hh700774.aspx"&gt;WinJS.Binding.List&lt;/a&gt;类型，它包含许多成员，可惜目前文档奇缺，多亏Visual Studio 2011对JavaScript的强大提示功能，可以让我们了解到该类型有哪些成员，例如：&lt;/p&gt;
&lt;img alt="WinJS.Binding.List事件" src="http://img.zhaojie.me/blog/metro-html-binding/01.png" /&gt; 

&lt;p&gt;这样我们便能知道如何实现一些常见的场景：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;list = &lt;span style="color: blue"&gt;new &lt;/span&gt;WinJS.Binding.List();
list.push(&lt;span style="color: maroon"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;); &lt;span style="color: green"&gt;// iteminserted
&lt;/span&gt;list.push(&lt;span style="color: maroon"&gt;&amp;quot;World&amp;quot;&lt;/span&gt;); &lt;span style="color: green"&gt;// iteminserted
&lt;/span&gt;list.push(&lt;span style="color: maroon"&gt;&amp;quot;How are you?&amp;quot;&lt;/span&gt;); &lt;span style="color: green"&gt;// iteminserted
&lt;/span&gt;list.setAt(0, &lt;span style="color: maroon"&gt;&amp;quot;Hello World&amp;quot;&lt;/span&gt;); &lt;span style="color: green"&gt;// itemchanged
&lt;/span&gt;list.move(0, 2); &lt;span style="color: green"&gt;// itemmoved
&lt;/span&gt;list.slice(1, 2); &lt;span style="color: green"&gt;// itemremoved
&lt;/span&gt;list.notifyMutated(0); &lt;span style="color: green"&gt;// itemmutated
&lt;/span&gt;list.notifyReload(); &lt;span style="color: green"&gt;// reload&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;与ObservableCollection的集中式CollectionChanged事件相比，WinJS.Binding.List的事件更为分散。此外，还有两个事件是需要外部方法notifyMutated和notifyReload来触发的。其中itemsMutated事件比较特殊，它似乎是在通知集合中某个元素自身状态产生了变化，在XAML开发中这事儿完全不关ObservableCollection管，而是后面要说的，由元素自己实现INotifyPropertyChanged接口来负责。&lt;/p&gt;

&lt;p&gt;WinJS.Binding.List的各种接口和JavaScript数组基本一致，此外它还包括一些有趣的功能，例如它的&lt;a href="http://msdn.microsoft.com/en-us/library/windows/apps/hh700764.aspx"&gt;构造函数&lt;/a&gt;可以传递一个数组以及一个options参数，如果options.proxy为true，则直接将该数组作为内部的存储结构。另外，其实这个集合会为其中每个元素分配一个key，通过&lt;a href="http://msdn.microsoft.com/en-us/library/windows/apps/hh700753.aspx"&gt;getItem(index)&lt;/a&gt;可以得到一个key/value对，通过&lt;a href="http://msdn.microsoft.com/en-us/library/windows/apps/hh700750.aspx"&gt;getItemFromKey&lt;/a&gt;可以通过key得到元素。交换数组中元素的顺序并不会改变key对应的元素，因此这个功能在某些情况下会很有帮助。&lt;/p&gt;

&lt;p&gt;更有特色的可能是&lt;a href="http://msdn.microsoft.com/en-us/library/windows/apps/hh700741.aspx"&gt;createFiltered&lt;/a&gt;，&lt;a href="http://msdn.microsoft.com/en-us/library/windows/apps/hh700742.aspx"&gt;createGrouped&lt;/a&gt;和&lt;a href="http://msdn.microsoft.com/en-us/library/windows/apps/hh700743.aspx"&gt;createSorted&lt;/a&gt;方法，他们都是在原有集合的基础上，返回一个（或一组）新的WinJS.Binding.List对象，每个新集合都和原有集合的内容保持“一致”。例如，基于一个过滤条件由createFiltered方法创建出来的新集合，假如向原集合添加一个满足该过滤条件的元素，则这个元素也会添加到新的容器里，同时触发新容器的iteminserted事件。同样，其他对原集合的修改也会影响新的集合。要实现类似的功能并不困难，只不过比较麻烦，如果您有兴趣，也不妨实验下。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;HTML Metro开发里的数据绑定（1）：WinJS.Binding.List &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2012/05/metro-html-binding-1-winjs-binding-list.html#comments</comments>
      <pubDate>Tue, 01 May 2012 14:53:21 GMT</pubDate>
      <lastBuildDate>Tue, 01 May 2012 14:53:21 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>关于C#中async/await中的异常处理（下）</title>
      <link>http://blog.zhaojie.me/2012/04/exception-handling-in-csharp-async-await-2.html</link>
      <guid>http://blog.zhaojie.me/2012/04/exception-handling-in-csharp-async-await-2.html</guid>
      <description>&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2012/04/exception-handling-in-csharp-async-await-1.html"&gt;上一篇文章&lt;/a&gt;里我们讨论了某些async/await的用法中出现遗漏异常的情况，并且谈到该如何使用WhenAll辅助方法来避免这种情况。WhenAll辅助方法将会汇总一系列的任务对象，一旦其中某个出错，则会抛出“其中一个”异常。那么究竟是哪个异常？如果我们要处理所有的异常怎么办？我们这次就来详细讨论await操作在异常分派时的相关行为。&lt;/p&gt;

&lt;h1&gt;await抛出异常时的行为&lt;/h1&gt;

&lt;p&gt;要理解await的行为，还是从理解Task对象的异常表现开始。Task对象有一个Exception属性，类型为AggregateException，在执行成功的情况下该属性返回null，否则便包含了“所有”出错的对象。既然是AggregateException，则意为着可能包含多个子异常，这种情况往往会在任务的父子关系中出现，&lt;a href="http://msdn.microsoft.com/en-us/library/dd997415.aspx"&gt;具体情况可以参考MSDN中的相关说明&lt;/a&gt;。在许多情况下一个Task内部只会出现一个异常，此时这个AggregateException的InnerExceptions属性自然也就只一个元素。&lt;/p&gt;

&lt;p&gt;Task对象本身还有一个Wait方法，它会阻塞当前执行代码，直到任务完成。在出现异常的时候，它会将自身的AggregateException抛出：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;try
&lt;/span&gt;{
    t.Wait();
}
&lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;AggregateException &lt;/span&gt;ex)
{
    ...
}&lt;/pre&gt;

&lt;p&gt;Wait方法是“真阻塞”，而await操作则是使用阻塞语义的代码实现非阻塞的效果，这个区别一定要分清。与Wait方法不同的是，await操作符效果并非是“抛出”Task对象上的Exception属性，而只是抛出这个AggregateException对象上的“其中一个”元素。我在内部邮件列表中询问这么做的设计考虑，C#开发组的同学回答道，这个决策在内部也经历了激烈的争论，最终的选择这种方式而不是直接抛出Task对象上的AggregateException是为了避免编写出冗余的代码，并让代码与传统同步编程习惯更为接近。&lt;/p&gt;

&lt;p&gt;他们举了一个简单的示例，假如一个Task对象t可能抛出两种异常，现在的错误捕获方式为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;try
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;await &lt;/span&gt;t1;
}
&lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;NotSupportedException &lt;/span&gt;ex)
{
    ...
}
&lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;NotImplementedException &lt;/span&gt;ex)
{
    ...
}
&lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
{
    ...
}&lt;/pre&gt;

&lt;p&gt;假如await操作抛出的是AggregateException，那么代码就必须写为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;try
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;await &lt;/span&gt;t1;
}
&lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;AggregateException &lt;/span&gt;ex)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;innerEx = ex.InnerExceptions[0];

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(innerEx &lt;span style="color: blue"&gt;is &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotSupportedException&lt;/span&gt;)
    {
        ...
    }
    &lt;span style="color: blue"&gt;else if &lt;/span&gt;(innerEx &lt;span style="color: blue"&gt;is &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;)
    {
        ...
    }
    &lt;span style="color: blue"&gt;else
    &lt;/span&gt;{
        ...
    }
}&lt;/pre&gt;

&lt;p&gt;显然前者更贴近传统的同步编程习惯。但是问题在于，如果这个Task中包含了多个异常怎么办？之前的描述是抛出“其中一个”异常，对于开发者来说，“其中一个”这种模糊的说法自然无法令人满意，但事实的确如此。从内部邮件列表中的讨论来看，C#开发团队提到他们“故意”不提供文档说明究竟会抛出哪个异常，因为他们并不想做出这方面的约束，因为这部分行为一旦写入文档，便成为一个规定和限制，为了类库的兼容性今后也无法对此做出修改。&lt;/p&gt;

&lt;p&gt;他们也提到，如果单论目前的实现，await操作会从Task.Exception.InnerExceptions集合中挑出第一个异常，并对外“抛出”，这是System.Runtime.CompilerServices.TaskAwaiter类中定义的行为。但是既然这并非是“文档化”的固定行为，开发人员也尽量不要依赖这点。&lt;/p&gt;

&lt;h1&gt;WhenAll的异常汇总方式&lt;/h1&gt;

&lt;p&gt;其实这个话题跟async/await的行为没有任何联系，WhenAll返回的是普通的Task对象，TaskAwaiter也丝毫不关心当前等待的Task对象是否来自于WhenAll，不过既然WhenAll是最常用的辅助方法之一，也顺便将其讲清楚吧。&lt;/p&gt;

&lt;p&gt;WhenAll得到Task对象，其结果是用数组存放的所有子Task的结果，而在出现异常时，其Exception属性返回的AggregateException集合会包含所有子Task中抛出的异常。请注意，每个子Task中抛出的异常将会存放在它自身的AggregateException集合中，WhenAll返回的Task对象将会“按顺序”收集各个AggregateException集合中的元素，而并非收集每个AggregateException对象。&lt;/p&gt;

&lt;p&gt;我们使用一个简单的例子来理解这点：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;Task &lt;/span&gt;all = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
&lt;span style="color: blue"&gt;try
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;await &lt;/span&gt;(all = &lt;span style="color: #2b91af"&gt;Task&lt;/span&gt;.WhenAll(
        &lt;span style="color: #2b91af"&gt;Task&lt;/span&gt;.WhenAll(
            ThrowAfter(3000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Exception&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Ex3&amp;quot;&lt;/span&gt;)),
            ThrowAfter(1000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Exception&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Ex1&amp;quot;&lt;/span&gt;))),
        ThrowAfter(2000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Exception&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Ex2&amp;quot;&lt;/span&gt;))));
}
&lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
{
    ...
}&lt;/pre&gt;

&lt;p&gt;这段代码使用了嵌套的WhenAll方法，总共会出现三个异常，按其抛出的时机排序，其顺序为Ex1，Ex2及Ex3。那么请问：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;catch语句捕获的异常是哪个？ &lt;/li&gt;

  &lt;li&gt;all.Exception这个AggregateException集合中异常按顺序是哪些？ &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;结果如下：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;catch语句捕获的异常是Ex3，因为它是all.Exception这个AggregateException集合中的第一个元素，但还是请牢记这点，这只是当前TaskAwaiter所实现的行为，而并非是由文档规定的结果。 &lt;/li&gt;

  &lt;li&gt;all.Exception这个AggregateException集合中异常有三个，按顺序是Ex3，Ex1和Ex2。WhenAll得到的Task对象，是根据输入的Task对象顺序来决定自身AggreagteException集合中异常对象的存放顺序。这个顺序跟异常的抛出时机没有任何关系。 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这里我们也顺便可以得知，如果您不想捕获AggregateException集合中的“其中一个”异常，而是想处理所有异常的话，也可以写这样的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;Task &lt;/span&gt;all = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
&lt;span style="color: blue"&gt;try
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;await &lt;/span&gt;(all = &lt;span style="color: #2b91af"&gt;Task&lt;/span&gt;.WhenAll(
        ThrowAfter(1000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Exception&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Ex1&amp;quot;&lt;/span&gt;)),
        ThrowAfter(2000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Exception&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Ex2&amp;quot;&lt;/span&gt;))));
}
&lt;span style="color: blue"&gt;catch
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;ex &lt;span style="color: blue"&gt;in &lt;/span&gt;all.Exception.InnerExceptions)
    {
        ...
    }
}&lt;/pre&gt;

&lt;p&gt;当然，这里使用Task.WhenAll作为示例，是因为这个Task对象可以明确包含多个异常，但并非只有Task.WhenAll返回的Task对象才可能包含多个异常，例如Task对象在创建时指定了父子关系，也会让父任务里包含各个子任务里出现的异常。&lt;/p&gt;

&lt;h1&gt;假如异常未被捕获&lt;/h1&gt;

&lt;p&gt;最后再来看一个简单的问题，我们一直在关注一个async方法中“捕获”异常的行为，假如异常没有成功捕获，直接对外抛出的时候，对任务本身的有什么影响呢？且看这个示例：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static async &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Task &lt;/span&gt;SomeTask()
{
    &lt;span style="color: blue"&gt;try
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;await &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Task&lt;/span&gt;.WhenAll(
            ThrowAfter(2000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotSupportedException&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Ex1&amp;quot;&lt;/span&gt;)),
            ThrowAfter(1000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Ex2&amp;quot;&lt;/span&gt;)));
    }
    &lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;) { }
}

&lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
{
    _watch.Start();

    SomeTask().ContinueWith(t =&amp;gt; PrintException(t.Exception));

    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.ReadLine();
}&lt;/pre&gt;

&lt;p&gt;这段代码的输出结果是：&lt;/p&gt;

&lt;pre class="code"&gt;System.AggregateException: One or more errors occurred. ---&amp;gt; System.NotSupportedException: Ex1
   at AsyncErrorHandling.Program.&lt;throwafter&gt;d__0.MoveNext() in ...\Program.cs:line 16
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at AsyncErrorHandling.Program.&lt;sometask&gt;d__3.MoveNext() in ...\Program.cs:line 30
   --- End of inner exception stack trace ---
---&amp;gt; (Inner Exception #0) System.NotSupportedException: Ex1
   at AsyncErrorHandling.Program.&lt;throwafter&gt;d__0.MoveNext() in ...\Program.cs:line 16
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at AsyncErrorHandling.Program.&lt;sometask&gt;d__3.MoveNext() in ...\Program.cs:line 30&amp;lt;---&lt;/pre&gt;

&lt;p&gt;AggregateException的打印内容不那么容易读，我们可以关注它Inner Exception #0这样的信息。从时间上说，Ex2先于Ex1抛出，而catch的目标是NotImplementedException。但从之前的描述我们可以知道，WhenAll返回的Task内部的异常集合，与各异常抛出的时机没有关系，因此await操作符抛出的是Ex1，是NotSupportedException，而它不会被catch到，因此SomeTask返回的Task对象也会包含这个异常——也仅仅是抛出这个异常，而Ex2对于外部就不可见了。&lt;/p&gt;

&lt;p&gt;如果您想在外部处理所有的异常，则可以这样：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;Task &lt;/span&gt;all = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
&lt;span style="color: blue"&gt;try
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;await &lt;/span&gt;(all = &lt;span style="color: #2b91af"&gt;Task&lt;/span&gt;.WhenAll(
        ThrowAfter(2000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotSupportedException&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Ex1&amp;quot;&lt;/span&gt;)),
        ThrowAfter(1000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Ex2&amp;quot;&lt;/span&gt;))));
}
&lt;span style="color: blue"&gt;catch
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;throw &lt;/span&gt;all.Exception;
}&lt;/pre&gt;

&lt;p&gt;此时打印的结果便是一个AggregateException包含着另一个AggregateException，其中包含了Ex1和Ex2。为了“解开”这种嵌套关系，AggregateException也提供了一个Flatten方法，可以将这种嵌套完全“铺平”，例如：&lt;/p&gt;

&lt;pre class="code"&gt;SomeTask().ContinueWith(t =&amp;gt; PrintException(t.Exception.&lt;span style="color: red"&gt;Flatten()&lt;/span&gt;));&lt;/pre&gt;

&lt;p&gt;此时打印的结果便直接是一个AggregateException包含着Ex1与Ex2了。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2012/04/exception-handling-in-csharp-async-await-1.html"&gt;关于C#中async/await中的错误处理（上）&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;关于C#中async/await中的错误处理（下） &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2012/04/exception-handling-in-csharp-async-await-2.html#comments</comments>
      <pubDate>Thu, 12 Apr 2012 01:26:43 GMT</pubDate>
      <lastBuildDate>Thu, 12 Apr 2012 03:50:07 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>关于C#中async/await中的异常处理（上）</title>
      <link>http://blog.zhaojie.me/2012/04/exception-handling-in-csharp-async-await-1.html</link>
      <guid>http://blog.zhaojie.me/2012/04/exception-handling-in-csharp-async-await-1.html</guid>
      <description>&lt;p&gt;在同步编程中，一旦出现错误就会抛出异常，我们可以使用try…catch来捕捉异常，而未被捕获的异常则会不断向上传递，形成一个简单而统一的错误处理机制。不过对于异步编程来说，异常处理一直是件麻烦的事情，这也是C#中async/await或是&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;等异步编程模型的优势之一。但是，同步的错误处理机制，并不能完全避免异步形式的错误处理方式，这需要一定实践规范来保证，至少我们需要了解async/await到底是如何捕获和分发异常的。在开发Jscex的过程中，我也在C#内部邮件邮件列表中了解了很多关于TPL和C#异步特性的问题，错误处理也是其中之一。在此记录一下吧。&lt;/p&gt;

&lt;h1&gt;使用try…catch捕获异常&lt;/h1&gt;

&lt;p&gt;首先我们来看下这段代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static async &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Task &lt;/span&gt;ThrowAfter(&lt;span style="color: blue"&gt;int &lt;/span&gt;timeout, &lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
{
    &lt;span style="color: blue"&gt;await &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Task&lt;/span&gt;.Delay(timeout);
    &lt;span style="color: blue"&gt;throw &lt;/span&gt;ex;
}

&lt;span style="color: blue"&gt;static void &lt;/span&gt;PrintException(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
{
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: maroon"&gt;&amp;quot;Time: {0}\n{1}\n============&amp;quot;&lt;/span&gt;, _watch.Elapsed, ex);
}

&lt;span style="color: blue"&gt;static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stopwatch &lt;/span&gt;_watch = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stopwatch&lt;/span&gt;();

&lt;span style="color: blue"&gt;static async &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Task &lt;/span&gt;MissHandling()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;t1 = ThrowAfter(1000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotSupportedException&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Error 1&amp;quot;&lt;/span&gt;));
    &lt;span style="color: blue"&gt;var &lt;/span&gt;t2 = ThrowAfter(2000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Error 2&amp;quot;&lt;/span&gt;));

    &lt;span style="color: blue"&gt;try
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;await &lt;/span&gt;t1;
    }
    &lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;NotSupportedException &lt;/span&gt;ex)
    {
        PrintException(ex);
    }
}

&lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
{
    _watch.Start();

    MissHandling();

    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.ReadLine();
}&lt;/pre&gt;

&lt;p&gt;这段代码的输出如下：&lt;/p&gt;

&lt;pre class="code"&gt;Time: 00:00:01.2058970
System.NotSupportedException: Error 1
   at AsyncErrorHandling.Program.&lt;throwafter&gt;d__0.MoveNext() in ...\Program.cs:line 16
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at AsyncErrorHandling.Program.&lt;misshandling&gt;d__3.MoveNext() in ...\Program.cs:line 33
============&lt;/pre&gt;

&lt;p&gt;在MissingHandling方法中，我们首先使用ThrowAfter方法开启两个任务，它们会分别在一秒及两秒后抛出两个不同的异常。但是在接下来的try中，我们只对t1进行await操作。很容易理解，t1抛出的NotSupportedException将被catch捕获，耗时大约为1秒左右——当然，从上面的数据可以看出，其实t1在被“捕获”时已经耗费了1.2时间，误差较大。这是因为程序刚启动，TPL内部正处于“热身”状态，在调度上会有较大开销。这里反倒是另一个问题倒更值得关注：t2在两秒后抛出的NotImplementedException到哪里去了？&lt;/p&gt;

&lt;h1&gt;未捕获的异常&lt;/h1&gt;

&lt;p&gt;C#的async/await功能基于TPL的Task对象，每个await操作符都是“等待”一个Task完成。在之前（或者说如今）的TPL中，Task对象的析构函数会查看它的Exception对象有没有被“访问”过，如果没有，且Task对象出现了异常，则会抛出这个异常，最终导致的结果往往便是进程退出。因此，我们必须小心翼翼地处理每一个Task对象的错误，不得遗漏。&lt;a href="http://msdn.microsoft.com/en-us/library/hh367887(v=VS.110).aspx#core"&gt;在.NET 4.5中这个行为被改变了&lt;/a&gt;，对于任何没有被检查过的异常，便会触发TaskSchedular.UnobservedTaskException事件——如果您不监听这个事件，未捕获的异常也就这么无影无踪了。&lt;/p&gt;

&lt;p&gt;为此，我们对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: #2b91af"&gt;TaskScheduler&lt;/span&gt;.UnobservedTaskException += (_, ev) =&amp;gt; PrintException(ev.Exception);

    _watch.Start();

    MissHandling();

    &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;)
    {
        &lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;.Sleep(1000);
        &lt;span style="color: #2b91af"&gt;GC&lt;/span&gt;.Collect();
    }
}&lt;/pre&gt;

&lt;p&gt;改造有两点，一是响应TaskScheduler.UnobservedTaskException，这自然不必多说。还有一点便是不断地触发垃圾回收，以便Finalizer线程调用析构函数。如今这段代码除了打印出之前的信息之外，还会输出以下内容：&lt;/p&gt;

&lt;pre class="code"&gt;Time: 00:00:03.0984560
System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---&amp;gt; System.NotImplementedException: Error 2
   at AsyncErrorHandling.Program.d__0.MoveNext() in ...\Program.cs:line 16
   --- End of inner exception stack trace ---
---&amp;gt; (Inner Exception #0) System.NotImplementedException: Error 2
   at AsyncErrorHandling.Program.d__0.MoveNext() in ...\Program.cs:line 16&amp;lt;---
============&lt;/pre&gt;

&lt;p&gt;从上面的信息中可以看出，UnobservedTaskException事件并非在“抛出”异常后便立即触发，而是在某次垃圾收集过程，从Finalizer线程里触发并执行。从中也不难得出这样的结论：便是该事件的响应方法不能过于耗时，更加不能阻塞，否则便会对程序性能造成灾难性的影响。&lt;/p&gt;

&lt;p&gt;那么假如我们要同时处理t1和t2中抛出的异常该怎么做呢？此时便是Task.WhenAll方法上场的时候了：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static async &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Task &lt;/span&gt;BothHandled()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;t1 = ThrowAfter(1000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotSupportedException&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Error 1&amp;quot;&lt;/span&gt;));
    &lt;span style="color: blue"&gt;var &lt;/span&gt;t2 = ThrowAfter(2000, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NotImplementedException&lt;/span&gt;(&lt;span style="color: maroon"&gt;&amp;quot;Error 2&amp;quot;&lt;/span&gt;));
    
    &lt;span style="color: blue"&gt;try
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;await &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Task&lt;/span&gt;.WhenAll(t1, t2);
    }
    &lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;NotSupportedException &lt;/span&gt;ex)
    {
        PrintException(ex);
    }
}&lt;/pre&gt;

&lt;p&gt;如果您执行这段代码，会发现其输出与第一段代码相同，但其实不同的是，第一段代码中t2的异常被“遗漏”了，而目前这段代码t1和t2的异常都被捕获了，只不过await语句仅仅“抛出”了“其中一个”异常而已。&lt;/p&gt;

&lt;p&gt;WhenAll是一个辅助方法，它的输入是n个Task对象，输出则是个返回它们的结果数组的Task对象。新的Task对象会在所有输入全部“结束”后才完成。在这里“结束”的意思包括成功和失败（取消也是失败的一种，即抛出了OperationCanceledException）。换句话说，假如这n个输入中的某个Task对象很快便失败了，也必须等待其他所有输入对象成功或是失败之后，新的Task对象才算完成。而新的Task对象完成后又可能会有两种表现：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;所有输入Task对象都成功了：则返回它们的结果数组。 &lt;/li&gt;

  &lt;li&gt;至少一个输入Task对象失败了：则抛出“其中一个”异常。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;全部成功的情况自不必说，那么在失败的情况下，什么叫做抛出“其中一个”异常？如果我们要处理所有抛出的异常该怎么办？下次我们继续讨论这方面的问题。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;关于C#中async/await中的异常处理（上） &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2012/04/exception-handling-in-csharp-async-await-2.html"&gt;关于C#中async/await中的异常处理（下）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2012/04/exception-handling-in-csharp-async-await-1.html#comments</comments>
      <pubDate>Wed, 11 Apr 2012 01:15:55 GMT</pubDate>
      <lastBuildDate>Mon, 14 May 2012 14:16:32 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/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/news/">新闻信息</category>
      <title>增强Jscex目标代码可调试性：已包含输入代码</title>
      <link>http://blog.zhaojie.me/2012/02/jscex-debug-improved-target-code-contains-source-now.html</link>
      <guid>http://blog.zhaojie.me/2012/02/jscex-debug-improved-target-code-contains-source-now.html</guid>
      <description>&lt;p&gt;我们先来看一段Jscex生成的目标代码，您能看出其输入代码是什么样的吗？&lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition-before.png"&gt;&lt;img alt="target code without source" src="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition-before.png" width="450" /&gt; &lt;/a&gt;

&lt;p&gt;其实这段代码的原始输入是&lt;a href="files.zhaojie.me/jscex/samples/async/sorting-animations.html?quick"&gt;排序算法动画&lt;/a&gt;中“快速排序”的Partition方法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(array, begin, end) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;i = begin;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;j = end;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;pivot = array[Math.floor((begin + end) / 2)];

    &lt;span style="color: blue"&gt;while &lt;/span&gt;(i &amp;lt;= j) {
        &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;r = $await(compareAsync(array[i], pivot));
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;lt; 0) { i++; } &lt;span style="color: blue"&gt;else &lt;/span&gt;{ &lt;span style="color: blue"&gt;break&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;r = $await(compareAsync(array[j], pivot));
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;gt; 0) { j--; } &lt;span style="color: blue"&gt;else &lt;/span&gt;{ &lt;span style="color: blue"&gt;break&lt;/span&gt;; }
        }

        &lt;span style="color: blue"&gt;if &lt;/span&gt;(i &amp;lt;= j) {
            $await(swapAsync(array, i, j));
            i++;
            j--;
        }
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;i;
}&lt;/pre&gt;

&lt;p&gt;是不是显得比较繁琐？但其实Jscex生成代码的模式很简单，您只要忽视Combine，Delay等辅助方法，剩下的代码几乎都跟原始代码一一对应了。不过用肉眼来识别原始代码多少还是需要些脑力，于是即将发布的0.6.0版Jscex编译器便直接在目标代码的左侧生成对应的原始代码了：&lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition.png"&gt;&lt;img alt="target code with source" src="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition.png" width="450" /&gt; &lt;/a&gt;

&lt;p&gt;当然左侧原始代码并非与您输入的完全一致，它毕竟也是经过解析和重新生成的结果，并需要与目标代码逐行对应，多少会有些格式上的改变，但此时您想设置断点也好，调试也罢，只要把注意力更多放在左侧的输入代码上，而不用肉眼去识别相对复杂的Monad代码了。&lt;/p&gt;

&lt;p&gt;当然，复杂和简单总是相对的，Jscex的目标代码虽然比输入要复杂许多，但至少还能辨别出原始代码，也能通过肉眼识别给转化回去。如果您观察Jscex的同类产品&lt;a href="https://github.com/Sage/streamlinejs"&gt;streamline.js&lt;/a&gt;的话，就会发现它的目标代码真是如天书一般：&lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition-streamline.png"&gt;&lt;img alt="streamlined" src="http://img.zhaojie.me/blog/jscex-target-with-src/qsort-partition-streamline.png" width="450" /&gt; &lt;/a&gt;

&lt;p&gt;这是因为streamline.js则使用了传统的状态机展开，Jscex使用的是Monad生成方式，其主要目的之一便是考虑到目标代码的可读性。Jscex是基于成熟理论，并经过精心设计的类库，并没有很多人想象中的那么多问题。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2012/02/jscex-debug-improved-target-code-contains-source-now.html#comments</comments>
      <pubDate>Thu, 16 Feb 2012 15:30:03 GMT</pubDate>
      <lastBuildDate>Thu, 16 Feb 2012 15:32:01 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/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>Node.js中相同模块是否会被加载多次？</title>
      <link>http://blog.zhaojie.me/2011/12/same-node-module-load-multiple-times.html</link>
      <guid>http://blog.zhaojie.me/2011/12/same-node-module-load-multiple-times.html</guid>
      <description>&lt;p&gt;JavaScript的包管理一直是个软肋，我很难想象，连这一基础功能都没有做好的语言，现在居然会如此流行。在我看来，其实JavaScript流行的最主要元素还是把持了浏览器，而Web应用在这几年掀起了一阵腥风血雨。任意一门语言，只要能像JavaScript般被标准采纳，被所有浏览器接受，它都能“成功”，真是所谓宿命。&lt;/p&gt;

&lt;p&gt;当然，既然它流行了，既然人们想要用它做大事了，就要开始为它制定一些模块的约定。这几天我为&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;实现&lt;a href="https://github.com/amdjs/amdjs-api/wiki/AMD"&gt;AMD规范&lt;/a&gt;的时候，便深刻体会到模块化的优势。Node.js也使用了&lt;a href="http://wiki.commonjs.org/"&gt;CommonJS&lt;/a&gt;模块机制，最近&lt;a href="http://www.infoq.com/cn/articles/nodejs-module-mechanism"&gt;在InfoQ上有一篇文章讨论了这方面的问题&lt;/a&gt;。这篇文章提到Node.js在载入模块时，如果之前该模块已经加载过则不会有重复开销，因为模块加载有缓存机制。这篇文章是我初审的，当时也正好在思考Jscex在Node.js使用时的模块化问题，但研究了它的规则之后，我发现在某些情况下还是可能加载多次。现在我们便来分析这个问题。&lt;/p&gt;

&lt;p&gt;当我们使用require方法加载另一模块的时候，Node.js会去查询一系列的目录。我们可以从module.paths中得到这些路径，例如：&lt;/p&gt;

&lt;pre class="code"&gt;[ &lt;span style="color: maroon"&gt;'/Users/jeffz/Projects/node-test/node_modules'&lt;/span&gt;,
  &lt;span style="color: maroon"&gt;'/Users/jeffz/Projects/node_modules'&lt;/span&gt;,
  &lt;span style="color: maroon"&gt;'/Users/jeffz/node_modules'&lt;/span&gt;,
  &lt;span style="color: maroon"&gt;'/Users/node_modules'&lt;/span&gt;,
  &lt;span style="color: maroon"&gt;'/node_modules'&lt;/span&gt;]&lt;/pre&gt;

&lt;p&gt;这里是我在运行/User/jeffz/Projects/node-test目录下一个模块时得到的结果。可见，Node.js会从当前模块所在目录的node_modules（这里怎么不遵守Unix习惯，而使用了下划线呢？）开始找起，如果没找到再会去找上级目录的node_modules，直到根目录为止。当然，实际情况下还会有NODE_PATH环境变量标识的目录等等。当模块的位置确定之后，Node.js便会查看这个位置的模块是否已经被加载，如果已加载，则直接返回。&lt;/p&gt;

&lt;p&gt;简单地说，Node.js是根据模块所在路径来缓存模块的。&lt;/p&gt;

&lt;p&gt;这么看来，“相同模块是否会被加载多次”这个问题，其实就演变成了“相同模块是否会出现在不同路径里”。简单想来这似乎不太可能，因为如果我们要使用某个模块的时候，它的位置总是确定的。例如，使用npm安装的模块，总是会出现在当前目录的node_modules里，加载时总是会找到相同的路径。那么，在“间接”依赖相同模块的情况下呢？&lt;/p&gt;

&lt;p&gt;例如我们想要使用Express框架，于是使用npm来安装，便会得到：&lt;/p&gt;

&lt;pre class="code"&gt;$ npm install express
express@2.5.2 ./node_modules/express 
├── mkdirp@0.0.7
├── qs@0.4.0
├── mime@1.2.4
└── connect@1.8.5&lt;/pre&gt;

&lt;p&gt;可见，Express依赖了其他一些模块，它们都存放在express模块自己的目录里面，例如./node_modules/express/node_modules/mime。好，假如我们项目自身也要使用mime项目，我们自然也可以使用npm来安装：&lt;/p&gt;

&lt;pre class="code"&gt;$ npm install mime
mime@1.2.4 ./node_modules/mime &lt;/pre&gt;

&lt;p&gt;于是我们最终得到的是这样的结构：&lt;/p&gt;

&lt;pre class="code"&gt;./node_modules
├── &lt;span style="color: red"&gt;&lt;strong&gt;mime&lt;/strong&gt;&lt;/span&gt;
└── express
    └── node_modules
        ├── mkdirp
        ├── qs
        ├── &lt;span style="color: red"&gt;&lt;strong&gt;mime&lt;/strong&gt;&lt;/span&gt;
        └── connect&lt;/pre&gt;

&lt;p&gt;请注意，这里的mime模块便出现在两个位置上，它们名称版本都一致，完全是一个模块。那么试想，如果我们在自己的代码里加载的mime模块，以及express内部加载的mime模块是同一个吗？显然不是，可见，在这里相同的模块被重复加载了两次，产生了两个模块“实例”。&lt;/p&gt;

&lt;p&gt;这种重复加载在一般情况下不会有太大问题，最多内存占用大一点而已，不会影响程序的正确性。但是，我们也可以轻易设想到一些意外的情况。例如，在Jscex中，每个Task对象我都会给定一个ID，不断增长。要实现这点我们需要维护一个“种子”，全局唯一。之前这个种子定义在闭包内部，但由于Jscex模块会被加载多次，这样从不同模块“实例”生成的Task对象，它们的ID便有可能重复。当然，解决这个问题也并不困难，只需要将种子定义在根对象上即可，不同的模块“实例”共享相同的根对象。&lt;/p&gt;

&lt;p&gt;还有个问题可能就显得隐蔽些了，我们可以通过一个简单的实验来观察结果。我们先来定义一个jeffz-a模块，其中暴露出一个MyType类型：&lt;/p&gt;

&lt;pre class="code"&gt;module.exports.MyType = &lt;span style="color: blue"&gt;function &lt;/span&gt;() { }&lt;/pre&gt;

&lt;p&gt;然后将其发布到npm上。然后再写一个jeffz-b模块，依赖jeffz-a，并将jeffz-a中定义的MyType类型直接暴露出去：&lt;/p&gt;

&lt;pre class="code"&gt;module.exports.MyType = require(&lt;span style="color: maroon"&gt;&amp;quot;jeffz-a&amp;quot;&lt;/span&gt;).MyType;&lt;/pre&gt;

&lt;p&gt;接着将jeffz-b也发布置npm上。再重新写一个测试模块，使用npm安装jeffz-a和jeffz-b，最终目录会是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;./node_modules
├── jeffz-a
└── jeffz-b
    └── node_modules
        └── jeffz-a&lt;/pre&gt;

&lt;p&gt;在测试模块内，我们来测试实例与类型之间的关系：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;a = require(&lt;span style="color: maroon"&gt;&amp;quot;jeffz-a&amp;quot;&lt;/span&gt;);
&lt;span style="color: blue"&gt;var &lt;/span&gt;b = require(&lt;span style="color: maroon"&gt;&amp;quot;jeffz-b&amp;quot;&lt;/span&gt;);

console.log(&lt;span style="color: blue"&gt;new &lt;/span&gt;a.MyType() &lt;span style="color: blue"&gt;instanceof &lt;/span&gt;a.MyType); &lt;span style="color: #006400"&gt;// true&lt;/span&gt;
console.log(&lt;span style="color: blue"&gt;new &lt;/span&gt;b.MyType() &lt;span style="color: blue"&gt;instanceof &lt;/span&gt;b.MyType); &lt;span style="color: #006400"&gt;// true&lt;/span&gt;

console.log(&lt;span style="color: blue"&gt;new &lt;/span&gt;a.MyType() &lt;span style="color: blue"&gt;instanceof &lt;/span&gt;b.MyType); &lt;span style="color: #006400"&gt;// false&lt;/span&gt;
console.log(&lt;span style="color: blue"&gt;new &lt;/span&gt;b.MyType() &lt;span style="color: blue"&gt;instanceof &lt;/span&gt;a.MyType); &lt;span style="color: #006400"&gt;// false&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;从表面上看，jeffz-b和jeffz-a暴露出的应该是相同的MyType类型，它们的对象通过instanceof相互判断应该都返回true，但实际上由于jeffz-b中的jeffz-a，与我们直接加载的jeffz-a模块是不同的实例，因此MyType类型自然也不是同一个了。&lt;/p&gt;

&lt;p&gt;这对于Jscex的影响在于，Jscex的异步模块在取消时，原本是通过判断异常对象是否为CanceledError类型来决定Task的状态为cancelled还是faulted。但由于Node.js可能会将相同的模块加载为多个实例，因此即便抛出的的确是某个实例的CancelledError，也无法通过另一个实例内部的判断。因此，目前Jscex的判断方式修改为检查异常对象的isCancellation字段，简单地解决了这个问题。&lt;/p&gt;

&lt;p&gt;当然，Node.js这种“重复加载”的影响也并非完全是负面的，至少它天然的解决了多版本共存的问题。例如，express v2.5.2依赖mime v1.2.4，但我们程序自身又想使用mime v1.2.5。此时，express内部自然使用mime v1.2.4，而我们自己的程序使用的便是mime v1.2.5。&lt;/p&gt;

&lt;p&gt;有些情况下您可能也想避免这种重复加载，这就必须手动地删除模块内部被间接依赖的模块，将其移动到模块查询路径的公用部分上了。就目前看来，这些操作必须手动进行，因为npm在安装模块时不会关心依赖的模块是否已经安装过了（例如在NODE_PATH环境变量标识的路径里），它一定会重新下载所有依赖的模块。可惜如果您使用的是托管形式的Node.js服务，则很有可能无法做到这一点。&lt;/p&gt;

&lt;p&gt;因此，我们在编写Node.js模块的时候，便事先考虑下它会被重复加载时的情况吧。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/12/same-node-module-load-multiple-times.html#comments</comments>
      <pubDate>Tue, 27 Dec 2011 15:19:29 GMT</pubDate>
      <lastBuildDate>Wed, 28 Dec 2011 04:45:46 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>受禁锢的异步编程思维</title>
      <link>http://blog.zhaojie.me/2011/12/the-stuck-mind-of-asynchronous-programming.html</link>
      <guid>http://blog.zhaojie.me/2011/12/the-stuck-mind-of-asynchronous-programming.html</guid>
      <description>&lt;p&gt;最近一直在努力推广&lt;a href="http://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;，补充了很多中文文档和示例，因此博客上都已经有两篇文章有了“上”而没有“下”，即使最复杂的图示也已经绘制完毕。在推广Jscex的过程中，我发现有个比较明显的问题是，许多使用JavaScript的程序员已经习惯旧有的编程方式，甚至推崇一些据他们说很“漂亮”的模式。但在我看来，这其实跟许多GoF模式是在修补OO语言的不足有些类似，很多异步模式都只是因为JavaScript语言特性不足而设计出来的“权宜之计”。我们在传统JavaScript编程环境下并没有其他选择，单纯地认为这是“美”，说实话只不过是一种安慰罢了。&lt;/p&gt;

&lt;p&gt;Jscex的重头戏便是处理异步操作，但异步操作并不只是如Node.js中通过回调函数传回结果的那些方法，或者是网页上的AJAX请求等等。异步操作的定义其实可以概括成“&lt;span style="color:#ff0000"&gt;会在未来某个时刻完成的操作&lt;/span&gt;”，就只是这么简单。什么事情会在未来发生，那么它对你来说就是个异步操作。因此其实在日常开发过程中可谓到处是异步操作，例如：&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;各类事件（数据流会在未来关闭，WebWorker会在未来获得消息，图片会在未来加载成功） &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些示例实在数不胜数。但是，在许多JavaScript程序员眼中，似乎只有AJAX或是Node.js中的那些异步方法才算是异步操作。其他的东西，比如用户点击一个按钮，这难道不是个天然的“事件”吗？其实这就要视这个异步任务的性质如何了。如果它是一系列操作的“发起者”，那么的确，使用事件触发的方式来对待这次点击操作可能是最合理的。但如果，这个操作只是一系列过程中的一个步骤，那么如果依然把它视为一个事件型的操作，就只会破坏我们的逻辑了。&lt;/p&gt;

&lt;p&gt;举个例子，和Jscex的&lt;a href="https://github.com/JeffreyZhao/jscex/blob/master/README-cn.md"&gt;快速入门&lt;/a&gt;比较类似，即&lt;a href="http://en.wikipedia.org/wiki/Fibonacci_number"&gt;菲薄纳契（Fibonacci）数列&lt;/a&gt;：&lt;/p&gt;
&lt;img class="embed" title="F_n = F_{n-1} + F_{n - 2}" src="http://latex.codecogs.com/gif.latex?F_n = F_{n-1} + F_{n - 2}" /&gt; 

&lt;p&gt;其边界情况为：&lt;/p&gt;
&lt;img class="embed" title="F_0 = 0, F_1 = 1" src="http://latex.codecogs.com/gif.latex?F_0 = 0, F_1 = 1" /&gt; 

&lt;p&gt;以上是其标准定义，直接写成算法即是：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;fib = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {

    console.log(0);
    console.log(1);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;a = 0, current = 1;
    &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;b = a;
        a = current;
        current = a + b;

        console.log(current);
    }
};&lt;/pre&gt;

&lt;p&gt;上述代码将会无限地循环下去，不断输出数列的每一项。快速入门里的要求，是将其修改为“每隔一秒输出一个数字”，于是有同学就说：这不天生是计时器的场景吗？但事实并非如此。“计时器”或是setTimeout函数，都只是环境提供给我们的唯一可用的功能，我们要意识到这不是我们主动的“选择”。如果一看到“每隔一秒”这样的需求，JavaScript程序员就认为“计时器”是“最好”的办法，这就说明思维被禁锢了。我相信这样的功能交给其他任何平台的程序员，他们的第一感觉几乎都会是“使用Sleep函数暂停一秒”。这其实才是最简单的做法，直接，清晰，完整保留现有代码逻辑。&lt;/p&gt;

&lt;p&gt;这也是基于Jscex之后的实现方式。这里我再将要求修改一下，改为用户“每点击一次按钮”输出一个数字，又该怎么做？基于Jscex的做法如下：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;Async = Jscex.Async;

&lt;span style="color: blue"&gt;var &lt;/span&gt;fibAsync = 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;() {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;button = document.getElementById(&lt;span style="color: maroon"&gt;&amp;quot;button&amp;quot;&lt;/span&gt;);

    $await(Async.onEvent(button, &lt;span style="color: maroon"&gt;&amp;quot;click&amp;quot;&lt;/span&gt;)); &lt;span style="color: #006400"&gt;// 等待用户点击
    &lt;/span&gt;console.log(0);

    $await(Async.onEvent(button, &lt;span style="color: maroon"&gt;&amp;quot;click&amp;quot;&lt;/span&gt;)); &lt;span style="color: #006400"&gt;// 等待用户点击
    &lt;/span&gt;console.log(1);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;a = 0, current = 1;
    &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;b = a;
        a = current;
        current = a + b;

        $await(Async.onEvent(button, &lt;span style="color: maroon"&gt;&amp;quot;click&amp;quot;&lt;/span&gt;)); &lt;span style="color: #006400"&gt;// 等待用户点击
        &lt;/span&gt;console.log(current);
    }
}));

fibAsync().start();&lt;/pre&gt;

&lt;p&gt;有朋友可能会问：用户点击按钮不是需要响应事件的嘛，这个事件到哪里去了？其实正像我所说的那样，把这里的“用户点击按钮”当作事件对待并非最合理的方式，因为它只是“整个过程”中的一个环节而已。在这里，我们其实只是要在输出数字之前“等待用户点击”即可，这个“输出”以及相关的“计算”操作，并非是由“按钮点击”所触发的逻辑，而是一个连续的统一过程中的一部分而已。&lt;/p&gt;

&lt;p&gt;您可以试试纯粹使用事件机制来实现这个功能，保证您需要重新实现这段斐波那契数列的逻辑。当然，菲薄纳契数列的逻辑很简单，重写下估计也不会花太大的功夫，但如果您需要改造&lt;a href="http://files.zhaojie.me/jscex/samples/async/hanoi.html"&gt;汉诺塔的动画效果&lt;/a&gt;呢？&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;hanoiAsync = 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;(n, from, to, mid) {
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(n &amp;gt; 0) {
        $await(hanoiAsync(n - 1, from, mid, to));
    }

    &lt;span style="color: #006400"&gt;// 等待按钮点击
    // var btnNext = document.getElementById(&amp;quot;btnNext&amp;quot;);
    // $await(Jscex.Async.onEvent(btnNext, &amp;quot;click&amp;quot;));

    &lt;/span&gt;$await(moveDishAsync(n, from, to));

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(n &amp;gt; 0) {
        $await(hanoiAsync(n - 1, mid, to, from));
    }
}));&lt;/pre&gt;

&lt;p&gt;以上代码是以动画形式表现汉诺塔的解题过程，但如果用户提出想要“每点一次按钮”才移动一个盘子，那其实我们只要将上面两行代码取消注释即可。如果忽然有一天，老板要求通过一个选项来决定是否“自动移动”，在Jscex里只要加一个if判断即可。您可以简单设想一下直接裸写这些代码会遇到什么样的景象，改造时会遇到哪些困难。&lt;/p&gt;

&lt;p&gt;我还为Jscex准备了一个示例，是关于“&lt;a href="https://github.com/JeffreyZhao/jscex/blob/master/doc/async/samples/modal-dialog-cn.md"&gt;模态对话框&lt;/a&gt;”配合相关异步操作的。由于是“模态对话框”，我们是要在对话框关闭之后才继续做某些事情。可惜在JavaScript中，如果您直接把一个界面元素展现为一个模态对话框，它是无法阻止后面的代码继续执行的，要阻止则只能使用confirm或alert方法。于是，我们只能把后续操作放到一个回调函数中去，并在模态对话框关闭之后才执行。但是您要知道，模态对话框只不过是整个过程中的一个步骤，理想状况下我们的完整功能不该被拆成多个部分，再使用所谓“美妙”的回调串联起来。&lt;/p&gt;

&lt;p&gt;这点在Jscex中还是那么简单，直接按最简单的逻辑来进行即可：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// 显示模态对话框&lt;/span&gt;
$await($(&lt;span style="color: maroon"&gt;&amp;quot;#dialog-confirm&amp;quot;&lt;/span&gt;).dialogAsync({ modal: &lt;span style="color: blue"&gt;true &lt;/span&gt;}));

&lt;span style="color: #006400"&gt;// 发起AJAX请求&lt;/span&gt;
&lt;span style="color:blue"&gt;var&lt;/span&gt; response = $await($.ajaxAsync({ url: &lt;span style="color: maroon"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;, dataType: &lt;span style="color: maroon"&gt;&amp;quot;text&amp;quot;&lt;/span&gt; }));

&lt;span style="color: #006400"&gt;// 继续做事&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;而无需：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// 继续做事&lt;/span&gt;
$(&lt;span style="color: maroon"&gt;&amp;quot;#dialog-confirm&amp;quot;&lt;/span&gt;).dialog({
    modal: &lt;span style="color: blue"&gt;true&lt;/span&gt;,
    close: &lt;span style="color: blue"&gt;function&lt;/span&gt; () {
        &lt;span style="color: #006400"&gt;// 发起AJAX请求&lt;/span&gt;
        $.ajax({
            url: &lt;span style="color: maroon"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;,
            dataType: &lt;span style="color: maroon"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;,
            success: &lt;span style="color: blue"&gt;function&lt;/span&gt; () {
                &lt;span style="color: #006400"&gt;// 继续做事&lt;/span&gt;
            }
        });
});&lt;/pre&gt;

&lt;p&gt;经常会听到有些朋友谈起，说在实际开发过程中很少遇到异步场景。但在我看来，实在可谓遍地是异步，这种观念的差别只是在于是否经过了“抽象”。不加抽象地使用技术平台为我们提供的异步操作，会让我们的思维被它所禁锢。在JavaScript编程中浸淫太久了，可能就会忘记我们从最初是如何编程的。Jscex的目标，便是将这些东西回归自然，将逻辑以最自然的方式表达出来。循环？那就用for或是while吧，在函数之间跳来跳去是做什么的？&lt;/p&gt;

&lt;p&gt;我从来不担心的Jscex的实用价值。Jscex来自C#，F#以及Scala等现成的理念，各种开发模式都是被翻来覆去讨论过，总结过，验证过的。这些语言其实都能实现与JavaScript类似的编程模式，但它们不需要，因为语言特性让程序员可以使用更简单直接的做法来解决问题。Jscex只是将这些现成的内容，从其他模式带到JavaScript编程领域上而已。&lt;/p&gt;

&lt;p&gt;如今我唯一担心的，只是那些被禁锢的编程思维。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/12/the-stuck-mind-of-asynchronous-programming.html#comments</comments>
      <pubDate>Mon, 19 Dec 2011 13:49:37 GMT</pubDate>
      <lastBuildDate>Tue, 27 Dec 2011 15:17:21 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/news/">新闻信息</category>
      <title>Jscex正式发布至npm</title>
      <link>http://blog.zhaojie.me/2011/12/jscex-officially-released-to-npm.html</link>
      <guid>http://blog.zhaojie.me/2011/12/jscex-officially-released-to-npm.html</guid>
      <description>&lt;p&gt;之前一直不敢将&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;发布至&lt;a href="http://npmjs.org/"&gt;npm&lt;/a&gt;上，因为有些问题还没有完全拿定主意，例如“取消”任务的模型。一个异步任务一定是需要取消功能，尽管这个模型&lt;a href="http://www.cnblogs.com/iamzhanglei/archive/2011/11/17/2253253.html"&gt;不一定需要直接定义在Jscex里&lt;/a&gt;。但是我还是为Jscex提供了一个统一的标准化的取消模型，一是易于使用，二是提供模型这个之后，API在设计时可以有更多表现力（例如，现在的task的status可能是canceled了）。上周末我实现这个模型（其实也就十几二十行代码），昨天我又修改了模块的加载方式，使其支持&lt;a href="http://www.commonjs.org/"&gt;CommonJS&lt;/a&gt;规范。在发布到npm之后，现在可以说Jscex已经正式对外公开了！&lt;/p&gt;

&lt;p&gt;在加载Jscex代码之后，全局环境里会出现一个Jscex对象，这是所有Jscex功能的入口（根对象）。但对于&lt;a href="http://nodejs.org/"&gt;Node.js&lt;/a&gt;来说，相同的模块（无论版本是否一致）有可能还会被加载多次，因此这种方式并不适合Node.js。昨天的修改主要就是为了迎合Node.js的包管理机制（类似CommonJS），避免对全局环境造成污染。自然，如果脚本执行环境里没有实现CommonJS规范（例如浏览器），则Jscex的行为依然和之前保持不变。&lt;/p&gt;

&lt;p&gt;目前Jscex的核心部分已经发布到npm里，版本为0.5.0，分为两个互不依赖的三个部分：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;jscex：&lt;/strong&gt;提供了Jscex的基础功能，作为Jscex的根对象。 &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;jscex-jit：&lt;/strong&gt;提供了Jscex的JIT编译器，以“插件”形式为Jscex根对象进行扩展，一般仅作为开发工具使用。 &lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;jscex-async：&lt;/strong&gt;Jscex的异步支持，以“插件”形式为Jscex根对象进行扩展。在Node.js中一般与JIT编译器同时使用，但理论上可以在AOT编译之后摆脱对JIT编译器的依赖。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;您可以使用npm安装这三个模块：&lt;/p&gt;

&lt;pre class="code"&gt;$ &lt;strong&gt;npm install jscex jscex-jit jscex-async&lt;/strong&gt;
jscex@0.5.0 ./node_modules/jscex
jscex-jit@0.5.0 ./node_modules/jscex-jit
jscex-async@0.5.0 ./node_modules/jscex-async&lt;/pre&gt;

&lt;p&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;"jscex"&lt;/span&gt;);
require(&lt;span style="color:maroon"&gt;"jscex-jit"&lt;/span&gt;).init(Jscex);
require(&lt;span style="color:maroon"&gt;"jscex-async"&lt;/span&gt;).init(Jscex);&lt;/pre&gt;

&lt;p&gt;然后便可以像以前那样正常使用Jscex了。请注意，由于Jscex的实现方式所限，一定要在上下文里存在名为Jscex的根对象。此外，目前发布在npm上只有JIT编译器及其异步支持的核心功能，并没有包含如sleep或parallel方法那样的核心类库。这部分代码已经被剥离至jscex-async-powerpack模块内，将在合适的时候发布至npm上。&lt;/p&gt;

&lt;p&gt;目前使用Jscex的人依旧不多，有同学将Jscex配合HTML 5的绘图使用，进行了&lt;a href="http://www.cnblogs.com/iamzhanglei/"&gt;进行了许多尝试&lt;/a&gt;，&lt;a href="http://game.mhtml5.com/game/works/ztTnYEvyFrEbguW6/"&gt;颇为有趣&lt;/a&gt;。如果您对Jscex有什么意见或建议，也请及时告知。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/12/jscex-officially-released-to-npm.html#comments</comments>
      <pubDate>Thu, 01 Dec 2011 06:23:00 GMT</pubDate>
      <lastBuildDate>Wed, 28 Dec 2011 04:26:31 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>尾递归对时间与空间复杂度的影响（上）</title>
      <link>http://blog.zhaojie.me/2011/11/does-tail-recursion-improve-time-and-space-complexities-1.html</link>
      <guid>http://blog.zhaojie.me/2011/11/does-tail-recursion-improve-time-and-space-complexities-1.html</guid>
      <description>&lt;p&gt;以前我也在博客上简单谈过“&lt;a href="http://blog.zhaojie.me/2009/03/tail-recursion-and-continuation.html"&gt;尾递归&lt;/a&gt;”及其&lt;a href="http://blog.zhaojie.me/2009/03/tail-recursion-explanation.html"&gt;优化方式&lt;/a&gt;方面的话题。前几天有同学在写邮件向我提问，说是否所有的递归算法都能改写为尾递归，改写成尾递归之后，是否在时间和空间复杂度方面都能有所提高？他以斐波那契数列为例，似乎的确是这样的情况。我当时的回答有些简单，后来细想之后似乎感觉有点问题，而在仔细操作之后发现事情并没有理论上那么简单，因此还是计划写篇文章来讨论下这方面的问题。&lt;/p&gt;

&lt;h1&gt;斐波那契数列&lt;/h1&gt;

&lt;p&gt;大家对于斐波那契数列（Fibonacci）的认识一定十分统一，唯一的区别可能仅在于n是从0开始还是从1开始算起。这里我们使用&lt;a href="http://en.wikipedia.org/wiki/Fibonacci_number"&gt;维基百科上的标准递归定义&lt;/a&gt;：&lt;/p&gt;
&lt;img class="embed" title="F_n = F_{n-1} + F_{n - 2}" src="http://latex.codecogs.com/gif.latex?F_n = F_{n-1} + F_{n - 2}" /&gt; 

&lt;p&gt;其边界情况为：&lt;/p&gt;
&lt;img class="embed" title="F_0 = 0, F_1 = 1" src="http://latex.codecogs.com/gif.latex?F_0 = 0, F_1 = 1" /&gt; 

&lt;p&gt;使用这个定义可以直接写出程序，这里我们用F#来表达：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let rec &lt;/span&gt;fib n =
    &lt;span style="color: blue"&gt;if &lt;/span&gt;n &amp;lt; 2 &lt;span style="color: blue"&gt;then &lt;/span&gt;n
    &lt;span style="color: blue"&gt;else &lt;/span&gt;fib (n - 1) + fib (n - 2)&lt;/pre&gt;

&lt;p&gt;这个算法最容易理解，但其时间复杂度确是：&lt;/p&gt;
&lt;img class="embed" title="O((\frac{1 + \sqrt5}{2})^n) \approx O(1.618^n)" src="http://latex.codecogs.com/gif.latex?O((\frac{1 + \sqrt5}{2})^n) \approx O(1.618^n)" /&gt; 

&lt;p&gt;这种指数级的时间复杂度在实际应用中是十分可怕的（虽然这个数字是美妙的黄金分割）。因此，我们如果真要“计算”斐波那契数列第n项的值（即不使用“通项公式”），则往往会使用迭代的方式进行，写作尾递归则是：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;fibTail n = 
    &lt;span style="color: green"&gt;// 第i项的值v1，以及即将累加上去的值v2&lt;/span&gt;
    &lt;span style="color: blue"&gt;let rec &lt;/span&gt;fibTail' i v1 v2 =
        &lt;span style="color: blue"&gt;if &lt;/span&gt;i &amp;gt;= n &lt;span style="color: blue"&gt;then &lt;/span&gt;v1
        &lt;span style="color: blue"&gt;else &lt;/span&gt;fibTail' (i + 1) (v1 + v2) v1
    fibTail' 0 0 1&lt;/pre&gt;

&lt;p&gt;从代码上也可以轻易地判断出，这个算法的时间复杂度是O(n)，实际上它也会被F#或是Scala等支持尾递归的编译器优化为循环操作。这里我们使用命令式编程语言C#来表达编译后的结果：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static int &lt;/span&gt;FibTail(&lt;span style="color: blue"&gt;int &lt;/span&gt;n, &lt;span style="color: blue"&gt;int &lt;/span&gt;i, &lt;span style="color: blue"&gt;int &lt;/span&gt;v1, &lt;span style="color: blue"&gt;int &lt;/span&gt;v2)
{
    &lt;span style="color: blue"&gt;while &lt;/span&gt;(i &amp;lt; n)
    {
        &lt;span style="color: blue"&gt;int &lt;/span&gt;temp = v1 + v2;
        v2 = v1;
        v1 = temp;
        i++;
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;v1;
}&lt;/pre&gt;

&lt;p&gt;时间复杂度从O(1.618&lt;sup&gt;n&lt;/sup&gt;)降低到O(n)，可谓是质的飞跃。&lt;/p&gt;

&lt;h1&gt;尾调用对空间复杂度的影响&lt;/h1&gt;

&lt;p&gt;那么，在空间复杂度方面，尾递归带来什么优化吗？我们首先还是来分析标准的递归算法：&lt;/p&gt;
&lt;img class="embed" title="F_n = F_{n-2} + F_{n - 1}" src="http://latex.codecogs.com/gif.latex?F_n = F_{n-2} + F_{n - 1}" /&gt; 

&lt;p&gt;假设，我们知道，在一个（无副作用的）方法执行完毕之后，除了返回值以外的空间会完全释放出来，因此在fib(n - 2)执行结束之后，它的空间占用是常数级的。且fib(n - 1)的空间占用一定大于fib(n - 2)，假设其fib(n)的空间占用为S(n)，可得：&lt;/p&gt;
&lt;img class="embed" title="S(n) = S(n - 1) + O(1)" src="http://latex.codecogs.com/gif.latex?S(n) = S(n - 1) + O(1)" /&gt; 

&lt;p&gt;于是fib的空间复杂度是显而易见的O(n)。这个空间复杂度其实并不大，例如经典的&lt;a href="http://en.wikipedia.org/wiki/Merge_sort"&gt;归并排序&lt;/a&gt;算法的空间复杂度也同样是O(n)。但不幸的是，这里的递归操作占用的完全是栈空间，而栈空间的大小是极其有限的（例如一个Windows应用程序默认情况下只有1M，ASP.NET甚至只有250K）。因此，只需一个稍大一点的数字会产生栈溢出。经试验，在我的机器上只需51K便能出现StackOverflowException：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// 50K不会出现StackOverflowException&lt;/span&gt;
51 * 1024 |&amp;gt; fib |&amp;gt; printfn &lt;span style="color: maroon"&gt;&amp;quot;%d&amp;quot;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;那么尾递归算法的空间复杂度呢？我们刚才提到，编译器会将尾递归优化成循环，那在实际运行时这个算法的空间复杂度自然是常数级，即O(1)。但这是我们实际观察到的编译器优化后的结果，从理论上说，我们并无法保证这里的尾递归会被优化成循环。因此我们不妨也从“字面”上来理解代码，看看理论上这样的尾递归调用会形成怎样的空间占用。&lt;/p&gt;

&lt;p&gt;对于尾递归来说，理论上我们只能期待它形成“尾调用”。也就是说，针对某个方法的调用（无论是否是递归操作）是父方法的最后一个操作。在这个情况下，我们无需保留父方法当前的栈空间，因此可以将其完全释放。于是，无论调用多少次，只要每次都将栈空间释放（或重用），其空间占用也始终是个常数，即O(1)。&lt;/p&gt;

&lt;p&gt;因此，无论从理论上（从字面上分析）还是实际上（观察编译结果）来说，似乎将斐波那契数列修改为尾递归，能显著地降低时间及空间复杂度，这也是那位同学提出“尾递归能改进时间和空间复杂度”的依据。那么我们重新回顾一下文章开头所提出的两个问题：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;每个递归算法都能改写为尾递归吗？ &lt;/li&gt;

  &lt;li&gt;改写为尾递归都能改进时间及空间复杂度吗？ &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下次我们继续讨论这两个问题。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;尾递归对时间与空间复杂度的影响（上）&lt;/li&gt;

  &lt;li&gt;&lt;a href="#"&gt;尾递归对时间与空间复杂度的影响（下）&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/03/tail-recursion-and-continuation.html"&gt;尾递归与Continuation&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/03/tail-recursion-explanation.html"&gt;浅谈尾递归的优化方式&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2011/11/does-tail-recursion-improve-time-and-space-complexities-1.html#comments</comments>
      <pubDate>Tue, 15 Nov 2011 14:12:18 GMT</pubDate>
      <lastBuildDate>Wed, 16 Nov 2011 06:29:18 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>挖坟鞭尸：当年Sun公司的白皮书《About Microsoft “Delegates”》</title>
      <link>http://blog.zhaojie.me/2011/11/sun-whitepaper-about-microsoft-delegates.html</link>
      <guid>http://blog.zhaojie.me/2011/11/sun-whitepaper-about-microsoft-delegates.html</guid>
      <description>&lt;a href="http://img.zhaojie.me/blog/java-putong-wenyi-erbi.png"&gt;&lt;img alt="普通文艺二逼的Java" src="http://img.zhaojie.me/blog/java-putong-wenyi-erbi.png" width="200" class="floatRight" /&gt;&lt;/a&gt; 

&lt;p&gt;这是一桩当年的Sun公司与Java的旧事，还要追溯到C#还没出现，微软还在搞J++的时代。这篇著名的白皮书便是《&lt;a href="http://java.sun.com/docs/white/delegates.html"&gt;About Microsoft “Delegates”&lt;/a&gt;》，其中列出了Sun眼中的Delegate，或者说Bound Method Reference这一更广泛概念的多个缺点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;它为语言带来了复杂度。但实际上，这里更多的是“编译器”或是语言“实现者”需要应付的复杂度。对语言的“使用者”来说，尽管C#或Scala比Java语言要复杂不少，但是用起来却更为省事，大大减少了代码编写和理解的复杂度。 &lt;/li&gt;

  &lt;li&gt;它让语言变得不够“面向对象”。在我看来这种为了“面向对象”而“面向对象”可谓本末倒置，“面向对象”是“手段”而不是“目标”。事实上Java近年来的发展，例如&lt;a href="http://download.oracle.com/javase/1,5.0/docs/guide/language/static-import.html"&gt;静态引入&lt;/a&gt;，&lt;a href="http://download.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandle.html"&gt;MethodHandle&lt;/a&gt;，乃至社区不断诉求却一再延后的Lambda表达式，其实都在为了易用性而做出的妥协。 &lt;/li&gt;

  &lt;li&gt;它的表达能力不够。这点我始终不能理解，因为Bound Method Reference是运行时的概念，表达能力确是语言设计本身所影响的，一个运行时的概念为什么就没法用优美的语法形式表达出来？C# 2.0至3.0的飞跃，让它的表达能力早已远胜Java语言，而且这也与Delegate概念直接相关。 &lt;/li&gt;

  &lt;li&gt;它这不如适配器对象好用。Java语言的设计者们认为，用内部类/匿名类的语法来实现适配器对象虽然麻烦了一些，但也并没有带来什么问题。但是在使用者眼中，Java的语法噪音是出了名的多，直接影响Java语言的表达能力。如图，在C#中早就可以使用“文艺”的Scala方式来写程序，但如果想用相同的理念就只能变成十足的“二逼”了。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当然，白皮书还包含了更详细的讨论。最后，它信誓旦旦地写到：“Bound Method Reference并不是语言发展的正确道路”——至于现状如何就不多说了。我的感觉是：不谈Java语言设计者的水平如何，至少这篇白皮书的作者，在语言设计的能力或品味上几乎落后了Anders Hejlsberg十年。从我之前&lt;a href="http://www.infoq.com/cn/articles/neal-gafter-on-java"&gt;翻译过的一篇文章&lt;/a&gt;来看，Anders一直扮演着指引语言发展方向的作用，这也是为什么C#在这十年的发展能比Java要健康得多的主要原因之一。&lt;/p&gt;

&lt;p&gt;这篇文章的信息量并不大。原本我是在写另一篇文章，这些文字只是顺便提起的话题，但是一说却又感觉刹不住车。为了避免偏离话题，于是就把它单独成篇了——您可且看且过，倒是下一篇文章会更有趣一些。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/11/sun-whitepaper-about-microsoft-delegates.html#comments</comments>
      <pubDate>Tue, 08 Nov 2011 17:07:35 GMT</pubDate>
      <lastBuildDate>Wed, 09 Nov 2011 01:47:33 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/reading/">阅读相关</category>
      <title>老赵书托（4）：浪潮之巅（内含牢骚，不喜勿入）</title>
      <link>http://blog.zhaojie.me/2011/11/recommended-reading-4-langchaozhidian.html</link>
      <guid>http://blog.zhaojie.me/2011/11/recommended-reading-4-langchaozhidian.html</guid>
      <description>&lt;p&gt;我是一个码农，一个很彻底，很标准的码农，自认为比我遇到过的绝大部分技术人员要码农得多。我喜欢搞技术，玩技术，不喜欢搞管理，搞人，或是搞产品等等。我是死脑筋，凡事都要想很多遍，想个明白，想不明白就不表态。我不喜欢猜测，不喜欢无法验证的事物，因此我成不了那种常见的，著名的，经常指点江山的分析师。每次有人问我对某某事物未来的看法，十有八九我只会婉言谢绝。其实照我的个人口味，我是不太会对《&lt;a href="http://book.douban.com/subject/6709783/"&gt;浪潮之巅&lt;/a&gt;》这种类型的书感兴趣的，但之前在网上看过谷歌黑板报上的连载，感觉不错，因此也去“搞”来一本品读一番。&lt;/p&gt;

&lt;h1&gt;这是一本历史书&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/books/langchaozhidian-568x798.jpg"&gt;&lt;img class="floatRight" alt="浪潮之巅" src="http://img.zhaojie.me/blog/books/langchaozhidian-568x798.jpg" width="200" /&gt;&lt;/a&gt; 

&lt;p&gt;在我看来，《浪潮之巅》其实是本讲产业历史的书，记录了IT产业里那些曾经（或是现在）叱诧风云过的明星，从大家耳熟能详的微软谷歌，到许多人看来并不怎么引入注意的惠普和雅虎。这些明星从起步到鼎盛，再到平淡甚至是衰落，似乎只有苹果现在正好刚到达波峰——毕竟这些都是悠久历史的公司而不是互联网新贵。这样的书其实很难写，因为要去了解的东西太多，更难得是把这些信息有条不紊地整理起来，这点我就自忖做不到。&lt;/p&gt;

&lt;p&gt;而且在我看来，写历史一定要保持中立客观，应该以记录和描述为主，而不是阐述自己的观点，简单如“太史公云”便已足够。但是，既然已经了解到那么多的情况，显然远远胜过绝大部分人，有多少人又能够克制住自己内心叱咤风云的冲动，不要那么急于做出“预言”或“展望”？殊不知，同类书籍中的大部分都像是些预言家的作品，而互联网上也充斥着看过几篇二三手新闻报道，便能够自信满满指点江山的产业分析师。反而是码农同学们，你们都到哪里去了？&lt;/p&gt;

&lt;p&gt;这也是我为什么之前会说“按照个人口味”会对此类书籍不感兴趣，因为我只是一介码农。我希望类似书籍可以更多一些，如今能让我立刻回忆起来的，似乎也只有许多年前的《Borland传奇》，以及前两年出版过中译本的《观止》。实在应该多出一点，让人相互参考也好。即便我愿意相信《浪潮之巅》的品质，也想看一下其他人眼中的历史来对照一番——是吧，我就是那么死脑筋。&lt;/p&gt;

&lt;p&gt;当然，这本书也不只是记述了巨人们的历史，它也讲述了一些有影响力的事物，从摩尔定律的影响到风险投资，从硅谷到斯坦福大学，等等。说起来实在丢脸，从理论上说，我也是参与过创业的人了，但也只有看了这本书以后，才明白对于一个创业公司来讲，股权分配之类究竟是什么样的操作方式。&lt;/p&gt;

&lt;p&gt;值得一提的是，由于我&lt;a href="http://blog.zhaojie.me/2011/07/get-away-from-paper-books.html"&gt;决定远离纸书&lt;/a&gt;，因此这本书看的是电子版。感谢电子工业出版社Just Pub团队的电子书试点服务，要领先则必须做别人不敢做的，&lt;a href="http://www.lixiaolai.com/archives/11150.html"&gt;管那些小白们说什么&lt;/a&gt;。《浪潮之巅》的版式&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;这本书我觉得适合行业里绝大部份的人拿来看看吧，就当是开阔眼界。我也更希望读过这本书的人能够学会这本书里面点踏实做法。这虽然不是一本谈论技术的书籍，但却能对我这码农的胃口，我认为很关键的一点就是它符合我的思考的“规范”：有一分事实，说一分道理；有七分依据，不做八分肯定；即便无可避免，也尽量从各个角度给出各种推测，而不是只推测出自己喜欢的结果。我本以为这是个很朴实，也很普适的准则，但现在看来似乎还真不容易被人接受。&lt;/p&gt;

&lt;p&gt;就拿最近发生的一件事情来说吧，京东上个星期搞了次半价促销，结果服务器撑不住了，于是又引起了微薄上一些有头有脸的大牛对.NET技术的批判，说.NET和Windows性能差撑不住云云。我自然还是一贯以码农视角来应对这些问题：说.NET和Windows性能差，就一起来跑个测试，无论多简单的Benchmark来试试看嘛。我现在只是没有条件而已，以前有条件的时候做过好几次，性能绝对不差，远远胜过当时最火热的Apache——当时nginx还没有像现在这么流行，而且就算看现在nginx的数据，也并不比我当年测IIS领先多少嘛。更重要的是，这些数据早就没有什么意义了，因为对于绝大部分情况来说，性能瓶颈还是在你写的程序上，而不会是这些技术本身的能力上。&lt;/p&gt;

&lt;p&gt;可惜大牛们依然只是再不断地举出“事实”：为什么搞个促销活动，淘宝就好好的，但京东就出问题呢？显然，他们的答案是“Java的性能比.NET好”。这里我就不提什么“架构最重要”这种老生常谈的东西，我只想说，在把原因归结到技术本身的能力之前，先关注下双方对技术的态度如何吧。淘宝在各大会议分享他们的技术探索的时候，京东在做什么？当淘宝的技术人员在社区里与人交流的时候，京东方面似乎只能看到刘总本人在搞笑搬的叫着“&lt;a href="http://weibo.com/1866402485/xvqkEqkJS"&gt;加三倍服务器&lt;/a&gt;”。淘宝的技术团队（据说）独立架构，京东的技术团队隶属信息部。一个是正规军，一个是保安处，为什么说他们战斗力的差距来自手中武器？淘宝和京东在对待技术的态度上天差地远，在此之前比较技术又能有多大意义呢？&lt;/p&gt;

&lt;p&gt;我想大家都明白，技术是要看人怎么用的。比如StackExchange用.NET做的很不错，可能的确是因为他们的技术人员水平很高，平常人根本达不到（哎，原微博被删了）。但同样从这方面来讲，淘宝的技术人员在Java乃至各方面的水平都是国内，甚至在世界上首屈一指的。就拿薪资待遇来说吧，阿里巴巴技术人员的待遇在国内属于第一梯队，而今年早些时候我从京东内部打听下来的情况，似乎我能获得的待遇也就跟我工作一年的时候差不多。其实我也并非不知道身边“.NET转Java失败”的案例，要知道当时他们也是投入重金聘请了许多业内高手，但最后还是失败了，但我绝对不会以此事说Java平台如何——我只会骂Java语言，因为我只对这方面有十足把握，因此论证也绝对仔细。&lt;/p&gt;

&lt;p&gt;我相信大牛们的水平一定比我高很多，眼界也一定比我开阔，思维也一定比我严谨，逻辑也一定比我清晰。我说的这些你们肯定都了解，但为什么就回避了呢？技术人员有倾向性是好事，这让人们产生热情有动力去探索，但是在讨论技术时，无论如何也该用技术人员的方式啊。&lt;/p&gt;

&lt;p&gt;我始终坚持技术人员应该有技术人员的风骨。技术上的事情其实最容易讨论，因为人的观点是活的，人有喜好有感情，但技术不会，完全能够做到有一说一有二说二。但是，我总觉得国内许多技术人员在这方面有所欠缺，双重标准耍起来端的熟练。一会儿可以说：“很多事情不是技术决定的，XX更重要”，一会儿又表示“MySpace的失败一定是因为.NET技术问题”。这实在苦了我这个码农，无论是向人打听，还是从各个演讲里，都希望能找到到底是什么“技术问题”，&lt;a href="http://highscalability.com/blog/2011/3/25/did-the-microsoft-stack-kill-myspace.html"&gt;但都基本得不到结果&lt;/a&gt;。点评转Java也一样，他们到底因为.NET出了什么问题了呢？如果您了解这两家的情况，也请告诉我，无论是公开还是私下都行。&lt;/p&gt;

&lt;p&gt;我其实本已经对国内这种缺少“技术风骨”的技术风气有点习惯了，但这次被人点了名，一时没忍住又掺和了进去。之前还有某位反对微软的同学，据说是为了“兼听则明”会主动前来“挑战”。但有了即便拥有十年经验之后依然还是“三分事实，七分推测”。最近一次，则是根据Build大会上的一些演讲来批评微软的技术走向。我自诩看过的Build大会视频绝对多过这位同学，但我还是认为自己绝对还没有到可以对此表态或是做什么展望的程度，更谈不上能如此“自信满满”。这位同学，我相信您一定也会看到这篇文章，这次您一定能明白我的标准了吧？您要再来的话也请换种方式。还有，之前您一直看不上国内社区，那么这段时间里有没有在我推荐给您的StackExchange，Reddit或是Y Combinator上“兼听则明”呢？&lt;/p&gt;

&lt;p&gt;《浪潮之巅》有资格骄傲，但它给我的感觉却是无比的踏实，这值得我们学习——但我依然会怀疑，有些人看了浪潮之巅以后，是否会更加自信满满地叱咤风云，指点江山呢？&lt;/p&gt;

&lt;h1&gt;扩展阅读&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/books/borland-legend-436x544.jpg"&gt;&lt;img class="floatRight" alt="Borland传奇" src="http://img.zhaojie.me/blog/books/borland-legend-436x544.jpg" width="150" /&gt;&lt;/a&gt; 

&lt;p&gt;正如之前提到的那样，与《浪潮之巅》同类又靠谱的书实在不多。《&lt;a href="http://book.douban.com/subject/1027528/"&gt;Borland传奇&lt;/a&gt;》算是一本，如果您没有看过的话也不放搞来一看。我当时看这本书，是因为听说过Anders Hejlsberg的传奇（他其实也是我接触C#的主要原因之一），也对Borland当年的辉煌心生向往。这本书的作者李维与《浪潮之巅》的作者吴军一样，都是技术和文笔都十分了得的人，而李维Borland的资深员工，可谓亲身经历了公司的起伏，以他的角度记录公司的历史，相信可以胜过一些旁敲侧击所得。&lt;/p&gt;

&lt;p&gt;一个创始人Phillippe打工赚钱，一个创始人Anders开发产品，并最后靠Turbo Pascal一飞冲天，Borland就是这样诞生的。Borland在当年完全可以称为是一个软件帝国，相比微软绝对有过之而无不及。在当时程序员的眼中，Borland的产品可谓是各个精品，在与微软的竞争过程中多次获胜。可惜在巅峰时刻，Phillippe被迫辞职，郁郁不得志的Anders被Bill Gates揽至麾下，在微软继续实现他在开发平台上的构想。从我之前&lt;a href="http://www.infoq.com/cn/articles/neal-gafter-on-java"&gt;翻译过的一篇文章&lt;/a&gt;来看，Anders一直扮演着指引语言发展方向的作用，这也是为什么C#比Java发展更为健康的缘故。&lt;/p&gt;

&lt;p&gt;之前我也一直以为Borland的失败是由于微软挖人的结果，也会想为什么一个创始人会放弃自己的公司，而投入竞争对手的怀抱。看了《Borland传奇》之后，至少在这本书里，并没有将Anders的离开作为Borland失败的主要原因，毕竟他也只是一个人。Borland迅速衰退的主要原因还是决策上的失误，市场判断错误，产品线过多，从技术导向变成销售导向，以及Java开发工具和数据库产品上的失败等等。一群天才技术人员的成果败在一群高管的折腾里，说起来也算是技术浪潮使然，造化弄人。&lt;/p&gt;

&lt;p&gt;最后，如果您感兴趣的话，也不妨看看《&lt;a href="http://book.douban.com/subject/3699395/"&gt;观止&lt;/a&gt;》，记述了微软构建NT的风风雨雨。内容不错，可惜翻译质量实在有些不敢恭维。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/05/recommended-reading-1.html"&gt;老赵书托（1）：写在前面&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/recommended-reading-2-sicp.html"&gt;老赵书托（2）：计算机程序的构造与解释&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/11/recommended-reading-3-csapp.html"&gt;老赵书托（3）：深入理解计算机系统&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;老赵书托（4）：浪潮之巅（内含牢骚，不喜勿入） &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2011/11/recommended-reading-4-langchaozhidian.html#comments</comments>
      <pubDate>Sun, 06 Nov 2011 06:35:08 GMT</pubDate>
      <lastBuildDate>Tue, 08 Nov 2011 16:39:56 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/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>基于Node.js、Express和Jscex开发的ToDo网站示例</title>
      <link>http://blog.zhaojie.me/2011/07/nodejs-express-jscex-demo-website-todo.html</link>
      <guid>http://blog.zhaojie.me/2011/07/nodejs-express-jscex-demo-website-todo.html</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/jscex" target="_blank"&gt;Jscex&lt;/a&gt;的主要使用场景是“JavaScript异步编程”，不过并没有限制是跑在浏览器还是服务器端。最近&lt;a href="http://nodejs.org/" target="_blank"&gt;Node.js&lt;/a&gt;很火热，也刚发布了原生的Windows版，不少同学会用它来做一些网站这样的小程序。目前用Node.js开发网站最著名的框架是&lt;a href="http://expressjs.com/" target="_blank"&gt;Express&lt;/a&gt;，使用起来也是比较容易的。前段时间看到&lt;a href="http://cnodejs.org/blog/?p=1310" target="_blank"&gt;CNodeJS社区的一篇文章&lt;/a&gt;，有同学将一个&lt;a href="http://simple-is-better.com/news/309" target="_blank"&gt;Python写的ToDo列表网站&lt;/a&gt;移植到了Node.js上，我为了推广Jscex，就fork了这个项目，将其修改为&lt;a href="https://github.com/JeffreyZhao/todo" target="_blank"&gt;基于Jscex的版本&lt;/a&gt;，大伙儿可以来比较一下。当然这个网站过于简单，我也正在寻找更合适的项目。&lt;/p&gt;

&lt;p&gt;JavaScript是一个没有阻塞特性的语言，因此各类API都会设计为异步，这对于服务器的伸缩性和客户端网页的响应能力都有好处，不过在程序编写上就会遇到各种问题了。例如在ToDo示例中的一个简单的处理函数，因为需要查询数据库，就要写成带回调的样子：&lt;/p&gt;

&lt;pre class="code"&gt;exports.index = &lt;span style="color: blue"&gt;function &lt;/span&gt;(req, res, next) {
    db.query(&lt;span style="color: maroon"&gt;'select * from todo order by finished asc, id asc limit 50'&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(err, rows) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) &lt;span style="color: blue"&gt;return &lt;/span&gt;next(err);
        res.render(&lt;span style="color: maroon"&gt;'index'&lt;/span&gt;, { todos: rows });
    });
};&lt;/pre&gt;

&lt;p&gt;db变量用来操作MySQL数据库，它的query方法传入sql（可能还会有参数）并提供一个回调函数，用来提示错误或是返回查询结果。在回调中我们必须判断err是否存在，如果存在便调用next报告框架“出错了”。每个异步操作都必须如此，试想如果在这个查询后还有另一个查询，则还需要进行一次嵌套和err判断。每个处理函数都是如此，这也是异步编程的烦恼之一：难以进行统一的异常处理，处理代码总是需要分散在各处，一不小心就变成“野异常”，还很难排查出来。&lt;/p&gt;

&lt;p&gt;我将ToDo网站简单地Jscex化了一下。首先让MySQL的查询能够接入Jscex（lib\jscex.mysql.js）：&lt;/p&gt;

&lt;pre class="code"&gt;exports.jscexify = &lt;span style="color: blue"&gt;function &lt;/span&gt;(db) {
    db.queryAsync = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;_this = &lt;span style="color: blue"&gt;this&lt;/span&gt;;

        &lt;span style="color: blue"&gt;var &lt;/span&gt;args = [];
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; arguments.length; i++) {
            args.push(arguments[i]);
        }

        &lt;span style="color: blue"&gt;var &lt;/span&gt;delegate = {
            onStart: &lt;span style="color: blue"&gt;function &lt;/span&gt;(callback) {

                args.push(&lt;span style="color: blue"&gt;function &lt;/span&gt;(err, result) {
                    &lt;span style="color: blue"&gt;if &lt;/span&gt;(err) {
                        callback(&lt;span style="color: maroon"&gt;&amp;quot;failure&amp;quot;&lt;/span&gt;, err);
                    } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
                        callback(&lt;span style="color: maroon"&gt;&amp;quot;success&amp;quot;&lt;/span&gt;, result);
                    }
                });

                _this.query.apply(_this, args);
            }
        };

        &lt;span style="color: blue"&gt;return new &lt;/span&gt;Jscex.Async.Task(delegate);
    }
}&lt;/pre&gt;

&lt;p&gt;一般来说，将一个异步接口给Jscex化并不需要那么多代码（最关键的其实只是onStart函数）。这里近30行代码，其中大部分是为了支持“变长”参数，因此queryAsync函数会保留调用时的所有参数，补上一个callback，再去调用query函数本身。此时，便可以去改写之前的index等处理函数了（controllers\todo.js），例如：&lt;/p&gt;

&lt;pre class="code"&gt;exports.index = toHandler(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;todos = $await(db.queryAsync(&lt;span style="color: maroon"&gt;'select * from todo order by finished asc, id asc limit 50'&lt;/span&gt;));
    res.render(&lt;span style="color: maroon"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;, { todos: todos });

})));&lt;/pre&gt;

&lt;p&gt;toHandler函数的作用，是将一个“接受req和res，返回Task”的函数，封装成标准的“接受req、res和next三个参数”的处理函数，并提供统一的错误处理：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;toHandler = &lt;span style="color: blue"&gt;function &lt;/span&gt;(asyncFunc) {
    &lt;span style="color: blue"&gt;return function &lt;/span&gt;(req, res, next) {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;task = asyncFunc(req, res);
        task.addListener(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(task.status == &lt;span style="color: maroon"&gt;&amp;quot;failed&amp;quot;&lt;/span&gt;) {
                next(task.error);
            }
        });
        task.start();
    }
}&lt;/pre&gt;

&lt;p&gt;我在todo.js里保留了原有各个处理函数的实现，感兴趣的朋友可以对比一下它们之前的差别。可惜的是，由于ToDo实在过于简单，Jscex的优势并没有表现出来太多。例如，每个处理程序中只有一个MySQL查询，没有判断和循环，更别说为了充分利用IO并发能力，从而组合多个异步函数了。因此，我最近也一直在寻找更复杂一些的示例，不过似乎用Express的开源网站并不多见，我几乎都想自己写一个了。目前感觉&lt;a href="https://github.com/alexyoung/nodepad" target="_blank"&gt;Nodepad&lt;/a&gt;似乎还算不错，接下来可能会对它下手。&lt;/p&gt;

&lt;p&gt;ToDo网站依赖Express，ejs和MySQL驱动，同时我把Jscex作为添加为它的子模块。如果您要&lt;a href="https://github.com/JeffreyZhao/todo" target="_blank"&gt;克隆一份ToDo的代码&lt;/a&gt;把玩一番，可以：&lt;/p&gt;

&lt;pre class="code"&gt;&amp;gt; git clone git://github.com/JeffreyZhao/todo.git
&amp;gt; cd todo
&amp;gt; git submodule init
&amp;gt; git submodule update
&amp;gt; npm install express ejs mysql
&amp;gt; node server.js&lt;/pre&gt;

&lt;p&gt;从现在开始，我会在InfoQ中文站上发表一系列关于Jscex的文章，既有关于浏览器端的JavaScript开发，也有在服务器端利用Node.js开发的内容。可能您目前还可能会有所疑惑，例如为什么要使用危险的eval函数，eval和Jscex.compile函数不能封装起来吗？其实在看了我的文章并对Jscex有了基本了解之后，就会发现这些都是以“传统眼光”来看待Jscex时所形成的误解。Jscex的做法的确“另辟蹊径”，否则在JavaScript异步类库已经多如牛毛的情况下，我不知如何让它脱颖而出。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/07/nodejs-express-jscex-demo-website-todo.html#comments</comments>
      <pubDate>Fri, 15 Jul 2011 06:33:10 GMT</pubDate>
      <lastBuildDate>Fri, 15 Jul 2011 08:57:40 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/reading/">阅读相关</category>
      <category domain="http://blog.zhaojie.me/news/">新闻信息</category>
      <title>从今开始远离纸版图书</title>
      <link>http://blog.zhaojie.me/2011/07/get-away-from-paper-books.html</link>
      <guid>http://blog.zhaojie.me/2011/07/get-away-from-paper-books.html</guid>
      <description>&lt;p&gt;纸版图书的历史实在太长了，似乎也已经看得到它被淘汰的那一天。这几天在为去深圳做准备，手边许多技术图书都成了大问题。搬去深圳会大动干戈，况且这些书基本也都只是偶尔翻阅一下，不太可能再认真读起。但如果把它们留在上海，那跟空置也没什么区别。以前我有个习惯，看到经典的书，在看完电子版或国内引进版之后都会买本英文原版的来“收藏”，现在已经“幡然醒悟”，之前也已经转让了一部分。现在除了一部分书，都打算出手了。这里开了个书单，感兴趣的同学可以各取所需。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;《&lt;a href="http://book.douban.com/subject/4058874/"&gt;领域驱动设计与模式实战&lt;/a&gt;》，七成新，20元。&lt;/li&gt;

  &lt;li&gt;&lt;strike&gt;《&lt;a href="http://book.douban.com/subject/1229951/"&gt;Computer Networks, 4th Edition&lt;/a&gt;》，原版，几乎全新，经典教科书，80元。&lt;/strike&gt;&lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/4722708/"&gt;系统程序员成长计划&lt;/a&gt;》，九成新，15元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/1441780/"&gt;游戏之旅：我的编程感悟&lt;/a&gt;》，九成新，15元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;&lt;strike&gt;《&lt;a href="http://book.douban.com/subject/2977175/"&gt;Concurrent Programming on Windows&lt;/a&gt;》，原版，六成新，100元。&lt;/strike&gt;&lt;/li&gt;

  &lt;li&gt;&lt;strike&gt;《&lt;a href="http://book.douban.com/subject/2567634/"&gt;Windows Internals, 5th Edition&lt;/a&gt;》，硬壳原版，全新，100元。&lt;/strike&gt;&lt;/li&gt;

  &lt;li&gt;&lt;strike&gt;《&lt;a href="http://book.douban.com/subject/3674988/"&gt;Data Access Patterns: Database Interactions in Object-Oriented Applications&lt;/a&gt;》，硬壳原版（链接是软皮的），全新，60元。&lt;/strike&gt;&lt;/li&gt;

  &lt;li&gt;&lt;strike&gt;《&lt;a href="http://book.douban.com/subject/1237003/"&gt;Cryptography and Network Security: Principles and Practices, 3rd Edition&lt;/a&gt;》，硬壳原版，全新，80元。&lt;/strike&gt;&lt;/li&gt;

  &lt;li&gt;&lt;strike&gt;《&lt;a href="http://book.douban.com/subject/1246136/"&gt;.NET Patterns: Architecture, Design and Process&lt;/a&gt;》，原版，全新，50元。&lt;/strike&gt;&lt;/li&gt;

  &lt;li&gt;&lt;strike&gt;《&lt;a href="http://book.douban.com/subject/1246134/"&gt;Effective XML: 50 Specific Ways to Improve Your XML&lt;/a&gt;》，原版，全新，40元。&lt;/strike&gt;&lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/3117898/"&gt;Python源码剖析：深度探索动态语言核心技术&lt;/a&gt;》，八成新，20元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/4719230/"&gt;编程之魂：与27位编程语言创始人对话&lt;/a&gt;》，七成新，20元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/5273954/"&gt;Collective Intelligence实战&lt;/a&gt;》，九成新，20元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/1205482/"&gt;算法与数据结构（第二版）&lt;/a&gt;》，旧书，好书，15元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/3651015/"&gt;事务处理：概念与技术（影印版）&lt;/a&gt;》，几乎全新，40元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/3558788/"&gt;卓有成效的程序员&lt;/a&gt;》，八成新，15元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/4086029/"&gt;架构之美&lt;/a&gt;》，七成新，20元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/4214151/"&gt;并行开发艺术（影印版）&lt;/a&gt;》，几乎全新，15元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/3590768/"&gt;JavaScript语言精粹&lt;/a&gt;》，作者签名版，九成新，15元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/4736118/"&gt;结网：互联网产品经理改变世界&lt;/a&gt;》，全新，20元。&lt;/strike&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;strike&gt;《&lt;a href="http://book.douban.com/subject/1467587/"&gt;Unix编程艺术&lt;/a&gt;》，八成新，20元。&lt;/strike&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些书大约都以三折左右出售（购买多本亦可小刀）。我不送书，并不是想赚钱，而是希望可以让书到达真正想要的人手中，如果卖不掉我也会送给公司图书馆。如果您看上了其中某些书，可以在回复里留下您的联系方式（建议是邮箱或是新浪微博）以及书籍名称，我会与您联系。您可以来我这里现取（地址：上海市浦东新区郭守敬路356号），亦或是选择快递。国内快递的价格大约是每公斤10元，我与您各付一半。您可以在现取时付钱给我，也可以通过支付宝打款。&lt;/p&gt;

&lt;p&gt;接下来我会远离纸版图书，以购买正版电子书为主，也希望国内出版社能尽快跟上时代潮流，发行电子版。如果出版社无法做到的话，我愿意以两倍书价从作者那里购买电子版，保证绝不外泄。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/07/get-away-from-paper-books.html#comments</comments>
      <pubDate>Thu, 07 Jul 2011 16:07:09 GMT</pubDate>
      <lastBuildDate>Wed, 13 Jul 2011 14:35:31 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/news/">新闻信息</category>
      <title>两则.NET高级技术人员的招聘信息</title>
      <link>http://blog.zhaojie.me/2011/06/two-job-descriptions.html</link>
      <guid>http://blog.zhaojie.me/2011/06/two-job-descriptions.html</guid>
      <description>&lt;p&gt;几小时前我在微博上&lt;a href="http://weibo.com/1560442584/eCyiyZRpjmq"&gt;发布了一条消息&lt;/a&gt;，表示我即将加入一家外企，而且完全是大家耳熟能详的IT公司之一，而且这个公司会让大家感到“意外”。于是大伙有猜微软的，也有猜Google，Apple，Oracle，HP等等，当然也有猜对的童鞋。在此公布答案，它便是传说中的IBM公司，我将在那里继续我的.NET程序员之旅。同时，我也希望可以找到一位同学可以帮助我目前在&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;的&lt;a href="http://note.sdo.com/"&gt;项目&lt;/a&gt;继续良好地发展下去。&lt;/p&gt;

&lt;h1&gt;关于IBM的职位&lt;/h1&gt;

&lt;p&gt;估计大伙会对“在IBM做.NET开发”这点有些疑惑，这里我来解释一下。大约一个月前我收到一封邮件，说IBM目前和&lt;a href="http://www.jpmorgan.com/"&gt;J.P. Morgan&lt;/a&gt;有个合作项目，后者希望建立一个离岸开发中心，由IBM负责招聘员工。这些人员单为J.P Morgan服务（我现场“考察”下来，的确是一个挂着“摩根大通”招牌的独立工作区域），并且会在合同里写明，如果J.P. Morgan满意的话，可以在两年后收编这个团队。那位仁兄说，这个职位主要有三个好处：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;有机会进入投行，众所周知投行米多。 &lt;/li&gt;

  &lt;li&gt;在IBM可以无视那伟大光荣正确的防火墙。 &lt;/li&gt;

  &lt;li&gt;两年后有机会在香港工作。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;于是我就被打动了，表示可以尝试一下。经过好几轮笔试电话面现场面，我在两天前拿到了IBM的Offer，也是第一个通过的.NET技术人员。可以想象，如果没有J.P. Morgan，IBM也不会招聘.NET技术人员也不会来联系我；如果不是J.P. Morgan和香港，我也不会去尝试加入IBM，更何况要放弃很多东西，从上海千里迢迢前往深圳。我的目的其实也是希望几年后能够在香港工作，然后那时候也恰好可以生个娃什么的……创新院是个好地方，换工作也是个综合考虑的结果。&lt;/p&gt;

&lt;p&gt;那边也在努力招聘员工，虽然努力但也并没有放松要求，尤其是很难找到令人满意的.NET技术人员，因此他们也希望我可以推荐一些。这份.NET工作主要会用到的框架是WPF、WCF和少量ASP.NET，不过您没有接触过这些也没有关系，因为我也丝毫不懂WCF和WPF。笔试和面试时考察的大都是.NET的基础编程能力（CLR，BCL等等）以及计算机基础（例如操作系统与数据结构），还有便是架构、设计、排错以及解决问题的能力等等。这两天我也会单独成文记录一下这方面的情况。&lt;/p&gt;

&lt;p&gt;还有便是英语方面的要求。笔试都是英文，也有英文测试（可以查字典，但每道题都有时间限制）。电话面试和现场面试也都是从J.P. Morgan来的老外，除了您的名字以外听不到半个中文字。不过其实您也不用特别担心，我觉得只要您不是完全的哑巴英语，这方面应该都不会太大问题——要知道笔试面试都会围绕您最熟悉的技术方面展开。就拿我来说，语言方面的最大问题，是在跟老外HR用英语谈薪资时遇到的……&lt;/p&gt;

&lt;p&gt;这个职位在深圳。如果您对这个职位感兴趣，可以给我写封邮件说明一下您的个人情况，我可以考虑推荐给IBM的HR。&lt;/p&gt;

&lt;h1&gt;关于盛大创新院的职位&lt;/h1&gt;

&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;是个很不错的地方，有丰厚的薪资，优秀的同事，可以说是个很理想的工作环境。如果您对互联网感兴趣，在那里你永远能发现和玩到各种有趣的东西。例如，今天下班前&lt;a href="http://weibo.com/dflyingchen"&gt;陈黎夫&lt;/a&gt;同学随手拍了一个同事的工作区：&lt;/p&gt;
&lt;img src="http://ww2.sinaimg.cn/large/66573789jw1diesk5pqkzj.jpg" /&gt; 

&lt;p&gt;您可以认出其中多少设备？MBP、PC，iMac，iPad 2，Moto Xoom，自然还有iPhone 4和安卓手机——完全没有刻意地收集拍摄，只是突然意识到原来已经有了那么多设备。&lt;/p&gt;

&lt;p&gt;这个工作台的主人和我在一个项目组，“&lt;a href="http://note.sdo.com/"&gt;麦库&lt;/a&gt;”。这个项目是我和其他两位同事从零开始创建的，可以说有着很深的感情。这个项目的发展状况也比较不错，我这次也是花了很长时间才下决心“撤退”。我选择IBM最重要的原因还是“香港”，如果不是这一点，从薪资待遇、工作环境，未来发展来考虑，我几乎不可能离开创新院。更何况去了IBM之后我几乎是转了半个行业，从互联网转向企业应用了。即便离开这个团队，我自然也希望这个项目可以继续良好的发展项目，因此在这里也在临走前为组内找到一个合适的人才。&lt;/p&gt;

&lt;p&gt;我希望您可以有良好的.NET以及ASP.NET编程基础，麦库使用的技术是.NET 4和ASP.NET MVC 3。后台使用CentOS上的MongoDB作为存储，因此也希望您最好有一些Linux和NoSQL方面的经验。在项目的制作过程中，我也总结出了一些开源项目，例如&lt;a href="https://github.com/JeffreyZhao/EasyMongo"&gt;EasyMongo&lt;/a&gt;和&lt;a href="https://github.com/JeffreyZhao/JsonMe"&gt;JsonMe&lt;/a&gt;，我希望您也能够跟我一起完善这些项目，让麦库发展得更好。&lt;/p&gt;

&lt;p&gt;我们在上海工作，如果您对此感兴趣，也请通过邮件联系我。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/06/two-job-descriptions.html#comments</comments>
      <pubDate>Tue, 21 Jun 2011 15:25:47 GMT</pubDate>
      <lastBuildDate>Tue, 05 Jul 2011 16:39:17 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>匿名类型的硬伤：围绕this的成员捕获策略</title>
      <link>http://blog.zhaojie.me/2011/06/java-anonymous-method-closure-scope-this.html</link>
      <guid>http://blog.zhaojie.me/2011/06/java-anonymous-method-closure-scope-this.html</guid>
      <description>&lt;p&gt;时不时听到一些C#程序员说，希望在C#里出现像Java匿名类一样的特性。以前我也觉得Java里的匿名类是个不错的特性，C#应该吸取进来。不过前段时间我仔细地理解了Java语言规范中关于内部类、匿名类的部分之后，一下子就被恶心到了。恶心过后，我忽然也意识到有些问题的确也是硬伤，也不能指责Java设计者的“品位”。例如，现在我想要谈的关于匿名类中this使用的问题——如果C#没法漂亮地实现这个特性，我宁愿它继续保持现状。&lt;/p&gt;

&lt;h1&gt;Java匿名类中的this&lt;/h1&gt;

&lt;p&gt;Java的匿名类特性，在于可以在项目里“内联”地实现一个类型，它可以继承一个现有的具体或抽象类，或是实现接口，并提供完整的成员实现。例如，这里有个抽象类，定义了一个抽象方法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// Java
&lt;/span&gt;&lt;span style="color: blue"&gt;abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyAbstractClass&lt;/span&gt; {
    &lt;span style="color: #2b91af"&gt;String &lt;/span&gt;getName() {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;MyAbstractClass&amp;quot;&lt;/span&gt;;
    }

    &lt;span style="color: blue"&gt;abstract void &lt;/span&gt;print();
}&lt;/pre&gt;

&lt;p&gt;然后我们在另一个地方使用一个匿名类，继承这个类：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// Java&lt;/span&gt;
&lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyClass &lt;/span&gt;{
    &lt;span style="color: #2b91af"&gt;String &lt;/span&gt;getName() {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;MyClass&amp;quot;&lt;/span&gt;;
    }

    &lt;span style="color: #2b91af"&gt;MyAbstractClass &lt;/span&gt;someMethod() {
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyAbstractClass&lt;/span&gt;() {
            &lt;span style="color: blue"&gt;public void &lt;/span&gt;print() {
                &lt;span style="color: #2b91af"&gt;System&lt;/span&gt;.out.println(getName());
            }
        };
    }
}&lt;/pre&gt;

&lt;p&gt;好，现在提一个问题，运行下面这行代码会打印出什么结果？&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// Java&lt;/span&gt;
&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyClass&lt;/span&gt;().someMethod().print();&lt;/pre&gt;

&lt;p&gt;输出结果是MyAbstractClass而不是MyClass。换句话说，匿名类型中调用的getName方法是定义在MyAbstractClass里的，而不是定义在词法作用域（Lexical Scope）里的getName方法。根据Java规范，匿名类中的this（包括上面代码中“隐式”的this）表示类型本身对象，而与上下文无关。如果要访问词法作用域里的getName方法（即MyClass的方法），则反而必须显式指定MyClass类，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// Java&lt;/span&gt;
&lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyClass &lt;/span&gt;{
    &lt;span style="color: #2b91af"&gt;String &lt;/span&gt;getName() {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;MyClass&amp;quot;&lt;/span&gt;;
    }

    &lt;span style="color: #2b91af"&gt;MyAbstractClass &lt;/span&gt;someMethod() {
        &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyAbstractClass&lt;/span&gt;() {
            &lt;span style="color: blue"&gt;public void &lt;/span&gt;print() {
                &lt;span style="color: #2b91af"&gt;System&lt;/span&gt;.out.println(&lt;span style="background-color: yellow"&gt;&lt;span style="color: #2b91af"&gt;MyClass&lt;/span&gt;.&lt;span style="color: blue"&gt;this&lt;/span&gt;&lt;/span&gt;.getName());
            }
        };
    }
}&lt;/pre&gt;

&lt;h1&gt;可能会造成的问题&lt;/h1&gt;

&lt;p&gt;在我看来，Java的这个设计决策很不好，十分容易让人误解代码的意图，但我相信肯定也有人会认为这只是个“品位”区别而已，没有高低。那么现在我们撇开“品位”不谈，谈点这个决策可能会造成的问题吧。例如，程序员A写了一个抽象类：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// Java&lt;/span&gt;
&lt;span style="color: blue"&gt;abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyAbstractClass &lt;/span&gt;{
&lt;span style="color: green"&gt;    &lt;/span&gt;&lt;span style="color: blue"&gt;abstract void &lt;/span&gt;print();
}&lt;/pre&gt;

&lt;p&gt;程序员B在另一个类的方法中编写了MyAbstractClass的匿名子类：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// Java&lt;/span&gt;
&lt;span style="color: #2b91af"&gt;MyAbstractClass &lt;/span&gt;someMethod() {
    &lt;span style="color: blue"&gt;final &lt;/span&gt;&lt;span style="color: #2b91af"&gt;String &lt;/span&gt;name = &lt;span style="color: #a31515"&gt;&amp;quot;MyClass&amp;quot;&lt;/span&gt;;
    &lt;span style="color: blue"&gt;return new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyAbstractClass&lt;/span&gt;() {
        &lt;span style="color: blue"&gt;public void &lt;/span&gt;print() {
            &lt;span style="color: #2b91af"&gt;System&lt;/span&gt;.out.println(name);
        }
    };
}&lt;/pre&gt;

&lt;p&gt;很显然，print方法会打印出name变量的值MyClass。相安无事多日，忽然某一天，程序员A需要为MyAbstractClass添加一些新功能，新增了一个受保护的name字段：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// Java&lt;/span&gt;
&lt;span style="color: blue"&gt;abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MyAbstractClass&lt;/span&gt; {
    &lt;span style="color: blue"&gt;abstract void &lt;/span&gt;print();

    &lt;span style="color: green"&gt;// new field
    &lt;/span&gt;&lt;span style="color: blue"&gt;protected &lt;/span&gt;&lt;span style="color: #2b91af"&gt;String &lt;/span&gt;name = &lt;span style="color: #a31515"&gt;&amp;quot;MyAbstractClass&amp;quot;&lt;/span&gt;;
}&lt;/pre&gt;

&lt;p&gt;于是第二天程序员B惊奇地发现，自己明明没有动过任何一行代码，MyAbstractClass忽然就无法正常工作了。这真让人情何以堪。&lt;/p&gt;

&lt;h1&gt;Java 8的Lambda表达式&lt;/h1&gt;

&lt;p&gt;事实上，关于这种“内联”定义函数的写法，我能想到的语言都是采取“词法作用域”，因此我想Java这方面的“特立独行”的确容易让人误会。当然客观来说，Java设计成这样也是无奈之举，因为它过于强调“类型”，匿名类还是一个类，既然是个类便会有自己的成员，既然有成员就应该让内联的函数有办法调用这些成员。与之相对，虽然C#中也可以定义内联的函数，却完全不会有Java的困扰，因为C#中内联的只是“函数”而不是完整的“类型”。&lt;/p&gt;

&lt;p&gt;说到，底还是多亏.NET中提供了“委托”这种纯粹的，可以让“函数”独立存在的概念。当时在C# 1.0刚出现时，Sun官方还发布文章，认为“委托”破坏了面向对象的纯粹性，“内部类”完全可以作为委托来使用。现在看来，这中观点无疑是一个笑话。追求纯粹的面向对象与盲目套用设计模式类似，都是舍本逐末的做法，我们追求的是“良好的设计”，“面向对象”只是手段而不是目标。如今C#已经发布近十年了，Java社区也在努力向Java7、Java 8里引入部分C#的特性，例如Lambda表达式。&lt;/p&gt;

&lt;p&gt;但是，由于Java中没有“委托”，即便是Lambda表达式依旧无法提供单独函数，还是必须附带一个完整的类型。因此this问题依旧存在，这依然是个硬伤。例如我&lt;a href="http://blog.zhaojie.me/2010/06/first-version-of-lambda-and-closures-in-java-7.html"&gt;以前的文章&lt;/a&gt;里也提到过Java 7里的SAM类型和Lambda表达式上下文成员的捕获策略。从&lt;a href="http://hg.openjdk.java.net/lambda/lambda/langtools/file/7704dcd17e0b/test/tools/javac/lambda/LambdaScope01.java"&gt;当时的资料&lt;/a&gt;来看，Lambda表达式的策略与匿名类相同，依旧以“匿名类”的成员优先，换句话说Lambda表达式只是匿名类的简单写法而已。不过现在这方面有了些许变化，例如&lt;a href="http://www.ordina.nl/nl/~/media/Files/Onze%20dienstverlening/Technologie/Masterclass%20Brian%20Goetz%20%20Project%20Lambda.ashx?forcedownload=1"&gt;这份幻灯片&lt;/a&gt;第18页里提到：Lambda表达式是一个拥有词法作用域的匿名方法（A lambda expression is a lexically scoped anonymous method），它的上下文成员捕获与Java的内部类、匿名类有明显不同。&lt;/p&gt;

&lt;p&gt;当然，如果使用匿名类的语法定义一个SAM类型，this相关的策略还是要与以前保持不变。Java和C#这类工业化语言的一个包袱，便是要保证兼容性——包括类库等其他方面。所以我还是一直认为，像Python，Ruby这般“洒脱”的技术平台及社区，的确很难进入企业开发市场。&lt;/p&gt;

&lt;h1&gt;硬伤&lt;/h1&gt;

&lt;p&gt;this问题可以说是Java匿名类特性的硬伤。C#如果想要引入这个匿名特性，似乎也完全无法躲开这一点。我并不希望C#引入一个“丑陋”的语言特性，幸好也没有任何迹象表明C#有这方面的打算。有趣的是，F#提供了类似Java匿名类的特性，但完全没有这个问题。为什么呢？一看代码便知：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// Java&lt;/span&gt;
[&amp;lt;AbstractClass&amp;gt;]
&lt;span style="color: blue"&gt;type &lt;/span&gt;MyAbstractClass() =
    &lt;span style="color: blue"&gt;member &lt;/span&gt;this.Name = &lt;span style="color: maroon"&gt;&amp;quot;MyAbstractClass&amp;quot;
    &lt;/span&gt;&lt;span style="color: blue"&gt;abstract member &lt;/span&gt;Print: unit &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;unit

&lt;span style="color: blue"&gt;type &lt;/span&gt;MyClass() =
    &lt;span style="color: blue"&gt;let &lt;/span&gt;name = &lt;span style="color: maroon"&gt;&amp;quot;Local&amp;quot;

    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;this.Name = &lt;span style="color: maroon"&gt;&amp;quot;MyClass&amp;quot;
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;this.MyMethod () =
        { &lt;span style="color: blue"&gt;new &lt;/span&gt;MyAbstractClass() &lt;span style="color: blue"&gt;with
            override &lt;/span&gt;inner.Print () =
                printfn &lt;span style="color: maroon"&gt;&amp;quot;%s&amp;quot; &lt;/span&gt;this.Name
                printfn &lt;span style="color: maroon"&gt;&amp;quot;%s&amp;quot; &lt;/span&gt;inner.Name
                printfn &lt;span style="color: maroon"&gt;&amp;quot;%s&amp;quot; &lt;/span&gt;name }&lt;/pre&gt;

&lt;p&gt;在F#中，定义一个类型的成员时，需要指定“该方法中表示自身对象的标识符”，我们可以将标识符取名为this，也可以取名为inner或是任意值。再加上F#中没有“隐式”的this指针存在，一切都是指明的，自然没有任何问题。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/06/java-anonymous-method-closure-scope-this.html#comments</comments>
      <pubDate>Wed, 15 Jun 2011 09:27:14 GMT</pubDate>
      <lastBuildDate>Wed, 15 Jun 2011 09:27:14 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>JavaScript：假如default不是switch的最后一项</title>
      <link>http://blog.zhaojie.me/2011/05/javascript-when-break-is-not-the-last-choice-of-switch.html</link>
      <guid>http://blog.zhaojie.me/2011/05/javascript-when-break-is-not-the-last-choice-of-switch.html</guid>
      <description>&lt;p&gt;话说大家对于switch语句应该再熟悉不过了，各种类C语言都不例外，JavaScript自然也是如此。switch的逻辑很简单，根据switch内容的值执行对应的case项，否则执行default项即可。但是不同的语言在具体一些细节上面的处理却是不同的。例如在JavaScript里，每个case项都可以没有break，于是语句便会顺延到下个case或是default里面去——但某些语言设计者认为这种特性容易造成代码理解上的偏差，因此比如在C#里便要求每个非空的case都要有个break。那么再来一个细节问题：如果default之后还有case，那么会出现什么样的情况？如果default里没有break呢？&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;switch &lt;/span&gt;(a) {
    &lt;span style="color: blue"&gt;case &lt;/span&gt;0:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;default&lt;/span&gt;:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;case &lt;/span&gt;1:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
}&lt;/pre&gt;

&lt;p&gt;就好比这段代码，当a等于0、1或2的时候，将会输出什么样的内容呢？先猜猜，别急着往下看。&lt;/p&gt;

&lt;p&gt;当a等于0时，则会输出：&lt;/p&gt;

&lt;pre class="code"&gt;0
default
1&lt;/pre&gt;

&lt;p&gt;当a等于1时，则会输出：&lt;/p&gt;

&lt;pre class="code"&gt;1&lt;/pre&gt;

&lt;p&gt;当a等于2时，则会输出：&lt;/p&gt;

&lt;pre class="code"&gt;default
1&lt;/pre&gt;

&lt;p&gt;好吧，尽管这样的代码比较罕见，但执行结果也并没有什么“特殊”的。switch的规则依旧可以用一句话说清：如果匹配到某个case，则从该case处开始执行，否则就从default处开始执行，一直向下，直到出现break语句为止。至于default的位置是否在最后，对于执行的策略可谓完全没有影响。&lt;/p&gt;

&lt;p&gt;当然，我实在没想到为什么有人会写这样的代码，所以假如有人对这点感觉恍惚我也觉得没太大关系。不过既然我要写&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;，则还是必须对此类代码的行为有所了解。尽管语言的使用者可以选择合适的子集，但语言的开发者（编译器、解释器等等）却必须遵循完整的规范，这是Jscex这类项目需要应对的麻烦。&lt;/p&gt;

&lt;p&gt;既然Jscex号称支持“全部JavaScript语言特性”，自然对switch的支持也在包括在内。switch的麻烦之处在于它的每个分支不像if语句那样完全相互独立，而是会不断“穿透”下去直至遇上break。因此Jscex在处理switch的时候也使用了一些技巧。例如下面这段代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;switch &lt;/span&gt;(a) {
    &lt;span style="color: blue"&gt;case &lt;/span&gt;0:
        $await(helloWorld());
    &lt;span style="color: blue"&gt;default&lt;/span&gt;:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;case &lt;/span&gt;1:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
}&lt;/pre&gt;

&lt;p&gt;Jscex会将每个case及default中的语句“补齐”，以“确保”每项里都有完整的语句以及最后的break：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;switch &lt;/span&gt;(a) {
    &lt;span style="color: blue"&gt;case &lt;/span&gt;0:
        $await(helloWorld());
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;break&lt;/span&gt;;
    &lt;span style="color: blue"&gt;default&lt;/span&gt;:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;break&lt;/span&gt;;
    &lt;span style="color: blue"&gt;case &lt;/span&gt;1:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;break&lt;/span&gt;;
}&lt;/pre&gt;

&lt;p&gt;然后再将其编译为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;switch &lt;/span&gt;(a) {
    &lt;span style="color: blue"&gt;case &lt;/span&gt;0:
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Bind(helloWorld(), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
            console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
        });
    &lt;span style="color: blue"&gt;default&lt;/span&gt;:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;);
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
    &lt;span style="color: blue"&gt;case &lt;/span&gt;1:
        console.log(&lt;span style="color: maroon"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
    }
})&lt;/pre&gt;

&lt;p&gt;自然，如果switch里没有包含bind操作（例如$await语句），则整个switch语句都会得以保留，这也是&lt;a href="http://blog.zhaojie.me/2011/05/jscex-compiled-code-optimization.html"&gt;Jscex编译结果的优化策略&lt;/a&gt;之一。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/05/javascript-when-break-is-not-the-last-choice-of-switch.html#comments</comments>
      <pubDate>Tue, 24 May 2011 03:33:03 GMT</pubDate>
      <lastBuildDate>Wed, 25 May 2011 16:59:52 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>上周末Jscex项目介绍的幻灯片</title>
      <link>http://blog.zhaojie.me/2011/05/jscex-write-sexy-javascript-slide.html</link>
      <guid>http://blog.zhaojie.me/2011/05/jscex-write-sexy-javascript-slide.html</guid>
      <description>&lt;p&gt;上周末，在风景秀丽的浙江大学校园内，举行了&lt;a href="http://cnodejs.org/blog/?p=956"&gt;NodeParty杭州站的活动&lt;/a&gt;。我在活动上结合&lt;a href="http://nodejs.org/"&gt;Node.js&lt;/a&gt;项目对&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;进行了简单介绍，包括其设计目的，设计原则，使用方式，高级模式，组成部分等等。在场的许多朋友也提出了不少问题，我也一一作了解答或是演示。总体感觉还算不错，毕竟是亲手编写的项目，对其各方面还是了然于胸的。在此发布演讲用的幻灯片，希望能给不在现场的同学带来一些帮助。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_7976001"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="Jscex: Write Sexy JavaScript (中文)" href="http://www.slideshare.net/jeffz/jscex-write-sexy-javascript-cn"&gt;Jscex: Write Sexy JavaScript (中文)&lt;/a&gt;&lt;/strong&gt; &lt;iframe height="355" marginheight="0" src="http://www.slideshare.net/slideshow/embed_code/7976001" 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/"&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;/p&gt;

&lt;div style="width: 425px" id="__ss_7975832"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="Jscex: Write Sexy JavaScript" href="http://www.slideshare.net/jeffz/jscex-write-sexy-javascript"&gt;Jscex: Write Sexy JavaScript&lt;/a&gt;&lt;/strong&gt; &lt;iframe height="355" marginheight="0" src="http://www.slideshare.net/slideshow/embed_code/7975832" 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/"&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;欢迎您使用Jscex，遇到什么问题也请及时与我联系。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;GitHub（英文主站）：&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;https://github.com/JeffreyZhao/jscex&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;SNDACode（中文站）：&lt;a href="http://www.sndacode.com/projects/jscex"&gt;http://www.sndacode.com/projects/jscex&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2011/05/jscex-write-sexy-javascript-slide.html#comments</comments>
      <pubDate>Mon, 16 May 2011 05:43:40 GMT</pubDate>
      <lastBuildDate>Mon, 16 May 2011 05:45:21 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>浅谈Jscex的$await语义及异步任务模型</title>
      <link>http://blog.zhaojie.me/2011/05/jscex-async-task-model-and-await-semantic.html</link>
      <guid>http://blog.zhaojie.me/2011/05/jscex-async-task-model-and-await-semantic.html</guid>
      <description>&lt;p&gt;从某些程度上说，&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;的是提供了“新语言”，只不过这种新语言和JavaScript长的一模一样，最多添加了一个$await操作这个语义而已。其他方面，JavaScript的各种语法都可以让Jscex编译，所以它基本可以说是个完备的方案。之前有朋友提出疑问，说$await只能执行单个任务，那么岂不是多个任务之间就出现了先后依赖关系？假如有三个任务：A和B可以并行，但C依赖前两者，ABC如果串行的话，系统的总耗时便不够理想了。其实Jscex并没有这种限制，因为它的任务模型和$await语义简单且具有深厚的理论基础，灵活、丰富而统一。&lt;/p&gt;

&lt;p&gt;首先谈下$await的语义，有些朋友阅读示例代码，可能会觉得它表示“执行一个异步任务”。其实不然。$await的语义实际上只是“等待该任务结束”，同时：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果该任务没有运行，则启动该任务。 &lt;/li&gt;

  &lt;li&gt;如果该任务已经完成，则立即返回结果（或抛出异常）。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在Jscex的异步类库中，“异步任务”是独立的模型，它有自己的start或addListener等成员。一个Jscex异步函数的执行结果也是个异步任务对象，我们最终也是调用其start方法来启动这个任务。在一个Jscex函数内部，我们也可以手动地启动任务。例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(taskA, taskB, taskC) {
    taskA.start();
    taskB.start();

    $await(taskA);
    $await(taskB);

    $await(taskC);
}&lt;/pre&gt;

&lt;p&gt;任务A和B的start方法会在调用后立即返回，并在两者都完成后，才会启动并等待C任务。以上便回答了之前那个朋友提出的问题。由于A和B没有依赖，我们便让其并行执行；C依赖前两者，于是便等A和B结束再启动，仅此而已。事实上，我们也可以这么做：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(taskA, taskB, taskC) {
    $await(Jscex.Async.parallel(taskA, taskB));
    $await(taskC);
}&lt;/pre&gt;

&lt;p&gt;Jscex.Async.parallel是一个辅助函数，接受一堆异步任务，并返回一个新的异步任务，执行它表示并行地执行这些子任务，它也会在子任务都完成后才结束。这个异步任务模型十分简单，人人都能轻松地使用及扩展，也十分灵活。假如我们将刚才的需求换一下：C依赖于B，但A与前两者都独立，则可以编写这样的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(taskA, taskB, taskC) {
    taskA.start();

    $await(taskB.continueWith(taskC));
    $await(taskA);
}&lt;/pre&gt;

&lt;p&gt;在JavaScript中，我们只要为Jscex.Async.Task对象扩展一个continueWith方法，表示taskC将于taskB之后执行，并作为一个新任务返回即可。此类&lt;a href="http://en.wikipedia.org/wiki/Futures_and_promises"&gt;Future/Promise模型&lt;/a&gt;，以及Jscex的Monadic编译形式在异步编程领域中都有着经典的理论基础，因此Jscex在异步编程方面的支持可谓简单而统一。&lt;/p&gt;

&lt;p&gt;Jscex一直在不断前进，如今Jscex又支持了一种$await语句形式，“赋值”：&lt;/p&gt;

&lt;pre class="code"&gt;hello.world = $await(...);&lt;/pre&gt;

&lt;p&gt;其实按理说，Jscex早该支持这种形式了，但可能是由于最早的思路有些过于借鉴F#的模式（无副作用，因此没有此类赋值），一不留意便疏忽至今了。JavaScript其实终究是一门更为“命令式”的语言，虽然Jscex由F#那里得到启发，但如今其具体实现和最终形式，却也是专为JavaScript而设计的，以确保其必要的功能（例如break，continue等中断逻辑流的语言特性）与性能。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/05/jscex-async-task-model-and-await-semantic.html#comments</comments>
      <pubDate>Tue, 10 May 2011 14:29:43 GMT</pubDate>
      <lastBuildDate>Tue, 10 May 2011 14:32: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/extension/">项目扩展</category>
      <title>浅谈Jscex编译结果的优化</title>
      <link>http://blog.zhaojie.me/2011/05/jscex-compiled-code-optimization.html</link>
      <guid>http://blog.zhaojie.me/2011/05/jscex-compiled-code-optimization.html</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/Jscex"&gt;Jscex&lt;/a&gt;的核心是一个JavaScript语言到Monadic形式的编译器。从理论上说，这种编译规则十分简单，要写一个能够“正常运行”的编译器很容易。但是“正常运行”不代表足够优化。优化不当，会导致生成的结果中产生太多函数及闭包，对性能产生负面影响。在Jscex的早期原型中，从AST生成最终代码的逻辑比较简单，只做了一些基础优化。后来&lt;a href="http://blog.zhaojie.me/2011/04/jscex-released-under-bsd-license.html"&gt;重构了编译器，减少了不必要的代码&lt;/a&gt;。而上周我提交了更新，实现了更复杂而有效的优化策略。如今的Jscex编译器部分应该已经足够稳定，剩下的便是类库方便的发展了。&lt;/p&gt;

&lt;h1&gt;基础优化&lt;/h1&gt;

&lt;p&gt;Jscex的基础编译策略十分精简，这里仅列出其中最简单一条来说明问题。比如有如下代码：&lt;/p&gt;

&lt;pre class="code"&gt;statement1;
statement2;&lt;/pre&gt;

&lt;p&gt;按照Jscex的转化标准，应该由Combine及Delay组合而成：&lt;/p&gt;

&lt;pre class="code"&gt;builder.Combine(
    builder.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        statement1;
    },
    builder.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        statement2;
    }));&lt;/pre&gt;

&lt;p&gt;在Jscex中，无论是Combine和Delay返回的都是协议相同的中间对象。出现Delay，是因为JavaScript一门Strict Evaluation的语言，需要有个机制确保“延迟执行”。Combine的作用是连接两个中间对象，并返回一个新的中间对象，该对象的“执行语义”表示“按序”执行两者。如果有三条语句则会继续使用Combine嵌套起来。&lt;/p&gt;

&lt;p&gt;那么如果有10条语句连续执行呢？这便会产生大量的Combine，Delay调用，以及一大堆函数、闭包等等。运行结果自然没有问题（就像Delay套Delay那样），但很显然会产生不必要的性能开销。因此，从最起初的Jscex原型开始，便对这样的连续语句做出了优化。例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// before&lt;/span&gt;
&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;a = 0;
    b = a + 1;
    a++;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;a + b;
}

&lt;span style="color: green"&gt;// after&lt;/span&gt;
(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = 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_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;a = 0;
            b = a + 1;
            a++;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Return(a + b);
        })
    );
})&lt;/pre&gt;

&lt;p&gt;无谓的Combine和Delay消失了，程序主体依旧是顺序执行的普通代码。&lt;/p&gt;

&lt;h1&gt;高级优化&lt;/h1&gt;

&lt;p&gt;连续的语句容易优化，所以在最早的Jscex原型中就已经有所体现。但是，如try/catch或是while循环这样的语言特性，又会变成怎么样的代码呢？例如，我们得到一个URL数组，然后使用个普通的for循环来遍历并请求每个URL里的内容：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// before
&lt;/span&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; urls.length; i++) {
        $await(requestAsync(urls[i]));
    }
}

&lt;span style="color: #006400"&gt;// after
&lt;/span&gt;(&lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = 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_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Loop(
                &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;i &amp;lt; urls.length;
                },
                &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    i++;
                },
                $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Bind(requestAsync(urls[i]), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
                    });
                }),
                &lt;span style="color: blue"&gt;false
            &lt;/span&gt;);
        })
    );
})&lt;/pre&gt;

&lt;p&gt;无论是for，while还是do循环，编译之后都会成为了Loop调用。以上的代码已经足够优化，但如果我们换种写法，将“顺序”请求变为“并发”请求：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// before
&lt;/span&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;requests = [];

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; urls.length; i++) {
        requests.push(requestAsync(urls[i]));
    }

    $await(Jscex.Async.parallel(requests));
}&lt;/pre&gt;

&lt;p&gt;在相当一段时间内，Jscex编译器输出的代码是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// after&lt;/span&gt;
(&lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = 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_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;requests = [];
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Combine(
                $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0;
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Loop(
                        &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                            &lt;span style="color: blue"&gt;return &lt;/span&gt;i &amp;lt; urls.length;
                        },
                        &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                            i++;
                        },
                        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                            requests.push(requestAsync(urls[i]));
                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
                        })
                    );
                }),
                $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(Jscex.Async.parallel(requests), &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Normal();
                    });
                })
            );
        });
    )
})&lt;/pre&gt;

&lt;p&gt;其实也很容易理解：一个标准的for循环嘛。但实际上，编译器完全没有必要生成那么复杂的代码。因为这个for循环内部没有涉及到绑定操作（例如异步类库下的$await），它完全只是段普通的代码，应该全部保留，就像这样：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// after&lt;/span&gt;
(&lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = 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_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;requests = [];
            &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; urls.length; i++) {
                requests.push(requestAsync(urls[i]));
            }
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Bind(Jscex.Async.parallel(requests), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
            });
        })
    );
})&lt;/pre&gt;

&lt;p&gt;以上便是目前编译器会生成的代码。从表面上看来，要实现这点并不困难，只要检查AST中的for节点内部有没有绑定操作，如果没有，则直接输出。我一开始也是这么认为的，但是写了一大堆代码以后发现事情并没有想象中那么简单，于是回滚代码，重新开始。试看这样一段简单的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// before&lt;/span&gt;
&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;try &lt;/span&gt;{
        &lt;span style="color: blue"&gt;throw &lt;/span&gt;&lt;span style="color: maroon"&gt;&amp;quot;Error&amp;quot;&lt;/span&gt;;
    } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex) {
        &lt;span style="color: blue"&gt;throw &lt;/span&gt;ex;
    }
}&lt;/pre&gt;

&lt;p&gt;您可以猜测一下，这段代码编译后的结果是怎么样的呢？应该是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// after&lt;/span&gt;
(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = 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_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;try &lt;/span&gt;{
                &lt;span style="color: blue"&gt;throw &lt;/span&gt;&lt;span style="color: maroon"&gt;&amp;quot;Error&amp;quot;&lt;/span&gt;;
            } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex) {
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Throw(ex);
            }
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
        })
    );
})&lt;/pre&gt;

&lt;p&gt;请注意，try内部的throw在目标代码里还是throw，而catch里的throw则是把异常抛向外部，因此则编译为Throw方法。如果再嵌套一个try/catch则会看的更加明显：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// before&lt;/span&gt;
&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;try &lt;/span&gt;{
        &lt;span style="color: blue"&gt;try &lt;/span&gt;{
            &lt;span style="color: blue"&gt;throw &lt;/span&gt;&lt;span style="color: maroon"&gt;&amp;quot;Error&amp;quot;&lt;/span&gt;;
        } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex1) {
            &lt;span style="color: blue"&gt;throw &lt;/span&gt;ex1;
        }
    } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex2) {
        &lt;span style="color: blue"&gt;throw &lt;/span&gt;ex2;
    }
}

&lt;span style="color: green"&gt;// after&lt;/span&gt;
(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = 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_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;try &lt;/span&gt;{
                &lt;span style="color: blue"&gt;try &lt;/span&gt;{
                    &lt;span style="color: blue"&gt;throw &lt;/span&gt;&lt;span style="color: maroon"&gt;&amp;quot;Error&amp;quot;&lt;/span&gt;;
                } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex1) {
                    &lt;span style="color: blue"&gt;throw &lt;/span&gt;ex1;
                }
            } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(ex2) {
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Throw(ex2);
            }
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
        })
    );
})&lt;/pre&gt;

&lt;p&gt;在这段代码里，内层catch里的throw会被外层的catch捕获到，因此它还是普通的throw。与F#不同的是，JavaScript的某些语言特性（如throw，break，continue）会中断代码正常的执行流。因此，即便是一个普通的if分支里的的break，我们也必须判断这是个普通的break语句还是一个Break输出。不过实现这样的编译器也并不困难，例如我只是在生成代码时记录当前位置而已。例如一个普通try内部的throw便是普通的throw，如果这个try变成了Try结构，则必然是Throw输出了。&lt;/p&gt;

&lt;p&gt;优化到这步之后，应该说编译器的输出结果已经定型，剩下的就是类库方面的事情了。顺便一提，&lt;a href="http://www.sndacode.com/projects/jscex/wiki"&gt;Jscex中文站上的说明&lt;/a&gt;也已经同步更新，并补充了更多内容。此外，下周六我将会在&lt;a href="http://cnodejs.org/blog/?p=897"&gt;杭州CNodeJS聚会&lt;/a&gt;上和大家讨论Jscex类库方面的话题，感兴趣的朋友不妨同去同去。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/05/jscex-compiled-code-optimization.html#comments</comments>
      <pubDate>Thu, 05 May 2011 10:45:31 GMT</pubDate>
      <lastBuildDate>Thu, 05 May 2011 10:55:01 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>Jscex编译器更新：已支持嵌套Jscex函数</title>
      <link>http://blog.zhaojie.me/2011/04/jscex-compiler-update-support-nested-jscex-function.html</link>
      <guid>http://blog.zhaojie.me/2011/04/jscex-compiler-update-support-nested-jscex-function.html</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;的编译器更新了。之前的编译器并不会将一个Jscex函数内部的其他Jscex函数代码一并展开，这导致内嵌的Jscex函数会在外部函数调用时反复编译，性能开销较大；不过更重要问题，可能是AOT编译后的代码无法彻底解除与编译器的依赖。嵌套Jscex函数是否合理是一回事儿，使用者可以不去这么做，但是编译器本身还是该支持的。这也是Jscex编译器改进计划中的重要一步。&lt;/p&gt;

&lt;p&gt;之前，如果您编写这样的函数：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;outerAsync = 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;() {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;innerAsync = 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;() {
        &lt;span style="color: #006400"&gt;// inner implementations
    &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;outerAsync = (&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &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;,
        $$_builder_$$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {

            &lt;span style="color: blue"&gt;var &lt;/span&gt;innerAsync = 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;() {
                &lt;span style="color: #006400"&gt;// inner implementations&lt;/span&gt;
            }));

            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$.Normal();
        })
    );
})&lt;/pre&gt;

&lt;p&gt;这样在每次调用outerAsync的时候，都会重新启用Jscex.compile及eval的过程，这个性能开销还是比较可观的。不过目前的编译器已经会将内部的Jscex函数彻底展开：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;outerAsync = (&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_0 = 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_$$_0.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
        $$_builder_$$_0.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {

            &lt;span style="color: blue"&gt;var &lt;/span&gt;innerAsync = (&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;var &lt;/span&gt;$$_builder_$$_1 = 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_$$_1.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;,
                    &lt;span style="color: #006400"&gt;// compiled inner implementations&lt;/span&gt;
                    $$_builder_$$_1.Normal()
                );
            });

            &lt;span style="color: blue"&gt;return &lt;/span&gt;$$_builder_$$_0.Normal();
        })
    );
})&lt;/pre&gt;

&lt;p&gt;除了不会多次编译内部的Jscex函数外，还彻底解除了与Jscex编译器的依赖，AOT编译器表示情绪愉快。您可能发现了，原本的builder变量名是固定的，不过为了支持内嵌函数，我在代码里添加了一个种子，这样每次编译时的builder变量名就不同了。例如在上面的代码中，编译后的外层函数使用$$_builder_$$_0，而内层函数使用的则是$$_builder_$$_1。&lt;/p&gt;

&lt;p&gt;于是现在我们便可以使用内嵌Jscex函数了，例如&lt;a href="http://blog.zhaojie.me/2010/11/the-coming-talks-and-jscex.html"&gt;之前提到过的“动画”示例&lt;/a&gt;，现在便可以写为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;moveSquareAsync = 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;(e) {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;moveAsync = 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;(startPos, endPos, duration) {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0; t &amp;lt; duration; t += 50) {
            e.style.left = (startPos.x + (endPos.x - startPos.x) * t / duration) + &lt;span style="color: maroon"&gt;&amp;quot;px&amp;quot;&lt;/span&gt;;
            e.style.top = (startPos.y + (endPos.y - startPos.y) * t / duration) + &lt;span style="color: maroon"&gt;&amp;quot;px&amp;quot;&lt;/span&gt;;
            $await(Jscex.Async.sleep(50));
        }

        e.style.left = endPos.x;
        e.style.top = endPos.y;
    }));

    $await(moveAsync({x:100, y:100}, {x:400, y:100}, 1000));
    $await(moveAsync({x:400, y:100}, {x:400, y:400}, 1000));
    $await(moveAsync({x:400, y:400}, {x:100, y:400}, 1000));
    $await(moveAsync({x:100, y:400}, {x:100, y:100}, 1000));
}));&lt;/pre&gt;

&lt;p&gt;内层的moveAsync函数直接使用外部函数的参数e，这代码绝对美观大方——至于是否真写这样的代码，就靠开发人员自身的考量了。&lt;/p&gt;

&lt;p&gt;话说回来，其实让现有的编辑器支持内嵌的Jscex函数并不困难，事实上原本“不支持”的原因也只是“没想太多”，就这么机械地写下来了。例如最核心的Jscex.compile，其“逻辑”大约是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;Jscex.compile = &lt;span style="color: blue"&gt;function &lt;/span&gt;(builderName, func) {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;code = &lt;span style="color: maroon"&gt;&amp;quot;var f = &amp;quot; &lt;/span&gt;+ func.ToString() + &lt;span style="color: maroon"&gt;&amp;quot;;&amp;quot;&lt;/span&gt;;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;ast = UglifyJS.parse(code);

    &lt;span style="color: #006400"&gt;// [ &amp;quot;toplevel&amp;quot;, [ [ &amp;quot;var&amp;quot;, [ [ &amp;quot;f&amp;quot;, [...] ] ] ] ] ]
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;funcAst = ast[1][0][1][0][1];

    &lt;span style="color: #006400"&gt;// compile funcAst with builderName;
&lt;/span&gt;}&lt;/pre&gt;

&lt;p&gt;简单地说，我是根据传入函数的AST生成代码，当然还有些中间过程，但总体而言的“输入”只是函数本身（外加个builderName）。而为了能够在“生成代码”的过程中继续编译内嵌函数，其实我们也只要支持“标准模式（即eval(Jscex.compile(...))这种形式）”的AST即可：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;compileStandardPattern(evalAst) {
    &lt;span style="color: #006400"&gt;// ...
&lt;/span&gt;}

Jscex.compile = &lt;span style="color: blue"&gt;function &lt;/span&gt;(builderName, func) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;funcCode = func.toString();
    &lt;span style="color: blue"&gt;var &lt;/span&gt;evalCode = &lt;span style="color: maroon"&gt;&amp;quot;eval(Jscex.compile(&amp;quot; &lt;/span&gt;+ JSON.stringify(builderName) + &lt;span style="color: maroon"&gt;&amp;quot;, &amp;quot; &lt;/span&gt;+ funcCode + &lt;span style="color: maroon"&gt;&amp;quot;))&amp;quot;
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;evalCodeAst = UglifyJS.parse(evalCode);

    &lt;span style="color: #006400"&gt;// [ &amp;quot;toplevel&amp;quot;, [ [ &amp;quot;stat&amp;quot;, [ &amp;quot;call&amp;quot;, ... ] ] ] ]
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;evalAst = evalCodeAst[1][0][1];
    &lt;span style="color: blue"&gt;var &lt;/span&gt;newCode = compileStandardPattern(evalAst);

    &lt;span style="color: #006400"&gt;// ...
&lt;/span&gt;}&lt;/pre&gt;

&lt;p&gt;compileStandardPattern方法会根据“标准模式”的AST输出展开后的代码。而对于从Jscex.compile调用中输入的函数对象，我们也将其构造为标准形式并生成AST。然后，compileStandardPattern方法内部在生成代码的时候，如果发现某一部分AST又恰好符合“标准模式”，则递归调用自身来生成内嵌Jscex函数的代码，最终便得到了充分展开的结果。&lt;/p&gt;

&lt;p&gt;当然，这么做和AOT编译器都有一个限制：由于其展开方式依靠静态代码分析，因此只能识别出“标准模式”的代码。例如，以下代码可以在JIT编译器下正常工作：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;compile = Jscex.compile;
&lt;span style="color: blue"&gt;var &lt;/span&gt;builderName = &lt;span style="color: maroon"&gt;&amp;quot;async&amp;quot;&lt;/span&gt;;
&lt;span style="color: blue"&gt;var &lt;/span&gt;func = &lt;span style="color: blue"&gt;function &lt;/span&gt;() { ... };
&lt;span style="color: blue"&gt;var &lt;/span&gt;newCode = compile(builderName, func);
&lt;span style="color: blue"&gt;var &lt;/span&gt;funcAsync = eval(newCode);&lt;/pre&gt;

&lt;p&gt;但显然，如果这段代码出现在某个Jscex函数内部，或是使用AOT编译器，便无法将其展开了。不过这个“限制”只是理论上的，我还没有想到不用“标准模式”的理由。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/jscex-compiler-update-support-nested-jscex-function.html#comments</comments>
      <pubDate>Fri, 29 Apr 2011 16:11:16 GMT</pubDate>
      <lastBuildDate>Fri, 29 Apr 2011 16:13:12 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <title>SNDACode及Jscex项目中文站</title>
      <link>http://blog.zhaojie.me/2011/04/jscex-chinese-repository-sndacode.html</link>
      <guid>http://blog.zhaojie.me/2011/04/jscex-chinese-repository-sndacode.html</guid>
      <description>&lt;p&gt;之前有朋友问我，为什么&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex只有英文站&lt;/a&gt;却没有中文的。其实原因很简单：国际化的项目势必更容易推广，而且国内看的懂英文的同学肯定比懂中文的外国人多嘛。而且我也不希望在一个源码库里维护两种语言的说明，这样对于浏览者也会造成误解——但更不希望维护两个源码库。因此我理想中的情况便是：某个站点可以完整地同步我的源码库，并提供一个供我放置文档的地方。最近我的同事搞了一个&lt;a href="http://www.sndacode.com"&gt;SNDACode&lt;/a&gt;站点，公开了部分盛大员工的开源项目。虽然远不如&lt;a href="https://github.com/"&gt;GitHub&lt;/a&gt;等类似站点来的完备，但恰好能满足我的需求，我自然很乐意将它作为&lt;a href="http://www.sndacode.com/projects/jscex"&gt;Jscex的中文站点&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;以下为SNDACode的&lt;a href="http://www.sndacode.com/doc/about.html"&gt;自述&lt;/a&gt;：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;我们创建这样一个平台的目的是：希望通过它，通过我们负责任的、开放的心态，在一定程度上加强同行之间的交流、促进中国开源事业的良性有序发展。&lt;/p&gt;

  &lt;p&gt;目前SNDACode平台上所有的开源项目均由盛大内部员工贡献，或者由盛大内部项目组贡献，（如果没有指明开源协议）我们的所有项目都遵循《GNU通用公共许可协议》发布，这意味着，您享有本站开源项目的：运行、复制软件的自由，发行传播软件的自由，获得软件源码的自由，改进软件并将自己作出的改进版本向社会发行传播的自由。&lt;/p&gt;

  &lt;p&gt;在SNDACode，开发者们可以针对这个平台上开源的项目，讨论交流，提交Issue，进而参与到这些项目中来，从而使得这些项目，能够为更多的开发者带来价值。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;目前Jscex的详细说明文字也已经翻译为中文，发布在&lt;a href="http://www.sndacode.com/projects/jscex/wiki"&gt;项目的Wiki&lt;/a&gt;上。一般来说，中文文档会与英文版保持同步，视心情等原因也可能会有一至两天的延迟。希望有了中文资源以后，能够吸引更多的国内同学来使用Jscex。欢迎您在SNDACode上提出各种问题（当然最好还是在GitHub上提出英文的Issue），与我一齐参与到Jscex的建设中来。发起一个开源项目十分简单，几乎只是几分钟的事情，但最关键的还是看后续的努力。&lt;/p&gt;

&lt;p&gt;希望Jscex能够发展壮大，我认为这还是有一定希望的。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/jscex-chinese-repository-sndacode.html#comments</comments>
      <pubDate>Wed, 27 Apr 2011 15:20:17 GMT</pubDate>
      <lastBuildDate>Wed, 27 Apr 2011 15:20:17 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>Jscex使用BSD授权协议正式发布</title>
      <link>http://blog.zhaojie.me/2011/04/jscex-released-under-bsd-license.html</link>
      <guid>http://blog.zhaojie.me/2011/04/jscex-released-under-bsd-license.html</guid>
      <description>&lt;p&gt;这次打算把Jscex好好搞一下了，其实很少会有技术方面的障碍能“轮到”我们去突破，但我觉得Jscex的确有机会，HTML 5、Node.js各个都是红火的玩意儿。前几天我花了两个晚上用半生不熟的中式英语写了一篇自认为比较完整的说明文字放到了Github上的项目首页上，没想到几个小时后便收到了&lt;a href="http://onilabs.com/stratifiedjs"&gt;StratifiedJS&lt;/a&gt;（一个与Jscex目标有些类似的项目）作者的邮件，提到了一些关于StratifiedJS的事情。我向他咨询了StratifiedJS的某些细节问题，也向他简单介绍了Jscex的实现原理。如今&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex已经使用BSD授权协议正式发布&lt;/a&gt;（中文站也会在近期推出），再进行一些细节上的优化便要开始作推广了。&lt;/p&gt;

&lt;p&gt;最近的一次优化便是去除不必要的Delay方法调用。Delay的目的是延迟某段代码的执行时间，确保它只会在合适的时间执行，这对于非“延迟”及有副作用的语言来说十分重要。在F#中Delay方法的签名如下（Jscex与之类似）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// async.Delay
&lt;/span&gt;((&lt;span style="color: blue"&gt;unit &lt;/span&gt;-&amp;gt; Async&amp;lt;'a&amp;gt;) -&amp;gt; Async&amp;lt;'a&amp;gt;)&lt;/pre&gt;

&lt;p&gt;Delay函数可以不断嵌套，从效果说来讲不会有所区别，例如：&lt;/p&gt;

&lt;pre class="code"&gt;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;return &lt;/span&gt;builder.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;return &lt;/span&gt;builder.Loop(...);
        });
    });
});&lt;/pre&gt;

&lt;p&gt;上面这段代码和直接一个builder.Loop相比不会对结果产生任何影响，但是显然，从生成代码的整洁、美观程度（这涉及到可调试性）及性能都会有所损耗。之前的Jscex编译器会生成不必要的Delay（当然也不会有上面那么夸张），那是因为以前的代码生成器只是直接遍历从UglifyJS解析器那里获得的AST，以此来生成代码。新的编译器重新调整了结构和策略，会先将UglifyJS的AST转化为一个中间形式——我把它叫做Jscex AST，然后再让代码生成器从Jscex AST生成代码。新的生成器在工作时，会略过某些delay节点，直接从它的自节点中生成代码，以此达到去除不必要的Delay方法调用的目的。&lt;/p&gt;

&lt;p&gt;举例来说，一个快速排序的Jscex函数：&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;(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;它会生成如下的代码（与&lt;a href="http://blog.zhaojie.me/2011/04/jscex-status-uglifyjs-parser-and-aot-compiler.html"&gt;之前的结果&lt;/a&gt;相比省去了一些Delay方法调用）：&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;, $_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;strike&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {&lt;/strike&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;strike&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {&lt;/strike&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;strike&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;$_builder_$.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {&lt;/strike&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还是有进一步优化的空间。例如包括保留“没有bind操作”的代码块，以及“嵌套Jscex函数”等等。最近一段时间应该会不断有一些更新。如果您在用JavaScript开发异步程序，也不妨一起来使用Jscex吧，它毫无疑问能显著改善您的编程生活。&lt;/p&gt;

&lt;p&gt;&lt;font color="#ff0000"&gt;&lt;strong&gt;广告时间：&lt;/strong&gt;&lt;/font&gt;第四届nBazaar技术交流会将于4月23日（本周六）于畅星大厦（上海市浦东新区碧波路888号，地铁二号线张江高科站下，步行10分钟可达）副楼3楼会议厅举行，在此欢迎您的到来，请于下午1点前准时入场。没有报名的朋友也可以现场报名。由于领导支持，我们将在现场送出&lt;a href="http://bambook.sdo.com/"&gt;市场价999元的盛大Bambook&lt;/a&gt;一台作为幸运观众的礼品，此外图灵出版社也赞助了十几本图书将会送给积极发言的观众。更多详细信息请参考&lt;a href="http://nbazaar.org/"&gt;活动首页&lt;/a&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/04/jscex-released-under-bsd-license.html#comments</comments>
      <pubDate>Thu, 21 Apr 2011 16:15:04 GMT</pubDate>
      <lastBuildDate>Thu, 21 Apr 2011 16:15:04 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/discussion/">思考讨论</category>
      <title>道理人人有，就看怎么说</title>
      <link>http://blog.zhaojie.me/2011/03/mac-windows-valuable-arguments.html</link>
      <guid>http://blog.zhaojie.me/2011/03/mac-windows-valuable-arguments.html</guid>
      <description>&lt;p&gt;前几天猛鸟兄（&lt;a href="http://twitter.com/#!/raptorz"&gt;@raptorz&lt;/a&gt;）作为在使用Mac OS X时跌了个跟头，他之前是长期的Ubuntu用户，最近也买了台Mac。情况是这样的：OS X在文件夹复制（包括移动）的时候，如果目标地方也出现了同名文件夹，则OS X的策略是“替换（Replace）”，而Windows和*nix下cp命令的策略则是“合并（Merge）”。前者相当于完全删除了重名文件夹，而后者则是把其中的文件合并。对于习惯了后者的人来说，一没看清楚前者的提示，于是就丢失文件了。我当时也遇到过这情况，不过估计猛鸟兄不如我能忍，于是&lt;a href="http://twitter.com/#!/raptorz/status/48373090060746752"&gt;在吐槽推里fuck了乔教主&lt;/a&gt;，果不其然引发了各方争论。今天丁宇同学将他的观点（&lt;a href="http://twitter.com/#!/felixding"&gt;@felixding&lt;/a&gt;）整理为&lt;a href="http://dingyu.me/blog/hci-metaphors"&gt;一篇文章&lt;/a&gt;，表示OS X的做法更合理，对于这个说法我不敢苟同。现在先提一下这个问题，再引出真正想说的内容。&lt;/p&gt;

&lt;p&gt;丁宇同学的主要观点就是：OS X的做法更符合“文件夹”这个概念在现实中的隐喻，在这方面用户是给微软带坏了（完整内容请&lt;a href="http://dingyu.me/blog/hci-metaphors"&gt;阅读原文&lt;/a&gt;）：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;相比Win，Mac桌面的隐喻是非常贴近现实世界和统一的。比如刚才说的文件夹替换的问题，在现实世界中要替换两个文件夹的话，是把旧的拿走把新的放这，谁也不会预期说把新的往桌子上一放，结果啪的一下它里面又出现了旧的文件吧！喔，很不幸，ms的工程师认为世界应该是这样神奇的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;而我的看法简单来说是：软件系统不应该以“符合现实隐喻”为尊，Windows的做法更好用，也并没有什么问题：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;还是就看你愿意怎么去解释，容我也来解释一番。&lt;/p&gt;

  &lt;p&gt;你说Mac的文件夹“替换”方式是“符合现实隐喻”，但什么时候开始计算机系统设计时就以这点为尊了呢？现实世界不是“没有”文件夹的合并操作，也并非“不需要”合并，而是“合并”的代价太高。但是，操作系统完全有能力快速地完成“合并”操作，这就是计算机辅助人类工作，改进人类生活啊，为什么非要符合现实世界里低效率的“人肉”工作方式？&lt;/p&gt;

  &lt;p&gt;Unix下的cp也是合并，说明这个策略是被人广泛接受的，更早于Mac的出现。Win在出现重名文件夹的时候，策略是“合并”，也是符合比Mac更早的传统。更何况，Windows也根本没说它在“替换”，“合并”写的清清楚楚明明白白，并非打着“替换”的旗号做“合并”的事情，这只是Windows所选择的策略而已。&lt;/p&gt;

  &lt;p&gt;再者，在Windows里如果想要实现你要的“替换”，删除目标文件夹，再复制过去即可。而在Mac下要实现“合并”，几乎就是如现实一样，人力不可为了。所以在我看来，Windows的策略弹性更大。&lt;/p&gt;

  &lt;p&gt;所以，你说这点Windows带坏用户，我不敢苟同。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;好，那么您说，我和他的说法哪个是有问题的？我觉得都可以说的过去，而且都可以继续往下说。每个人都是聪明人，每个人也都有自己的道理，这就是互联网上各种争论的来源，说着说着谁一个不小心就变味成了“争吵”（惭愧地说有时连我也不能例外）。&lt;/p&gt;

&lt;p&gt;这样的例子还可以举好多。例如，人类的适应能力是很强的，刚上手时感觉各种不好用的软件，如果坚持用过一段时间也会习惯，甚至切换回之前使用的软件也会觉得碍手碍脚。于是支持方会说：“看这软件多好用，之前的做法都不习惯了”，反对方会说“这软件真难用，好难上手”。谁对？谁错？更关键的是，谁能有力地证明自己的观点，而不是讲大道理？要知道，正反这两种说法都是有一定道理的。人类就是那么矛盾而变态的东西，否则也不会有&lt;a href="http://zh.wikipedia.org/zh-cn/%E6%96%AF%E5%BE%B7%E5%93%A5%E5%B0%94%E6%91%A9%E7%BB%BC%E5%90%88%E7%97%87"&gt;斯德哥尔摩综合症&lt;/a&gt;这种心理问题了。&lt;/p&gt;

&lt;p&gt;其实，如果能把问题从两个方面说清楚，这也是讨论的魅力所在。但现实中有个问题是，在许多情况下我们能够让别人相信自己是完全中立的，自己也很难保持没有偏向性——《美国宪政历程》里提到某首席大法官，为了保持不受社会舆论影响而坚持不看报纸，这实在不是件容易的事情。尤其是对于“用户体验”，“产品设计”这种难以道明的东西，可谓“人人都是产品经理”。现在好多时候在我看来充斥着“事后诸葛”、“成王败寇”的道道，搞得跟很多成功学一样，让我接受不了，能躲即躲。&lt;/p&gt;

&lt;p&gt;更关键的是，因为大伙都是聪明人，一件事情正说反说都有一定道理，于是人们很容易选择有利于自己一方选择“论据”，正所谓“你讲道理我跟你摆事实，你摆事实我跟你讲道理”，在实际情况下，上面提到的“好用”和“难上手”两派在“合适的时机”都会选择对方的思维方式，而且这种“切换”也可以很有道理，叫做“就事论事”，您又能奈我何。&lt;/p&gt;

&lt;p&gt;当然，即便我懒得搞这种正说反说，也不代表我就支持中间派（显然也不代表我反对中间派），更不代表我没有自己的看法。比如我对于PHP的观点。昨天正好又有&lt;a href="http://robbin.javaeye.com/blog/970047"&gt;关于Rails和PHP的讨论&lt;/a&gt;，于是我也正好拿它作例子。我对PHP的看法是：Quick and Dirty，随便找个人来就能写点代码，再加上历史积淀，可谓是最最流行的Web开发技术，没有之一。说的好听点，PHP叫做有着“辉煌的大尾巴”，难听点叫作“又臭又硬”。我一直强调要打开视野，但如果要我推荐，我不会推荐别人去学习PHP，即便工作上要使用PHP，我也建议去学习Ruby，Python平台上更有“文化”，更为“优雅”，开发效率更高的Web框架。有些人的理由就是，PHP社区更大，更流行，搜索引擎上的记录更多，某某排名都更高，怎么可能不是更好呢？要我看来，这种靠市场“成王败寇”的判断方式，我就根本不会认同，例如认可这种观点的同学，您认为“Windows比Mac OS要优秀十几倍”吗？要我说，这些还都是PHP的“历史优势”，不能直接说它在各方面就多么多么好了，你想说它哪方面好，还得有针对性的说清楚。而且话说回来，比尔盖茨如今560亿美元身价不可不谓之成功，但也没说陈天桥12亿美元这样的“零头身价”就变成“失败”，就不能做为榜样了。换言之，Rails等平台的质量足以受人推崇，足以干些了不起的事情了。引霍炬同学（&lt;a href="http://twitter.com/#!/virushuo"&gt;@virushuo&lt;/a&gt;）的&lt;a href="http://twitter.com/#!/virushuo/status/50282676300693504"&gt;一句话&lt;/a&gt;（&lt;a href="http://blog.devep.net/virushuo/2011/03/23/rubyror.html"&gt;完整内容在此&lt;/a&gt;），道理正是如此：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;寻找ruby开发的“top项目”没意义。DHH和37s创造的都不是适合所有人的产品，他们只愿意在某一领域做到最好。不是所有人都有兴趣创造亿级别产品，何况也没几个人真能做成。对大部分项目，千万是一个很好的级别，那是ror最佳实践领域。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;同样，JavaEye的站长范凯同学（&lt;a href="http://twitter.com/#!/robbinfan"&gt;@robbinfan&lt;/a&gt;）也提到了&lt;a href="http://twitter.com/#!/robbinfan/status/50369318151667712"&gt;JavaEye的近况&lt;/a&gt;：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaEye的PV到了140万了，一年前才100万出头，增长算不错的。仍然是单台Web服务器，Rails处理动态请求超过340万，除了真实用户访问，还有API，RSS以及很多爬虫的请求。看JE的alexa排名，CN排92名，全球790名，其实需要屠龙术的网站并不多。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;此外他还&lt;a href="http://robbin.javaeye.com/blog/972242"&gt;谈了其他一些话题&lt;/a&gt;，例如PHP和Rails程序员的培训和招聘等等，其中也谈到前来面试的PHP程序员很多基本功不过关，和某同学一直坚持认为“PHP程序员老nb，ASP.NET程序员啥都不懂”颇有出入。同样，还有人老说.NET产品作不大，其实您先超过StackOverflow再说吧，到那时您已经相当成功了，像MySpace什么就先不提了。&lt;p&gt;

&lt;p&gt;好象有点谈远了，再把话题扯回来：JavaScript在浏览器端处于垄断地位，是因为它有多么优秀吗？我觉得明显是因为用户没有第二选择嘛。当然我也没说JavaScript不好，只是正确的命题一定要用正确的论据来证明——好累，这句话本身应该是不言自明的，不是么？&lt;/p&gt;

&lt;p&gt;总之，所谓正反、对错、好坏、优劣……好多情况下就看您想怎么说。也正因为如此，我越来越懒得讨论这方面话题，除非心情特别好或是有人非要前来挑逗。但是久而久之，搞得我都有些虚无了，例如，现在除非是我上手就觉得好用（比如Mac触摸板的二指控制滚动条），否则您说其他一些东西的体验有多么多么好或是设计有多么多么合理（例如OS X Lion上的二指控制突然与之前方向相反了），我都会懒得去相信。当然，我个人觉得自己还是比较容易被说服的，例如有人提到了一篇文章：《&lt;a href="http://cmoney.info/dm/Knowledge/210.html"&gt;理念的缺失：Windows 7任务栏 vs. OS X Dock&lt;/a&gt;》，就一下子就让我高潮了。尽管我在这方面的能力还远不能跟他有所呼应，甚至，即使他是在骗人我也发现不了，但我就是愿意相信他的说法。就像有人在推上发发私信能跟女生上床，有人只能招人厌恶。&lt;/p&gt;

&lt;p&gt;最后，比如PHP什么的又碰到了您的G点，轻请拍。我懒。&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;即将举办第四届活动。为了便于组织或活动通知，我们申请了&lt;a href="http://groups.google.com/group/nbazaar"&gt;官方的Google群组服务&lt;/a&gt;。该群组的作用只是为了联系和通知，活动的具体信息和资料发布依然会使用&lt;a href="http://nbazaar.org/"&gt;http://nbazaar.org/&lt;/a&gt;。我们已经向历届活动的报名者发出了邀请，请查收邮件（包括垃圾箱）。不过由于众所周知的原因，您可能无法接受该邀请，我们也在设法直接添加成员，只苦于Google群组的每日限制。此外，第四节活动还有一个讲师席位正虚位以待，如果您有这方面的意愿，或者对邮件列表有任何疑问，请发邮件至&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/mac-windows-valuable-arguments.html#comments</comments>
      <pubDate>Tue, 22 Mar 2011 12:50:13 GMT</pubDate>
      <lastBuildDate>Thu, 24 Mar 2011 05:20:37 GMT</lastBuildDate>
    </item>
  </channel>
</rss>
