1.5工程管理213$ cd ~/goyard$ go build hello.go$-/helloHello,world.你好,世界!可以看出,Go命令行工具是一个非常强大的源代码管理工具。我们将在第4章中详细讲解G命令行工具所包含的更多更强大的功能。从根本上说,Go命令行工具只是一个源代码管理工具,或者说是一个前端。真正的Go编译器和链接器被Go命令行工具隐藏在后面,我们可以直接使用它们:$ 6g helloworld.go$ 6l helloworld.6$./6.outHello,world。你好,世界!6g和61是64位版本的Go编译器和链接器,对应的32位版本工具为8g和81。Go还有另外一个GCC版本的编译器,名为gccgo,但不在本书的讨论范围内。1.4开发工具选择Google并没有随着Go1的发布推出官方的Go集成开发环境(IDE),因此开发者需要自行考虑和选择合适的开发工具。自前比较流行的开发工具如下:口文本编辑工具gedit(Linux)/Notepad++(Windows)/Fraise(MacOsX):口安装了GoClipse插件的Eclipse,集成性做得很好;口Vim/Emacs,万能开发工具;口LiteIDE,一款专为Go语言开发的集成开发环境。由于Go代码的轻巧和模块化特征,其实一般的文本编辑工具就可以胜任Go开发工作。本书的所有代码均使用Linux上的gedit工具完成。Go社区提供了各种文本编辑器的语法高亮设置方法,这在本书最后一章也有所介绍。1.5工程管理在实际的开发工作中,直接调用编译器进行编译和链接的场景是少而又少,因为在工程中不会简单到只有一个源代码文件,且源文件之间会有相互的依赖关系。如果这样一个文件一个文件逐步编译,那不亚于一场灾难。Go语言的设计者作为行业老将,自然不会忽略这一点。早期GC语言使用makefile作为临时方案,到了Go1发布时引人了强大无比的Go命令行工具。Go命令行工具的革命性之处在于彻底消除了工程文件的概念,完全用目录结构和包名来推导工程结构和构建顺序。针对只有一个源文件的情况讨论工程管理看起来会比较多余,因为这口以直接用gorun和gobui1d搞定。下面我们将用一个更接近现实的虚拟项目来展示Go语言的基本工程管理方法。假设有这样一个场景:我们需要开发一个基于命令行的计算器程序。下面为此程序的基本图灵社区会员soooldier(soooldier@live.com)专享尊重版权
1.5 工程管理 13 1 2 3 4 5 9 6 7 8 2 $ cd ~/goyard $ go build hello.go $ ./hello Hello, world. 你好,世界! 可以看出,Go命令行工具是一个非常强大的源代码管理工具。我们将在第4章中详细讲解Go 命令行工具所包含的更多更强大的功能。 从根本上说,Go命令行工具只是一个源代码管理工具,或者说是一个前端。真正的Go编译 器和链接器被Go命令行工具隐藏在后面,我们可以直接使用它们: $ 6g helloworld.go $ 6l helloworld.6 $ ./6.out Hello, world. 你好,世界! 6g和6l是64位版本的Go编译器和链接器,对应的32位版本工具为8g和8l。Go还有另外一个 GCC版本的编译器,名为 gccgo,但不在本书的讨论范围内。 1.4 开发工具选择 Google并没有随着Go 1的发布推出官方的Go集成开发环境(IDE),因此开发者需要自行考 虑和选择合适的开发工具。目前比较流行的开发工具如下: 文本编辑工具gedit(Linux)/Notepad++(Windows)/Fraise(Mac OS X); 安装了GoClipse插件的Eclipse,集成性做得很好; Vim/Emacs,万能开发工具; LiteIDE,一款专为Go语言开发的集成开发环境。 由于Go代码的轻巧和模块化特征,其实一般的文本编辑工具就可以胜任Go开发工作。本书 的所有代码均使用Linux上的gedit工具完成。 Go社区提供了各种文本编辑器的语法高亮设置方法,这在本书最后一章也有所介绍。 1.5 工程管理 在实际的开发工作中,直接调用编译器进行编译和链接的场景是少而又少,因为在工程中不 会简单到只有一个源代码文件,且源文件之间会有相互的依赖关系。如果这样一个文件一个文件 逐步编译,那不亚于一场灾难。Go语言的设计者作为行业老将,自然不会忽略这一点。早期Go 语言使用makefile作为临时方案,到了Go 1发布时引入了强大无比的Go命令行工具。 Go命令行工具的革命性之处在于彻底消除了工程文件的概念,完全用目录结构和包名来推 导工程结构和构建顺序。针对只有一个源文件的情况讨论工程管理看起来会比较多余,因为这可 以直接用go run和go build搞定。下面我们将用一个更接近现实的虚拟项目来展示Go语言的 基本工程管理方法。 假设有这样一个场景:我们需要开发一个基于命令行的计算器程序。下面为此程序的基本 图灵社区会员 soooldier(soooldier@live.com) 专享 尊重版权
14第1章初识Go语言用法:$ calc helpUSAGE:calc command [arguments]The commands are:sqrtSquare root of a non-negative value.addAddition of two values.$calcsqrt4#开根号2$calcadd12#加法3我们假设这个工程被分割为两个部分:口可执行程序,名为calc,内部只包含一二个calc.go文件;口算法库,名为simplemath,每个command对应于一个同名的go文件,比如add.go。则一个正常的工程目录组织应该如下所示:<calcproj><src><calc>calc.go-<simplemath>-add.go-add_test.go-sqrt.gosqrt_test.go<bin><pkg>#包将被安装到此处在上面的结构里,带尖括号的名字表示其为目录。xxxtest.go表示的是一个对于xxx.go的单元测试,这也是Go工程里的命名规则。为了让读者能够动手实践,这里我们会列出所有的源代码并以注释的方式解释关键内容,如代码清单1-5至代码清单1-9所示。需要注意的是,本示例主要用于示范工程管理,并不保证代码达到产品级质量。代码清单1-5calc.go//calc.gopackage mainimport“"os"//用于获得命今行参数os.Argsimport "fmt"import "simplemath"import"strconv"varUsage=func()ffmt.Println("UsAGE:calc command[arguments]fmt.Println("\nThecommandsare:Inltadd\tAdditionoftwovalues.InltsgrtltSguare图灵社区会员soooldier(soooldier@live.com)专享尊重版权
14 第 1 章 初识 Go 语言 用法: $ calc help USAGE: calc command [arguments] . The commands are: sqrt Square root of a non-negative value. add Addition of two values. $ calc sqrt 4 # 开根号 2 $ calc add 1 2 # 加法 3 我们假设这个工程被分割为两个部分: 可执行程序,名为calc,内部只包含一个calc.go文件; 算法库,名为simplemath,每个command对应于一个同名的go文件,比如add.go。 则一个正常的工程目录组织应该如下所示: <calcproj> ├─<src> ├─<calc> ├─calc.go ├─<simplemath> ├─add.go ├─add_test.go ├─sqrt.go ├─sqrt_test.go ├─<bin> ├─<pkg>#包将被安装到此处 在上面的结构里,带尖括号的名字表示其为目录。xxx_test.go表示的是一个对于xxx.go的单元 测试,这也是Go工程里的命名规则。 为了让读者能够动手实践,这里我们会列出所有的源代码并以注释的方式解释关键内容,如 代码清单1-5至代码清单1-9所示。需要注意的是,本示例主要用于示范工程管理,并不保证代码 达到产品级质量。 代码清单1-5 calc.go //calc.go package main import "os"// 用于获得命令行参数os.Args import "fmt" import "simplemath" import "strconv" var Usage = func() { fmt.Println("USAGE: calc command [arguments] .") fmt.Println("\nThe commands are:\n\tadd\tAddition of two values.\n\tsqrt\tSquare 图灵社区会员 soooldier(soooldier@live.com) 专享 尊重版权
151.5工程管理root of a non-negative value.")11func main()(args:=os.Argsif args ==Inil111len(args)<2(Usage()returnswitch args[o](case "add":if len(args)1=3(fmt.Println("UsAGE: calc add <integeri><integer2>")return1errl := strconv.Atoi(args[1])v1,V2,err2 := strconv.Atoi(args[2])if errl != nil ll err2 l= nil (fmt.PrintIn("USAGE: calc add<integerl><integer2>")return3ret := $simplemath.Add(vl,v2)fmt.Println("Result:",ret)case "sqrt":if len(args) 1= 2 (fmt.Println("USAGE: calc sqrt <integer>")return3err := strconv.Atoi(args[1])V,iferr 1= nilfmt.Println("USAGE: calc sqrt <integer>")return3ret := simplemath.Sqrt(v)fmt.Println("Result:", ret)default:Usage()1代码清单1-6add.go1/ add.gopackage simplemathfunc Add(a int,b int)int (return a + b1图灵社区会员soooldier(soooldier@live.com)专享尊重版权
1.5 工程管理 15 1 2 3 4 5 9 6 7 8 2 root of a non-negative value.") } func main() { args := os.Args if args == nil || len(args) < 2 { Usage() return } switch args[0] { case "add": if len(args) != 3 { fmt.Println("USAGE: calc add <integer1><integer2>") return } v1, err1 := strconv.Atoi(args[1]) v2, err2 := strconv.Atoi(args[2]) if err1 != nil || err2 != nil { fmt.Println("USAGE: calc add <integer1><integer2>") return } ret := simplemath.Add(v1, v2) fmt.Println("Result: ", ret) case "sqrt": if len(args) != 2 { fmt.Println("USAGE: calc sqrt <integer>") return } v, err := strconv.Atoi(args[1]) if err != nil { fmt.Println("USAGE: calc sqrt <integer>") return } ret := simplemath.Sqrt(v) fmt.Println("Result: ", ret) default: Usage() } } 代码清单1-6 add.go // add.go package simplemath func Add(a int, b int) int { return a + b } 图灵社区会员 soooldier(soooldier@live.com) 专享 尊重版权
第1章16初识GO语言代码清单1-7add_test.go// add_test.gopackage simplemathimport "testing"func TestAddi(t *testing.T)r :=Add(1, 2)ifr=3(t.Errorf("Add(1,2) failed.Got &d,expected 3.",r)7代码清单1-8sqrt.go// sqrt.gopackage simplemathimport"math"func Sqrt(i int)int (v := math.Sqrt(float64(i))return int(v))代码清单1-9sqrttest.goIl sqrt_test.gopackage simplemathimport "testing"func TestSqrti(t *testing.T)(v := Sqrt(16)ifv!=4tt.Errorf("Sqrt(i6)failed.Got gv,expected 4.",v)11为了能够构建这个工程,需要先把这个工程的根目录加人到环境变量GOPATH中。假设calcproj目录位于~/goyard下,则应编辑~/.bashrc文件,并添加下面这行代码:exportGOPATH=-/goyard/calcproj然后执行以下命令应用该设置:$ source -/.bashrcGOPATH和PATH环境变量一样,也可以接受多个路径,并且路径和路径之间用冒号分割。设置完GOPATH后,现在我们开始构建工程。假设我们希望把生成的可执行文件放到calcproj/bin目录中,需要执行的一系列指令如下:图灵社区会员soooldier(soooldier@live.com)专享尊重版权
16 第 1 章 初识 Go 语言 代码清单1-7 add_test.go // add_test.go package simplemath import "testing" func TestAdd1(t *testing.T) { r := Add(1, 2) if r != 3 { t.Errorf("Add(1, 2) failed. Got %d, expected 3.", r) } } 代码清单1-8 sqrt.go // sqrt.go package simplemath import "math" func Sqrt(i int) int { v := math.Sqrt(float64(i)) return int(v) } 代码清单1-9 sqrt_test.go // sqrt_test.go package simplemath import "testing" func TestSqrt1(t *testing.T) { v := Sqrt(16) if v != 4 { t.Errorf("Sqrt(16) failed. Got %v, expected 4.", v) } } 为了能够构建这个工程,需要先把这个工程的根目录加入到环境变量GOPATH中。假设calcproj 目录位于~/goyard下,则应编辑~/.bashrc文件,并添加下面这行代码: export GOPATH=~/goyard/calcproj 然后执行以下命令应用该设置: $ source ~/.bashrc GOPATH和PATH环境变量一样,也可以接受多个路径,并且路径和路径之间用冒号分割。 设置完GOPATH后,现在我们开始构建工程。假设我们希望把生成的可执行文件放到 calcproj/bin目录中,需要执行的一系列指令如下: 图灵社区会员 soooldier(soooldier@live.com) 专享 尊重版权
1.5工程管理17$cd~/goyard/calcproj$ mkdir bin$ cd bin$go build calc顺利的话,将在该目录下发现生成的一个叫做calc的可执行文件,执行该文件以查看帮助信息并进行算术运算:$./calcUsAGE:calc command[arguments].The commands are:addAddition of two valuessqrtsquare root of a non-negative value.$./calc add23Result:5$./calc sqrt 9Result: 3从上面的构建过程中可以看到,真正的构建命令就一句:go build calc这就是为什么说Go命令行工具是非常强天的。我们不需要写makefile,因为这个工具会替我们分析,知道目标代码的编译结果应该是一个包还是一个可执行文件,并分析import语句以了解包的依赖关系,从而在编译calc.go之前先把依赖的simplemath编译打包好。Go命令行程序制定的目录结构规则让代码管理变得非常简单。另外,我们在写simplemath包时,为每一个关键的函数编写了对应的单元测试代码,分别位于addtest.go和sqrttest.go中。那么我们到底怎么运行这些单元测试呢?这也非常简单。因为已经设置了GOPATH,所以可以在任意目录下执行以下命令:$ go test simplemathoksimplemath0.014s可以看到,运行结果列出了测试的内容、测试结果和测试时间。如果我故意把addtest.go的代码改成这样的错误场景:func TestAddi(t *testing.T)(r := Add(1, 2)if!=2(//这里本该是3,故意改成2测试错误场景t.Errorf("Add(1,2)failed.Got&d,expected3.",r)1J然后我们再次执行单元测试,将得到如下的结果:$ go test simplemathFAIL:TestAdd1 (0.00seconds)add_test.go:8:Add(1,2) failed. Got 3, expected 3.FAILFAILsimplemath0.013s打印的错误信息非常简洁,却已经足够让开发者快速定位到问题代码所在的文件和行数,从而在最短的时间内确认是单元测试的问题还是程序的问题。图灵社区会员soooldier(soooldier@live.com)专享尊重版权
1.5 工程管理 17 1 2 3 4 5 9 6 7 8 2 $ cd ~/goyard/calcproj $ mkdir bin $ cd bin $ go build calc 顺利的话,将在该目录下发现生成的一个叫做calc的可执行文件,执行该文件以查看帮助信 息并进行算术运算: $ ./calc USAGE: calc command [arguments] . The commands are: addAddition of two values. sqrtSquare root of a non-negative value. $ ./calc add 2 3 Result: 5 $ ./calc sqrt 9 Result: 3 从上面的构建过程中可以看到,真正的构建命令就一句: go build calc 这就是为什么说Go命令行工具是非常强大的。我们不需要写makefile,因为这个工具会替我 们分析,知道目标代码的编译结果应该是一个包还是一个可执行文件,并分析import语句以了 解包的依赖关系,从而在编译calc.go之前先把依赖的simplemath编译打包好。Go命令行程序制 定的目录结构规则让代码管理变得非常简单。 另外,我们在写simplemath包时,为每一个关键的函数编写了对应的单元测试代码,分别 位于add_test.go和sqrt_test.go中。那么我们到底怎么运行这些单元测试呢?这也非常简单。因为 已经设置了GOPATH,所以可以在任意目录下执行以下命令: $ go test simplemath ok simplemath0.014s 可以看到,运行结果列出了测试的内容、测试结果和测试时间。如果我故意把add_test.go的 代码改成这样的错误场景: func TestAdd1(t *testing.T) { r := Add(1, 2) if r != 2 { // 这里本该是3,故意改成2测试错误场景 t.Errorf("Add(1, 2) failed. Got %d, expected 3.", r) } } 然后我们再次执行单元测试,将得到如下的结果: $ go test simplemath - FAIL: TestAdd1 (0.00 seconds) add_test.go:8: Add(1, 2) failed. Got 3, expected 3. FAIL FAILsimplemath0.013s 打印的错误信息非常简洁,却已经足够让开发者快速定位到问题代码所在的文件和行数,从 而在最短的时间内确认是单元测试的问题还是程序的问题。 图灵社区会员 soooldier(soooldier@live.com) 专享 尊重版权