《Go语言编程》
第7章 工 程 管 理
一般在介绍语言的书中不会出现工程管理的内容,但只要讲到Go语言,我们就不应该把语法和工程管理区分开。因为Go语言在设计之初就考虑了在语言层面如何更好地解决当前工程管理中的一些常见问题,而自带的Go工具则更是从工程管理的方方面面来考虑,并提供了完善的功能。让学习者在学习语言阶段自然而然地养成了工程的习惯,避免出现学院派和工程派两种明显不同的侧重点。
归根到底,Go语言是一门工程语言。
本章我们将从以下几个方面介绍Go语言所引入的工程管理思想、工具和规范:
- 代码风格
- 文档风格和管理
- 单元测试与性能测试方法
- 项目工程结构
- 跨平台开发
- 打包分发
但在介绍这些知识之前,我们会首先介绍一个名为Go的命令行工具,因为后面很多地方都会频繁提到它和使用它。
7.1 Go 命令行工具
Go作为一门新语言,除了语言本身的特性在高速发展外,其相关的工具链也在逐步完善。任何一门程序设计语言要能推广开来并投入到生产环境中,高效、易用、有好的开发环境都是必不可少的。
Go这个名字在本书内已经被用过两次:第一次当然就是这门语言的名字是Go语言;第二次则是在介绍并发编程的时候,我们隆重介绍了go关键字,因为它实现了Go语言最为核心的goroutine功能,使其真正成为一门适合开发高并发服务的语言。这里我们再提起这个名字,Go 又成了一个Go语言包自带的命令行工具的名字。从这个名字我们可以了解到,这个工具在Go语言设计者心目中的重要地位。
为了避免产生名字上的困扰,在本章中我们将用Gotool来称呼这个工具。
基本用法
在安装了Go语言的安装包后,就直接自带Gotool。我们可以运行以下命令来查看Gotool的版本,也就是当前你安装的Go语言的版本:
$ go version go version go1
Gotool的功能非常强大,我们可以查看一下它的功能说明,具体如下所示:
$ go help Go is a tool for managing Go source code. Usage: go command [arguments] The commands are: build compile packages and dependencies clean remove object files doc run godoc on package sources fix run go tool fix on packages fmt run gofmt on package sources get download and install packages and dependencies install compile and install packages and dependencies list list packages run compile and run Go program test test packages tool run specified go tool version print Go version vet run go tool vet on packages Use “go help [command]” for more information about a command. Additional help topics: gopath GOPATH environment variable packages description of package lists remote remote import path syntax testflag description of testing flags testfunc description of testing functions Use “go help [topic]” for more information about that topic.
简而言之,Gotool可以帮你完成以下这几类工作:
- 代码格式化
- 代码质量分析和修复
- 单元测试与性能测试
- 工程构建
- 代码文档的提取和展示
- 依赖包管理
- 执行其他的包含指令,比如6g等
我们会在随后的章节中一一展开Gotool的这些用法,你只需要记住这个工具的用法就能够开始专业的开发了。非常简单!
7.2 代码风格
“代码必须是本着写给人阅读的原则来编写,只不过顺便给机器执行而已。”这段话来自《计 算机程序设计与解释》,很精练地说明了代码风格的作用。当你阅读一段天津麻花似的代码时, 你会深深赞同上述观点。代码风格,是一个与人相关、与机器无关的问题。代码风格的好坏,不影响编译器的工作,但是影响团队协同,影响代码的复用、演进以及缺陷修复。
Go语言很可能是第一个将代码风格强制统一的语言。一些对于其他语言的编译器完全忽视的问题,在Go编译器前就会被认为是编译错误,比如如果花括号新起了一行摆放,你就会看到一个醒目的编译错误。这一点会让很多人觉得不可思议。无论喜欢还是讨厌,与其他那些单单编码规范就能写出一本书的语言相比,毫无疑问Go语言的这种做法简化了问题。
我们接下来介绍Go语言的编码规范,主要分两类,分别:由Go编译器进行强制的编码规范以及由Gotool推行的非强制性编码风格建议。其他的一些编码规范里通常会列出的细节,比如应该用Tab还是用4个空格,这些不在本书的讨论范围之内。
7.2.1 强制性编码规范
我们可以认为,由Go编译器进行强制的编码规范也是Go语言设计者认为最需要统一的代码风格,下面我们来一一诠释。
- 命名
命名规则涉及变量、常量、全局函数、结构、接口、方法等的命名。Go语言从语法层面进行了以下限定:任何需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则应该以小写字母开头。
软件开发行业最流行的两种命名法分别为骆驼命名法(类似于DoSomething和doSomething) 和下划线法(对应为do_something),而Go语言明确宣告了拥护骆驼命名法而排斥下划线法。骆驼命名法在Java和C#中得到官方的支持和推荐,而下划线命名法则主要用在C语言的世界里,比如Linux内核和驱动开发上。在开始Go语言编程时,还是忘记下划线法吧,避免写出不伦不类的名字。
- 排列
Go语言甚至对代码的排列方式也进行了语法级别的检查,约定了代码块中花括号的明确摆放位置。下面先列出一个错误的写法:
// 错误写法 func Foo(a, b int)(ret int, err error) { if a > b { ret = a } else { ret = b } }
这个写法对于众多在微软怀抱里长大的程序员们是最熟悉不过的了,但是在Go语言中会有编译错误:
$ go run hello.go # command-line-arguments ./hello.go:9: syntax error: unexpected semicolon or newline before { ./hello.go:18: non-declaration statement outside function body ./hello.go:19: syntax error: unexpected }
通过上面的错误信息就能猜到,是左花括号{的位置出问题了。下面我们将上面的代码调整一下:
// 正确写法 func Foo(a, b int)(ret int, err error) { if a > b { ret = a } else { ret = b } }
可以看到,else甚至都必须紧跟在之前的右花括号}后面并且不能换行。Go语言的这条规则基本上就保证了所有Go代码的逻辑结构写法是完全一致的,也不会再出现有洁癖的程序员在维护别人代码之前非要把所有花括号的位置都调整一遍的问题。
7.2.2 非强制性编码风格建议
Gotool中包含了一个代码格式化的功能,这也是一般语言都无法想象的事情。下面让我们来看看格式化工具的用法:
$ go help fmt usage: go fmt [packages] Fmt runs the command 'gofmt -l -w' on the packages named by the import paths. It prints the names of the files that are modified. For more about gofmt, see 'godoc gofmt'. For more about specifying packages, see 'go help packages'. To run gofmt with specific options, run gofmt itself. See also: go doc, go fix, go vet.
可以看出,用法非常简单。接下来试验一下它的格式化效果。
先看看我故意制造的比较丑陋的代码,具体如代码清单7-1所示。
代码清单7-1 hello1.go
package main import "fmt" func Foo(a, b int)(ret int, err error){ if a > b { return a, nil }else{ return b, nil } return 0, nil } func main() { i, _ := Foo(1, 2) fmt.Println("Hello, 世界", i)}
这段代码能够正常编译,也能正常运行,只不过丑陋的让人看不下去。现在我们用Gotool中的格式化功能美化一下(假设上述代码被保存为hello.go):
$ go fmt hello.go hello.go
执行这个命令后,将会更新hello1.go文件,此时再打开hello1.go,看一下旧貌换新颜的代码,如代码清单7-2所示。
代码清单7-2 hello2.go
package main import "fmt" func Foo(a, b int) (ret int, err error) { if a > b { return a, nil } else { return b, nil } return 0, nil } func main() { i, _ := Foo(1, 2) fmt.Println("Hello, 世界", i) }
可以看到,格式化工具做了很多事情:
- 调整了每条语句的位置
- 重新摆放花括号的位置
- 以制表符缩进代码
- 添加空格
当然,格式化工具不知道怎么帮你改进命名,这就不要苛求了。
这个工具并非只能一次格式化一个文件,比如不带任何参数直接运行go fmt的话,可以直接格式化当前目录下的所有*.go文件,或者也可以指定一个GOPATH中可以找到的包名。
只不过我们并不是非常推荐使用这个工具。毕竟,保持良好的编码风格应该是一开始写代码时就注意到的。第一次就写成符合规范的样子,以后也就不用再考虑如何美化的问题了。
- 7.3远程 import 支持
我们知道,如果要在Go语言中调用包,可以采用如下格式:
package main import ( "fmt" )
其中fmt是我们导入的一个本地包。实际上,Go语言不仅允许我们导入本地包,还支持在语言级别调用远程的包。假如,我们有一个用于计算CRC32的包托管于Github,那么可以这样写:
package main import ( "fmt" "github.com/myteam/exp/crc32" )
然后,在执行go build或者go install之前,只需要加这么一句:
go get github.com/myteam/exp/crc32
是的,就这么简单。
当我们执行完go get 之后,我们会在src 目录中看到github.com 目录,其中包含
myteam/exp/crc32目录。在crc32中,就是该包的所有源代码。也就是说,go工具会自动帮你获取位于远程的包源码,在随后的编译中,也会在pkg目录中生成对应的.a文件。
所有魔术般的工作,其实都是go工具在完成。对于Go语言本身来讲,远程包 github.com/myteam/ exp/crc32只是一个与fmt无异的本地路径而已。