<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Some reminiscences, some memories</title>
	<atom:link href="http://www.mikespook.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.mikespook.com</link>
	<description>Just another boring day</description>
	<lastBuildDate>Thu, 16 May 2013 08:49:42 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>Z-Node: a cluster for sys-ops based on doozer &amp; zookeeper</title>
		<link>http://www.mikespook.com/2013/05/z-node-a-cluster-for-sys-ops-based-on-doozer-zookeeper/</link>
		<comments>http://www.mikespook.com/2013/05/z-node-a-cluster-for-sys-ops-based-on-doozer-zookeeper/#comments</comments>
		<pubDate>Thu, 16 May 2013 08:38:21 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[Stardy & Research]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[open source]]></category>
		<category><![CDATA[z-node]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=1650</guid>
		<description><![CDATA[I&#8217;m happy to announce the system operations cluster: Z-Node here.

https://github.com/mikespook/z-node

Intro

<span class="readmore"><a href="http://www.mikespook.com/2013/05/z-node-a-cluster-for-sys-ops-based-on-doozer-zookeeper/" title="Z-Node: a cluster for sys-ops based on doozer &#038; zookeeper">阅读全文——共785字</a></span>]]></description>
				<content:encoded><![CDATA[<p>I&#8217;m happy to announce the system operations cluster: Z-Node here.</p>
<p><a href="https://github.com/mikespook/z-node" target="_blank">https://github.com/mikespook/z-node</a></p>
<h2>Intro</h2>
<p>Z-Node is a cluster for helping system operations. It works with <a href="https://github.com/ha/doozerd" target="_blank">Doozer</a> and <a href="http://zookeeper.apache.org/" target="_blank">ZooKeeper</a> cluster.</p>
<p>Every Z-Node watches at least two files:</p>
<ul>
<li>/z-node/node/$HOST/$PID &#8211; for one-node tasks;</li>
<li>/z-node/$REGION/wire &#8211; for cluster tasks (Every Z-Node instance can watch multi-regions).</li>
</ul>
<p>Z-Node will register itself as file /z-node/info/$HOST/$PID with running informations. It watches the file /z-node/node/$HOST/$PID for one-node tasks. When the file was changed, Z-Node will be notified.</p>
<p>All of Z-Nodes watch the file /z-node/$REGION/wire for cluster tasks. When the file is changed, all of Z-Nodes will be notified.</p>
<h2>Special Thanks</h2>
<p>The <a href="https://github.com/skynetservices/skynet" target="_blank">skynet</a> project enlightened me on the developing with Z-Node. Also, I&#8217;m a user with skynet. <img src='http://www.mikespook.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>So, express my thanks to the skynet&#8217;s founder &#8211; Brian Ketelsen.</p>
<p>Thank you for your great job!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2013/05/z-node-a-cluster-for-sys-ops-based-on-doozer-zookeeper/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[翻译]绝妙的 channel</title>
		<link>http://www.mikespook.com/2013/05/%e7%bf%bb%e8%af%91%e7%bb%9d%e5%a6%99%e7%9a%84-channel/</link>
		<comments>http://www.mikespook.com/2013/05/%e7%bf%bb%e8%af%91%e7%bb%9d%e5%a6%99%e7%9a%84-channel/#comments</comments>
		<pubDate>Mon, 06 May 2013 15:02:28 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[channel]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[翻译]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=1635</guid>
		<description><![CDATA[在编写 golang 程序的过程中，channel 会经常使用。本文对 channel 的使用的确很特别，同时也非常实用。

原文在此：http://dave.cheney.net/2013/04/30/curious-channels

&#8212;&#8212;&#8212;&#8212;翻译分隔线&#8212;&#8212;&#8212;&#8212;

<span class="readmore"><a href="http://www.mikespook.com/2013/05/%e7%bf%bb%e8%af%91%e7%bb%9d%e5%a6%99%e7%9a%84-channel/" title="[翻译]绝妙的 channel">阅读全文——共4000字</a></span>]]></description>
				<content:encoded><![CDATA[<p>在编写 golang 程序的过程中，channel 会经常使用。本文对 channel 的使用的确很特别，同时也非常实用。</p>
<p>原文在此：<a href="http://dave.cheney.net/2013/04/30/curious-channels" title="Curious channels" target="_blank">http://dave.cheney.net/2013/04/30/curious-channels</a></p>
<p>&#8212;&#8212;&#8212;&#8212;翻译分隔线&#8212;&#8212;&#8212;&#8212;</p>
<h1>绝妙的 channel</h1>
<p>在 <a href="http://golang.org/" target="_blank">Go 编程语言</a>中，channel 是一个闪耀的特性。它提供了一种强大的、在不使用锁或临界区的情况下，从某个 goroutine 向其他 goroutine 发送数据流的方法。</p>
<p>今天我想讨论关于 channel 的两个重要的特性，这些特性不但使其在控制数据流方面极为有用，而且用在流程控制方面也十分有效。</p>
<p><span id="more-1635"></span></p>
<h2>一个已经被关闭的 channel 永远都不会阻塞</h2>
<p>第一个特性，我想谈一谈已经被关闭的 channel。当一个 channel 一旦被关闭，就不能再向这个 channel 发送数据，不过你仍然可以尝试从 channel 中获取值。</p>
<pre class="brush: cpp; title: ; notranslate">
package main

import &quot;fmt&quot;

func main() {
        ch := make(chan bool, 2)
        ch &lt;- true
        ch &lt;- true
        close(ch)

        for i := 0; i &lt; cap(ch) +1 ; i++ {
                v, ok := &lt;- ch
                fmt.Println(v, ok)
        }
}
</pre>
<p>在这个例子里，我们创建了一个缓冲区为两个值的 channel，填充缓冲区并且关闭掉它。</p>
<pre class="brush: bash; title: ; notranslate">
true true
true true
false false
</pre>
<p>执行这个程序，首先会向我们展示那两个发送到 channel 的值，然后第三次在 channel 上的尝试会返回 flase 和 false。第一个 false 是 channel 类型的零值，channel 的类型是 chan bool，那么就是 false。第二个表示 channel 的启用状态，当前是 false，表示 channel 被关闭。channel 会一直返回这些值。作为尝试，可以修改这个例子使其从 channel 里取 100 次值看看。</p>
<p>能够检测 channel 是否关闭是一个很有用的特性，可用于对 channel 进行 range 操作，并且当 channel 清空后退出循环。</p>
<pre class="brush: cpp; title: ; notranslate">
package main

import &quot;fmt&quot;

func main() {
        ch := make(chan bool, 2)
        ch &lt;- true
        ch &lt;- true
        close(ch)

        for v := range ch {
                fmt.Println(v) // 被调用两次
        }
}
</pre>
<p>但是其真正的价值是与 select 联合时体现的。先从这个例子开始</p>
<pre class="brush: cpp; title: ; notranslate">
package main

import (
        &quot;fmt&quot;
        &quot;sync&quot;
        &quot;time&quot;
)

func main() {
        finish := make(chan bool)
        var done sync.WaitGroup
        done.Add(1)
        go func() {
                select {
                case &lt;-time.After(1 * time.Hour):
                case &lt;-finish:
                }
                done.Done()
        }()
        t0 := time.Now()
        finish &lt;- true // 发送关闭信号
        done.Wait()    // 等待 goroutine 结束
        fmt.Printf(&quot;Waited %v for goroutine to stop\n&quot;, time.Since(t0))
}
</pre>
<p>在我的系统上，这个程序的运行用了很短的等待延迟，因此很明显 goroutine 不会等待整整一个小时，然后调用 done.Done()</p>
<pre class="brush: bash; title: ; notranslate">
Waited 129.607us for goroutine to stop
1

但是这个程序里存在一些问题。首先是 finish channel 是不带缓冲的，因此如果接收方忘记在其 select 语句中添加 finish，向其发送数据可能会导致阻塞。可以通过对要发送到的 select 块进行封装，以确保不会阻塞，或者设置 finish channel 带有缓冲来解决这个问题。然而，如果有许多 goroutine 都监听在 finish channel 上，那就需要跟踪这个情况，并记得发送正确数量的数据到 finish channel。如果无法控制 goroutine 的创建会很棘手；同时它们也可能是由程序的另一部分来创建的，例如在响应网络请求的时候。

对于这个问题，一个很好的解决方案是利用已经被关闭的 channel 会实时返回这一机制。使用这个特性改写程序，现在包含了 100 个 goroutine，而无需跟踪 goroutine 生成的数量，或调整 finish channel 的大小。

1

在我的系统上，它返回

1
Waited 231.385us for 100 goroutines to stop
</pre>
<p>那么这里发生了什么？当 finish channel 被关闭后，它会立刻返回。那么所有等待接收 time.After channel 或 finish 的 goroutine 的 select 语句就立刻完成了，并且 goroutine 在调用 done.Done() 来减少 WaitGroup 计数器后退出。这个强大的机制在无需知道未知数量的 goroutine 的任何细节而向它们发送信号而成为可能，同时也不用担心死锁。</p>
<p>在进入下一个话题前，再来看一个许多 Go 程序员都喜爱的简单示例。在上面的例子中，从未向 finish channel 发送数据，接受方也将收到的任何数据全部丢弃。因此将程序写成这样就很正常了：</p>
<pre class="brush: cpp; title: ; notranslate">
package main

import (
        &quot;fmt&quot;
        &quot;sync&quot;
        &quot;time&quot;
)

func main() {
        finish := make(chan struct{})
        var done sync.WaitGroup
        done.Add(1)
        go func() {
                select {
                case &lt;-time.After(1 * time.Hour):
                case &lt;-finish:
                }
                done.Done()
        }()
        t0 := time.Now()
        close(finish)
        done.Wait()
        fmt.Printf(&quot;Waited %v for goroutine to stop\n&quot;, time.Since(t0))
}
</pre>
<p>当 close(finish) 依赖于关闭 channel 的消息机制，而没有数据收发时，将 finish 定义为 type chan struct{} 表示 channel 没有任何数据；只对其关闭的特性感兴趣。</p>
<h2>一个 nil channel 永远都是阻塞的</h2>
<p>我想谈的第二个特性正好与已经关闭的 channel 的特性正好相反。一个 nil channel；当 channel 的值尚未进行初始化或赋值为 nil 是，永远都是阻塞的。例如</p>
<pre class="brush: cpp; title: ; notranslate">
package main

func main() {
        var ch chan bool
        ch &lt;- true // 永远阻塞
}
</pre>
<p>当 ch 为 nil 时将会死锁，并且永远都不会发送数据。对于接收是一样的</p>
<pre class="brush: cpp; title: ; notranslate">
package main

func main() {
        var ch chan bool
        &lt;- ch // 永远阻塞
}
</pre>
<p>这看起来似乎并不怎么重要，但是当使用已经关闭的 channel 机制来等待多个 channel 关闭的时候，这确实是一个很有用的特性。例如</p>
<pre class="brush: cpp; title: ; notranslate">
// WaitMany 等待 a 和 b 关闭。
func WaitMany(a, b chan bool) {
        var aclosed, bclosed bool
        for !aclosed || !bclosed {
                select {
                case &lt;-a:
                        aclosed = true
                case &lt;-b:
                        bclosed = true
                }
        }
}
</pre>
<p>WaitMany() 用于等待 channel a 和 b 关闭是个不错的方法，但是有一个问题。假设 channel a 首先被关闭，然后它会立刻返回。但是由于 bclosed 仍然是 false，程序会进入死循环，而让 channel b 永远不会被判定为关闭。</p>
<p>一个解决这个问题的安全的方法是利用 nil channel 的阻塞特性，并且将程序重写如下</p>
<pre class="brush: cpp; title: ; notranslate">
package main

import (
        &quot;fmt&quot;
        &quot;time&quot;
)

func WaitMany(a, b chan bool) {
        for a != nil || b != nil {
                select {
                case &lt;-a:
                        a = nil 
                case &lt;-b:
                        b = nil
                }
        }
}

func main() {
        a, b := make(chan bool), make(chan bool)
        t0 := time.Now()
        go func() {
                close(a)
                close(b)
        }()
        WaitMany(a, b)
        fmt.Printf(&quot;waited %v for WaitMany\n&quot;, time.Since(t0))
}
</pre>
<p>在重写的 WaitMany() 中，一旦接收到一个值，就将 a 或 b 的引用设置为 nil。当 nil channel 是 select 语句的一部分时，它实际上会被忽略，因此，将 a 设置为 nil 便会将其从 select 中移除，仅仅留下 b 等待它被关闭，进而退出循环。</p>
<p>在我的系统上运行得到</p>
<pre class="brush: bash; title: ; notranslate">
waited 54.912us for WaitMany
</pre>
<p>总结来说，正是关闭和 nil <del datetime="2013-05-07T01:10:26+00:00">channle</del>channel 这些特性非常简单，使得它们成为创建高并发的程序的强有力的构件。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2013/05/%e7%bf%bb%e8%af%91%e7%bb%9d%e5%a6%99%e7%9a%84-channel/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>[翻译] Go 1.1 介绍</title>
		<link>http://www.mikespook.com/2013/03/%e7%bf%bb%e8%af%91-go-1-1-%e4%bb%8b%e7%bb%8d/</link>
		<comments>http://www.mikespook.com/2013/03/%e7%bf%bb%e8%af%91-go-1-1-%e4%bb%8b%e7%bb%8d/#comments</comments>
		<pubDate>Thu, 28 Mar 2013 12:32:08 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[翻译]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=1600</guid>
		<description><![CDATA[由于年后工作实在太忙，一直也没写点什么。不过这篇我觉得值得，所以……

原文：https://tip.golang.org/hg/doc/go1.1.html

原文链接我进行了替换，现在指向 tip 大多数应该正确吧。不过如果是 Go 1.1 正式发布半年后，我可不保证了。

<span class="readmore"><a href="http://www.mikespook.com/2013/03/%e7%bf%bb%e8%af%91-go-1-1-%e4%bb%8b%e7%bb%8d/" title="[翻译] Go 1.1 介绍">阅读全文——共9997字</a></span>]]></description>
				<content:encoded><![CDATA[<p>由于年后工作实在太忙，一直也没写点什么。不过这篇我觉得值得，所以……<br />
原文：<a href="https://tip.golang.org/hg/doc/go1.1.html" target="_blank">https://tip.golang.org/hg/doc/go1.1.html</a><br />
原文链接我进行了替换，现在指向 tip 大多数应该正确吧。不过如果是 Go 1.1 正式发布半年后，我可不保证了。<br />
&#8212;&#8212;&#8212;&#8212;翻译分隔线&#8212;&#8212;&#8212;&#8212;</p>
<h1>Go 1.1 介绍</h1>
<p><a title="Go 第一版" href="https://tip.golang.org/doc/go1.html" target="_blank">Go 第一版</a>（简称 Go 1 或 Go 1.0）发布于 2012 年三月，这个版本提供了稳定的 Go 语言和库。其稳定性让全世界 Go 用户社区和相关系统茁壮成长。从那时起，就发布了若干个“关键点”——1.0.1、1.0.2 和 1.0.3。这些点的发布修复了若干已知 bug，但是对于实现本身并没有进行修改。</p>
<p>这个新的发布版，Go 1.1，在保持<a href="https://tip.golang.org/doc/go1compat.html" target="_blank">兼容性</a>的前提下添加了若干重要的（当然，向后兼容）语言变化，而库变化的清单也很长（也向后兼容），还有在编译器、库和运行时环境实现的主要工作。焦点是性能。测试并不是十分精确，但是对于许多测试程序来说都有着重要的、有时是戏剧性的性能改善。我们相信，通过升级 Go 的安装包，并且重新编译，许多用户的程序也能让人体会到这一改进。<br />
<span id="more-1600"></span><br />
这一文档汇总了从 Go 1 到 Go 1.1 的变化。虽然这个发布版有一些极为罕见的错误情况，而当这些情况在发生时必须被处理。在 Go 1.1 下运行，几乎不需要修改任何代码。下面描述了细节；参阅 <a href="https://tip.golang.org/hg/doc/go1.1.html#int" target="_blank">64 位整数</a>和 <a href="https://tip.golang.org/hg/doc/go1.1.html#unicode_literals" target="_blank">Unicode 文字</a>的特别说明。</p>
<h2>语言的变化</h2>
<p>Go 的兼容性文档保证了用 Go 1 语言规范编写的程序仍然可以使用，并且可以会继续被维护。尽管有一些细节的错误情况已经被指出，但是规范本身的完善还是相当有趣的。同时还增加了一些语言的新特性。</p>
<h3>整数除以零</h3>
<p>在 Go 1 中，整数被一个常量零整除会产生一个运行时 panic：</p>
<pre class="brush: cpp; title: ; notranslate">
func f(x int) int {
	return x/0
}
</pre>
<p>在 Go 1.1 中，一个整数被一个常量零整除不是合法的程序，因此这会是一个编译时错误。</p>
<h3>代用的 Unicode 文字</h3>
<p>细化了 string 和 rune 文字的定义，以便将代用部分排除在合法的 Unicode 编码值以外。参阅 <a href="https://tip.golang.org/hg/doc/go1.1.html#unicode" target="_blank">Unicode</a> 部分了解更多信息。</p>
<h3>方法值</h3>
<p>现在 Go 1.1 实现了<a href="https://tip.golang.org/ref/spec#Method_values" target="_blank">方法值</a>，也就是将函数绑定在特定的接收者的值上。例如，有一个 <a href="https://tip.golang.org/pkg/bufio/#Writer" target="_blank">Writer</a> 的值 w，表达式 w.Write，是一个方法值，作为用于向 w 写入的函数；这与函数文法中对 w 进行闭包是等价的：</p>
<pre class="brush: cpp; title: ; notranslate">
func (p []byte) (n int, err error) {
	return w.Write(p)
}
</pre>
<p>方法值与方法表达式是不同的，方法表达式从方法中利用指定的类型构造了一个函数；方法表达式 (*bufio.Writer).Write 与第一个参数类型指定为 (*bufio.Writer) 的函数等价：</p>
<pre class="brush: cpp; title: ; notranslate">
func (w *bufio.Writer, p []byte) (n int, err error) {
	return w.Write(p)
}
</pre>
<p><em>更新</em>：已有代码不受影响；这个变动是严格的向后兼容。</p>
<h3>Return requirements</h3>
<p>在 Go 1.1 之前，一个函数返回一个值必须明确的在函数结束时“return”或调用 panic；这是一个让程序明确函数的概念的简单的途径。但是，显然有许多情况最后的“return”没有必要，例如, 一个只有死循环“for”的函数。</p>
<p>在 Go 1.1 中，关于最后的“return”语句的规则更加宽松。它引入了一个<a href="https://tip.golang.org/ref/spec/#Terminating_statements" target="_blank">终止语句</a>的概念，它保证了在函数中这个语句总是最后被执行。例如在没有条件的“for”循环中，也没有“if-else”语句用来在中间通过“return”结束。那么函数的最后一个语句可以在语法上被认为是终止语句，而不需要最后的“return”语句。</p>
<p>注意这个规则纯粹是语法上的：它并不关注代码中的值，因此也没有复杂的分析。</p>
<p><em>更新</em>：这个变动是向后兼容的，不过有着多余“return”语句或调用 panic 的已有代码可能需要手工处理一下。这些代码可用 <code>go vet</code> 来标识。</p>
<h2>实现和工具的变更</h2>
<h3>命令行参数解析</h3>
<p>在 gc 工具链中，编译器和链接器现在使用与 Go 的 <code>flag</code> 包一致的命令行参数解析规则，而与传统的 Unix 参数解析背道而驰。这可能会对直接调用工具的脚本产生影响。例如，<code>go tool 6c -Fw -Dfoo</code> 现在必须写为 <code>go tool 6c -F -w -D foo</code>。</p>
<h3>在 64 位平台上的整数大小</h3>
<p>该语言允许根据具体实现选择 int 类型和 uint 类型是 32 或 64 位的。之前 Go 的实现是在所有系统上都让 int 和 uint 是 32 位的。现在 gc 和 gccgo 的实现都让 int 和 uint 在如 AMD64/x86-64 这样的平台上是 64 位的。抛开别的不说，单这个就使得 slice 在 64 位平台上可以分配超过 20 亿的元素。</p>
<p><em>更新</em>：大多数程序不会受到这个的影响。 由于 Go 不允许不同<a href="https://tip.golang.org/ref/spec/#Numeric_types" target="_blank">数字类型</a>之间的隐式转换，不会有程序在编译时报错。然而，那些隐式假设 int 是 32 位的程序，在行为上可能发生变化。例如，这个程序在 64 位系统中会打印正数，在 32 位系统中会打印负数：</p>
<pre class="brush: cpp; title: ; notranslate">
x := ^uint32(0) // x is 0xffffffff
i := int(x)     // i is -1 on 32-bit systems, 0xffffffff on 64-bit
fmt.Println(i)
</pre>
<p>要保留 32 位的符号（在所有系统上都是 -1）应该用下面的具有可移植性的代码代替：</p>
<pre class="brush: cpp; title: ; notranslate">
i := int(int32(x))
</pre>
<h3>Unicode</h3>
<p>为了能够表达 UTF-16 中超过 65535 的编码值，Unicode 定义了代用部分，一个仅用于组装更大的值的编码值范围，且仅在 UTF-16 中。在这个代用范围内的编码值如果用于其他任何情况都是非法的，如作为 UTF-8 编码，或作为独立的 UTF-16 编码。例如在遇到将一个 rune 转换成 UTF-8 时，它被当作一个编码错误对待，并产生一个替代的 rune，<a href="https://tip.golang.org/pkg/unicode/utf8/#RuneError" target="_blank">utf8.RuneError</a>, U+FFFD。</p>
<p>这个程序，</p>
<pre class="brush: cpp; title: ; notranslate">
import &quot;fmt&quot;

func main() {
    fmt.Printf(&quot;%+q\n&quot;, string(0xD800))
}
</pre>
<p>在 Go 1.0 中打印“\ud800”，但在 Go 1.1 中打印“\ufffd”。</p>
<p>半个代用 Unicode 值现在在 rune 和 string 常量中都是非法的，因此如“\ud800”和“\ud800”的常量现在会被编译器拒绝。当编写为独立的 UTF-8 编码的字节时，这样字符串还是可以被创建的，例如“\xed\xa0\x80”。然而，当这个字符串被作为一个 rune 序列解码时，比如在 range 循环中，它只会生成 utf8.RuneError 值。</p>
<p>Unicode 字节顺序让 U+FFFE 和 U+FEFF 在 UTF-8 编码下可以作为 Go 源码的第一个字符出现。虽然在字节顺序未设定的 UTF-8 编码中，它是完全不必要的，不过有些编辑器会将其作为“魔法数值”添加进去，用来标识一个 UTF-8 编码的文件。</p>
<p><em>更新</em>：大多数程序不会受到代用变更的影响。基于旧的行为的程序应当通过修改来避免问题。字节顺序标识的变更是严格向后兼容的。</p>
<h3>gc 汇编</h3>
<p>基于如 <a href="https://tip.golang.org/hg/doc/go1.1.html#int" target="_blank">int</a> 到 64 位和其他一些变化，在 gc 工具链的函数参数的栈布局发生了变化。使用汇编编写的函数至少需要一个 frame 指针偏移量。</p>
<p><em>更新</em>：现在 <code>go vet</code> 命令可以检查用汇编实现的函数是否匹配 Go 的函数原型。</p>
<h3>go 命令的变化</h3>
<p>为了让新的 Go 用户获得更好的体验，<a href="https://tip.golang.org/cmd/go/" target="_blank">go</a> 命令做了若干改动。</p>
<p>首先，当编译、测试或运行 Go 代码的时候，go 命令会给出更多的错误信息细节，当一个包无法被定位时，会列出搜索的路径清单。</p>
<pre class="brush: bash; title: ; notranslate">
$ go build foo/quxx
can't load package: package foo/quxx: cannot find package &quot;foo/quxx&quot; in any of:
        /home/you/go/src/pkg/foo/quxx (from $GOROOT)
        /home/you/src/foo/quxx (from $GOPATH)
</pre>
<p>其次，<code>go get</code> 命令不再允许下载包源码时，将 $GOROOT 作为默认的目的路径。要使用 <code>go get</code> 命令，必须有一个合法的 $GOPATH。</p>
<pre class="brush: bash; title: ; notranslate">
$ GOPATH= go get code.google.com/p/foo/quxx
package code.google.com/p/foo/quxx: cannot download, $GOPATH not set. For more details see: go help gopath
</pre>
<p>最后，作为前面变化的结果，<code>go get</code> 命令会在 $GOPATH 和 $GOROOT 设置为相同值的时候报错。</p>
<pre class="brush: bash; title: ; notranslate">
$ GOPATH=$GOROOT go get code.google.com/p/foo/quxx
warning: GOPATH set to GOROOT (/home/User/go) has no effect
package code.google.com/p/foo/quxx: cannot download, $GOPATH must not be set to $GOROOT. For more details see: go help gopath
</pre>
<h3>go test 命令的变化</h3>
<p><code>go test</code> 命令在进行性能测试时不再删除二进制内容，以便更容易的分析性能测试。实现上是在运行的时候设置了 -c 参数。</p>
<pre class="brush: bash; title: ; notranslate">
$ go test -cpuprofile cpuprof.out mypackage
</pre>
<p>在 <code>go test</code> 运行之后，mypackage.test 将会留在目录中。</p>
<p><code>go test</code> 命令现在可以报告 goroutine 在哪里阻塞的测试信息，也就是说，它们在哪一直等着某个事件，例如一个 channel 通讯之类的。当用 <code>-blockprofile</code> 开启 <code>go test</code> 的阻塞测试时，就会展示这些信息。 运行 <code>go help test</code> 了解更多信息。</p>
<h3>go fix 命令的变化</h3>
<p><a href="https://tip.golang.org/cmd/fix/" target="_blank">fix</a> 命令通常以 <code>go fix</code> 执行，不再提供从 Go1 之前的版本升级到 Go 1 API 的功能。如果要升级 Go 1 之前的代码到 Go 1.1，首先应当使用 Go 1.0 的工具链，将代码转化到 Go 1.0。</p>
<h2>性能</h2>
<p>用 Go 1.1 的 gc 工具集编译出来的代码的性能对于大多数 Go 程序来说应当有显著的提升。一般来说，与 Go 1.0 相比，大约有 30%-40% 的提升，有时甚至更高，当然也会比这个值低，甚至没有提升。对于工具和库来说，有太多的小的性能驱使的改动，以至于无法将它们全部列在这里。不过下面的主要变更还是有必要留意的：</p>
<ul>
<li>gc 编译器在大多数情况下都会生成较好的代码，尤其是在 32 位 Intel 架构下的浮点值。</li>
<li>gc 编译器做了更多的内连，包括在运行时的一些操作，例如 <a href="https://tip.golang.org/pkg/builtin/#append" target="_blank">append</a> 和接口转换。</li>
<li>Go 的 map 有了新的实现，在内存复制和 CPU 时间上有了重大的改进。</li>
<li>垃圾回收实现了更多的并行，这可以降低在多 CPU 环境下运行的程序的延迟。</li>
<li>垃圾回收同时也更加精准，这增加了一点 CPU 时间开销，但是极大的降低了堆的大小，尤其是在 32 位的架构下。</li>
<li>通过紧密结合运行时和网络库，在网络操作时需要的上下文切换会更少。</li>
</ul>
<h2>标准库的变化</h2>
<h3>bufio.Scanner</h3>
<p>在 <a href="https://tip.golang.org/pkg/bufio/" target="_blank">bufio</a> 包中有多种方式获取文本输入，<a href="https://tip.golang.org/pkg/bufio/#Reader.ReadBytes" target="_blank">ReadBytes</a>、<a href="https://tip.golang.org/pkg/bufio/#Reader.ReadString" target="_blank">ReadString</a> 和特别的 <a href="https://tip.golang.org/pkg/bufio/#Reader.ReadLine" target="_blank">ReadLine</a>，对于简单的目的这些都有些过于复杂了。在 Go 1.1 中，添加了一个新类型，<a href="https://tip.golang.org/pkg/bufio/#Scanner" target="_blank">Scanner</a>，以便更容易的处理如按行读取输入序列或空格分隔的词等，这类简单的任务。它终结了如输入一个很长的有问题的行这样的输入错误，并且提供了简单的默认行为：基于行的输入，每行都剔除分隔标识。这里的代码展示来一次输入一行：</p>
<pre class="brush: cpp; title: ; notranslate">
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
    fmt.Println(scanner.Text()) // Println will add back the final '\n'
}
if err := scanner.Err(); err != nil {
    fmt.Fprintln(os.Stderr, &quot;reading standard input:&quot;, err)
}
</pre>
<p>输入的行为可以通过一个函数控制，来控制输入的每个部分（参阅 <a href="https://tip.golang.org/pkg/bufio/#SplitFunc" target="_blank">SplitFunc</a> 的文档），但是对于复杂的问题或持续传递错误的，可能还是需要原有接口。</p>
<h3>net</h3>
<p>在 <a href="https://tip.golang.org/pkg/net/" target="_blank">net</a> 包中的协议特定的解析器之前对传递入的网络名很宽松。虽然文档明确指出对于 <a href="https://tip.golang.org/pkg/net/#ResolveTCPAddr" target="_blank">ResolveTCPAddr</a> 合法的网络名只有“tcp”，“tcp4”和“tcp6”，Go 1.0 的实现对于任何字符串都会接受。而 Go 1.1 的实现，如果网络名不在这些字符串中，就会返回一个错误。这对于其他协议特定的解析器 <a href="https://tip.golang.org/pkg/net/#ResolveIPAddr" target="_blank">ResolveIPAddr</a>、<a href="https://tip.golang.org/pkg/net/#ResolveUDPAddr" target="_blank">ResolveUDPAddr</a> 和 <a href="https://tip.golang.org/pkg/net/#ResolveUnixAddr" target="_blank">ResolveUnixAddr</a> 也是一样。</p>
<p>之前的的实现，<a href="https://tip.golang.org/pkg/net/#ListenUnixgram" target="_blank">ListenUnixgram</a> 返回一个 <a href="https://tip.golang.org/pkg/net/#UDPConn" target="_blank">UDPConn</a> 作为接收连接的端点。在 Go 1.1 的实现里，用 <a href="https://tip.golang.org/pkg/net/#UnixConn" target="_blank">UnixConn</a> 来代替，这允许用它的 <a href="https://tip.golang.org/pkg/net/#UnixConn.ReadFrom" target="_blank">ReadFrom</a> 和 <a href="https://tip.golang.org/pkg/net/#UnixConn.WriteTo" target="_blank">WriteTo</a> 方法读写。</p>
<p>数据结构 IPAddr、TCPAddr 和 UDPAddr 添加了一个叫做 Zone 的新字符串字段。由于新的字段，使用没有标签的复合文法（例如 net.TCPAddr{ip, port}）的代码代替有标签的文法（net.TCPAddr{IP: ip, Port: port}）会出错。Go 1 的兼容性规则允许这个变化：客户端代码必须使用标签化的文法以避免这种破坏。</p>
<p><em>更新</em>：为了修正由于新的结构体字段带来的破坏，<code>go fix</code> 将会重写这些类型的代码以添加标签。更通用的是，<code>go vet</code> 将会标识出所有应当使用字段标签的复合文法。</p>
<h3>reflect</h3>
<p><a href="https://tip.golang.org/pkg/reflect/" target="_blank">reflect</a> 包有若干重大改进。</p>
<p>现在用 reflect 包返回一个“select”语句是可能的；参阅 <a href="https://tip.golang.org/pkg/reflect/#Select" target="_blank">Select</a> 和 <a href="https://tip.golang.org/pkg/reflect/#SelectCase" target="_blank">SelectCase</a> 了解更多细节。</p>
<p>新的方法 <a href="https://tip.golang.org/pkg/reflect/#Value.Convert" target="_blank">Value.Convert</a>（或 <a href="https://tip.golang.org/pkg/reflect/#Type" target="_blank">Type.ConvertibleTo</a>）提供了对一个 <a href="https://tip.golang.org/pkg/reflect/#Value" target="_blank">Value</a> 进行 Go 的转换和类型断言操作（或者是检测这种可能性）的函数方式。</p>
<p>新的函数 <a href="https://tip.golang.org/pkg/reflect/#MakeFunc" target="_blank">MakeFunc</a> 创建了一个使得在已有 <a href="https://tip.golang.org/pkg/reflect/#Value" target="_blank">Value</a> 上调用函数更加容易的封装函数，可以用于标准的 Go 参数的转换，例如将一个 int 传递为 interface{}。</p>
<p>最后，新的函数 <a href="https://tip.golang.org/pkg/reflect/#ChanOf" target="_blank">ChanOf</a>、<a href="https://tip.golang.org/pkg/reflect/#MapOf" target="_blank">MapOf</a> 和 <a href="https://tip.golang.org/pkg/reflect/#SliceOf" target="_blank">SliceOf</a> 可以从已有类型中构造新 <a href="https://tip.golang.org/pkg/reflect/#Type" target="_blank">Type</a>，例如在仅提供 T 的情况下构造 []T。</p>
<h3>time</h3>
<p>之前的 <a href="https://tip.golang.org/pkg/time/" target="_blank">time</a> 包在 FreeBSD、Linux、NetBSD、OS X 和 OpenBSD 上精确到微秒。Go 1.1 在这些操作系统上的实现可以精确到纳秒。程序用微妙的精确度向外部写入再读出，若覆盖掉原有值的话，将会产生精度的损失。<a href="https://tip.golang.org/pkg/time/#Time" target="_blank">Time</a> 有两个新方法，<a href="https://tip.golang.org/pkg/time/#Time.Round" target="_blank">Round</a> 和 <a href="https://tip.golang.org/pkg/time/#Time.Truncate" target="_blank">Truncate</a>，可以用来在向外部存储写入前，从时间里去除精度。</p>
<p>新方法 <a href="https://tip.golang.org/pkg/time/#Time.YearDay" target="_blank">YearDay</a> 返回指定 time 值在一年中的某天的唯一整数序数。</p>
<p><a href="https://tip.golang.org/pkg/time/#Timer" target="_blank">Timer</a> 类型有一个新方法 <a href="https://tip.golang.org/pkg/time/#Timer.Reset" target="_blank">Reset</a>，让定时器在指定的间隔后过期。</p>
<p>最后，一个新函数 <a href="https://tip.golang.org/pkg/time/#ParseInLocation" target="_blank">ParseInLocation</a> 与已有的 <a href="https://tip.golang.org/pkg/time/#Parse" target="_blank">Parse</a> 类似，不过会忽略解析的字符串中的时区信息，而使用传入的位置（时区）来解析时间。这个函数解决了时间 API 中常见的混乱情况。</p>
<p><em>更新</em>：对于那些使用更低精度的外部格式来读写时间的代码，应当修改用新的方法。</p>
<h3>Exp 旧的代码树移动到 go.exp 和 go.text 子版本库</h3>
<p>为了让使用二进制发布版的用户在需要的时候访问更加容易，不包含在二进制发布版的 exp 和旧的源码树被移动到新的子版本库 code.google.com/p/go.exp。举例来说，如果要访问 ssa 包，执行</p>
<pre class="brush: bash; title: ; notranslate">
$ go get code.google.com/p/go.exp/ssa
</pre>
<p>然后在 Go 代码中，</p>
<pre class="brush: cpp; title: ; notranslate">
import &quot;code.google.com/p/go.exp/ssa&quot;
</pre>
<p>旧的包 exp/norm 也迁移到了新的版本库 go.text，这里包含了正在开发的 Unicode API 和其他文本相关的包。</p>
<h3>库的微小变更</h3>
<p>下面的清单列出了库的微小改动，大多数是一些增强。对于每个变更可参阅包相关的文档了解更多信息。</p>
<ul>
<li><a href="https://tip.golang.org/pkg/bytes/"><code>bytes</code></a> 包有两个新函数，<a href="https://tip.golang.org/pkg/bytes/#TrimPrefix"><code>TrimPrefix</code></a> 和 <code><a href="https://tip.golang.org/pkg/bytes/#TrimSuffix">TrimSuffix</a>，含义不言而喻。同样，</code><a href="https://tip.golang.org/pkg/bytes/#Buffer"><code>Buffer</code></a> 类型有一个新方法 <code><a href="https://tip.golang.org/pkg/bytes/#Buffer.Grow">Grow</a>，提供了一些控制缓存内部内存分配的能力。最后，</code><a href="https://tip.golang.org/pkg/bytes/#Reader"><code>Reader</code></a> 类型现在有 <a href="https://tip.golang.org/pkg/strings/#Reader.WriteTo"><code>WriteTo</code></a> 方法，因此它也实现了 <a href="https://tip.golang.org/pkg/io/#WriterTo"><code>io.WriterTo</code></a> 接口。</li>
<li><a href="https://tip.golang.org/pkg/crypto/hmac/"><code>crypto/hmac</code></a> 有一个新函数，<code><a href="https://tip.golang.org/pkg/crypto/hmac/#Equal">Equal</a>，来比较两个 </code>MAC。</li>
<li><a href="https://tip.golang.org/pkg/crypto/x509/"><code>crypto/x509</code></a> 包现在支持 PEM 块（实例参阅 <code><a href="https://tip.golang.org/pkg/crypto/x509/#DecryptPEMBlock">DecryptPEMBlock</a>），以及一个新的函数</code> <a href="https://tip.golang.org/pkg/crypto/x509/#ParseECPrivateKey"><code>ParseECPrivateKey</code></a> 用来解析椭圆曲线私钥。</li>
<li><a href="https://tip.golang.org/pkg/database/sql/"><code>database/sql</code></a> 包在 <code><a href="https://tip.golang.org/pkg/database/sql/#DB">DB</a> 类型上</code>有了新的 <a href="https://tip.golang.org/pkg/database/sql/#DB.Ping"><code>Ping</code></a> 方法用于检测连接的健康状况。</li>
<li><a href="https://tip.golang.org/pkg/database/sql/driver/"><code>database/sql/driver</code></a> 有了一个新的 <a href="https://tip.golang.org/pkg/database/sql/driver/#Queryer"><code>Queryer</code></a> 接口，这样 <a href="https://tip.golang.org/pkg/database/sql/driver/#Conn"><code>Conn</code></a> 可以通过实现该接口对性能做一些改进。</li>
<li><a href="https://tip.golang.org/pkg/encoding/json/"><code>encoding/json</code></a> 包的 <a href="https://tip.golang.org/pkg/encoding/json/#Decoder"><code>Decoder</code></a> 有了新方法 <code><a href="https://tip.golang.org/pkg/encoding/json/#Decoder.Buffered">Buffered</a>，以提供访问在其缓存内剩余数据的功能，同样新方法</code> <a href="https://tip.golang.org/pkg/encoding/json/#Decoder.UseNumber"><code>UseNumber</code></a> 会将一个值解码为其实是字符串的新类型 <code><a href="https://tip.golang.org/pkg/encoding/json/#Number">Number</a>，而不是</code><code>一个 </code>float64。</li>
<li><a href="https://tip.golang.org/pkg/encoding/xml/"><code>encoding/xml</code></a> 包有了一个新函数 <code><a href="https://tip.golang.org/pkg/encoding/xml/#EscapeText">EscapeText</a>，用于输出</code> escape 过的 XML，<code><a href="https://tip.golang.org/pkg/encoding/xml/#Encoder">Encoder</a> 的方法</code> <code><a href="https://tip.golang.org/pkg/encoding/xml/#Encoder.Indent">Inden</a> 则专门用于输出带缩进的格式。</code></li>
<li>在 <a href="https://tip.golang.org/pkg/go/ast/"><code>go/ast</code></a> 包中，新类型 <a href="https://tip.golang.org/pkg/go/ast/#CommentMap"><code>CommentMap</code></a> 和其关联的方法使得从 Go 程序中分离和处理注释变得更加容易。</li>
<li>在 <a href="https://tip.golang.org/pkg/go/doc/"><code>go/doc</code></a> 包中，解析器现在可以更好的跟踪一些如 TODO 这样的标识，<a href="https://tip.golang.org/cmd/godoc/"><code>godoc</code></a> 命令可以根据 -notes 参数选择过滤或呈现这些信息。</li>
<li>一个新的包，<code><a href="https://tip.golang.org/pkg/go/format/">go/format</a>，为程序提供了更加方便的方式来获得 gofmt 的格式化的能力。它有两个函数，</code><a href="https://tip.golang.org/pkg/go/format/#Node"><code>Node</code></a> 用来格式化 Go 的解析 <code><a href="https://tip.golang.org/pkg/go/ast/#Node">Node</a>，而</code> <a href="https://tip.golang.org/pkg/go/format/#Source"><code>Source</code></a> 用来格式化 Go 的源代码。</li>
<li><code><a href="https://tip.golang.org/pkg/html/template/">html/template</a> 包中</code>没有文档并且只部分实现的“noescape”特性被移除；那些依赖它的程序会被破坏。</li>
<li><a href="https://tip.golang.org/pkg/io/"><code>io</code></a> 包现在将 <a href="https://tip.golang.org/pkg/io/#ByteWriter"><code>io.ByteWriter</code></a> 接口导出，用以满足一次写一个字节这样的常见功能。</li>
<li><a href="https://tip.golang.org/pkg/log/syslog/"><code>log/syslog</code></a> 包现在更好的提供了系统特定的日志功能。</li>
<li><a href="https://tip.golang.org/pkg/math/big/"><code>math/big</code></a> 包的 <a href="https://tip.golang.org/pkg/math/big/#Int"><code>Int</code></a> 类型现在有了方法 <a href="https://tip.golang.org/pkg/math/big/#Int.MarshalJSON"><code>MarshalJSON</code></a> 和 <a href="https://tip.golang.org/pkg/math/big/#Int.UnmarshalJSON"><code>UnmarshalJSON</code></a> 用以转换到或从 JSON 格式转换。同样，<a href="https://tip.golang.org/pkg/math/big/#Int"><code>Int</code></a> 现在可以通过 <a href="https://tip.golang.org/pkg/math/big/#Int.Uint64"><code>Uint64</code></a> 和 <code><a href="https://tip.golang.org/pkg/math/big/#Int.SetUint64">SetUint64</a> </code>直接转换到 uint64 或从 uint64 转换，而 <a href="https://tip.golang.org/pkg/math/big/#Rat"><code>Rat</code></a> 通过 <a href="https://tip.golang.org/pkg/math/big/#Rat.Float64"><code>Float64</code></a> and <a href="https://tip.golang.org/pkg/math/big/#Rat.SetFloat64"><code>SetFloat64</code></a>.</li>
<li><a href="https://tip.golang.org/pkg/mime/multipart/"><code>mime/multipart</code></a> 包的 <code><a href="https://tip.golang.org/pkg/mime/multipart/#Writer">Writer</a> 有了新的方法，</code><code><a href="https://tip.golang.org/pkg/mime/multipart/#Writer.SetBoundary">SetBoundary</a> 用来定义包输出的边界分隔。</code></li>
<li><a href="https://tip.golang.org/pkg/net/"><code>net</code></a> 包的 <a href="https://tip.golang.org/pkg/net/#ListenUnixgram"><code>ListenUnixgram</code></a> 函数修改了返回值的类型：现在它返回 <a href="https://tip.golang.org/pkg/net/#UnixConn"><code>UnixConn</code></a> 而不是 <code><a href="https://tip.golang.org/pkg/net/#UDPConn">UDPConn</a>，这明显是 Go 1.0 的一个错误。因此这个 API 的变更修复了一个 bug，这符合 Go 1 的兼容性规则。</code></li>
<li><a href="https://tip.golang.org/pkg/net/"><code>net</code></a> 包包含了一个新函数，<code><a href="https://tip.golang.org/pkg/net/#DialOpt">DialOpt</a>，为</code> <code><a href="https://tip.golang.org/pkg/net/#Dial">Dial</a> 增加选项。每个选项都由新的接口</code> <a href="https://tip.golang.org/pkg/net/#DialOption"><code>DialOption</code></a> 体现。新的函数 <code><a href="https://tip.golang.org/pkg/net/#Deadline">Deadline</a>、</code><code><a href="https://tip.golang.org/pkg/net/#Timeout">Timeout</a>、</code><code><a href="https://tip.golang.org/pkg/net/#Network">Network</a> 和</code> <a href="https://tip.golang.org/pkg/net/#LocalAddress"><code>LocalAddress</code></a> 然会一个 <code>DialOption。</code></li>
<li><a href="https://tip.golang.org/pkg/net/"><code>net</code></a> 增加了带区域验证的本地 IPv6 地址的支持，如 <code>fe80::1%lo0。地址结构体</code> <code><a href="https://tip.golang.org/pkg/net/#IPAddr">IPAddr</a>、</code><code><a href="https://tip.golang.org/pkg/net/#UDPAddr">UDPAddr</a> 和 </code><a href="https://tip.golang.org/pkg/net/#TCPAddr"><code>TCPAddr</code></a> 将区域信息记录在一个新的字段里，那些需要字符串格式作为地址的函数，例如 <code><a href="https://tip.golang.org/pkg/net/#Dial">Dial</a>、</code><code><a href="https://tip.golang.org/pkg/net/#ResolveIPAddr">ResolveIPAddr</a>、</code><code><a href="https://tip.golang.org/pkg/net/#ResolveUDPAddr">ResolveUDPAddr</a> 和</code> <code><a href="https://tip.golang.org/pkg/net/#ResolveTCPAddr">ResolveTCPAddr</a> 现在接受带区域验证的格式。</code></li>
<li><a href="https://tip.golang.org/pkg/net/"><code>net</code></a> 包添加了 <a href="https://tip.golang.org/pkg/net/#LookupNS"><code>LookupNS</code></a> 作为解析函数。<code>LookupNS</code> 根据主机名返回一个 <a href="https://tip.golang.org/pkg/net/#NS">NS records</a> 。</li>
<li><a href="https://tip.golang.org/pkg/net/"><code>net</code></a> 包向 <a href="https://tip.golang.org/pkg/net/#IPConn"><code>IPConn</code></a>（<a href="https://tip.golang.org/pkg/net/#IPConn.ReadMsgIP"><code>ReadMsgIP</code></a> 和 <code><a href="https://tip.golang.org/pkg/net/#IPConn.WriteMsgIP">WriteMsgIP</a>）和</code> <a href="https://tip.golang.org/pkg/net/#UDPConn"><code>UDPConn</code></a>（<a href="https://tip.golang.org/pkg/net/#UDPConn.ReadMsgUDP"><code>ReadMsgUDP</code></a> 和 <a href="https://tip.golang.org/pkg/net/#UDPConn.WriteMsgUDP"><code>WriteMsgUDP</code></a>）加了指定协议的读写方法。还有个 <code><a href="https://tip.golang.org/pkg/net/#PacketConn">PacketConn</a> 的特别版本的</code> <code>ReadFrom</code> 和 <code>WriteTo</code> 方法，提供了访问数据包的带外数据的能力。</li>
<li><a href="https://tip.golang.org/pkg/net/"><code>net</code></a> 为 <a href="https://tip.golang.org/pkg/net/#UnixConn"><code>UnixConn</code></a> 添加了方法以便半关闭连接（<a href="https://tip.golang.org/pkg/net/#UnixConn.CloseRead"><code>CloseRead</code></a> 和 <code><a href="https://tip.golang.org/pkg/net/#UnixConn.CloseWrite">CloseWrite</a>），这与</code> <code><a href="https://tip.golang.org/pkg/net/#TCPConn">TCPConn</a> 的已有方法匹配。</code></li>
<li><a href="https://tip.golang.org/pkg/net/http/"><code>net/http</code></a> 包包含了若干新增。<a href="https://tip.golang.org/pkg/net/http/#ParseTime"><code>ParseTime</code></a> 解析一个时间字符串，会尝试若干种常见的 HTTP 时间格式。<code><a href="https://tip.golang.org/pkg/net/http/#Request">Request</a> 的 </code><a href="https://tip.golang.org/pkg/net/http/#Request.PostFormValue">PostFormValue</a> 方法与 <code><a href="https://tip.golang.org/pkg/net/http/#Request.FormValue">FormValue</a> 类似，不过忽略了 URL 参数。</code><a href="https://tip.golang.org/pkg/net/http/#CloseNotifier"><code>CloseNotifier</code></a> 接口提供了服务器端处理程序发现客户端断开连接的一种机制。<code>ServeMux</code> 类型现在有了 <code><a href="https://tip.golang.org/pkg/net/http/#ServeMux.Handler">Handler</a> </code>方法来访问 <code>Handler</code> 的路径而不需要执行它。<code>Transport</code> 现在可以通过 <code><a href="https://tip.golang.org/pkg/net/http/#Transport.CancelRequest">CancelRequest</a> </code>取消一个正在进行的请求。最后， 当 <code><a href="https://tip.golang.org/pkg/net/http/#Response">Response.Body</a> 在完全被处理之前被关闭的话，T</code>ransport 现在会对关闭 TCP 连接保持更乐观的态度。</li>
<li>新的 <a href="https://tip.golang.org/pkg/net/http/cookiejar/"><code>net/http/cookiejar</code></a> 包提供了基础的管理 HTTP cookie 的功能。</li>
<li><a href="https://tip.golang.org/pkg/net/mail/"><code>net/mail</code></a> 包有了两个新函数，<a href="https://tip.golang.org/pkg/net/mail/#ParseAddress"><code>ParseAddress</code></a> 和 <code><a href="https://tip.golang.org/pkg/net/mail/#ParseAddressList">ParseAddressList</a>，来解析 R</code>FC 5322 格式化的地址到 <a href="https://tip.golang.org/pkg/net/mail/#Address"><code>Address</code></a> 结构体。</li>
<li><a href="https://tip.golang.org/pkg/net/smtp/"><code>net/smtp</code></a> 包的 <a href="https://tip.golang.org/pkg/net/smtp/#Client"><code>Client</code></a> 类型有了一个新的方法，<a href="https://tip.golang.org/pkg/net/smtp/#Client.Hello"><code>Hello</code></a>，用于向服务器发送 <code>HELO</code> 或 <code>EHLO</code> 消息。</li>
<li><a href="https://tip.golang.org/pkg/net/textproto/"><code>net/textproto</code></a> 有两个新函数，<a href="https://tip.golang.org/pkg/net/textproto/#TrimBytes"><code>TrimBytes</code></a> 和 <code><a href="https://tip.golang.org/pkg/net/textproto/#TrimString">TrimString</a>，用来仅在 </code>ASCII 下进行前后空符的切除。</li>
<li>新方法 <a href="https://tip.golang.org/pkg/os/#FileMode.IsRegular"><code>os.FileMode.IsRegular</code></a> 让了解一个文件是否是普通文件变得更加简单。</li>
<li><a href="https://tip.golang.org/pkg/image/jpeg/"><code>image/jpeg</code></a> 现在可以读取预加载 JPEG 文件，并且处理某些二次取样配置信息。</li>
<li><a href="https://tip.golang.org/pkg/regexp/"><code>regexp</code></a> 包现在通过 <code><a href="https://tip.golang.org/pkg/regexp/#Regexp.Longest">Regexp.Longest</a> 可以</code>支持 Unix 原生的最左最长匹配，而 <a href="https://tip.golang.org/pkg/regexp/#Regexp.Split"><code>Regexp.Split</code></a> 使用正则表达式定义的分离器将字符串分解成组的。</li>
<li><a href="https://tip.golang.org/pkg/runtime/debug/"><code>runtime/debug</code></a> 有三个关于内存使用的新函数。<a href="https://tip.golang.org/pkg/runtime/debug/#FreeOSMemory"><code>FreeOSMemory</code></a> 函数触发垃圾回收，并尝试将未使用的内存退回操作系统；<a href="https://tip.golang.org/pkg/runtime/debug/#ReadGCStats"><code>ReadGCStats</code></a> 获得控制器的统计信息；而 <a href="https://tip.golang.org/pkg/runtime/debug/#SetGCPercent"><code>SetGCPercent</code></a> 提供了一个可编程的途径来控制控制器执行频率，包括永远禁止其执行。</li>
<li><a href="https://tip.golang.org/pkg/sort/"><code>sort</code></a> 包有一个新函数，<code><a href="https://tip.golang.org/pkg/sort/#Reverse">Reverse</a>。作为调用</code> <a href="https://tip.golang.org/pkg/sort/#Sort"><code>sort.Sort</code></a> 的参数的包裹，通过调用 <code>Reverse</code> 可以让排序结果反续。</li>
<li><a href="https://tip.golang.org/pkg/strings/"><code>strings</code></a> 包有两个新函数，<a href="https://tip.golang.org/pkg/strings/#TrimPrefix"><code>TrimPrefix</code></a> 和 <a href="https://tip.golang.org/pkg/strings/#TrimSuffix"><code>TrimSuffix</code></a> 含义不言而喻，还有 <a href="https://tip.golang.org/pkg/strings/#Reader.WriteTo"><code>Reader.WriteTo</code></a> 方法，因此 <a href="https://tip.golang.org/pkg/strings/#Reader"><code>Reader</code></a> 现在实现了 <a href="https://tip.golang.org/pkg/io/#WriterTo"><code>io.WriterTo</code></a> 接口。</li>
<li><a href="https://tip.golang.org/pkg/syscall/"><code>syscall</code></a> 包的有许多更新，包括对每个支持的操作系统的系统调用进行加固。</li>
<li><a href="https://tip.golang.org/pkg/testing/"><code>testing</code></a> 包现在可以在性能测试中使用 <a href="https://tip.golang.org/pkg/testing/#AllocsPerRun"><code>AllocsPerRun</code></a> 函数和 <code><a href="https://tip.golang.org/pkg/testing/#BenchmarkResult">BenchmarkResult</a> 的</code> <a href="https://tip.golang.org/pkg/testing/#BenchmarkResult.AllocsPerOp"><code>AllocsPerOp</code></a> 方法自动生成内存分配统计。还有 <a href="https://tip.golang.org/pkg/testing/#Verbose"><code>Verbose</code></a> 函数来检测 -v 的命令行参数状态，和 <a href="https://tip.golang.org/pkg/testing/#B"><code>testing.B</code></a> 和 <code><a href="https://tip.golang.org/pkg/testing/#T">testing.T</a> 的</code>新方法 <a href="https://tip.golang.org/pkg/testing/#B.Skip"><code>Skip</code></a> 来简单跳过一些不必要的测试。</li>
<li>在 <a href="https://tip.golang.org/pkg/text/template/"><code>text/template</code></a> 和 <a href="https://tip.golang.org/pkg/html/template/"><code>html/template</code></a> 包中，模板现在可以用圆括号来对字符序列分组，这简化了创建复杂的字符序列的过程。TODO：链接到一个实例。同时，作为新的解析器的一部分，<a href="https://tip.golang.org/pkg/text/template/parse/#Node"><code>Node</code></a> 接口有两个方法用来提供更好的错误报告。这同样遵循 Go 1 兼容性规则，由于这个接口被明确期望只有 <a href="https://tip.golang.org/pkg/text/template/"><code>text/template</code></a> 和 <code><a href="https://tip.golang.org/pkg/html/template/">html/template</a> 包使用，而其安全机制保证了这点，所以应当</code>没有代码会受到影响。</li>
<li>在 <a href="https://tip.golang.org/pkg/unicode/utf8/"><code>unicode/utf8</code></a> 包中，新函数 <a href="https://tip.golang.org/pkg/unicode/utf8/#ValidRune"><code>ValidRune</code></a> 报告了一个 rune 是否是一个合法的 Unicode 编码值。为了确保合法，rune 的值必须在范围内，且不能为半个代用符。</li>
<li><a href="https://tip.golang.org/pkg/unicode/"><code>unicode</code></a> 包的实现被更新到 Unicode 7.2.0 版本。</li>
</ul>
<p>&#8212;&#8212;&#8212;&#8212;翻译分隔线&#8212;&#8212;&#8212;&#8212;<br />
时运不济，几片去痛片压制下才勉强撑了几天。只是看来去痛片解决不了发烧问题。回到家被强制休息了……<br />
拖到今天才翻译完，而且我相信，质量一定很差很差很差……<br />
大家自己看吧！有问题给我留言，我休眠了。<br />
如果明天早上我没回来，请诸位向 God of Gopher 礼拜，我一定是去了那美丽的地方。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2013/03/%e7%bf%bb%e8%af%91-go-1-1-%e4%bb%8b%e7%bb%8d/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Death is coming. Let&#8217;s Go!</title>
		<link>http://www.mikespook.com/2013/02/death-is-coming-lets-go/</link>
		<comments>http://www.mikespook.com/2013/02/death-is-coming-lets-go/#comments</comments>
		<pubDate>Fri, 22 Feb 2013 04:01:57 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=1568</guid>
		<description><![CDATA[早上某 Golang 群里有同学对 Python 社区的荐言给予了高度评价：



人生苦短，我用 Python。

<span class="readmore"><a href="http://www.mikespook.com/2013/02/death-is-coming-lets-go/" title="Death is coming. Let&#8217;s Go!">阅读全文——共107字</a></span>]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.mikespook.com/wp-content/uploads/2013/02/life-is-short-use-python.jpg"><img class="alignleft size-thumbnail wp-image-1570" alt="Life is short. Use Python." src="http://www.mikespook.com/wp-content/uploads/2013/02/life-is-short-use-python-150x150.jpg" width="150" height="150" /></a><a href="http://www.mikespook.com/wp-content/uploads/2013/02/zq-python.jpg"><img class="size-thumbnail wp-image-1579 alignleft" alt="zq-python" src="http://www.mikespook.com/wp-content/uploads/2013/02/zq-python-150x150.jpg" width="150" height="150" /></a></p>
<p>早上某 Golang 群里有同学对 Python 社区的荐言给予了高度评价：</p>
<blockquote><p>
<strong>人生苦短，我用 Python。</strong><br />
<strong>Lift is short, use Python.</strong>
</p></blockquote>
<p>对此，地鼠大神托我转告大家：</p>
<h1 style="clear: both; text-align: center;"><strong>Death is coming. Let&#8217;s <a href="http://golang.org" target="_blank">Go</a>!</strong></h1>
<p style="clear: both; text-align: center;"><a href="http://www.mikespook.com/wp-content/uploads/2013/02/death-is-coming-let-us-go.jpg"><img class=" wp-image-1569 aligncenter" alt="God of Gophers" src="http://www.mikespook.com/wp-content/uploads/2013/02/death-is-coming-let-us-go-1024x644.jpg" width="695" height="437" /></a></p>
<p><a href="http://www.mikespook.com/wp-content/uploads/2013/02/life-is-short-use-python.jpg"> </a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2013/02/death-is-coming-lets-go/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>[翻译]Go 和汇编</title>
		<link>http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91go-%e5%92%8c%e6%b1%87%e7%bc%96/</link>
		<comments>http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91go-%e5%92%8c%e6%b1%87%e7%bc%96/#comments</comments>
		<pubDate>Wed, 20 Feb 2013 11:55:29 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[汇编]]></category>
		<category><![CDATA[翻译]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=1554</guid>
		<description><![CDATA[使用 cgo 让 Go 跟 C 一起工作已经不是啥稀奇的了。有大量的第三方包直接对 C 的库做了封装，提供给 Go 使用。从 Go 项目本身的代码中可以看到，不但有 C 代码，还有汇编代码存在。那么在自己的项目中是否能跟汇编结合呢？这篇文章完整并清晰的解说了如何让 Go 和汇编协同工作。真得性能敏感？上汇编吧！！

&#8212;&#8212;&#8212;&#8212;翻译分隔线&#8212;&#8212;&#8212;&#8212;

Go 和汇编

<span class="readmore"><a href="http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91go-%e5%92%8c%e6%b1%87%e7%bc%96/" title="[翻译]Go 和汇编">阅读全文——共4155字</a></span>]]></description>
				<content:encoded><![CDATA[<p>使用 cgo 让 Go 跟 C 一起工作已经不是啥稀奇的了。有大量的第三方包直接对 C 的库做了封装，提供给 Go 使用。从 Go 项目本身的代码中可以看到，不但有 C 代码，还有汇编代码存在。那么在自己的项目中是否能跟汇编结合呢？<a href="http://www.doxsey.net/blog/go-and-assembly" target="_blank">这篇文章</a>完整并清晰的解说了如何让 Go 和汇编协同工作。真得性能敏感？上汇编吧！！</p>
<p>&#8212;&#8212;&#8212;&#8212;翻译分隔线&#8212;&#8212;&#8212;&#8212;</p>
<h1>Go 和汇编</h1>
<p>关于 Go，我最喜欢的部分之一就是它那坚定不移的实用主义线路。有时我们过于强调语言的设计，而忘记了编程所包含的其他内容。例如：<br />
<span id="more-1554"></span></p>
<ul>
<li>Go 的编译器很快</li>
<li>Go 有着强大的<a href="http://golang.org/pkg/">标准库</a></li>
<li>Go 可以工作在<a href="http://code.google.com/p/go/downloads/list">多种</a>平台下</li>
<li>Go 有着可以通过命令行/本地 Web 服务/ Internet 访问的完整文档</li>
<li>所有 Go 的代码是静态编译的，因此部署的问题微不足道</li>
<li>全部 Go 的代码都以良好的格式发布，可以在线阅读（就像<a href="http://golang.org/src/pkg/math/">这个</a>）</li>
<li>Go 有着良好定义（和文档）的<a href="http://golang.org/ref/spec">语法</a>。（不像 <a href="http://www.nobugs.org/developer/parsingcpp/">C++</a> 或 <a href="http://programmingisterrible.com/post/42432568185/how-to-parse-ruby">Ruby</a>）</li>
<li>Go 自带包管理工具。<code><a href="http://golang.org/cmd/go/#hdr-Download_and_install_packages_and_dependencies">go get X</a></code>（例如 <code>go get code.google.com/p/go.net/websocket</code>）</li>
<li>跟其他语言一样，Go 有编码样式指引，有一些是编译器强制的（例如大写和小写），而其他一些仅仅是约定，不过它还是提供了整理代码的工具：<code><a href="http://blog.golang.org/2013/01/go-fmt-your-code.html">gofmt name_of_file.go</a></code>。</li>
<li>还有工具 <code>go fix</code> 可以将 Go 代码从早版本自动迁移到新的版本</li>
<li>Go 带有测试工具来测试包：<code><a href="http://golang.org/cmd/go/#hdr-Test_packages">go test /path/to/package</a></code>。它还可以进行<a href="http://golang.org/cmd/go/#hdr-Description_of_testing_functions">性能评估</a>。</li>
<li>可以<a href="http://golang.org/doc/gdb">调试</a> 和<a href="http://blog.golang.org/2011/06/profiling-go-programs.html">评估</a> Go 程序。</li>
<li>你知道有个<a href="http://play.golang.org/">游乐场</a>可以在线尝试 Go 吗？</li>
<li>通过 <a href="http://golang.org/cmd/cgo/">cgo</a> Go 可以整合 C 的库。</li>
</ul>
<p>这些都已经有一些例子了，不过这里我想聚焦在一个不怎么为人所知的话题：<strong>Go 可以无缝调用汇编编写的函数</strong>。</p>
<h2>如何在 Go 中使用汇编</h2>
<p>假设我们需要编写一个汇编版本的 <code>sum</code> 函数。首先创建一个叫做 <code>sum.go</code> 的文件，内容如下：</p>
<pre class="brush: cpp; title: ; notranslate">
package sum

func Sum(xs []int64) int64 {
  var n int64
  for _, v := range xs {
    n += v
  }
  return n
}
</pre>
<p>这个函数将一个整型的 slice 相加，并返回结果。为了测试这个函数，创建一个叫做 <code>sum_test.go</code> 的文件，内容如下：</p>
<pre class="brush: cpp; title: ; notranslate">
package sum

import (
  &quot;testing&quot;
)

type (
  testCase struct {
    n int64
    xs []int64
  }
)

var (
  cases = []testCase{
    { 0, []int64{} },
    { 15, []int64{1,2,3,4,5} },
  }
)

func TestSum(t *testing.T) {
  for _, tc := range cases {
    n := Sum(tc.xs)
    if tc.n != n {
      t.Error(&quot;Expected&quot;, tc.n, &quot;got&quot;, n, &quot;for&quot;, tc.xs)
    }
  }
}
</pre>
<p>为你的代码编写测试是个不错的主意，不但可以检验库的代码（<del datetime="2013-04-08T02:27:22+00:00">只要不是 <code>package main</code></del>|译注：package main 中的方法也是可以使用 <code>go test</code> 进行测试的），还是一个用于试验的好方法。在命令行输入 <code>go test</code> 就可以运行这个测试。</p>
<p>现在让我们用汇编来代替这个函数。我们可以来看看 Go 编译器到底生成了什么。用命令 <code>go tool 6g -S sum.go</code> 来代替 <code>go test</code> 或者 <code>go build</code>（对于 64 位来说）。你会得到下面的内容：</p>
<pre class="brush: cpp; title: ; notranslate">
--- prog list &quot;Sum&quot; ---
0000 (sum.go:3) TEXT    Sum+0(SB),$16-24
0001 (sum.go:4) MOVQ    $0,SI
0002 (sum.go:5) MOVQ    xs+0(FP),BX
0003 (sum.go:5) MOVQ    BX,autotmp_0000+-16(SP)
0004 (sum.go:5) MOVL    xs+8(FP),BX
0005 (sum.go:5) MOVL    BX,autotmp_0000+-8(SP)
0006 (sum.go:5) MOVL    xs+12(FP),BX
0007 (sum.go:5) MOVL    BX,autotmp_0000+-4(SP)
0008 (sum.go:5) MOVL    $0,AX
0009 (sum.go:5) MOVL    autotmp_0000+-8(SP),DI
0010 (sum.go:5) LEAQ    autotmp_0000+-16(SP),BX
0011 (sum.go:5) MOVQ    (BX),CX
0012 (sum.go:5) JMP     ,14
0013 (sum.go:5) INCL    ,AX
0014 (sum.go:5) CMPL    AX,DI
0015 (sum.go:5) JGE     ,20
0016 (sum.go:5) MOVQ    (CX),BP
0017 (sum.go:5) ADDQ    $8,CX
0018 (sum.go:6) ADDQ    BP,SI
0019 (sum.go:5) JMP     ,13
0020 (sum.go:8) MOVQ    SI,.noname+16(FP)
0021 (sum.go:8) RET     ,
sum.go:3: Sum xs does not escape
</pre>
<p>汇编是相当难理解的，一会我们会详细了解一下这个部分……不过，首先用这个作为模板接着往下做。在 <code>sum.go</code> 同一目录创建一个叫做 <code>sum_amd64.s</code> 的文件，内容如下：</p>
<pre class="brush: cpp; title: ; notranslate">
// func Sum(xs []int64) int64
TEXT ·Sum(SB),$0
    MOVQ    $0,SI
    MOVQ    xs+0(FP),BX
    MOVQ    BX,autotmp_0000+-16(SP)
    MOVL    xs+8(FP),BX
    MOVL    BX,autotmp_0000+-8(SP)
    MOVL    xs+12(FP),BX
    MOVL    BX,autotmp_0000+-4(SP)
    MOVL    $0,AX
    MOVL    autotmp_0000+-8(SP),DI
    LEAQ    autotmp_0000+-16(SP),BX
    MOVQ    (BX),CX
    JMP     L2
L1: INCL    AX
L2: CMPL    AX,DI
    JGE     L3
    MOVQ    (CX),BP
    ADDQ    $8,CX
    ADDQ    BP,SI
    JMP     L1
L3: MOVQ    SI,.noname+16(FP)
    RET
</pre>
<p>基本上，我所做的所有处理就是将硬编码的用于跳转（JMP，JGE）的行号替换为标签，并且在函数名前增加了中点符（·）。（确保文件保存为 UTF-8 编码）接下来，从 <code>sum.go</code> 中移除我们的函数定义：</p>
<pre class="brush: cpp; title: ; notranslate">
package sum

func Sum(xs []int64) int64
</pre>
<p>现在，应当可以用 <code>go test</code> 运行测试，它将使用自定义的汇编版本的函数。</p>
<h2>工作原理</h2>
<p>这里对汇编做一些更为详细的说明。我将简短的说明一下它做了什么。</p>
<pre class="brush: cpp; title: ; notranslate">
MOVQ    $0,SI
</pre>
<p>首先，将 0 放入 SI（源变址）寄存器，它表示执行的指令的位置。Q 表示四个字，8 比特，下面还会看到 L 表示 4 比特。参数的顺序是（源，目标）。</p>
<pre class="brush: cpp; title: ; notranslate">
MOVQ    xs+0(FP),BX
MOVQ    BX,autotmp_0000+-16(SP)
MOVL    xs+8(FP),BX
MOVL    BX,autotmp_0000+-8(SP)
MOVL    xs+12(FP),BX
MOVL    BX,autotmp_0000+-4(SP)
</pre>
<p>接下来接收传入的参数，并将其值保存在栈上。一个 Go 的 slice 有三个部分：指向其所在的内存的指针、长度和容量。指针是 8 比特，长度和容量都是 4 比特。因此这段代码从 BX 寄存器复制了这些值出来。（参阅<a href="http://golang.org/doc/articles/slices_usage_and_internals.html" target="_blank">这里</a>了解更多关于 slice 的细节）</p>
<pre class="brush: cpp; title: ; notranslate">
MOVL    $0,AX
MOVL    autotmp_0000+-8(SP),DI
LEAQ    autotmp_0000+-16(SP),BX
MOVQ    (BX),CX
</pre>
<p>接下来，将 0 放入 AX，用于循环变量。将 slice 的长度放入 DI，并且加载指向 xs 元素的指针到 CX。</p>
<pre class="brush: cpp; title: ; notranslate">
    JMP     L2
L1: INCL    AX
L2: CMPL    AX,DI
    JGE     L3
</pre>
<p>现在到达代码的主体。首先跳转到 L2 比较 AX 和 DI。如果相等，说明已经计算了 slice 中的所有元素，因此跳到 L3。（也就是 <code>i == len(xs)</code>）。</p>
<pre class="brush: cpp; title: ; notranslate">
MOVQ    (CX),BP
ADDQ    $8,CX
ADDQ    BP,SI
JMP     L1
</pre>
<p>这里进行了求和。首先从 CX 中获取值保存到 BP。然后将 CX 向前移动 8 字节。最后将 BP 加到 SI 并跳转到 L1。L1 增加 AX 并且再次开始循环。</p>
<pre class="brush: cpp; title: ; notranslate">
L3: MOVQ    SI,.noname+16(FP)
  RET
</pre>
<p>结束求和后，将结果保存在传递到函数的所有的参数之后（由于一个 slice 是 16 字节，所以这里是 16 字节）。这时就返回了。</p>
<h2>重写</h2>
<p>这里我重写了代码：</p>
<pre class="brush: cpp; title: ; notranslate">
// func Sum(xs []int64) int64
TEXT ·Sum2(SB),7,$0
    MOVQ    $0, SI       // n
    MOVQ    xs+0(FP), BX // BX = &amp;xs[0]
    MOVL    xs+8(FP), CX // len(xs)
    MOVLQSX CX, CX       // len as int64
    INCQ    CX           // CX++

start:
    DECQ    CX           // CX--
    JZ done              // jump if CX = 0
    ADDQ    (BX), SI     // n += *BX
    ADDQ    $8, BX       // BX += 8
    JMP start

done:
    MOVQ    SI, .noname+16(FP) // return n
    RET
</pre>
<p>希望这会更容易理解一些。</p>
<h2>忠告</h2>
<p>可以这么做当然很酷，但是不要忽视了这些忠告：</p>
<ul>
<li>汇编很难编写，特别是很难写好。通常编译器会比你写出更快的代码（从前文来看，Go 编译器会做得更好）。</li>
<li>汇编仅能运行在一个平台上。在这个例子中，代码仅能运行在 amd64 上。这个问题有一个解决方案是给 Go 对于 x86 和 arm 不同版本的代码（像<a href="http://golang.org/src/pkg/math/atan2_arm.s" target="_blank">这样</a>）。</li>
<li>汇编让你和底层绑定在一起，而标准的 Go 不会。例如，slice 的长度当前是 32 位整数。但是也不是不可能为长整型。当发生这些变化时，这些代码就被破坏了（也可能是编译器无法检测到的更恶心的途径来破坏）</li>
<li>当前 Go 编译器不能将汇编编译为函数的内联，但是对于小的 Go 函数是可以的。因此使用汇编可能意味着让你的程序更慢。</li>
</ul>
<p>对于下面的两个原因，这还是很有用的：</p>
<ul>
<ol>有时需要汇编给你带来一些力量（不论是性能方面的原因，还是一些相当特殊的关于 CPU 的操作）。对于什么时候应该使用它，Go 源码包括了若干相当好的例子（可以看看 <a href="http://golang.org/src/pkg/crypto/" target="_blank">crypto</a> 和 <a href="http://golang.org/src/pkg/math/" target="_blank">math</a>）。</ol>
<ol>由于它非常容易实践，所以这绝对是个学习汇编的好途径。</ol>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91go-%e5%92%8c%e6%b1%87%e7%bc%96/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>[翻译] Go(#golang) 实现的状态机</title>
		<link>http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91-gogolang-%e5%ae%9e%e7%8e%b0%e7%9a%84%e7%8a%b6%e6%80%81%e6%9c%ba/</link>
		<comments>http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91-gogolang-%e5%ae%9e%e7%8e%b0%e7%9a%84%e7%8a%b6%e6%80%81%e6%9c%ba/#comments</comments>
		<pubDate>Tue, 19 Feb 2013 13:42:08 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[状态机]]></category>
		<category><![CDATA[翻译]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=1543</guid>
		<description><![CDATA[春节前，粗略研究了一下微信的公共帐号。用 Golang 实现了一个简单的 package wechat，用于接入微信公共帐号。当时就在思考，微信的文字交互过程如果要实现有一定逻辑的复杂过程，可能需要使用到状态机。然后，就看到了这篇文章：《State machines in Go (#golang)》。非常合时宜啊！翻译于此，以飨读者！

&#8212;&#8212;&#8211;翻译分隔线&#8212;&#8212;&#8211;

Go(#golang) 实现的状态机

<span class="readmore"><a href="http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91-gogolang-%e5%ae%9e%e7%8e%b0%e7%9a%84%e7%8a%b6%e6%80%81%e6%9c%ba/" title="[翻译] Go(#golang) 实现的状态机">阅读全文——共1911字</a></span>]]></description>
				<content:encoded><![CDATA[<p>春节前，粗略研究了一下微信的公共帐号。用 Golang 实现了一个简单的 package <a href="https://github.com/mikespook/wechat" title="wechat" target="_blank">wechat</a>，用于接入微信公共帐号。当时就在思考，微信的文字交互过程如果要实现有一定逻辑的复杂过程，可能需要使用到状态机。然后，就看到了这篇文章：《<a href="http://denis.papathanasiou.org/?p=1190" title="State machines in Go (#golang)" target="_blank">State machines in Go (#golang)</a>》。非常合时宜啊！翻译于此，以飨读者！</p>
<p>&#8212;&#8212;&#8211;翻译分隔线&#8212;&#8212;&#8211;</p>
<h1>Go(#golang) 实现的状态机</h1>
<p>我已经用 <a href="http://golang.org/" target="_blank">Go</a> 代替 <a href="http://python.org/" target="_blank">Python</a> 重写了一个关键的服务组件。<a href="http://docs.python.org/release/2.5.2/api/threads.html" target="_blank">由于 Python 的解释器不是线程安全的，所以在解析的时候使用了全局锁。</a>Go 与 Python 不同，它内建了并发支持，并且是静态编译的。<br />
<span id="more-1543"></span></p>
<p>首先要实现一个<a href="http://en.wikipedia.org/wiki/State_machine" target="_blank">状态机</a>。<a href="http://denis.papathanasiou.org/?p=747" target="_blank">Python 的版本</a>是基于 <a href="http://www.ibm.com/developerworks/library/l-python-state/index.html" target="_blank">David Mertz 的这篇文章</a>。</p>
<p>Mertz 使用了面向对象的形式，定义了一个有着数据和方法的类。他的代码，抛开语法不谈，对于任何有着 C++、C# 和 Java 的面向对象经验的人来说都不会陌生。</p>
<p>不过 Go 没有提供在特定数据结构上内部关联方法的机制。作为代替，Go 允许<a href="http://golangtutorials.blogspot.com/2011/06/structs-in-go-instead-of-classes-in.html" target="_blank">联合方法到数据结构</a>，这样任何方法都可以应用到任何结构上。（译注：class { methods } 和 struct { }; methods 的区别。）</p>
<p>这种形式与 <a href="http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en" target="_blank">Alan Kay 所表达的</a>，关于最初的面向对象阶段比较接近。</p>
<p>先别忘了这些，下面是我最开始用 Go 结构体编写的状态机的类：</p>
<pre class="brush: cpp; title: ; notranslate">
type Machine struct {
    Handlers   map[string]func(interface{}) (string, interface{})
    StartState string
    EndStates  map[string]bool
}
</pre>
<p>跟 Mertz 的定义一样，Handlers 是一个用 string 做键名的 map，map 项保存的值是函数，可以接收一个“物料”，并且返回下一个状态名的字符串和更新后的物料值。</p>
<p>Go 认为函数是一等公民对象，因此将它们在状态之间存储和传递跟在 Python 中的方式一样。</p>
<p>我仅仅在状态列表的最后做了一些改变：Mertz 使用一个字符串的列表，但由于没有办法在 Go 的列表中进行快速的定位，我使用了 map（在 Go 中，只能通过迭代遍历整个字符串列表，直到找到一个匹配项）。</p>
<p>由于处理函数的原型比较笨重，我为其建立了一个<a href="http://www.laktek.com/2012/02/23/learning-go-functions/" target="_blank">自定义函数类型</a>：</p>
<pre class="brush: cpp; title: ; notranslate">
type Handler func(interface{}) (string, interface{})

type Machine struct {
    Handlers   map[string]Handler
    StartState string
    EndStates  map[string]bool
}
</pre>
<p>剩下的就是定义 Machine 结构体关联的方法。</p>
<p>首先定义的两个方法，一个提供了状态名关联到处理函数，另一个设定了结束状态：</p>
<pre class="brush: cpp; title: ; notranslate">
func (machine *Machine) AddState(handlerName string, handlerFn Handler) {
    machine.Handlers[handlerName] = handlerFn
}

func (machine *Machine) AddEndState(endState string) {
    machine.EndStates[endState] = true
}
</pre>
<p>值得说明的是由于 EndStates 是一个 map（在 Mertz 原始的版本中是 list），所以可以有多个终止处理过程的状态。</p>
<p>最后一个方法用于执行状态机，应用恰当的处理函数，并在到达结束状态时终止。</p>
<p>由于函数集合作为一等公民对象保存在 map 中，基于名字找到它们并且进行调用是很轻松的：</p>
<pre class="brush: cpp; title: ; notranslate">
func (machine *Machine) Execute(cargo interface{}) {
    if handler, present := machine.Handlers[machine.StartState]; present {
        for {
            nextState, nextCargo := handler(cargo)
            _, finished := machine.EndStates[nextState]
            if finished {
                break
            } else {
                handler, present = machine.Handlers[nextState]
                cargo = nextCargo
            }
        }
    }
}
</pre>
<p>唯一美中不足的是 Go 的强类型，在处理函数的原型中，需要指定物料的类型。</p>
<p>使用通用的 interface{} 作为类型，所有处理函数都需要对输入的物料进行<a href="http://golang.org/ref/spec#Type_assertions" target="_blank">类型断言</a>，这样它们就可以处理任何数据（<a href="https://github.com/dpapathanasiou/go-statemachine/blob/master/statemachine-test.go" target="_blank">测试的例子</a>使用了浮点作为物料，不过其实它可以是任何数据类型，甚至是自定义的结构体）。</p>
<p>完整的状态机已经<a href="https://github.com/dpapathanasiou/go-statemachine" target="_blank">作为 Go 包发布</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91-gogolang-%e5%ae%9e%e7%8e%b0%e7%9a%84%e7%8a%b6%e6%80%81%e6%9c%ba/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>[翻译]在 Go 应用中使用简明架构（5）</title>
		<link>http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91%e5%9c%a8-go-%e5%ba%94%e7%94%a8%e4%b8%ad%e4%bd%bf%e7%94%a8%e7%ae%80%e6%98%8e%e6%9e%b6%e6%9e%845/</link>
		<comments>http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91%e5%9c%a8-go-%e5%ba%94%e7%94%a8%e4%b8%ad%e4%bd%bf%e7%94%a8%e7%ae%80%e6%98%8e%e6%9e%b6%e6%9e%845/#comments</comments>
		<pubDate>Sun, 17 Feb 2013 13:15:27 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[翻译]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=1522</guid>
		<description><![CDATA[原文在此，续前……

&#8212;&#8212;&#8211;翻译分隔线&#8212;&#8212;&#8211;

在 Go 应用中使用简明架构（5）

<span class="readmore"><a href="http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91%e5%9c%a8-go-%e5%ba%94%e7%94%a8%e4%b8%ad%e4%bd%bf%e7%94%a8%e7%ae%80%e6%98%8e%e6%9e%b6%e6%9e%845/" title="[翻译]在 Go 应用中使用简明架构（5）">阅读全文——共3805字</a></span>]]></description>
				<content:encoded><![CDATA[<p>原文<a href="http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/" target="_blank">在此</a>，续<a href="http://www.mikespook.com/2012/09/%e7%bf%bb%e8%af%91%e5%9c%a8-go-%e5%ba%94%e7%94%a8%e4%b8%ad%e4%bd%bf%e7%94%a8%e7%ae%80%e6%98%8e%e6%9e%b6%e6%9e%844/" target="_blank">前</a>……</p>
<p>&#8212;&#8212;&#8211;翻译分隔线&#8212;&#8212;&#8211;</p>
<h1>在 Go 应用中使用简明架构（5）</h1>
<h2>基础层</h2>
<p>就像上面提到的，我们的存储认为“数据库”是一个可以用 SQL 请求发送或接收数据行的抽象。它们不关心基础构建的问题，例如链接到数据库，或使用哪个数据库。这是在 src/infrastructure/sqlitehandler.go 中完成的，高层次的 DbHandler 接口是通过调用低层次的功能来实现的：<br />
<span id="more-1522"></span></p>
<pre class="brush: cpp; title: ; notranslate">
package infrastructure

import (
	&quot;database/sql&quot;
	&quot;fmt&quot;
	_ &quot;github.com/mattn/go-sqlite3&quot;
	&quot;interfaces&quot;
)

type SqliteHandler struct {
	Conn *sql.DB
}

func (handler *SqliteHandler) Execute(statement string) {
	handler.Conn.Exec(statement)
}

func (handler *SqliteHandler) Query(statement string) interfaces.Row {
	//fmt.Println(statement)
	rows, err := handler.Conn.Query(statement)
	if err != nil {
		fmt.Println(err)
		return new(SqliteRow)
	}
	row := new(SqliteRow)
	row.Rows = rows
	return row
}

type SqliteRow struct {
	Rows *sql.Rows
}

func (r SqliteRow) Scan(dest ...interface{}) {
	r.Rows.Scan(dest...)
}

func (r SqliteRow) Next() bool {
	return r.Rows.Next()
}

func NewSqliteHandler(dbfileName string) *SqliteHandler {
	conn, _ := sql.Open(&quot;sqlite3&quot;, dbfileName)
	sqliteHandler := new(SqliteHandler)
	sqliteHandler.Conn = conn
	return sqliteHandler
}
</pre>
<p>（再次强调，没有错误处理或其他什么东西，这是为了让那些对架构没有贡献的代码不要干扰思路。）</p>
<p>使用 Yasuhiro Matsumoto 的 sqlite3 库，这个基础代码实现了 DbHandler 接口，这让存储可以在不知道底层细节的情况下与数据库通信。</p>
<h2>将所有组合到一起</h2>
<p>就这样，关于架构的所有建筑模块已经准备好了——让我们将他们在 main.go 中组合到一起：</p>
<pre class="brush: cpp; title: ; notranslate">
package main

import (
	&quot;usecases&quot;
	&quot;interfaces&quot;
	&quot;infrastructure&quot;
	&quot;net/http&quot;
)

func main() {
	dbHandler := infrastructure.NewSqliteHandler(&quot;/var/tmp/production.sqlite&quot;)

	handlers := make(map[string] interfaces.DbHandler)
	handlers[&quot;DbUserRepo&quot;] = dbHandler
	handlers[&quot;DbCustomerRepo&quot;] = dbHandler
	handlers[&quot;DbItemRepo&quot;] = dbHandler
	handlers[&quot;DbOrderRepo&quot;] = dbHandler

	orderInteractor := new(usecases.OrderInteractor)
	orderInteractor.UserRepository = interfaces.NewDbUserRepo(handlers)
	orderInteractor.ItemRepository = interfaces.NewDbItemRepo(handlers)
	orderInteractor.OrderRepository = interfaces.NewDbOrderRepo(handlers)

	webserviceHandler := interfaces.WebserviceHandler{}
	webserviceHandler.OrderInteractor = orderInteractor

	http.HandleFunc(&quot;/orders&quot;, func(res http.ResponseWriter, req *http.Request) {
		webserviceHandler.ShowOrder(res, req)
	})
	http.ListenAndServe(&quot;:8080&quot;, nil)
}
</pre>
<p>鉴于非常极端的依赖注入的使用，所以非常有必要在运行应用模块之前进行一些构建工作。DbHandler 的实现会注入到存储层，另一方面，存储层也被注入到用例层中。orderInteractor 注入到路由 webserviceHandler 中。最后，启动 HTTP 服务器。</p>
<p>盒子在盒子在盒子里面，每个独立的部件都可以被替换为底层工作原理完全不同的其他东西——只要有相同的 API，就可以工作。</p>
<p>可以用下面的 SQL 在 /var/tmp/production.sqlite 中创建一个最小的数据集：</p>
<pre class="brush: sql; title: ; notranslate">
CREATE TABLE users (id INTEGER, customer_id INTEGER, is_admin VARCHAR(3));
CREATE TABLE customers (id INTEGER, name VARCHAR(42));
CREATE TABLE orders (id INTEGER, customer_id INTEGER);
CREATE TABLE items (id INTEGER, name VARCHAR(42), value FLOAT, available VARCHAR(3));
CREATE TABLE items2orders (item_id INTEGER, order_id INTEGER);

INSERT INTO users (id, customer_id, is_admin) VALUES (40, 50, &quot;yes&quot;);
INSERT INTO customers (id, name) VALUES (50, &quot;John Doe&quot;);
INSERT INTO orders (id, customer_id) VALUES (60, 50);
INSERT INTO items (id, name, value, available) VALUES (101, &quot;Soap&quot;, 4.99, &quot;yes&quot;);
INSERT INTO items (id, name, value, available) VALUES (102, &quot;Fork&quot;, 2.99, &quot;yes&quot;);
INSERT INTO items (id, name, value, available) VALUES (103, &quot;Bottle&quot;, 6.99, &quot;no&quot;);
INSERT INTO items (id, name, value, available) VALUES (104, &quot;Chair&quot;, 43.00, &quot;yes&quot;);

INSERT INTO items2orders (item_id, order_id) VALUES (101, 60);
INSERT INTO items2orders (item_id, order_id) VALUES (104, 60);
</pre>
<p>现在可以运行这个应用，然后在浏览器中访问 http://localhost:8080/orders?userId=40&#038;orderId=60。结果应该是：</p>
<p>item id: 101<br />
item name: Soap<br />
item value: 4.990000<br />
item id: 104<br />
item name: Chair<br />
item value: 43.000000<br />
And with this, it’s time to pat ourselves on the shoulder.</p>
<h2>反思</h2>
<p>这个应用并不是不能进一步改进了。例如，由于所有的存储都必须是 DbHandler，使用到其他存储的存储层现在是无法实现的；或者当决定将产品保存在 MongoDB 同时将订单保存在关系数据库，而 DbOrderRepo 不能用这个方式创建 DbItemRepo；可以创建一个注册表或依赖注入容器提供所有的存储，而不是 DbHandler，来解决这个问题。</p>
<p>不过，我们已经建立了一个可以很容易实施这些变化的架构。应用只有特定的部分会需要修改，而不会对用例或领域逻辑带来破坏的风险。这就是漂亮的简明架构。</p>
<h2>感谢</h2>
<p>如果没有 Bob Martin “大叔”不厌其烦的向我们讲授如何进行软件开发和软件架构设计，也就不会有这个指南。</p>
<p>来自 golang-nuts 邮件列表的诸位提供了非常有帮助的反馈（无特定顺序）：Gheorghe Postelnicu, Hannes Baldursson, Francesc Campoy Flores, Christoph Hack, Gaurav Garg, Paddy Foran, Sanjay Menakuru, Larry Clapp, Steven Degutis, Sanjay, Jesse McNelis, Mateusz Czapliński, 和 Rob Pike。Jon Jagger 提供了极有帮助的批评和指导。</p>
<p>&#8212;&#8212;&#8211;翻译分隔线&#8212;&#8212;&#8211;</p>
<p>总算是搞掂了……到底是拖到了年后……</p>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2013/02/%e7%bf%bb%e8%af%91%e5%9c%a8-go-%e5%ba%94%e7%94%a8%e4%b8%ad%e4%bd%bf%e7%94%a8%e7%ae%80%e6%98%8e%e6%9e%b6%e6%9e%845/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[翻译]我其实没办法学 Erlang，因为它不存在，所以我发明了它</title>
		<link>http://www.mikespook.com/2013/01/%e7%bf%bb%e8%af%91%e6%88%91%e5%85%b6%e5%ae%9e%e6%b2%a1%e5%8a%9e%e6%b3%95%e5%ad%a6-erlang%ef%bc%8c%e5%9b%a0%e4%b8%ba%e5%ae%83%e4%b8%8d%e5%ad%98%e5%9c%a8%ef%bc%8c%e6%89%80%e4%bb%a5%e6%88%91%e5%8f%91/</link>
		<comments>http://www.mikespook.com/2013/01/%e7%bf%bb%e8%af%91%e6%88%91%e5%85%b6%e5%ae%9e%e6%b2%a1%e5%8a%9e%e6%b3%95%e5%ad%a6-erlang%ef%bc%8c%e5%9b%a0%e4%b8%ba%e5%ae%83%e4%b8%8d%e5%ad%98%e5%9c%a8%ef%bc%8c%e6%89%80%e4%bb%a5%e6%88%91%e5%8f%91/#comments</comments>
		<pubDate>Fri, 25 Jan 2013 05:08:55 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Stardy & Research]]></category>
		<category><![CDATA[erlang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[history]]></category>
		<category><![CDATA[翻译]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=1533</guid>
		<description><![CDATA[这是 Joe Armstrong，Erlang 之父在昨天（2013-01-24）发表在 Erlang 邮件列表里的内容。详细解说了他自己从开始学习编码到发明 Erlang 的整个历史。并且，对如何学习编程；如何成为大牛等一系列热门问题提出了自己的看法。当然，文中槽点无数（包括坚定不移的黑我大PHP）。这也是我觉得有必要翻译出来的原因之一。

由于是发在邮件列表的，所以原文是没有正式的标题的。我从 HN 上抄了这个标题回来。

整体上，虽然是和 Erlang 相关的话题。但是读过之后，会突然领会到 Golang Team 一直以来坚持的一些东西的更深层次的含义。

<span class="readmore"><a href="http://www.mikespook.com/2013/01/%e7%bf%bb%e8%af%91%e6%88%91%e5%85%b6%e5%ae%9e%e6%b2%a1%e5%8a%9e%e6%b3%95%e5%ad%a6-erlang%ef%bc%8c%e5%9b%a0%e4%b8%ba%e5%ae%83%e4%b8%8d%e5%ad%98%e5%9c%a8%ef%bc%8c%e6%89%80%e4%bb%a5%e6%88%91%e5%8f%91/" title="[翻译]我其实没办法学 Erlang，因为它不存在，所以我发明了它">阅读全文——共2530字</a></span>]]></description>
				<content:encoded><![CDATA[<p>这是 Joe Armstrong，Erlang 之父在昨天（2013-01-24）发表在 Erlang 邮件列表里的内容。详细解说了他自己从开始学习编码到发明 Erlang 的整个历史。并且，对如何学习编程；如何成为大牛等一系列热门问题提出了自己的看法。当然，文中槽点无数（包括坚定不移的黑我大PHP）。这也是我觉得有必要翻译出来的原因之一。</p>
<p>由于是发在邮件列表的，所以原文是没有正式的标题的。我从 HN 上抄了这个标题回来。</p>
<p>整体上，虽然是和 Erlang 相关的话题。但是读过之后，会突然领会到 Golang Team 一直以来坚持的一些东西的更深层次的含义。</p>
<p>&#8212;&#8212;&#8212;&#8212;翻译分隔线&#8212;&#8212;&#8212;&#8212;</p>
<p>回到过去……</p>
<p>当我开始学习编程的时候（1967），我可以在 FORTRAN 和（传说中的）Algol 之间选择，不过没有任何人了解 Algol，所以我选择了 FORTRAN。</p>
<p>编程的一个周期是三个星期</p>
<ul>
<ol><strong>第一周</strong> 在纸上的表格里编写代码，然后送到计算机中心将其转换为打孔卡</ol>
<ol><strong>第二周</strong> 复查打孔卡，如果没问题就放到设备上执行</ol>
<ol><strong>第三周</strong> 得到结果</ol>
</ul>
<p>编译器遇到第一个语法错误的时候会终止运行，这会将你带回第一周——因此，如果在你的程序中有十个错误，它会需要花费 30 周的时间让它运行起来。</p>
<p>这种氛围是非常好的——教会大家不要犯错，并且首先思考。</p>
<p><span id="more-1533"></span><br />
大约在 1970 我在大学的时候，这个周期已经减少到了 4 个小时，并且你可以自己给卡片打孔——仍然是 FORTRAN。</p>
<p>在 1974 我可以访问一台计算机了——霍尼韦尔 DDP 516，有着 32KB 之大的内存。因此 474 FORTRAN 编译器可以在少于一周的时间里编译上百行代码。（大概如此……）</p>
<p>事情总在发展，我去了 CERN 并且使用可以在一皮秒编译十万行 FORTRAN 代码的 CRAY1（大概比我现在用的手机慢无数倍）。</p>
<p>还是 FORTRAN。</p>
<p>在 1974（大约）我玩了 DEC10 ——现在我可以编写 FORTRAN、Basic、汇编，并且有了按时间轮换的分时（哇～～）。如果我当时在美国的话，我可能就是比尔盖茨了，可惜那是在爱丁堡。</p>
<p>在 1976 我得到了一份在 NORD10 上用 FORTRAN/汇编编程的工作，而按时间轮换变得非常快。</p>
<p>大约在 1980，我仍然在用 FORTRAN 编程。我不记得那个设备的名字了，所有的文件保存在一个目录中，没有全屏编辑器，没有版本控制系统。我为其大约编写了 15 万行的 FORTRAN 代码。</p>
<p>1985，我加入了爱立信，神奇的 VAX11/750，需要学习新的语言。再见啦，FORTRAN。</p>
<p>我学习了（在不同的熟练程度下）Lisp、Prolog、awk、bash、smalltalk、TCL，并且成为了 Prolog 专家（哈哈哈哈～那个小美人……）。</p>
<p>同时我也对那些随手就能玩的语言进行了尝试（ML、forth 等等）。</p>
<p>然后（1986）我进入了我的 Erlang 时代（我其实没办法学 Erlang，因为它不存在，所以我发明了它）。它其实是 Prolog + Smalltalk 再配合点错误处理、并发和分布式的产物。</p>
<p>然后我学习了 C（学得很差）。但是 Mike Williams 说我的 C 是垃圾，并且看起来像 FORTRAN，因此他回收了我的 C……（为啥要用 malloc 和 free，还有指针什么的……）</p>
<p>我看到 C++ 的出世并且看了本书，或者说至少尝试去读了本书。在我的钢琴后面的墙上有个坑，就是那本书砸出来的。对 C 进行改进应当让事情变得简单，而不是更复杂，我这样想到。</p>
<p>时间飞逝。</p>
<p>我尝试了 Java（没什么深刻的印象，比 C++ 好点，但是如此罗嗦。在编写 FORTRAN 的时候，你不得不为一个很小的事情编写上百行代码，我甚至为此打肿了手。Java 也差不多，如此罗嗦）。（后来）我还尝试了 Python（还行）、Ruby（还行）、Lua（不错）、Javascript（我喜欢 <img src='http://www.mikespook.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> ）。</p>
<p>实际上学习所有这些语言花费了我相当长的时间，并且它们不是同时进行的。我和 FORTRAN 有 15 年的美好时光，对于用好它来说足够长了。Prolong 10年， Erlang 20 年，等等。</p>
<p>我也花了很长时间来消化新的主意。那些关于编程的主意或者其他人的好主意，出现得很慢，差不多二十年一个。在过去的二十年里，编程并没有多大的改进。那个时候它就是一坨，现在还是一坨。</p>
<p>IDE 和版本控制系统让事情变得更糟，现在你有了所有版本的一坨，以及那一坨本身，而 IDE 意味着你看不到那一坨了。</p>
<p>世界上最好的 IDE 是你的大脑，它比这些咔啦咔啦的东西好无数倍。</p>
<p>教育为此做了什么？</p>
<p>假设你刚刚起步。</p>
<p>你可以在二十余种语言中进行选择（它们每个都有一个或多个好理由），这花费了我 40 年来学习，你必须在 2-3 年里明白这些，看起来不太可能。</p>
<p>初学者应当学习什么语言，学校应当教什么语言？</p>
<p>现在我们遇到了一个选择的悖论，由于有太多选项而无法选择。</p>
<p>以前说“为了问题而选择语言”，当你已经了解了二十余种语言（在不同的熟练程度下）这么说很容易。但是，如果你只了解两种语言 Java 和 C，那么这就没什么帮助。</p>
<p>许多实际问题的解决方案在 CLP 语言中只需要几行代码，而在 C 中需要几千行。</p>
<p>我建议学习的是什么？</p>
<ul>
<ol>C</ol>
<ol>Prolog</ol>
<ol>Erlang（我偏心）</ol>
<ol>Smalltalk</ol>
<ol>Javascript</ol>
<ol>Hakell / ML /OCaml</ol>
<ol>LISP/Scheme/Clojure</ol>
</ul>
<p>（每个语言）花几年的时间应该是够了。</p>
<p>注意这里没有捷径，如果你想要捷径去买本《十分钟学会 PHP》，然后用接下来的二十年在 Google 上搜索“我该如何计算字符串长度”。</p>
<p>将所有东西组织在一起仍然是极度糟糕到令人发狂的地步。组织到一起最好的办法还是 UNIX 的管道</p>
<pre class="brush: bash; title: ; notranslate">
    find ... | grep | uniq | sort | ...
</pre>
<p>这其中最<strong>基础的原理</strong>是组件应当通过某个统一的媒介语言中，良好定义的协议来进行分离。</p>
<p>通过消息传递来组织是个途径。这是 OO 编程的基础，但是大多数编程语言做得很差。</p>
<p>如果世界上所有的应用都通过 socket + LISP S 表达式作为接口（通信），并且将协议描述用格式化的标准编写，那么重用的时候就会（更）容易。</p>
<p>当前，对于语言有种不良的关注和推进，而忽视用什么协议来如何组织。应该讲授协议，而不是语言。</p>
<p>并且应当讲授算法。</p>
<p>祝福大家</p>
<p>/Joe</p>
<p>（所有日期都是大概时间）</p>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2013/01/%e7%bf%bb%e8%af%91%e6%88%91%e5%85%b6%e5%ae%9e%e6%b2%a1%e5%8a%9e%e6%b3%95%e5%ad%a6-erlang%ef%bc%8c%e5%9b%a0%e4%b8%ba%e5%ae%83%e4%b8%8d%e5%ad%98%e5%9c%a8%ef%bc%8c%e6%89%80%e4%bb%a5%e6%88%91%e5%8f%91/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>墙</title>
		<link>http://www.mikespook.com/2013/01/%e5%a2%99/</link>
		<comments>http://www.mikespook.com/2013/01/%e5%a2%99/#comments</comments>
		<pubDate>Wed, 23 Jan 2013 00:59:01 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[My life]]></category>
		<category><![CDATA[gfw]]></category>
		<category><![CDATA[墙]]></category>
		<category><![CDATA[封锁]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=1530</guid>
		<description><![CDATA[这副图，我已经发过很多次了。在德国的一个儿童游乐场的摩天轮下，它静静的矗立在那里。我不知道有多少人倒在它的脚下。我也不知道有多少人，因为守卫士兵“抬高一厘米”的枪口而幸存。在我拍这张照片的时候，仅仅是觉得好玩。并无多想。但是若干次的回想，当时的场景。回想那堵看得见的墙时。我总会有一阵阵心悸。

当这些士兵面对靠近墙体的人群扫射的时候，他们在想什么呢？他们的家人、朋友是否也在那人群之中惶恐不安的奔跑呢？

我不知道，我也不敢知道。

<span class="readmore"><a href="http://www.mikespook.com/2013/01/%e5%a2%99/" title="墙">阅读全文——共414字</a></span>]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.mikespook.com/2009/06/%e3%80%8a%e5%9b%b4%e5%a2%99%e3%80%8b%e2%80%94%e2%80%94%e9%92%b1%e7%bb%88%e8%be%93/wall/" rel="attachment wp-att-343"><img src="http://www.mikespook.com/wp-content/uploads/2009/06/wall.jpg" alt="The wall" width="299" height="400" class="alignleft size-full wp-image-343" /></a> 这副图，我已经发过很多次了。在德国的一个儿童游乐场的摩天轮下，它静静的矗立在那里。我不知道有多少人倒在它的脚下。我也不知道有多少人，因为守卫士兵“抬高一厘米”的枪口而幸存。在我拍这张照片的时候，仅仅是觉得好玩。并无多想。但是若干次的回想，当时的场景。回想那堵看得见的墙时。我总会有一阵阵心悸。</p>
<p>当这些士兵面对靠近墙体的人群扫射的时候，他们在想什么呢？他们的家人、朋友是否也在那人群之中惶恐不安的奔跑呢？</p>
<p>我不知道，我也不敢知道。</p>
<p>有人说，这些被墙阻隔、射杀的人群是“自作孽，不可活”，“一小撮……總是干這種幼稚愚蠢的無用的事情，遲早殃及其他……”。我不知道他们又是怀着怎么样的心情说这样的话。纳粹想要的自我审查在这些丑陋的，但却可怜的嘴脸上表现得淋漓尽致。</p>
<p>有形的墙迟早都会倒的……</p>
<p>或许最难逾越的是人们心中那堵看不见、摸不着的墙吧？</p>
<p>“Across the Great Wall we can reach every corner in the world。”</p>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2013/01/%e5%a2%99/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[翻译]在 Go 应用中使用简明架构（4）</title>
		<link>http://www.mikespook.com/2012/12/%e7%bf%bb%e8%af%91%e5%9c%a8-go-%e5%ba%94%e7%94%a8%e4%b8%ad%e4%bd%bf%e7%94%a8%e7%ae%80%e6%98%8e%e6%9e%b6%e6%9e%844/</link>
		<comments>http://www.mikespook.com/2012/12/%e7%bf%bb%e8%af%91%e5%9c%a8-go-%e5%ba%94%e7%94%a8%e4%b8%ad%e4%bd%bf%e7%94%a8%e7%ae%80%e6%98%8e%e6%9e%b6%e6%9e%844/#comments</comments>
		<pubDate>Tue, 25 Dec 2012 08:08:36 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[翻译]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=1499</guid>
		<description><![CDATA[原文在此，续前……

&#8212;&#8212;&#8211;翻译分隔线&#8212;&#8212;&#8211;

在 Go 应用中使用简明架构（4）

<span class="readmore"><a href="http://www.mikespook.com/2012/12/%e7%bf%bb%e8%af%91%e5%9c%a8-go-%e5%ba%94%e7%94%a8%e4%b8%ad%e4%bd%bf%e7%94%a8%e7%ae%80%e6%98%8e%e6%9e%b6%e6%9e%844/" title="[翻译]在 Go 应用中使用简明架构（4）">阅读全文——共6981字</a></span>]]></description>
				<content:encoded><![CDATA[<p>原文<a href="http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/" target="_blank">在此</a>，续<a href="http://www.mikespook.com/2012/09/%e7%bf%bb%e8%af%91%e5%9c%a8-go-%e5%ba%94%e7%94%a8%e4%b8%ad%e4%bd%bf%e7%94%a8%e7%ae%80%e6%98%8e%e6%9e%b6%e6%9e%843/" target="_blank">前</a>……</p>
<p>&#8212;&#8212;&#8211;翻译分隔线&#8212;&#8212;&#8211;</p>
<h1>在 Go 应用中使用简明架构（4）</h1>
<h2>接口层</h2>
<p>关于这点，必须说，所有东西都得有编码智慧，不论是真实的商业还是我们的应用用例。让我们看看对于接口层的代码这意味着什么。不像在各个内部层次中，所有代码都属于一个逻辑，接口层是由若干独立的部分构建而成。因此，我们将这个层次的代码拆分为若干个文件。</p>
<p>由于我们的商店要通过 Web 访问，就从 Web 服务开始吧：</p>
<p><span id="more-1499"></span></p>
<pre class="brush: cpp; title: ; notranslate">
package interfaces

import (
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;net/http&quot;
	&quot;strconv&quot;
	&quot;usecases&quot;
)

type OrderInteractor interface {
	Items(userId, orderId int) ([]usecases.Item, error)
	Add(userId, orderId, itemId int) error
}

type WebserviceHandler struct {
	OrderInteractor OrderInteractor
}

func (handler WebserviceHandler) ShowOrder(res http.ResponseWriter, req *http.Request) {
	userId, _ := strconv.Atoi(req.FormValue(&quot;userId&quot;))
	orderId, _ := strconv.Atoi(req.FormValue(&quot;orderId&quot;))
	items, _ := handler.OrderInteractor.Items(userId, orderId)
	for _, item := range items {
		io.WriteString(res, fmt.Sprintf(&quot;item id: %d\n&quot;, item.Id))
		io.WriteString(res, fmt.Sprintf(&quot;item name: %v\n&quot;, item.Name))
		io.WriteString(res, fmt.Sprintf(&quot;item value: %f\n&quot;, item.Value))
	}
}
</pre>
<p>由于各个 Web 服务看起来都差不多，所以这里并没有实现所有的。在实际的应用中，添加商品到订单和管理用途的展示订单也应当作为 Web 服务。</p>
<p>关于这段代码的作用，最为明显的就是它没有做太多工作！接口层，正确的设计就是简单，这是因为他们的主要任务是在不同层之间传输数据。这是要点。实际情况是这段代码其实无法识别某个 HTTP 请求是来自于用例层的。</p>
<p>再次强调，注入是为了用来处理依赖。OrderInteractor 在生产环境可能实际上是 usecases.OrderInteractor，这意味着在单元测试的时候可以被替换，让 Web 服务在模拟环境下被测试。也就是说单元测试可以仅仅测试处理 Web 服务本身的这部分行为（在调用 OrderInteractor.Items 的时候是使用“userId”作为第一个请求参数吗？）。</p>
<p>讨论一下完整的 Web 服务可能的样子是值得的。这里没有对身份进行验证，确信请求提供的 userId 是合法的——在真实应用中，Web 服务处理程序可能需要从会话中获取请求的用户，例如从 cookie 中获得。</p>
<p>呃，等等，我们已经有了客户或者用户，现在又有了会话和 cookie？或多或少，总有些什么是一样的东西吧？</p>
<p>当然，或多或少，这就死要点。它们存在于不同的概念层次中。cookie 是非常底层的机制，处理那些在浏览器内存和 HTTP 头的字节包。会话就更加的抽象了，是用于确定不同的无状态请求属于某个客户端的概念——而 cookie 是具体的实现形式。</p>
<p>用户是相当高的层次——一个非常抽象的说法“一个可以被确认的、与应用进行交互的人”——用会话来作为具体的实现形式。最后，就是关于客户，这个实体可以被认为是纯粹的商业术语——用用户来……好吧，你应该懂的。</p>
<p>我建议让这些明确成为不同的东西，而不是面对由于在不同的概念层次使用使用相同的表达而带来的痛苦。当在会话的传输机制上，需要使用 SSL 客户端验证来代替 cookie 时，只需要在基础层的底层实现上引入一个新的库来进行验证，并且需要修改接口层的代码，以便确保会话使用这些底层的 HTTP 细节——而用户和客户并不知道这些变化。</p>
<p>在接口层，同样有用于从用例层的数据结构创建 HTML 响应的代码。在真实的应用中，这可能是通过一个在基础层中的模板库来完成的。</p>
<p>现在来看看我们应用的最后一块砖：持久化。我们已经有了可以工作的领域模型，有了让领域生效的用例，并且实现了允许用户通过 web 访问我们应用的接口。现在，剩下的全部工作就是将商业和应用数据保存到硬盘中，然后我们就准备好 IPO 了。</p>
<p>为了完成这个，就需要实现领域和用例层的抽象的存储接口。由于存储一边是底层的数据库，一边是高层的业务，所以这个实现属于接口层。存储的任务就是从其中一个传递给另一个。</p>
<p>鉴于其用途，对于接口层以及更低层次来说，某些存储的实现可能是受到限制的，例如编写运行时的纯内存的对象缓存，或者为了单元测试模拟一个存储。而大多数真实世界的存储都需要同外部的持久化机制进行通讯，例如数据库，也可能使用库来处理底层连接和查询细节——这些是在系统的基础层。因此，跟在其他层次一样，我们需要确保不会违反依赖原则。</p>
<p>这不是说存储是数据库透明的！它必然会知道要跟 SQL 数据库通讯。但是它只需要关心高层次，或者说，“逻辑”方面的内容。从这个表获取数据，将数据放到那个表。低层次，或者说“物理”方面，不在这个范围内——例如从网络连接到数据库，使用从库读主库写，处理超时等等，这都是基础层的破事儿。</p>
<p>换句话说，我们的存储更像是恰当的使用一个高层次接口，而将那些讨厌的基础细节加以隐藏，并且只是决定将哪些 SQL 发给服务器，就这样，就可以工作了。</p>
<p>现在在 src/interfaces/repositories.go 中建立这个接口：</p>
<pre class="brush: cpp; title: ; notranslate">
type DbHandler interface {
  Execute(statement string)
  Query(statement string) Row 
}

type Row interface {
  Scan(dest ...interface{})
  Next() bool
}
</pre>
<p>这确实是一个很有限的接口，不过它有了存储所需要的所有的操作：增、删、查、改行记录。</p>
<p>在基础层，将实现一些胶水代码，使用 sqlite3 库来和实际的数据库进行通讯，以便满足这个接口。不过首先，还是先完整实现存储：</p>
<p>$GOPATH/src/interfaces/repositories.go</p>
<pre class="brush: cpp; title: ; notranslate">
package interfaces

import (
	&quot;domain&quot;
	&quot;fmt&quot;
	&quot;usecases&quot;
)

type DbHandler interface {
	Execute(statement string)
	Query(statement string) Row
}

type Row interface {
	Scan(dest ...interface{})
	Next() bool
}

type DbRepo struct {
	dbHandlers map[string]DbHandler
	dbHandler  DbHandler
}

type DbUserRepo DbRepo
type DbCustomerRepo DbRepo
type DbOrderRepo DbRepo
type DbItemRepo DbRepo

func NewDbUserRepo(dbHandlers map[string]DbHandler) *DbUserRepo {
	dbUserRepo := new(DbUserRepo)
	dbUserRepo.dbHandlers = dbHandlers
	dbUserRepo.dbHandler = dbHandlers[&quot;DbUserRepo&quot;]
	return dbUserRepo
}

func (repo *DbUserRepo) Store(user usecases.User) {
	isAdmin := &quot;no&quot;
	if user.IsAdmin {
		isAdmin = &quot;yes&quot;
	}
	repo.dbHandler.Execute(fmt.Sprintf(`INSERT INTO users (id, customer_id, is_admin)
	                                    VALUES ('%d', '%d', '%v')`,
	                                    user.Id, user.Customer.Id, isAdmin))
	customerRepo := NewDbCustomerRepo(repo.dbHandlers)
	customerRepo.Store(user.Customer)
}

func (repo *DbUserRepo) FindById(id int) usecases.User {
	row := repo.dbHandler.Query(fmt.Sprintf(`SELECT is_admin, customer_id
	                                         FROM users WHERE id = '%d' LIMIT 1`,
	                                         id))
	var isAdmin string
	var customerId int
	row.Next()
	row.Scan(&amp;isAdmin, &amp;customerId)
	customerRepo := NewDbCustomerRepo(repo.dbHandlers)
	u := usecases.User{Id: id, Customer: customerRepo.FindById(customerId)}
	u.IsAdmin = false
	if isAdmin == &quot;yes&quot; {
		u.IsAdmin = true
	}
	return u
}

func NewDbCustomerRepo(dbHandlers map[string]DbHandler) *DbCustomerRepo {
	dbCustomerRepo := new(DbCustomerRepo)
	dbCustomerRepo.dbHandlers = dbHandlers
	dbCustomerRepo.dbHandler = dbHandlers[&quot;DbCustomerRepo&quot;]
	return dbCustomerRepo
}

func (repo *DbCustomerRepo) Store(customer domain.Customer) {
	repo.dbHandler.Execute(fmt.Sprintf(`INSERT INTO customers (id, name)
	                                    VALUES ('%d', '%v')`,
	                                    customer.Id, customer.Name))
}

func (repo *DbCustomerRepo) FindById(id int) domain.Customer {
	row := repo.dbHandler.Query(fmt.Sprintf(`SELECT name FROM customers
	                                         WHERE id = '%d' LIMIT 1`,
	                                         id))
	var name string
	row.Next()
	row.Scan(&amp;name)
	return domain.Customer{Id: id, Name: name}
}

func NewDbOrderRepo(dbHandlers map[string]DbHandler) *DbOrderRepo {
	dbOrderRepo := new(DbOrderRepo)
	dbOrderRepo.dbHandlers = dbHandlers
	dbOrderRepo.dbHandler = dbHandlers[&quot;DbOrderRepo&quot;]
	return dbOrderRepo
}

func (repo *DbOrderRepo) Store(order domain.Order) {
	repo.dbHandler.Execute(fmt.Sprintf(`INSERT INTO orders (id, customer_id)
	                                    VALUES ('%d', '%v')`,
	                                    order.Id, order.Customer.Id))
	for _, item := range order.Items {
		repo.dbHandler.Execute(fmt.Sprintf(`INSERT INTO items2orders (item_id, order_id)
		                                    VALUES ('%d', '%d')`,
		                                    item.Id, order.Id))
	}
}

func (repo *DbOrderRepo) FindById(id int) domain.Order {
	row := repo.dbHandler.Query(fmt.Sprintf(`SELECT customer_id FROM orders
	                                         WHERE id = '%d' LIMIT 1`,
	                                         id))
	var customerId int
	row.Next()
	row.Scan(&amp;customerId)
	customerRepo := NewDbCustomerRepo(repo.dbHandlers)
	order := domain.Order{Id: id, Customer: customerRepo.FindById(customerId)}
	var itemId int
	itemRepo := NewDbItemRepo(repo.dbHandlers)
	row = repo.dbHandler.Query(fmt.Sprintf(`SELECT item_id FROM items2orders
	                                        WHERE order_id = '%d'`,
	                                        order.Id))
	for row.Next() {
		row.Scan(&amp;itemId)
		order.Add(itemRepo.FindById(itemId))
	}
	return order
}

func NewDbItemRepo(dbHandlers map[string]DbHandler) *DbItemRepo {
	dbItemRepo := new(DbItemRepo)
	dbItemRepo.dbHandlers = dbHandlers
	dbItemRepo.dbHandler = dbHandlers[&quot;DbItemRepo&quot;]
	return dbItemRepo
}

func (repo *DbItemRepo) Store(item domain.Item) {
	available := &quot;no&quot;
	if item.Available {
		available = &quot;yes&quot;
	}
	repo.dbHandler.Execute(fmt.Sprintf(`INSERT INTO items (id, name, value, available)
	                                    VALUES ('%d', '%v', '%f', '%v')`,
	                                    item.Id, item.Name, item.Value, available))
}

func (repo *DbItemRepo) FindById(id int) domain.Item {
	row := repo.dbHandler.Query(fmt.Sprintf(`SELECT name, value, available
	                                         FROM items WHERE id = '%d' LIMIT 1`,
	                                         id))
	var name string
	var value float64
	var available string
	row.Next()
	row.Scan(&amp;name, &amp;value, &amp;available)
	item := domain.Item{Id: id, Name: name, Value: value}
	item.Available = false
	if available == &quot;yes&quot; {
		item.Available = true
	}
	return item
}
</pre>
<p>你会说：不只一个人认为，这是糟糕的代码！许多重复，没有错误处理，还有一股怪味儿。不过这个指南的要点既不是代码样式，也不是设计模式——这是关于应用的架构的，因此我希望这些随意编写的简单代码是直白，容易理解的，无关优雅和精明——噢，当然，我还是个 Go 的初学者，你们看到了。</p>
<p>值得注意的是，在每个存储中都有 dbHandlers map，这样就可以不放弃依赖注入的前提下让存储之间相互调用。如果某个存储使用了与其他不同的 DbHandler 实现，那么这些用到其他存储的存储也不需要明确知道谁用的什么；这也算是某种穷人的依赖注入容器吧。</p>
<p>让我们进一步解释下一个有趣的方法，DbUserRepo.FindById()。在我们的架构中，这是个很好的示例，说明了接口所做的一切就是从一个层次向另一个层次传递数据。FindById 从数据库读取行记录，并且生成领域和用例实体。我故意让数据库中的 User.IsAdmin 属性比正常更复杂了一些，用“yes”和“no”的 varchar 在数据库中进行排序。在用例实体 User 中，它被表达为一个布尔值。对这些不同的表达进行转换是存储的主要工作。</p>
<p>User 实例有一个 Customer 属性，这是一个领域实体；User 存储直接使用了 Customer 存储来获取它需要的实体。</p>
<p>现在不难想像当应用增长时，架构是如何为我们提供帮助的了。通过遵循依赖原则，可以对实体的细节进行重构而无需变更实体本身。我们可能会将 User 实体的拆分到多个表中，存储需要处理这些细节，从多个表中获取数据，放到一个实体里，但是存储的用户并不知道这些情况。</p>
<p>&#8212;&#8212;&#8211;翻译分隔线&#8212;&#8212;&#8211;</p>
<p>这篇东西拖得太久了，争取在 2012 年内完成吧……</p>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2012/12/%e7%bf%bb%e8%af%91%e5%9c%a8-go-%e5%ba%94%e7%94%a8%e4%b8%ad%e4%bd%bf%e7%94%a8%e7%ae%80%e6%98%8e%e6%9e%b6%e6%9e%844/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
<!-- This Quick Cache file was built for ( sfjslfksdafj www.mikespook.com/feed/ ) in 0.56800 seconds, on May 23rd, 2013 at 12:39 am UTC. -->
<!-- This Quick Cache file will automatically expire ( and be re-built automatically ) on May 23rd, 2013 at 1:39 am UTC -->
<!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
<!-- Quick Cache Is Fully Functional :-) ... A Quick Cache file was just served for ( sfjslfksdafj www.mikespook.com/feed/ ) in 0.00046 seconds, on May 23rd, 2013 at 12:54 am UTC. -->