<?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/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>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>使用Jscex实现排序算法动画</title>
      <link>http://blog.zhaojie.me/2011/03/sorting-animations-with-jscex.html</link>
      <guid>http://blog.zhaojie.me/2011/03/sorting-animations-with-jscex.html</guid>
      <description>&lt;p&gt;用动画来观察排序算法是一件很酷的事情，例如&lt;a href="http://www.sorting-algorithms.com/"&gt;有人便为各种排序算法提供了动画效果&lt;/a&gt;。只可惜这些效果都是实现准备好的gif图片，并非由代码写成。在大部分平台上编写这样的程序并没有太大困难，只要在绘制出图形之后短暂地阻塞线程就行了。可惜，在JavaScript中我们只能“一蹴而就”，要暂停的话，只能使用setTimeout进行回调了。不过，这也正是&lt;a href="http://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;的用武之地，用Jscex编写的代码需要“暂停”，只需要简单地调用sleep异步方法，一切都很直接。&lt;/p&gt;

&lt;p&gt;例如，传统的冒泡排序算法，使用JavaScript实现可能是这样的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;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;/pre&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;算法实现上唯一的变化可能就是“比较”方法被独立成单独的compareAsync方法了。它和swapAsync方法的代码如下：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;compareAsync = 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;(x, y) {
    $await(Jscex.Async.sleep(10));
    &lt;span style="color: blue"&gt;return &lt;/span&gt;x - y;
}));

&lt;span style="color: blue"&gt;var &lt;/span&gt;swapAsync = 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, x, y) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;t = array[x];
    array[x] = array[y];
    array[y] = t;

    repaint(array);

    $await(Jscex.Async.sleep(20));
}));&lt;/pre&gt;

&lt;p&gt;简单地说，一个排序算法的性能如何，关键看它的比较和元素交换的次数。因此，我在compareAsync和swapAsync方法中都使用sleep进行了一定时间的停顿（其中后者更长一些）。由于改变了元素位置，因此在swapAsync方法中我还重新绘制了图案。有了这两个方法，我们只要将其简单地组合进bubbleSortAsync方法中即可。&lt;/p&gt;

&lt;p&gt;我们轻松实现的冒泡排序算法，Jscex编译器则会将其转化为复杂的Monadic代码，我本想贴在这里，但发现过于复杂凌乱，也太占地方。如果您感兴趣可以打开浏览器的console窗口，查看其输出结果。基于Jscex实现的选择排序和快速排序也和传统实现可谓毫无二致：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;selectionSortAsync = 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;j = 0; j &amp;lt; array.length - 1; j++) {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;mi = j;
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = j + 1; i &amp;lt; array.length; i++) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;r = $await(compareAsync(array[i], array[mi]));
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(r &amp;lt; 0) { mi = i; }
        }
        $await(swapAsync(array, mi, j));
    }
}));

&lt;span style="color: blue"&gt;var &lt;/span&gt;quickSortAsync = 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) {
    $await(_quickSortAsync(array, 0, array.length - 1));
}));

&lt;span style="color: blue"&gt;var &lt;/span&gt;_quickSortAsync = 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, begin, end) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;index = $await(_partitionAsync(array, begin, end));

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(begin &amp;lt; index - 1) {
        $await(_quickSortAsync(array, begin, index - 1));
    }

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(index &amp;lt; end) {
        $await(_quickSortAsync(array, index, end));
    }
}));

&lt;span style="color: blue"&gt;var &lt;/span&gt;_partitionAsync = 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, 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;言止于此，我们现在就来看一下（&lt;a href="http://files.zhaojie.me/demos/jscex/samples/async/sorting-animations.html?bubble" target="_blank"&gt;冒泡&lt;/a&gt;，&lt;a href="http://files.zhaojie.me/demos/jscex/samples/async/sorting-animations.html?selection" target="_blank"&gt;选择&lt;/a&gt;，&lt;a href="http://files.zhaojie.me/demos/jscex/samples/async/sorting-animations.html?quick" target="_blank"&gt;快速&lt;/a&gt;）三种排序方式的动画吧（请使用支持canvas的浏览器，例如IE 9、FireFox、Chrome、Safari）。我很难想象，使用传统方式实现一个快速排序的动画会是什么情况。&lt;/p&gt;

&lt;p&gt;我最近愈发懒惰，愈发不愿意去想这些事情了。如今Jscex已经支持JavaSript语言中绝大部分语言特性，包括条件判断，各种循环（以及循环中的break和continue），还有异常处理等等。对于Jscex带来的编程体验我有十足的信心，但现在的最大问题却是不知道该如何推广。我也希望可以有更多人可以参与并接受这个项目。之前想过用它来实现一个小游戏等等，但素材方面却总是难以落实。我十分希望能从您那里得到各方面的意见和建议。&lt;/p&gt;

&lt;p&gt;最后还是一则&lt;span style="color: red"&gt;招聘广告&lt;/span&gt;：盛大创新院某英语学习方向的项目招聘1至2名ASP.NET程序员：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;岗位职责：负责服务器及数据库的搭建、维护工作，并配合前端工程师完成网站架构。 &lt;/li&gt;

  &lt;li&gt;岗位要求：熟悉.NET 3.5开发，有关系型数据库（MySQL优先）开发管理经验。有MongoDB等NoSQL开发经验者优先。有网站数据负载均衡经验者优先。 &lt;/li&gt;

  &lt;li&gt;办事认真踏实，对行业充满热情，了解行业最新动态。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;很显然，这不是我的招聘需求，不过我也帮忙筛选面试。欢迎大家自荐或推荐，邮件请至：&lt;img class="embed" src="http://services.nexodyne.com/email/icon/T3pYH0sugOKOkgan4fyp/GoPfEek%3D/R01haWw%3D/0/image.png" /&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/03/sorting-animations-with-jscex.html#comments</comments>
      <pubDate>Thu, 10 Mar 2011 15:35:09 GMT</pubDate>
      <lastBuildDate>Fri, 11 Mar 2011 03:41:49 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>我在面试.NET/C#程序员时会提出的问题</title>
      <link>http://blog.zhaojie.me/2011/03/my-interview-questions-for-dotnet-programmers.html</link>
      <guid>http://blog.zhaojie.me/2011/03/my-interview-questions-for-dotnet-programmers.html</guid>
      <description>&lt;p&gt;说起来我也面试过相当数量的.NET（包括C#，后文不重复）程序员了，有的通过电话，有的面谈。后来发现，其实提的问题来来回回也就那么几个。这些问题有的已经有十年历史了，至少也有三年。我想对于一个“不错”的.NET程序员来说，在简单的提示下绝大部分问题应该可以“对答如流”。可能您也会觉得这些太细节，真要追究起来似乎也大都不是必须的，无视这些照样可以写程序，做网站，赚工资，但是我不会满足于成为（包括招聘）这样的程序员，暂时也懒得解释掌握这些东西的益处和重要性。 每个人都有自己的看法， 一切就看您自己的选择了。&lt;/p&gt;

&lt;p&gt;那么现在就开始吧。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;什么是.NET？什么是CLI？什么是CLR？IL是什么？JIT是什么，它是如何工作的？GC是什么，简述一下GC的工作方式？&lt;/li&gt;

  &lt;li&gt;类（class）和结构（struct）的区别是什么？它们对性能有影响吗？.NET BCL里有哪些是类（结构），为什么它们不是结构（类）？在自定义类型时，您如何选择是类还是结构？&lt;/li&gt;

  &lt;li&gt;在.NET程序运行过程中，什么是堆，什么是栈？什么情况下会在堆（栈）上分配数据？它们有性能上的区别吗？“结构”对象可能分配在堆上吗？什么情况下会发生，有什么需要注意的吗？&lt;/li&gt;

  &lt;li&gt;泛型的作用是什么？它有什么优势？它对性能有影响吗？它在执行时的行为是什么？.NET BCL中有哪些泛型类型？举例说明平时编程中您定义的泛型类型。&lt;/li&gt;

  &lt;li&gt;异常的作用是什么？.NET BCL中有哪些常见的异常？在代码中您是如何捕获/处理异常的？在“catch (ex)”中，“throw”和“throw ex”有什么区别？您会如何设计异常的结构，什么情况下您会抛出异常？&lt;/li&gt;

  &lt;li&gt;List&amp;lt;T&amp;gt;和T[]的区别是什么，平时你如何进行选择？Dictionary&amp;lt;TKey, TValue&amp;gt;是做什么的？.NET BCL中还有哪些常用的容器？它们分别是如何实现的（哪种数据结构）？分别是适用于哪些场景？&lt;/li&gt;

  &lt;li&gt;抽象类和接口有什么区别？使用时有什么需要注意的吗？如何选择是定义一个“完全抽象”的抽象类，还是接口？什么是接口的“显式实现”？为什么说它很重要？&lt;/li&gt;

  &lt;li&gt;字符串是引用类型类型还是结构类型？它和普通的引用类型相比有什么特别的地方吗？使用字符串时有什么需要注意的地方？为什么说StringBuilder比较高效？在连接多个字符串时，它无论何时都比直接相加更高效吗？&lt;/li&gt;

  &lt;li&gt;如何高效地进行数组复制？“二维数组”和“数组的数组”有什么区别？在使用双重循环遍历一个二维数组时，如何选择内外层的遍历顺序？&lt;/li&gt;

  &lt;li&gt;什么是元编程，.NET有哪些元编程的手段和场景？什么是反射？能否举一些反射的常用场景？有人说反射性能较差，您怎么看待这个问题？有什么办法可以提高反射的性能吗？&lt;/li&gt;

  &lt;li&gt;委托是什么？匿名方法是什么？在C# 3.0中，Lambda表达式是什么？扩展方法是什么？LINQ是什么？您觉得C# 3.0中还有哪些重要的特性，它们带来了什么优势？BCL中哪些类库和这些特性有关？您平时最常用哪些？&lt;/li&gt;

  &lt;li&gt;工作之外您看哪些技术相关的书、网站、社区、项目等等？您还接触哪些.NET以外的技术，能和.NET或.NET中有针对性的部分做个对比吗？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以上便是暂时想到的问题，以后有需要再做补充──当然，不提供答案，一是写出来太累了，二是这些东西往往也没有完全标准的答案。您一定发现了，这些问题其实都是和.NET基础相关，与某个特定的框架或是类库并没有多大关系。在实际面试时，如果是Web开发人员，我一定还会考察对方对ASP.NET及Web基础（更主要是协议和理念相关，而不是HTML，JS，CSS编程）的了解，某些情况下还会有关于多线程、并发的知识点。其他可能还会有一些例如软件开发、设计、实现等实践方面的问题。更重要的是，我一定会需要您在白板上当场写代码来解决一个小问题。不难，也不会直接要求写某个经典算法，事实上甚至是“&lt;a href="http://blog.zhaojie.me/2009/05/1491052.html"&gt;交换元素&lt;/a&gt;”这种简单到近乎毫无意义的问题，只可惜这类题目也能筛掉八成以上的人。&lt;/p&gt;

&lt;p&gt;我并不担心大家知道这些问题，而且我可以表示以后的面试估计也逃不开这些。如果您有某些意向，做些针对性的准备可能也是不错的。当然，既然是面试，就会有“随机应变”，你我都一样。我有自信可以在不断追问下发现到底是“真材实料”还是“临时抱佛脚”。&lt;/p&gt;

&lt;p&gt;您觉得还有哪些问题值得补充呢？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/03/my-interview-questions-for-dotnet-programmers.html#comments</comments>
      <pubDate>Thu, 03 Mar 2011 07:38:54 GMT</pubDate>
      <lastBuildDate>Fri, 04 Mar 2011 01:10:37 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/02/there-is-always-a-trade-off.html</link>
      <guid>http://blog.zhaojie.me/2011/02/there-is-always-a-trade-off.html</guid>
      <description>&lt;p&gt;这年头技术社区里的一些观点让我很看不惯，例如动辄就说什么什么要被淘汰，什么什么要被替换。比如微软出个F#，就以为它要淘汰C#了，但C#和VB.NET不都已经共存很多年了吗？在我看来，明明是“多了一个选择”，绝对是好事情，又没逼你学，也没人说不学就落伍（不学VB.NET您就不觉得落伍吗？）。ASP.NET MVC也是如此，但非要有人说ASP.NET WebForms要被淘汰了，却“选择性忽视”至今WebForms还在不断成长的事实。MVC和WebForms都是好东西，各有千秋，两种选择罢了，仅此而以。&lt;/p&gt;

&lt;p&gt;某段时间常会看到很多人说WebForms是垃圾，一大堆问题，比如说ViewState放在页面上很占空间。我说，ViewState是个很神奇的东西，自动保持状态，许多场景下都能十分方便。如果你嫌它占空间，也可以选择性地关闭，不会影响你什么。可是，某些人还是不愿接受，嗷嗷数落WebForms的不是。好吧，MVC编程模型的确在许多方面胜过WebForms，用好就好。但是，怎么现在就会有人写信问我，诸如在ASP.NET MVC里怎么实现多个下拉框联动这种问题呢？此时往往还会提到WebForms时代实现这个功能有多么方便。我说，您也体会到WebForms的优势了么？结果某位兄台还继续抱怨说，用MVC也真是麻烦，进而说ASP.NET上面怎么就没好用东西。于是我说爱干嘛去干嘛去，还“既要马儿跑得快，又要马儿不吃草”么？&lt;/p&gt;

&lt;p&gt;ASP.NET不是傻瓜设计的，否则也无法如此轻易地实现出ASP.NET MVC。WebForms模型也十分精妙，尤其是带上UpdatePanel，绝对让人耳目一新（我知道某些人又要说UpdatePanel性能差了，我还是那句话，我读过UpdatePanel的完整代码，性能绝对不差）。只要不是愚蠢的作品，基本有失就会有得。刚好，前两天和公司里某个项目的弟兄聊天，他说他建议后台数据管理系统使用WebForms开发，结果项目组还是决定使用传统的方式写，原本计划三天完成，现在已经做了两个星期了，还在和大堆数据表的增删改查以及六个下拉框联动搏斗。&lt;/p&gt;

&lt;p&gt;还有个“马儿”的例子是关于.NET的。我见过有人抱怨说.NET的程序集管理很混乱，例如一个程序集还会在系统中存在多个版本。有趣的是，他同时还提到了Dll Hell，认为微软怎么总是搞不定此类问题。其实.NET程序集的管理方式，不就是为了解决Dll Hell问题嘛。所谓Dll Hell，往往就是因为不同程序间同名的库文件相互覆盖所造成的。如果高版本的库文件被低版本所覆盖，那么依赖高版本中新增API的程序就无法正常运行了。而且，即使是高版本的库，也有可能无法做到百分百兼容低版本（疏忽谁都会有）。因此，最合适的做法其实就是如.NET那样，同名没有问题，版本不同也不会相互影响，程序集自带元数据。A程序用1.9版本，B程序用2.0版本，大家和平共处，互不干涉。如果一个程序依赖1.0版本的程序集，那么即便系统中已经存在2.0版本也照样无法运行。如果没有这样的机制，即便是传说中伟大的Java平台也会有类似&lt;a href="http://en.wikipedia.org/wiki/Java_Classloader#JAR_hell"&gt;Jar Hell&lt;/a&gt;问题，最终可能还是需要一个类似于.NET中GAC的东西。&lt;/p&gt;

&lt;p&gt;很多东西都是这种权衡。世界上聪明人不多，但也没那么多笨蛋，我们大家其实都差不多。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/02/there-is-always-a-trade-off.html#comments</comments>
      <pubDate>Sun, 27 Feb 2011 17:01:21 GMT</pubDate>
      <lastBuildDate>Wed, 02 Mar 2011 04:53:38 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>在MongoDB中实现乐观并发控制</title>
      <link>http://blog.zhaojie.me/2011/02/optimistic-concurrency-control-in-mongodb.html</link>
      <guid>http://blog.zhaojie.me/2011/02/optimistic-concurrency-control-in-mongodb.html</guid>
      <description>&lt;p&gt;说起来，自从接触了&lt;a href="http://www.mongodb.org/"&gt;MongoDB&lt;/a&gt;以后，我在大小项目中就再也没有接触过关系型数据库了。性能倒不是什么主要问题，主要是方便，例如我可以在MongoDB中直接保存数组，然后把其中的元素当作查询条件，而在关系型数据库中，则需要使用额外的表格，然后再JOIN等等。当然，在MongoDB中很难进行JOIN，于是对于某些场景下会略显麻烦，但在记忆中我似乎真没什么束手束脚的情况。这方面我还没有仔细分析，可能MongoDB支持保存复杂对象会有所帮助吧。以上都是废话，这里我简单谈一下如何在MongoDB中实现&lt;a href="http://en.wikipedia.org/wiki/Optimistic_concurrency_control"&gt;乐观并发控制&lt;/a&gt;。当然加入您对MongoDB的功能都有所了解，那么这种做法也是十分显而易见的。&lt;/p&gt;

&lt;p&gt;简单地说，“并发控制”便是避免在并发环境下某条记录被错误地覆盖。例如在一次“读取”、“修改”、“提交”的事务中，除非进行合理控制，否则可能其中某次提交的数据就遗失了。所谓“悲观”并发控制，则意味着在某次事务的“开始”和“提交”之间不会出现任何“读取”操作（即这条记录被锁定了），这自然不会有问题。而乐观并发控制，则保证的是在某次“读取”和“提交”之间没有进行任何“提交”操作，否则便会提交失败，于是当前事务便会重新从“读取”这个最早的步骤开始。此类概念（或者说并发处理方式）在许多地方都有体现，例如在普通的并发编程中，lock就近似于“悲观”并发控制，而“&lt;a href="http://en.wikipedia.org/wiki/Software_transactional_memory"&gt;软件事务内存&lt;/a&gt;”则类似于“乐观”并发控制。&lt;/p&gt;

&lt;p&gt;如果要在普通的关系型数据库里实现乐观并发控制，我们一般需要为其加上一个额外的Version字段，它是整型，也可能是个时间戳。在更新某条记录时，我们将这个字段的“旧值”作为UPDATE语句的条件之一，同时这个字段也会写入新的值。如果这次更新影响了某条记录，那么表示更新成功，反之则表示这条记录已经被删除，或是在“读取”和“提交”之间遇到了其他提交操作。在SQL Server中存在一个Timestamp类型，这个类型的字段会在记录修改时自动更新。&lt;/p&gt;

&lt;p&gt;在MongoDB中的做法也没有太大区别，只是它的update语句并不会返回它所影响的记录数，于是我们必须额外进行一次查询，例如&lt;a href="http://www.mongodb.org/display/DOCS/Atomic+Operations"&gt;文档上所记载&lt;/a&gt;：&lt;/p&gt;

&lt;pre class="code"&gt;&amp;gt; t.update({_id: 1, version: 3}}, {$set: {Content: &lt;span style="color: maroon"&gt;&amp;quot;New Content&amp;quot;&lt;/span&gt;, version: 4}});
&amp;gt; db.$cmd.findOne({getlasterror: 1});
{&lt;span style="color: maroon"&gt;&amp;quot;err&amp;quot;&lt;/span&gt;:, &lt;span style="color: maroon"&gt;&amp;quot;updatedExisting&amp;quot;&lt;/span&gt;: &lt;span style="color: blue"&gt;true&lt;/span&gt;, &lt;span style="color: maroon"&gt;&amp;quot;n&amp;quot;&lt;/span&gt;: 1 , &lt;span style="color: maroon"&gt;&amp;quot;ok&amp;quot;&lt;/span&gt;: 1} &lt;span style="color: #006400"&gt;// it worked

&lt;/span&gt;&amp;gt; t.update({_id: 1, version: 3}}, {$set: {Content: &lt;span style="color: maroon"&gt;&amp;quot;New Content&amp;quot;&lt;/span&gt;, version: 4}}); 
&amp;gt; db.$cmd.findOne({getlasterror: 1});
{&lt;span style="color: maroon"&gt;&amp;quot;err&amp;quot;&lt;/span&gt;:, &lt;span style="color: maroon"&gt;&amp;quot;updatedExisting&amp;quot;&lt;/span&gt;: &lt;span style="color: blue"&gt;false&lt;/span&gt;, &lt;span style="color: maroon"&gt;&amp;quot;n&amp;quot;&lt;/span&gt;: 0 , &lt;span style="color: maroon"&gt;&amp;quot;ok&amp;quot;&lt;/span&gt;: 1} &lt;span style="color: #006400"&gt;// did not work&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;我们可以在update语句后面跟上一句db.$cmd查询，如果它返回updatedExisting为true，则表示更新成功了。我一开始担心db.$cmd查询的结果是否准确：如果在update语句和db.$cmd查询之间，另外一个连接恰好也执行了一次update操作，那么db.$cmd返回的是哪次更新的结果？从后来从邮件列表中得知，db.$cmd查询是与连接相关，这便不会有问题了。不过值得注意的是，如果您使用的的驱动程序是“自动管理连接”的，则可能您在程序中发起的两次查询会使用两个不同的链接。不过我猜成熟的驱动应该都有办法解决这个问题，例如&lt;a href="http://www.mongodb.org/display/DOCS/CSharp+Driver+Tutorial"&gt;MongoDB的官方.NET驱动&lt;/a&gt;便可以要求直接返回db.$cmd查询的结果，或者在代码里显式“固定”某个链接。如今MongoDB的官方驱动已经十分完善，将MongoDB的功能体现地淋漓尽致，我也正在它的基础上更新&lt;a href="http://github.com/JeffreyZhao/EasyMongo"&gt;EasyMongo&lt;/a&gt;（经过几个项目使用，感想不错），和之前的“民间驱动”相比省了不少心——顺便一提，官方驱动其实也借用了民间驱动的不少代码，即便它们之间的API有许多差异。&lt;/p&gt;

&lt;p&gt;除此之外，您也可以使用基于runCommand的&lt;a href="http://www.mongodb.org/display/DOCS/findAndModify+Command"&gt;findAndModify&lt;/a&gt;命令进行更新，更新条件自然同样需要包括版本号。如果更新成功，那么findAndModify命令则会返回“更新前”的数据，否则则返回空文档。一般来说，MongoDB的驱动也已经包含了runCommand命令，甚至对findAndModify的直接支持（例如官方的.NET驱动）。&lt;/p&gt;

&lt;p&gt;正如我一开始所说的那样，其实如果您了解MongoDB的功能，本文内容其实是十分显而易见的，的确就是这么简单。&lt;/p&gt;

&lt;p&gt;最后顺手发布两条“招聘”消息（地点都是上海）：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;钢琴入门老师（1名）：指导我进行钢琴基础技术训练及乐理（如24个大小调音阶，快速识谱方法）。本人小时候学过点钢琴，但基础没有打好，现在打算认真来过。 &lt;/li&gt;

  &lt;li&gt;优秀.NET程序员（1~2名）：加入盛大创新院工作，要求“自认为对.NET有较好了解”，有一定编程基础，热爱技术学习。热情参与社区活动者为佳。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;欢迎各位推荐或自荐，联系方式（邮件）：&lt;img src="http://services.nexodyne.com/email/icon/T3pYH0sugOKOkgan4fyp/GoPfEek%3D/R01haWw%3D/0/image.png" class="embed" /&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/02/optimistic-concurrency-control-in-mongodb.html#comments</comments>
      <pubDate>Tue, 22 Feb 2011 01:39:51 GMT</pubDate>
      <lastBuildDate>Thu, 03 Mar 2011 08:41:24 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/essential/">重中之重</category>
      <category domain="http://blog.zhaojie.me/life/">生活心情</category>
      <title>我们不是牛人，所以还是老老实实跟着兴趣走吧</title>
      <link>http://blog.zhaojie.me/2011/02/piano-life-and-interests-driven-practice.html</link>
      <guid>http://blog.zhaojie.me/2011/02/piano-life-and-interests-driven-practice.html</guid>
      <description>&lt;p&gt;前几天周筠老师&lt;a href="http://blog.sina.com.cn/s/blog_6242cc7f0100oonu.html"&gt;写了篇博客&lt;/a&gt;，她认为兴趣十分重要，没有兴趣很难做成事情。我对此十分同意，有亲身经历为证。更值得一提的是，这个亲身经历和技术或是职业方面的关系真不大，我对许多技术都感兴趣，一时还真难找出对比反差明显的东西。这次来说说我近几个月找到——或者说“找回”的兴趣，弹钢琴。在看来，这对我来说实在是“兴趣”的最佳例证了。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/piano-mbp-score.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/piano-mbp-score.jpg" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;以上是我的装备，CASIO PX-130，相当著名的低端电钢琴。必要时前方放一纸箱，侧卧一个笔记本用于看谱，效果还算不错。对于在线的乐谱，我会把它截屏至PowerPoint中播放，再接个鼠标放地上，作为脚踏板用于翻页。电钢琴的声音和触键与真钢琴相比自然差一大截，但它的好处在于不会扰民，可以允许我半夜两三点尽情抒发情怀。等老子有钱了，一定搞间隔音好的屋子，放台斯坦威大三角，就像白天做梦时梦到的那种样子。&lt;/p&gt;

&lt;p&gt;就像大部分琴童那样，我从三四岁的时候开始学琴。当时家里花了大笔积蓄，也就是二十多年前的5000块钱，买了台现在也差不多值5000块钱的聂耳牌立式钢琴——话说这台钢琴已经破败不堪，许多键已经松弛疲软，延音踏板踩下去也没有延音效果了。过段时间打算找人来调试修理一下，然后给我妈用，她报名的老年班月底就要开始上课了。现在，再给这部老爷琴留个影吧。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/home-old-piano.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/home-old-piano.jpg" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;还是和大部分琴童那样，我没能在七八岁时达到演奏肖练的水平，在那个时候钢琴给我带来的似乎只有痛苦。这么想吧，一个小破孩儿，每天在同学朋友都在疯跑玩耍的时候，被父母逼着坐在钢琴前进行枯燥的练习。此外身边同时常常伴随着耳光皮带拖鞋板，所以我如今皮糙肉厚反应灵敏也非一朝一夕之功。您可能会说弹钢琴件多么美妙的事情哪，但是对于对于当年的我，乃至绝大部分琴童来说，要“享受”钢琴几乎是件不可能的事情。&lt;/p&gt;

&lt;p&gt;首先，除了某些天才型选手，很难有琴童能在一开始的几年里有能力驾驭钢琴，因此只能不断地弹奏特定的简单作品或是练习曲等等。其次，即便有了一定程度的技术水平，也不一定能够真正体会到音乐的美妙。其中第二点原因尤其关键。对琴童来说，弹琴必然意味着考级，而即便是业余十级水平关注的还是技术——我并没有觉得这点有太大问题，因为钢琴演奏首先的确是个技术活。只可惜，在应试教育的一贯优秀传统下，可怜的琴童们被强迫不断地练习，只为“跑完”整首曲目，“体会”也好“精雕细琢”也罢就先放一边吧。&lt;/p&gt;

&lt;p&gt;这真的很讽刺。我前段时间翻出90年代初的考级用书，发现其中的确也包含了丰富的著名曲目，但是我当时完全没有意识到这一点。我只是个苦恼地一边弹琴一边哭哭啼啼的小破孩，时刻准备着偷懒。终于，在上初中以后，我以学业为由终止了钢琴演奏，直到几个月前，期间十几年没有摸过钢琴。&lt;/p&gt;

&lt;p&gt;一切是从我上大学开始改变的。不知道是什么原因，我第一次迷上古典音乐，后来总结了一下，至少听了千余盘CD，其中大部分是钢琴演奏。名家作品名家演绎几乎一个不拉，包括&lt;a href="http://www.naxosdirect.co.uk/Liszt-The-Complete-Piano-Music/title/CDS44501/98/"&gt;Leslie Howard在Naxos录制的李斯特大全集&lt;/a&gt;在内的许多整套录音都听了不止一遍，如肖邦舒伯特莫扎特贝多芬钢琴作品全集这样的“流行音乐”更是反复欣赏比较了好几个甚至十几个不同的版本。逐渐，我自然无法满足与单纯地“聆听”，而想亲手体会一下这些乐章的美妙。但是，几次重新接触琴键，唯一的感觉还是只有痛苦。这样的痛苦尤甚小时候初学钢琴时，因为我发现自己完全无法控制自己的双手，技术也已经远不如童年的巅峰时期，一些基本的音阶琶音和弦已经完全无法跑下，只想剁手。&lt;/p&gt;

&lt;p&gt;但是，我现在还是在坚持，因为王力宏有首歌唱得好：“欺骗世界、欺骗自己”。和小时候相比，我除了身高体重之外，最大的长进可能就是自欺欺人的功夫了。我时刻告诉自己，我还能够练好；只要坚持，终有一天我的手下也会出现名家的演绎水准。我时常也会翻出些著名的曲目，演奏其中一些不是太难的片段，自我安慰/享受一番，以免自己重新迷失在枯燥的基础练习中。如今，在我编程疲惫之余以弹琴作为放松，反之亦然；为了能够在午休的时候也能摸上琴键，我还自费购买了一台电钢琴放在公司。使用这种方式，我居然也硬扛下来贝多芬“悲怆”钢琴奏鸣曲的第一乐章——这也是促使我重新开始弹琴的曲目。当然，只能算是半熟不熟地跑下而已，毫无质量可谈。总而言之，如今我痛并快乐着。&lt;/p&gt;

&lt;p&gt;在钢琴演奏方面，可谓“兴趣”是我的唯一动力。小时候的技术比现在好得多，但却体会不到如今的快乐。其中唯一的区别就是兴趣了。我想，如果重来一次，我有机会可以引导小时候的我领悟一些简单的音乐之美，甚至只需要播放一些录音，可能一切就都会不一样了。说起来，我也打算引导（但不强迫）我的小孩学习钢琴，因为这的确是个美妙的事物。我们都不是&lt;a href="http://www.douban.com/group/topic/17278934/"&gt;郎朗般的牛人&lt;/a&gt;，还是跟着兴趣走下去吧。&lt;/p&gt;

&lt;p&gt;没有兴趣？培养兴趣也要产生兴趣。“欺骗世界，欺骗自己”。&lt;/p&gt;

&lt;embed src="http://www.tudou.com/v/vFMiG7X9K3A/&amp;rpid=55026969/v.swf" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" wmode="opaque" width="480" height="400"&gt;&lt;/embed&gt; 

&lt;p&gt;以上是我目前的水平（各位暂时先不要苛求电钢琴那单薄的录音效果了），一年后我们再来看。&lt;/p&gt;

&lt;p&gt;&lt;font color="#ff0000"&gt;广告时间：&lt;/font&gt;第三届nBazzar技术交流会所有演讲资料已经发布，&lt;a href="http://nbazaar.org/"&gt;欢迎关注&lt;/a&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/02/piano-life-and-interests-driven-practice.html#comments</comments>
      <pubDate>Wed, 09 Feb 2011 17:28:51 GMT</pubDate>
      <lastBuildDate>Thu, 10 Feb 2011 08:07:27 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/discussion/">思考讨论</category>
      <title>分清“语言/规范”以及“平台/实现”，以及跨平台.NET开发</title>
      <link>http://blog.zhaojie.me/2011/01/be-clear-with-language-spec-and-platform-implementation-dotnet-cross-platform.html</link>
      <guid>http://blog.zhaojie.me/2011/01/be-clear-with-language-spec-and-platform-implementation-dotnet-cross-platform.html</guid>
      <description>&lt;p&gt;在许多年前，“语言”就等同于“平台”，例如C，C++以及最早的Ruby和Python等等。但是随着技术发展，出现了一些通用的平台，例如.NET和Java，逐渐这些平台上的语言也越来越多。再后来，某些语言在不同平台上的实现也越来越多，事情也变得有些复杂。技术在发展，但是从目前社区的讨论中，我发现许多朋友的观念还没有跟上。简单地说，如今的观念，一定要从“语言即平台”切换成“语言及平台”，当分清“语言”和“平台”这两个不同事物之后，许多问题才能讨论地清楚。&lt;/p&gt;

&lt;p&gt;例如我写过一个太监系列《&lt;a href="http://blog.zhaojie.me/2010/04/why-java-sucks-and-csharp-rocks-1-thoughts-and-goals.html"&gt;Why Java Sucks and C# Rocks&lt;/a&gt;》，其中谈的是C#和Java两个“语言”而不是两者的“平台”。编程“语言”其实是一种“规范”，它涉及了程序员在使用这门语言时的文本表现形式（这里暂不考虑其他形式的语言），而“平台”则包括对这个规范的“实现”（广义的“平台”还包括整个生态环境等等）。C#和Java分别处在各自的平台上，但许多语言其实是跨多种平台的。例如Python，Ruby，Scala，Clojure，JavaScript等等，数不胜数。同样，一个平台上也会出现多种语言。而且事实上，由于.NET和Java这样的平台越来越成熟，语言的设计及实现者也都越来越倾向于让语言运行在“某个平台”上。这么做可以尽可能地利用前人的成果，而不是什么都要自己从头做起。&lt;/p&gt;

&lt;p&gt;其实基本的原则就是这么简单，但是真正在考虑问题的时候，可能就不是那么容易了，我们必须时刻保持清晰地头脑。&lt;/p&gt;

&lt;p&gt;例如有个人说“C#比Java执行效率高（或低）”，这个说法是否正确？其实这种说法有很大问题。因为我们知道，在这里C#和Java都是“语言”，它们的执行环境CLI及JVM一样都是“规范”，但“执行效率”是一种表现，这和“实现”得如何有很大关系。例如，C#是运行在.NET平台还是Mono上（它们都是CLI规范的具体实现），Java是运行在JRockit还是Hotspot（前者是Oracle的JVM商业实现，后者是Sun的开源实现——当然现在也是Oracle的），亦或是Android的Dalvik上？很显然，不同实现之间的表现会有区别，不可一概而论，否则也不会出现JavaScript引擎的效率之争了。同理，有些人使用Hotspot上的Java性能来说明Java在Android上运行时的表现，这也是不对的——要知道Google在和Oracle的Java专利官司中不断强调Dalvik不是“Oracle那种Java”。作为结论，Java在Android上的表现的确不错，但论证方式也必须正确才行。&lt;/p&gt;

&lt;p&gt;当然，有时候“规范”也会影响到“实现”，例如一个动态分发的语言，其性能基本百分百不如在编译期绑定的静态语言。所以事情原本就是这么复杂，做一个思路清晰的程序员并不是件容易的事情。顺便一提，女人在这方面的头脑一般都比较清楚，她们一般都知道骑白马的不一定是王子，也有可能是唐僧。&lt;/p&gt;

&lt;p&gt;对于俗称“.NET程序员”的那一批人来说，分清“语言”和“平台”更是一件十分重要的事情，因为C#语言可以说是目前“平台”、“实现”最为广泛的“语言”之一了。之前我为InfoQ写过一篇文章，其中提到Mono的创始人&lt;a href="http://tirania.org/blog/"&gt;Miguel de Icaza&lt;/a&gt;给出的&lt;a href="http://www.infoq.com/cn/news/2010/11/mono-cross-platform"&gt;目前C#语言可执行平台的“不完全”列表&lt;/a&gt;，几乎覆盖了各种流行的操作系统及设备等等，例如：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Windows &lt;/li&gt;

  &lt;li&gt;Mac OS &lt;/li&gt;

  &lt;li&gt;Linux / BSD / Solaris &lt;/li&gt;

  &lt;li&gt;Windows Phone，Android，iOS &lt;/li&gt;

  &lt;li&gt;XBox 360，Wii，PS3 &lt;/li&gt;

  &lt;li&gt;…… &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此就拿C#这一种语言来说，“实现”也会各自略有不同，这便是所谓的“配置（Profile）”。目前至少已经有这么多配置了：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;.NET 4.0配置 &lt;/li&gt;

  &lt;li&gt;Silverlight配置 &lt;/li&gt;

  &lt;li&gt;Windows Phone 7配置 &lt;/li&gt;

  &lt;li&gt;XBox360配置 &lt;/li&gt;

  &lt;li&gt;Mono核心配置：与.NET配置相同，可以在Linux，MacOS X，Solaris，Windows和BSD里使用。 &lt;/li&gt;

  &lt;li&gt;.NET Micro Framework &lt;/li&gt;

  &lt;li&gt;Mono的iOS配置 &lt;/li&gt;

  &lt;li&gt;Mono的Android配置 &lt;/li&gt;

  &lt;li&gt;Mono的PS3配置 &lt;/li&gt;

  &lt;li&gt;Mono的Wii配置 &lt;/li&gt;

  &lt;li&gt;Moonlight配置（与Silverlight兼容） &lt;/li&gt;

  &lt;li&gt;Moonlight扩展配置（Silverlight和完整的.NET 4 API） &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“配置”之间的区别主要体现在执行环境的能力（例如iOS不支持运行时代码生成，因此支持AOT但不能JIT）以及类库的覆盖面上（例如XNA类库只存在于Windows Phone及XBox 360等游戏平台），不过它们终究实现了一个核心规范，因此我们可以说在不同平台上都可以“使用.NET进行开发”。&lt;/p&gt;

&lt;p&gt;Mono实在是一个了不得的作品，它让我知道了“跨平台原来可以这么做”。之前我也写过有关&lt;a href="http://blog.zhaojie.me/2010/06/is-cross-platform-a-lie-or-not.html"&gt;跨平台的问题&lt;/a&gt;，其中谈到在“客户端的跨平台一般都很难得到最佳的体验”，这个论点的最佳证明便是Java。但Mono走的却是另一条跨平台的道路，它在各平台上实现了核心的执行引擎和类库之外，解决“体验”的方式便是在各个平台上提供原生平台的绑定。这样无论是在Mac OS，iOS，Android上都可以得到原生应用的体验。&lt;/p&gt;

&lt;p&gt;我很奇怪为什么有些搞.NET的人一边说.NET的适用面太小，一边却忽视Mono的成果，在我看来这完全是“自作孽不可活”，我愈发觉得是否接受Mono是判断一个.NET程序员是否优秀的重要准则。其实Mono实在很火，因为他为广大.NET程序员扩展了工作领域，使用现有的知识来开发iOS等平台的应用程序，还可以共享代码，何乐而不为？前不久苹果发布了Mac上的App Store，于是MonoMac也立即推出了&lt;a href="http://tirania.org/monomac/archive/2011/Jan-10-1.html"&gt;面向AppStore的打包器&lt;/a&gt;，&lt;a href="http://twitter.com/#!/praeclarum"&gt;Frank Krueger&lt;/a&gt;也开始着手移植它的作品&lt;a href="http://icircuitapp.com/"&gt;iCircuit&lt;/a&gt;，&lt;a href="http://vimeo.com/18651634"&gt;成果显著&lt;/a&gt;。因此在我看来，这才是一个现代.NET程序员该有的工作台：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/morden-dotnet-prog-workbench.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/morden-dotnet-prog-workbench.jpg" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;对于MonoTouch这样的新思路，带有疑惑是正常的。我也知道还有许多聪明人可以找到各种反对的理由。不管怎样，我现在这里随意列上几条吧：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;有人说，用MonoTouch等.NET实现来做iOS开发“不正式”；我说，这个说法颇有“血统论”的意味，不过既然在Windows上用C++和Delphi都很正式，那么为什么在iOS上使用Objective-C才是正途？&lt;/li&gt;

  &lt;li&gt;有人说，MonoTouch性能一定不如Objective-C好；我说，这是猜测，即使性能不如Objective-C，看看各种案例也知道这在实践中并不是问题（事实上MonoTouch的前身便是Unity3D对Mono的使用，而iOS上实在有太多游戏在使用Unity3D了）。&lt;/li&gt;

  &lt;li&gt;有人说，MonoTouch或MonoDroid没有大公司支持，不靠谱；我说，您之前不是经常鄙视类似“开源没有微软靠谱”或是“微软开发人员只知道微软技术”这种说法的吗？&lt;/li&gt;

  &lt;li&gt;有人说，用MonoTouch等于抛弃了CocoaTouch社区，出了问题都没人问；我说，MonoTouch的问题基本就是CocoaTouch的问题，MonoTouch的UI层就是CocoaTouch，有问题直接去CocoaTouch社区或CocoaTouch程序员，代码直接映射，类库直接使用。&lt;/li&gt;

  &lt;li&gt;有人说，用MonoTouch的人不好招；我说，用C#、.NET的人比用Objective-C、Cocoa多太多了。给我一个熟练使用.NET和C#的人，三天上手，一周成为能够开发出成品的iOS开发者。&lt;/li&gt;

  &lt;li&gt;有人说，难道就是为了用.NET所以用MonoTouch？我说，用MonoTouch/MonoDroid的好处很多，例如我可以在iOS、Android、Windows Phone甚至更多平台上共享UI以外的代码，并可以直接使用大量.NET上的类库——这点实在太方便了。不要问我为什么Android上不能使用Java类库，我只知道开发Andorid的同事发现SOAP访问类库没有，REST找不到好的，JSON支持也只有最原始的支持，于是痛苦万分。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我还知道，这些说法依旧挡不住出现基于MonoDroid的&lt;a href="http://deltaengine.net/"&gt;DeltaEngine&lt;/a&gt;，这是个跨平台的游戏引擎，在Mono的支持下可以运行在Linux，MacOS X，iOS和Android上，在微软.NET支持下可以运行在XBox 360，Windows Phone 7自然还有普通的Windows系统上。在CES 2011上&lt;a href="http://blogs.nvidia.com/2011/01/nvidia-press-conference-ces-2011/"&gt;NVidia演示了一个游戏&lt;/a&gt;，&lt;a href="http://mobilebits.de/Blog/post/2011/01/05/Delayed-blogging-of-building-the-first-SoulCraft-Tech-Demo-version.aspx"&gt;Soul Craft&lt;/a&gt;，它运行在&lt;a href="http://blogs.nvidia.com/2011/01/lg-launches-optmus-2x-dual-core-superphone-powered-by-tegra-2/"&gt;LG Optimus 2X&lt;/a&gt;，这个游戏正是使用了DeltaEngine。&lt;/p&gt;

&lt;p&gt;对于我们来说，最大的限制其实还是眼界和思维，突破这一屏障也是我组织&lt;a href="http://nbazaar.org/"&gt;nBazaar技术沙龙&lt;/a&gt;的目的之一。本周六将会举办第三届nBazaar技术交流会，具体信息请访问&lt;a href="http://nbazaar.org/"&gt;http://nbazaar.org/&lt;/a&gt;。如果您还没有报名，也可以直接前来，也欢迎带上感兴趣的朋友或同事。根据以往的经验，场地就像乳沟，挤挤总是有的……&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/01/be-clear-with-language-spec-and-platform-implementation-dotnet-cross-platform.html#comments</comments>
      <pubDate>Thu, 13 Jan 2011 15:35:21 GMT</pubDate>
      <lastBuildDate>Fri, 14 Jan 2011 02:29:51 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/discussion/">思考讨论</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <title>“花钱”购买App Hub Membership终于成功了</title>
      <link>http://blog.zhaojie.me/2011/01/paid-for-app-hub-membership.html</link>
      <guid>http://blog.zhaojie.me/2011/01/paid-for-app-hub-membership.html</guid>
      <description>&lt;p&gt;作为一个身在天朝的开发人员，要及早使用国外的服务总是一件杯具的事情，注册Windows Phone 7的App Hub Membership也不例外。不幸中的万幸，我工作于&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;，这也是个奇妙的地方，身边的同事就有美国人，在他的帮助下，从提及注册信息到审核成功也只用了6、7个小时。说起来美国人在这方面的确占有十分重大的优势，不光是审核速度快（其他国家用户还需要等待GeoTrust主动联系，并提交更多信息，多花上2、3个工作日十分正常），连价格也便宜（年费只需99美金，前两天让荷兰的朋友帮忙注册需要99欧元），不得不长叹一声。&lt;/p&gt;

&lt;p&gt;由于注册无法重现，我在这里只能简单描述一番。第一个环节是选择国家/地区，以及帐号类型等等，这与接下来的信用卡以及费用有关。接着则是各种信息，姓名电话地址邮编等等，也要和最后那张信用卡相符。之后则是提交信用卡的信息了，自不必说。值得一提的是，在提交信用卡的时候，网站总是说我的信息无法通过认证，几经尝试，最后使用我的美国VPS作代理才注册通过，看来是IP问题。通过之后，邮箱里会收到微软和GeoTrust发来的确认邮件，其中包含一个用于身份认证的链接，点开后则要求输入美国公民的各种信息，例如社会保障号等等。填写必需的字段，提交后便说“请等待处理”了。下午注册，晚上8点就收到邮件说成功了。总体而言，对于国内用户来说，App Hub Membership的“注册代理”比Android Market要宽松一些，后者似乎还要验证邮箱等等。&lt;/p&gt;

&lt;p&gt;App Hub包含了Windows Phone和XBox的开发和发布权限，我虽然也&lt;a href="http://blog.zhaojie.me/2011/01/htc-7-mozart-t8698-windows-phone-7-review-and-xbox-kinect.html"&gt;刚入手了XBox和Kinect&lt;/a&gt;，但我注册个App Hub完全就是为了Windows Phone上面的开发。诚然，如果不需要发布到Marketplace，使用微软提供的免费工具，&lt;a href="http://blog.zhaojie.me/2011/01/htc-7-mozart-t8698-windows-phone-7-review-and-xbox-kinect.html"&gt;再加上ChevronWP7的破解&lt;/a&gt;早已足够了，但我还是选择了购买App Hub。这不到700块钱用于体验时代潮流，仔细想来也并不太多——当然这也涉及到个人的价值观。有些人认为4、500块钱的手机用来打电话发短信也已够用，而另一部分人（包括我）的看法是，几千块钱本身说起来也不能做太多事情，不如用于体会互联网时代的价值，这也是一种进修。所以我会较快购进Windows Phone、Kinect以及MonoTouch等等，即使从某些角度来看Windows Phone并非是一个适合国内用户的产品。&lt;/p&gt;

&lt;p&gt;以前我总是舍不得花钱，和许多朋友一样觉得这也很贵，那也很贵，于是也会用盗版等等。后来我和别人一起创业，虽然并非直接掌管资金，但也对于各种花销建立了一些概念。例如我知道了开一天公司需要多少钱，给员工交工资和福利、网站托管、流量购买需要多少钱，于是我就不会认为一套不到3000的MonoTouch，或是其他某个组件的商业授权有多么昂贵，因为它们的确可以为我省下更多的钱。从那时起，我会适当地选择开发效率更高的.NET平台加上价格便宜（3000左右）工作足够对硬件也十分宽松的Windows Web Server 2008，配合价格便宜选择丰富的*nix作后端存储。&lt;/p&gt;

&lt;p&gt;学会合理花钱的好处其实很多，首先我可以自豪地宣布，我已经不用盗版软件许多年，其次我学会通过花费现在的钱来提高自己或是节省精力，以便赚到更多的钱。我现在越来越喜欢适当使用一些收费的组件，因为我相信他们为了卖更多的授权，会不断提高自己的产品以及服务质量，这让我可以轻松不少。反之如果一切都由自己从头做起，可能大量的时间都耗费在一些无谓的细节上面才能得到与商业产品差不多的质量。时间往往比金钱更有价值。如果纠缠于那些细节无法让人提高，我情愿将时间用在自我学习、娱乐甚至是简单的休息上面。&lt;/p&gt;

&lt;p&gt;说起来购买App Hub Membership也是类似的道理，我不想把大量精力耗费在刷机、破解等方面（虽然我知道许多朋友乐在其中）。非官方的解锁方式总是无法让人安心，可能一次系统升级就让手机重新锁定了，于是又要折腾半天。现在则不然，即便微软接下来会升级Windows Phone，我也可以放心继续使用。因为我已经成为了正式的开发人员，XBox Live的Avatar都可以使用新装备了，哈哈。&lt;/p&gt;

&lt;p&gt;如今是程序员的黄金年代，移动平台和应用市场的兴起让我们可以比以往任何时候都轻松地赚取零花钱，甚至开创一番事业——当然前提是要有创意，这也是我最缺乏的。因此，如果您有什么需求或是点子，也不妨给我一些指点，我也有点实际内容可做。&lt;/p&gt;

&lt;p&gt;最后，依然来推广一下本周六即将举办的“&lt;a href="http://nbazaar.org/"&gt;第三届nBazaar技术交流会&lt;/a&gt;”，欢迎继续报名（近日将发出邀请函）。在今后的交流会上，我也会引入更多有趣的话题，例如移动平台的开发，甚至是与Kinect相关的话题。Kinect的确是神器，在我家已经成为父母每天必修的娱乐活动，可能我还会再买一套送给他们。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/01/paid-for-app-hub-membership.html#comments</comments>
      <pubDate>Mon, 10 Jan 2011 17:45:57 GMT</pubDate>
      <lastBuildDate>Tue, 11 Jan 2011 05:25:36 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <title>HTC 7 Mozart T8698（Windows Phone 7）初体验</title>
      <link>http://blog.zhaojie.me/2011/01/htc-7-mozart-t8698-windows-phone-7-review-and-xbox-kinect.html</link>
      <guid>http://blog.zhaojie.me/2011/01/htc-7-mozart-t8698-windows-phone-7-review-and-xbox-kinect.html</guid>
      <description>&lt;p&gt;上个星期去香港旅游，对于我等IT从业人员来说，数码产品卖场自然是必逛的地方之一。香港的衣食住行都很贵，但是数码产品却十分便宜。看着手中使用了3个月滚珠就已失灵的奥巴马御用机型，决定就在香港采购一台吧。鉴于Android和iPhone 4都已经是烂大街的机器，我打算着重关注一下Windows Phone 7的机器。当时在香港到处可以看到HTC HD 7以及LG Optimus 7 E900，不过都是接近5000港币的价格，再加上实在不是很喜欢它们的外观，于是迟迟不愿下手。后来在百老汇发现新上市的HTC 7 Mozart T8698，据说是加强了多媒体能力的机型，外形和手感都不错，价格也相对较低（4200元港币，约合人民币约3600元），于是遍选中了这台。把玩了几天，记录一下感受。&lt;/p&gt;

&lt;p&gt;对于手机来说，iPhone 4就是iPhone 4，但是说到Android和Windows Phone，不同机器之间的感受还是有很大区别的（尤其是前者）。就拿屏幕来说，Windows Phone的固定配置是480*800的分辨率，于是屏幕越小，画面就显得越精致。例如Mozart是3.7寸屏幕，就比HD 7的4.3寸屏细腻许多。从显示效果上看，Mozart的画面质量远胜iPhone 3GS，从数据上看不如iPhone 4的Retina屏幕（每英寸326像素，Mozart是252像素），不过效果可以说是一个等级的。&lt;/p&gt;

&lt;p&gt;从操作上看，Windows Phone不同于iPhone和Android的图标式布局让人耳目一新，选中某项和程序界面的切换效果也很酷。系统和自带的应用程序使用起来都很流畅，毫无卡顿。系统内置的功能都不错，界面也都很酷。邮箱功能十分令人满意，尤其是对Hotmail和Gmail的支持。令人有些奇怪的是，WP7居然没有自带Live Messenger，不过可以从Marketplace里下载到第三方编写的免费Messenger程序，功能中规中矩。值得一提的是，这个Messenger可以在“退出”之后得到消息推送，让我对系统的“多任务”能力感到很好奇。之前有很多说法认为Windows Phone没有多任务功能，按理来说是无法实现此类功能的。&lt;/p&gt;

&lt;p&gt;其他的例如浏览器，搜索，地图之类的功能就不多说了。游戏方面，Xbox LIVE应该是Windows Phone的特色。Windows Phone的3D功能很强，XNA框架和XBOX游戏开发一脉相承，而且都是使用.NET托管代码（C#）编写，就从极品飞车的效果来看，相信可以让某些认为“托管代码”或是“自动GC”等等“不堪大用”的人更新自己的观念。总体来说，这台HTC 7 Mozart T8698还是让我比较满意的。&lt;/p&gt;

&lt;p&gt;接下来说说它的缺点，当然，这些估计也已经有很多人谈过了，例如暂时还没有复制粘贴等等。对于国内用户来说，最大的缺点莫过于缺少中文输入法的支持。由于微软没有提供相应的API，因此我们无法替换掉系统的输入法，于是如今所有的中文输入功能都是在某个特定程序中实现的，然后“发送”至短信、浏览器地址栏或是邮箱中。这对于一些基础功能来说还算够用，但是限制也很大。例如在联系人列表里（Windows Phone称之为People Hub），我们无法使用中文或是拼音来快速搜索某个联系人，十分不便，这迫使我不得不把联系人的姓名写成英文。现在我都打算开发一个支持中文输入的联系人管理工具了，顺便研究一下怎么开发一个简单的输入法。已有的中文输入法软件，功能可能还不错（如&lt;a href="http://www.cnblogs.com/crazylights"&gt;疯光输入法&lt;/a&gt;），但都很丑，使用也不太方便，我不很喜欢。&lt;/p&gt;

&lt;p&gt;说到软件市场，可以说是Windows Phone软肋了，数量实在太少，更别说面向国内用户的应用程序了。即使是如T-Sina这样的新浪微博客户端，界面也很难看，很没有Windows Phone的样子，这可能也是我打算抽空做的一个功能。考虑到这些，如果您不是手机“玩家”而是普通用户的话，那么Windows Phone的确不太适合您，至少等到今年下半年Windows Phone进入中国市场以后再考虑吧。&lt;/p&gt;

&lt;p&gt;如果您要开发Windows Phone应用程序，可以下载免费的&lt;a href="http://create.msdn.com/en-us/home/getting_started"&gt;Windows Phone Develop Tools&lt;/a&gt;，其中包含Visual Studio 2010 Express Edition和Expression Blend for Windows Phone等工具，应该够用。只可惜这也只能让您在Windows Phone模拟器上运行程序，如果要在真机上调试，还需要购买App Hub会员，这可以让您解锁一定数量的机器，于是可以直接将xap包安装进去。App Hub会员的年费是99美金，但最大的问题其实是无法使用国内的信用卡购买，您可以选择一些代理服务或是请朋友帮忙（希望有条件的朋友也可以来帮我一下，提供一些经验也行）——或者像我一样选择“破解”，俗称“越狱”。&lt;/p&gt;

&lt;p&gt;Windows Phone的破解很容易，应该也是不同机型通用的方式，因为我也是根据HD 7的破解方法来解锁Mozart的：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;准备一台PC，装好Windows Phone Develop Tools和Zune，用数据线将手机和PC连接起来，让Zune和手机处于完全同步的状态（即Sync Relationship不能是Guest）。 &lt;/li&gt;

  &lt;li&gt;下载&lt;a href="http://files.zhaojie.me/ChevronWP7.zip"&gt;ChevronWP7&lt;/a&gt;，将其中的ChevronWP7.cer证书文件传输至手机（可以放在某个站点上用Windows Phone的浏览器下载，或用Email做个中转）并安装。 &lt;/li&gt;

  &lt;li&gt;确保开着Zune，且机器并没有PIN-Locked（即不是黑屏待机或是主画面锁定状态），打开ChevronWP7.exe，选中两个checkbox，再点击Unlock即解锁成功。Unlock之后可以Relock。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;经多次试验，发现每次将手机连上机器后（并启动Zune），则会重新变成锁定状态，这时则需要使用ChevronWP7.exe重新解锁。为了避免被重新锁定，您可以在连接之前把手机的飞行模式（airplane mode）打开，连上以后再关闭。解锁后的机器，可以直接使用Application Deployment工具安装xap文件，也可以在VS里选择在Windows Phone 7 Device里调试。在真机上部署和调试都很快，很流畅，让人完全不想用模拟器。我写了点简单的程序（一个&lt;a href="http://instagr.am/"&gt;Instagram&lt;/a&gt;客户端雏形），不得不再次感叹F#写异步程序真的很爽。&lt;/p&gt;

&lt;p&gt;香港之行除了Windows Phone以外，我还带回了一台Xbox 360及Kinect（加起来大约3200人民币）。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/xbox-kinect.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/xbox-kinect.jpg" width="450" /&gt;&lt;/a&gt; 

&lt;p&gt;如果您还不知道这是什么的话，那么实在是落伍了。与Wii或PS3的控制杆不同，Kinect识别的是整个人体。因此，以后（其实应该说已经是“现在”了）人们完全是这样玩游戏的：&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMTgzMzE3MTE2/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;程序是这样操作的：&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjIwMTUxOTMy/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;而我们也可以写这样的程序：&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjMxMjM3NzE2/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;什么叫革命？什么叫突破？这就叫革命，这就叫突破。&lt;/p&gt;

&lt;p&gt;最后再补充个好消息：&lt;a href="http://monodroid.net/"&gt;MonoDroid&lt;/a&gt;的预览版已经对外公开了，可以自由安装。至此，.NET的“跨平台移动开发”战略又踏出了坚实的一步。此外据Mono老大之前的说法，接下来还会有Nokia……&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2011/01/htc-7-mozart-t8698-windows-phone-7-review-and-xbox-kinect.html#comments</comments>
      <pubDate>Wed, 05 Jan 2011 15:43:33 GMT</pubDate>
      <lastBuildDate>Wed, 05 Jan 2011 17:11:04 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/news/">新闻信息</category>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <title>第三届nBazaar技术交流会开始报名</title>
      <link>http://blog.zhaojie.me/2010/12/3rd-nbazaar-meeting-sign-up.html</link>
      <guid>http://blog.zhaojie.me/2010/12/3rd-nbazaar-meeting-sign-up.html</guid>
      <description>&lt;p&gt;为了错开年底密集的技术会议，第三届&lt;a href="http://nbazaar.org/"&gt;nBazaar技术交流会&lt;/a&gt;（即前“盛大创新院赞助的.NET技术交流会”）将于2011年1月15日举行。第三届的交流会将继续以往四场高质量的演讲，这也是确定nBazaar名称之后的第一次活动，希望nBazaar能够真正&lt;a href="http://blog.zhaojie.me/2010/10/status-of-iron-languages-and-nbazaar.html"&gt;成为“集市”般热闹的社区活动&lt;/a&gt;。从现在开始，nBazaar技术沙龙的相关信息将逐渐集中至独立域名中，欢迎关注。&lt;/p&gt;

&lt;h1&gt;志愿者招募&lt;/h1&gt;

&lt;p&gt;为了留下每次的活动资料，我们希望为每场演讲进行拍摄。如果您有这方面的志愿请发邮件至&lt;a href="mailto:jeffz@nbazaar.org"&gt;jeffz@nbazaar.org&lt;/a&gt;与我联系。&lt;/p&gt;

&lt;h1&gt;时间及议程安排&lt;/h1&gt;

&lt;p&gt;本次交流会首次向社区征集议题，我们在回复中挑选了三场：面向iPad平台的网站架构、面向企业应用语言ABAP、基于.NET的轻量级分布式框架，以及在创新院内部分享会上倍受好评的“分布式版本管理”话题。四位演讲者都是业界一线技术高手，四场话题都是他们的实战心得，希望能够让您满意。&lt;/p&gt;

&lt;table style="text-align: center" border="1" cellspacing="0" cellpadding="5"&gt;&lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;时间&lt;/th&gt;

      &lt;th&gt;议程&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;&lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;12:30 ~ 13:00&lt;/td&gt;

      &lt;td&gt;签到&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;13:00 ~ 14:00&lt;/td&gt;

      &lt;td&gt;针对iPad平台的高性能网站架构&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;14:00 ~ 14:10&lt;/td&gt;

      &lt;td&gt;短休&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;14:10 ~ 15:10&lt;/td&gt;

      &lt;td&gt;分布式版本管理&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;15:10 ~ 15:40&lt;/td&gt;

      &lt;td&gt;茶歇&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;15:40 ~ 16:40&lt;/td&gt;

      &lt;td&gt;企业开发领域的语言特性&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;16:40 ~ 16:50&lt;/td&gt;

      &lt;td&gt;短休&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;16:50 ~ 17: 50&lt;/td&gt;

      &lt;td&gt;使用.NET构建轻量级分布式框架&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;

&lt;h1&gt;演讲内容&lt;/h1&gt;

&lt;p&gt;以下为是四场演讲的信息：&lt;/p&gt;
&lt;a href="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/mashijie-450x600.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/mashijie-450x600.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;针对iPad平台的高性能网站架构&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;马士杰，EF英孚教育，Tech Leader。关注高性能网站架构和前沿技术在线教育领域的应用创新。曾经重点关注的技术领域包括ORM，AOP和SOA。近期的关注重点是针对移动平台的高性能网站架构。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;iPad的横空出世，几乎使得2010年成为全球IT领域的平板电脑之年。摩根士丹利发布的《移动互联网报告》认为移动互联网周期是50年来的第5个新技术周期，以Apple的iPad平板电脑和手机上网为代表的移动互联网的增长势头将超过电脑上网。本演讲的目的是和听众分享本人近一年在针对iPad平台的高性能网站架构方面的一些经验，包括兼容不同平台桌面和移动浏览器的表现层设计模式，针对iPad Mobile Safari浏览器的Web页面性能优化和iPad本地程序和在线网站的无缝整合等。&lt;/p&gt;
&lt;a href="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/lijun-400x600.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/lijun-400x600.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;分布式版本管理&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;李骏，中国第一批J2EE开发者，大中华区第一个中间件和SOA领域的Oracle ACE Director。有着10年以上企业级应用系统咨询/设计/实施以及软件公司管理经验的行业老兵，因为喜欢创造能影响人们日常生活的东西，所以来到盛大创新院，梦想能找到“正确的把软件作成业务”的方法。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;演讲：&lt;/strong&gt;源代码和其它软件工程产物的版本管理，是每个开发者每天都会碰到的问题，经过数十年的发展，CVS、SVN等上一代版本管理系统中的一些问题，催生着新的技术及其应用模式。在过去几年中以Git和Mercurial为代表的分布式版本管理工具有了较大的发展，已经基本具备了普及应用的基础。这里将介绍分布式版本管理欲解决的问题及其关键价值，并以Mercurial为例介绍具体使用的方法和流程。&lt;/p&gt;
&lt;a href="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/shijianzhuo-576x432.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/shijianzhuo-576x432.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;企业开发领域的语言特性&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;师建茁（Amos Shi），GRC软件工程师，SAP Labs China。中学时代在 286、486 系统上学会了 12个DOS内部命令，若干外部命令；使用QBasic开始程序生涯，半夜起来在图画本上设计流程图。后来跟着谭浩强的C语言课本和Borland的TC 2.0进入 Windows CMD 时代，后来有了 C++、Delphi、Python、.NET、Java，排名不分先后，意识到语言并非那么重要； 直到有一天，遇到了 ABAP，以及 Web Dynpro，意识到，对于优秀的软件，高级语言特性还是有所帮助的。2007年加入SAP Labs China，从事GRC软件产品的研发。日常工作涉及GRC 的多个部分。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;演讲：&lt;/strong&gt;作为一个程序员，在加入SAP之前用过一些各种各样流行的技术来写软件产品，到了Labs之后开始使用一种不广为所知的ABAP语言开发GRC软件产品。在学习和使用ABAP语言的过程中，经常会有类似“啊！对了，以前我就想过应该是这样的”、“是的！就是应该这样集成！”、“本来嘛，已经有足够的信息在那里了，她就是应该自动生成，一行代码都不应该写！”这样的感叹，对很多特性总是有相见恨晚的感觉。回想到以前的产品开发中遇到的种种问题，觉得如果这些特性已经有了的话，可以大大提高效率，降低错误。后来逐渐接触到其后面的NetWeaver平台，和Web Dynpro之后，更觉得这一整套东西为企业应用进行了精心设计。又回想起了“程序员如何在非洲捕捉大象？”这个经典笑话，哈哈哈哈。如果你要开发企业应用的话，确实有很多东西可以从这里借鉴，希望对你能有所裨益。&lt;/p&gt;
&lt;a href="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/qiaojie-400x622.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://nbazaar.org/_media/%E8%AE%B2%E5%B8%88/qiaojie-400x622.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;使用.NET构建轻量级分布式框架&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;乔捷，瓦格纳比罗舞台系统公司，技术主管。热爱技术，对多种技术领域都有涉猎，目前主要从事剧院舞台控制系统和虚拟舞台系统的设计和研发工作，主要涉及到分布式系统、实时控制系统、虚拟现实系统、3D实时/离线渲染等技术领域。主要的编程语言为C++/C#/JavaScript，喜爱.NET技术，最近比较关注分布式计算和并行计算技术，并已在实际的项目应用中取得了初步的成功。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;演讲：&lt;/strong&gt;分布式系统的设计和构建是一项复杂多变而很具有挑战性的任务，设计的目标包括服务的伸缩性、可靠性、安全性、实时性、性能、容错等多个方面，同时还可能需要面对各种异构平台的集成和整合。目前市面上已有的分布式框架包括.NET提供的Remoting和WCF，都不能很好的满足上述所有的这些需求。因此，打造一套轻量级的、高度可定制的、符合自身项目需求的分布式框架变得很有现实意义。本演讲就来讨论如何应用.NET技术构建这样一套轻量级的分布式框架。&lt;/p&gt;

&lt;h1&gt;地点&lt;/h1&gt;

&lt;p&gt;本次交流会举办地为&lt;strong style="color: red"&gt;上海市浦东新区碧波路888号畅星大厦&lt;/strong&gt;（地铁二号线张江高科站下，步行10分钟可达）3楼会议厅，地图如下：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-map.png"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-map.png" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;鸟瞰图：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-hybrid.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-hybrid.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;畅星大厦外观：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-building.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-building.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;会场实景照片：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-room.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-room.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;会场容量可以容纳超过200人，希望到时候不会显得太过空旷。:)&lt;/p&gt;

&lt;h1&gt;报名信息&lt;/h1&gt;
&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/sign-up-now.jpg" /&gt; 

&lt;p&gt;本次交流会&lt;a href="http://diaochapai.com/survey518744"&gt;现已开始报名，请填写报名表&lt;/a&gt;。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/12/3rd-nbazaar-meeting-sign-up.html#comments</comments>
      <pubDate>Mon, 20 Dec 2010 16:45:46 GMT</pubDate>
      <lastBuildDate>Mon, 20 Dec 2010 16:45:46 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <title>基于Jscex.Async的JavaScript动画/游戏</title>
      <link>http://blog.zhaojie.me/2010/12/animations-and-games-based-on-jscex-async.html</link>
      <guid>http://blog.zhaojie.me/2010/12/animations-and-games-based-on-jscex-async.html</guid>
      <description>&lt;p&gt;首先和大家宣布一个消息，Jscex的代码已经&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;提交至Github上&lt;/a&gt;了，感兴趣的朋友下载来Dog Fooding一把，并欢迎提出反馈意见。Jscex受到F#计算表达式的启发，是一个面向JavaScript语言的monadic扩展，最常见的用途便是编写异步程序，尤其是逻辑复杂的异步程序。不过除此之外，使用这套异步库来编写动画或是游戏也是十分容易的事情。例如，一个人物的走动或是爆炸效果，其实可以视为一个贴图随时间不断变化的过程。这个变化的过程是异步的，但是有了Jscex.Async，我们只需使用最直接的同步形式编写代码就行了。&lt;/p&gt;

&lt;p&gt;例如这里有个&lt;a href="http://files.zhaojie.me/demos/jscex/samples/async/bullet.html"&gt;我用一个多小时编写的示例&lt;/a&gt;（需要支持canvas的浏览器，例如IE 9，FireFox，Chrome，Safari等等），类似于“是男人就撑过20秒”，其中有这么一段代码：&lt;/p&gt;

&lt;pre class="code"&gt;BulletGame.prototype._bulletFlyAsync = 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;(f) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;options = &lt;span style="color: blue"&gt;this&lt;/span&gt;._options;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;bullet = {pos: {x: 0, y: 0}};

    &lt;span style="color: blue"&gt;this&lt;/span&gt;._addBullet(bullet);
    
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0; &lt;span style="color: blue"&gt;this&lt;/span&gt;._playing &amp;amp;&amp;amp; &lt;span style="color: blue"&gt;this&lt;/span&gt;._inArea(bullet.pos); t += 20) {
        $await(&lt;span style="color: blue"&gt;this&lt;/span&gt;._timer.setTimeoutAsync(20));
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._tryHit(bullet.pos); &lt;span style="color: green"&gt;// 判断是否击中&lt;/span&gt;
        bullet.pos = f(t); &lt;span style="color: green"&gt;// 改变子弹位置&lt;/span&gt;
    }

    &lt;span style="color: blue"&gt;this&lt;/span&gt;._removeBullet(bullet);
}));&lt;/pre&gt;

&lt;p&gt;这段代码是一个异步程序，表示一颗子弹的飞行过程。子弹的飞行轨迹（某一时刻的位置）由函数f来决定，因此我们只需要写一个for循环，并且在循环内部“暂停”一段时间就行了，就这么简单。在循环之前，我们将子弹添加到一个容器里，这样在一个不断循环的paint方法里就会把这颗子弹绘制在canvas上面。循环结束后，我们将子弹从容器中移出。这一切都十分顺其自然，虽然这段代码的执行过程完全是异步的。&lt;/p&gt;

&lt;p&gt;Jscex可以使用各种方式与现有代码组合至一块儿，例如这里我为BulletGame的prototype对象扩展了一个方法，为此我晚上还改进了一下Jscex的编译逻辑，以及Jscex.Async的实现，其目的就是在_bulletFlyAsync这样的函数内部保留this对象在JavaScript语义上的正确性。JavaScript语言中this引用的动态特征是种很强大的特性，但是对于“语言改造者”来说，在一个充满回调的过程中保持语义正确并不是件十分容易的事情（当然，也不算十分困难，只要“想明白”即可）。上面的代码经过编译之后，就类似于我们直接编写了这样的代码（暂时如此，未来会有修改和优化）：&lt;/p&gt;

&lt;pre class="code"&gt;BulletGame.prototype._bulletFlyAsync = &lt;span style="color: blue"&gt;function &lt;/span&gt;(f) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.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;var &lt;/span&gt;options = &lt;span style="color: blue"&gt;this&lt;/span&gt;._options;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;bullet = { pos: { x: 0, y: 0} };
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._addBullet(bullet);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Combine(
            $async.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0;
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Loop(
                    &lt;span style="color: blue"&gt;function &lt;/span&gt;() { &lt;span style="color: blue"&gt;return this&lt;/span&gt;._playing &amp;amp;&amp;amp; &lt;span style="color: blue"&gt;this&lt;/span&gt;._inArea(bullet.pos); },
                    &lt;span style="color: blue"&gt;function &lt;/span&gt;() { t += 20; },
                    $async.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(&lt;span style="color: blue"&gt;this&lt;/span&gt;._timer.setTimeoutAsync(20), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                            &lt;span style="color: blue"&gt;this&lt;/span&gt;._tryHit(bullet.pos);
                            bullet.pos = f(t);
                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Return();
                        });
                    })
                );
            }),
            $async.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;this&lt;/span&gt;._removeBullet(bullet);
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Return();
            })
        );
    });
};&lt;/pre&gt;

&lt;p&gt;此外，JavaScript是一个很容易编写此类动画或是游戏的平台，因为它所有的代码都在UI线程上执行，因此不会有任何多线程方面的问题。例如上面的代码，我们可以放心地向容器里添加或删除“子弹”对象，试想在一个并发环境里构造一个线程安全的双向链表是件多么麻烦的事情。&lt;/p&gt;

&lt;p&gt;最大的问题其实是浏览器环境里的性能问题，不过我对此并不怎么担心，因为V8已经给我们开了个好头，并且还在&lt;a href="http://blog.chromium.org/2010/12/new-crankshaft-for-v8.html"&gt;不断前进&lt;/a&gt;。此外对于动画和游戏来说，最大的性能问题更可能是canvas的绘图性能。现在这个简单的例子性能自然不会有太大问题，不过一旦加上贴图和素材，性能问题就会凸现出来了。对于目前的示例，我在安装Windows 7的台式机里试验了多种浏览器，帧数（fps）各有高低：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Firefox：110左右 &lt;/li&gt;

  &lt;li&gt;Chrome：210左右 &lt;/li&gt;

  &lt;li&gt;IE 9：300至800不等，比较常见的是500左右 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;而在我的MBP上：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Firefox：70左右 &lt;/li&gt;

  &lt;li&gt;Safari：60左右 &lt;/li&gt;

  &lt;li&gt;Chrome：190左右 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;此外还有同事测试了Safari最新的Nightly Build，帧数与Chrome不相上下。从中可以看出，IE 9的表现最为出色，可见它的GPU加速的确不是在吹牛的。值得一提的是，如果图像上没有子弹，那么帧数大约只有60出头，一旦同时出现了几十颗子弹，帧数变会飙升至500以上，峰值甚至可以到800。由此推测，IE 9也是在“按需”使用GPU，十分周全。&lt;/p&gt;

&lt;p&gt;Jscex的源码及示例都在Github上，欢迎尝试。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/12/animations-and-games-based-on-jscex-async.html#comments</comments>
      <pubDate>Wed, 15 Dec 2010 17:03:28 GMT</pubDate>
      <lastBuildDate>Thu, 16 Dec 2010 05:14:09 GMT</lastBuildDate>
    </item>
  </channel>
</rss>
