翻译-如何组织Go代码

原文地址:http://talks.golang.org/2014/organizeio.slide,译文尽量贴近原文,会适当的增删,勿拍。

Go 程序都是由包构成,每个文件都以 package 开头,程序主体执行从 main 包开始:

package main

import "fmt"

func main() {
        fmt.Println("Hello, world!")
}

最简单的一个 Go 程序,只需要写一个 main 包即可。

hello world 第二行导入了 fmt 包,第四行 Printlnfmt 包里的公开导出的函数。

示例包:fmt

// Package fmt implements formatted I/O.
package fmt

// Println formats using the default formats for its
// operands and writes to standard output.
func Println(a ...interface{}) (n int, err error) {
        ...
}

func newPrinter() *pp {
        ...
}

其中 Println 是公开(public)函数,只需要第一个字母大写,在其他模块内都是公开可见的(区别于私有 private 函数)。

另外一个函数 newPrinter 则是私有函数,以小写字母开头,这样的函数只能在模块(fmt)内使用。

通过包来组织和区分相关代码,包可大可小,也可以由多个文件构成,这些文件需要在一个目录内。

Go 源码 net/http 包导出 100 个命名(共 18 个文件),而 errors 包只导出 1 个名字(只有 1 文件)。

如何给包命名?

取名尽量简单和可阅读,不要使用下划线:

也不要太笼统,比如 util 意思很模糊。

包名也是类型和函数的一部分,比如:

buf := new(bytes.Buffer)

这里不要取名为 bytes.BytesBuffer,过于累赘。

选择一个好的命名真的很重要,大部分时候我们写代码都在为取名而烦恼啊。。。

包的测试

测试文件和源码同级目录,文件名都是 _test.go 结尾:

package fmt

import "testing"

var fmtTests = []fmtTest{
        {"%d", 12345, "12345"},
        {"%v", 12345, "12345"},
        {"%t", true, "true"},
}

func TestSprintf(t *testing.T) {
        for _, tt := range fmtTests {
            if s := Sprintf(tt.fmt, tt.val); s != tt.out {
                    t.Errorf("...")
            }
        }
}

差不多了,建议一个源码文件对应一个测试文件。

代码组织

工作目录(workspace)

新版 Go 依赖 $GOPATH 这个环境变量来区分工作目录。代码放在同一个 workspace 内,可以包含自己的包或远程仓库(如: githg),用过 go get 应该都知道。

Go 相关工具可以很容易的区分出工作空间,构建时候不需要依赖 Makefilebuild.xml 类似文件 ,按照目录划分好就可以啦。

比如:

$GOPATH/
    src/
       github.com/user/repo/
           mypkg/
              mysrc1.go
              mysrc2.go
           cmd/mycmd/
                main.go
   bin/
       mycmd

这里示例一下工作目录吧

mkdir /tmp/gows
GOPATH=/tmp/gows

$GOPATH 环境变量前面以及提到,后续的安装和构建包都依赖这个:

$ go get github.com/dsymonds/fixhub/cmd/fixhub

go get 命令则会远程仓库下载源码到自己的工作目录内(需要相关的版本工具,比如:git

go install 命令则可以编译和分发文件到 $GOPATH/bin/fixhub 位置。

我的工作目录大概如下:

$GOPATH/
    bin/fixhub                              # installed binary
    pkg/darwin_amd64/                       # compiled archives
        code.google.com/p/goauth2/oauth.a
        github.com/...
    src/                                    # source repositories
        code.google.com/p/goauth2/
            .hg
            oauth                           # used by package go-github
            ...
        github.com/
            golang/lint/...                 # used by package fixhub
                .git
            google/go-github/...            # used by package fixhub
                .git
            dsymonds/fixhub/
                .git
                client.go
                cmd/fixhub/fixhub.go        # package main

可以看到下载了很多远程的仓库,编译后可执行文件是 bin/fixhub

为什么要划分目录结构呢?

通过目录结构来区分,可以免去配置的麻烦,不像其他语言会依赖 Makefilebuild.xml 文件。

减少配置的时间,可以更多的时间去码字,另外大部分用户目录结构都类似,这样也更有利于去分享代码。

有时候我们可能有很多的工作目录,但是大部分人只用一个。有个建议这样来声明 GOPATH

GOPATH=$HOME

这样 srcbinpgk 都会在你的用户 home 目录下了。

这里的建议应该是针对一台机器上多个用户来说吧,一般来说 $HOME/bin 可能在 PATH 中了,调用起来更为方便一些。

快速切换目录

CDPATH=$GOPATH/src/github.com:$GOPATH/src/code.google.com/p

$ cd dsymonds/fixhub
/tmp/gows/src/github.com/dsymonds/fixhub
$ cd goauth2
/tmp/gows/src/code.google.com/p/goauth2
$

还可以写个函数放入到 ~/.profile 内:

gocd () { cd `go list -f '{{.Dir}}' $1` }

这样移动更方便一些:

$ gocd .../lint
/tmp/gows/src/github.com/golang/lint
$

go 工具有很多功能:

$ go help
Go is a tool for managing Go source code.

Usage:

go command [arguments]

The commands are:

常用的功能参数如:

build       compile packages and dependencies
get         download and install packages and dependencies
install     compile and install packages and dependencies
test        test packages

还有一些其他有用的功能参数,比如:vetfmt,后者是大招啊。

依赖管理

默认情况下 go get 都会去下载最新的代码然后构建,除非被中断。

在开发环境下这个没什么影响,但肯定不适用于生产环境。

略去数十字(vendoring 啥意思..),不喜欢这种做法,推荐 gopkg.in

gopkg.in/user/pkg.v3 -> github.com/user/pkg (branch/tag v3, v3.N, or v.3.N.M)

命名

程序其实就是一堆名字的构成,取名还是挺花时间和成本的。

简单来说,长的名字浪费空间,而且在可读性方面很重要,好的名字一眼就能看意图。

多花点时间,取一些简短和可理解的名字吧。

命名风格

不建议这样:

文档

文档位置在模块或者导出的名字前面:

// Join concatenates the elements of elem to create a single string.
// The separator string sep is placed between elements in the resulting string.
func Join(elem []string, sep string) string {

通过 godoc 在 web 查看该函数时候,文档位置位于函数原型下方(图略去)。

请用英语书写文档句子和段落(无视我大汉字啊)

// Join concatenates…         good
// This function…             bad

包文档位于模块最上面:

	// Package fmt…
	package fmt	

关于 Go docs 请参考:http://talks.golang.org/2014/godoc.org

最后,这是一份演讲稿,相对来说内容偏少,随意看看吧。。