您的位置:首页 > 编程语言 > Go语言

Go语言学习笔记 - 第十章 包机制和包的组织结构(The Go Programming Language)

2020-01-13 06:07 381 查看

第十章 包机制和包的组织结构

  • 第十章和第十一章主要讲述的是如何将一个工程组织成一系列的包,如果获取,构建,测试,性能测试,剖析,写文档,并且将这些包分享出去。

10.1包简介

划重点

  • Go语言的闪电般的编译速度主要得益于三个语言特性: 第一点,所有导入的包必须在每个文件的开头显式声明,这样的话编译器就没有必要读取和分析整个源文件来判断包的依赖关系。
  • 第二点,禁止包的环状依赖,因为没有循环依赖,包的依赖关系形成一个有向无环图,每个包可以被独立编译,而且很可能是被并发编译。
  • 第三点,编译后包的目标文件不仅仅记录包本身的导出信息,目标文件同时还记录了包的依赖关系。

10.2导入路径

划重点

  • 每个包是由一个全局唯一的字符串所标识的导入路径定位。

10.3包声明

划重点

  • 默认的包名就是包导入路径名的最后一段,因此即使两个包的导入路径不同,它们依然可能有一个相同的包名。例如,math/rand包和crypto/rand包的包名都是rand。
  • 默认包名一般采用导入路径名的最后一段的约定也有三种例外情况: 一、包对应一个可执行程序,也就是main包,这时候main包本身的导入路径是无关紧要的,这个包编译完之后必须调用连接器生成一个可执行程序。
  • 二、包所在的目录中可能有一些文件名是以test.go为后缀的Go源文件,并且这些源文件声明的包名也是以
    _test
    为后缀名的。
    go test
    会忽略前缀为
    _
    .
    的目录
  • 三、一些依赖版本号的管理工具会在导入路径后追加版本号信息,例如"gopkg.in/yaml.v2"。这种情况下包的名字并不包含版本号后缀,而是yaml。

10.4导入声明

划重点

  • 下面两种方式都是等价的:
import "fmt"
import "os"
import (
"fmt"
"os"
)
  • 导入的包之间可以通过添加空行来分组;通常将来自不同组织的包独自分组。包的导入顺序无关紧要,但是在每个分组中一般会根据字符串顺序排列。(gofmt和goimports工具都可以将不同分组导入的包独立排序。)
import (
"fmt"
"html/template"
"os"
"golang.org/x/net/html"
"golang.org/x/net/ipv4"
)
  • 如果我们想同时导入两个有着名字相同的包,例如math/rand包和crypto/rand包,那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突。这叫做导入包的重命名。导入包的重命名只影响当前的源文件。
import (
"crypto/rand"
mrand "math/rand" // alternative name mrand avoids conflict
)
  • 导入包重命名是一个有用的特性: 解决名字冲突
  • 用一个简短名称代替一些自动生成的代码中可能比较笨重的包名。
  • 可以帮助避免和本地普通变量名产生冲突。
  • 遇到包循环导入的情况,Go语言的构建工具将报告错误。
  • 10.5包的匿名导入

    划重点

    • 如果只是导入一个包而并不使用导入的包将会导致一个编译错误
    • 用下划线
      _
      空白标识符来重命名导入的包表示包不能被访问,这会有助于我们计算包级变量的初始化表达式和执行导入包的init初始化函数,这叫做包的匿名导入
    import _ "image/png" // register PNG decoder

    常用库及方法

    • image.Decode
      image.RegisterFormat
    • jpeg.Encode
      jpeg.Options
    • os.Exit

    10.6包和命名

    划重点

    • 一些关于Go语言独特的包和成员命名的约定 当创建一个包,一般要用短小的包名,但也不能太短导致难以理解。比如,不要将一个类似imageutil或ioutilis的通用包命名为util
    • 尽量避免包名使用可能被经常用于局部变量的名字,这样可能导致用
      重命名导入包,例如前面看到的path包
    • 包名一般采用单数的形式。标准库的bytes、errors和strings使用了复数形式,这是为了避免和预定义的类型冲突,同样还有go/types是为了避免和type关键字冲突。
    • 要避免包名有其它的含义。例如,2.5节中我们的温度转换包temp几乎是临时变量的同义词;使用temperature作为包名,虽然有名字并没有表达包的真实用途;tempconv包名类似于strconv标准包,比较推荐
    • 当设计一个包的时候,需要考虑包名和成员名两个部分如何很好地配合。比如,bytes.Equal,flag.Int,http.Get,json.Marshal
    • 其它一些包,可能只描述了单一的数据类型,例如html/template和math/rand等,只暴露一个主要的数据结构和与它相关的方法,还有一个以New命名的函数用于创建实例,这可能导致一些名字重复,例如template.Template或rand.Rand
    • 还有像net/http包那样含有非常多的名字和种类不多的数据类型,因为它们都是要执行一个复杂的复合任务。尽管有大量的类型和更多的函数,但是包中最重要的成员名字却是简单明了的:Get、Post、Handle、Error、Client、Server等

    10.7工具

    划重点

    • Go语言工具箱的具体功能,包括如何下载、格式化、构建、测试和安装Go语言编写的程序
    • 工具箱功能: 一个包管理,用于包的查询、计算的包依赖关系、从远程版本控制系统和下载它们等任务
    • 一个构建系统,计算文件的依赖关系,然后调用编译器、汇编器和连接器构建程序
    • 一个单元测试和基准测试的驱动程序
  • go最常用的命令:
  • $ go
    ...
    build compile packages and dependencies
    clean remove object files
    doc show documentation for package or symbol
    env print Go environment information
    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
    version print Go version
    vet run go tool vet on packages
    Use "go help [command]" for more information about a command.

    10.7.1工作区结构

    划重点

    • 对于大多数的Go语言用户,只需要配置一个名叫GOPATH的环境变量,用来指定当前工作目录即可。当需要切换到不同工作区的时候,只要更新GOPATH就可以了。
    • GOPATH对应的工作区目录有三个子目录: src子目录用于存储源代码。每个包被保存在与$GOPATH/src的相对路径为包导入路径的子目录中
    • pkg子目录用于保存编译后的包的目标文件
    • bin子目录用于保存编译后的可执行程序
  • GOROOT用来指定Go的安装目录,还有它自带的标准库包的位置。
  • GOOS环境变量用于指定目标操作系统(例如android、linux、darwin或windows)
  • GOARCH环境变量用于指定处理器的类型,例如amd64、386或arm等
  • 10.7.2下载包

    划重点

    • go get
      可以下载一个单一的包或者用
      ...
      下载整个子目录里面的每个包。Go语言工具箱的go命令同时计算并下载所依赖的每个包
    • go get
      命令支持当前流行的托管网站GitHub、Bitbucket和Launchpad,可以直接向它们的版本控制系统请求代码。
      go help importpath
      可以获取相关信息来获取其他的代码托管网站,这可能需要指定版本控制系统的具体路径和协议,例如 Git或Mercurial。
    • go get
      命令获取的代码是真实的本地存储仓库,而不仅仅只是复制源文件,因此你依然可以使用版本管理工具比较本地代码的变更或者切换到其它的版本。
    • go get -u
      将确保所有的包和依赖的包的版本都是最新的,然后重新编译和安装它们。如果不包含该标志参数的话,而且如果包已经在本地存在,那么代码那么将不会被自动更新。
    • go get -u
      对于发布程序则可能是不合适的,因为本地程序可能需要对依赖的包做精确的版本依赖管理。
    • 通常的解决方案是使用vendor的目录用于存储依赖包的固定版本的源代码,对本地依赖的包的版本更新也是谨慎和持续可控的。
    • 通过
      go help gopath
      命令查看Vendor的帮助文档
    • go get
      请求HTML页面时包含了
      go-get
      参数,以区别普通的浏览器请求,
      http://gopl.io/ch1/helloworld?go-get=1
      ,可以通过此查看代码的真实托管地址

    10.7.3构建包

    划重点

    • go build
      命令编译命令行参数指定的每个包。如果包是一个库,则忽略输出结果;如果包的名字是main,
      go build
      将调用连接器在当前目录创建一个可执行程序;以导入路径的最后一段作为可执行程序的名字。
    • 因为每个目录只包含一个包,因此每个对应可执行程序或者叫Unix术语中的命令的包,会要求放到一个独立的目录中。这些目录有时候会放在名叫cmd目录的子目录下面,例如用于提供Go文档服务的golang.org/x/tools/cmd/godoc命令就是放在cmd子目录
    • 每个包可以由它们的导入路径指定,或者用一个相对目录的路径知指定,相对路径必须以 . 或 … 开头。如果没有指定参数,那么默认指定为当前目录对应的包。
    • go build
      示例:
    // OK
    $ cd $GOPATH/src/gopl.io/ch1/helloworld
    $ go build
    ----------------------------------------
    // OK
    $ cd anywhere
    $ go build gopl.io/ch1/helloworld
    ----------------------------------------
    // OK
    $ cd $GOPATH
    $ go build ./src/gopl.io/ch1/helloworld
    ----------------------------------------
    // ERROR
    $ cd $GOPATH
    $ go build src/gopl.io/ch1/helloworld
    Error: cannot find package "src/gopl.io/ch1/helloworld".
    ----------------------------------------
    // OK
    $ go build quoteargs.go
    • go run
      命令实际上是结合了构建和运行的两个步骤,这对于一次性运行的程序很有用。
      go run
      的第一行的参数列表中,第一个不是以
      .go
      结尾的将作为可执行程序的参数运行。
    • go build
      命令构建指定的包和它依赖的包,然后丢弃除了最后的可执行文件之外所有的中间编译结果。
    • go install
      命令和
      go build
      命令很相似,但是它会保存每个包的编译成果,而不是将它们都丢弃。被编译的包会被保存到
      $GOPATH/pkg
      目录下,目录路径和 src目录路径对应,可执行程序被保存到
      $GOPATH/bin
      目录。
    • go install
      命令和
      go build
      命令都不会重新编译没有发生变化的包,这可以使后续构建更快捷。为了方便编译依赖的包,
      go build -i
      命令将安装每个目标所依赖的包。
    • 因为编译对应不同的操作系统平台和CPU架构,
      go install
      命令会将编译结果安装到
      GOOS
      GOARCH
      对应的目录。例如,在Mac系统,
      golang.org/x/net/html
      包将被安装到
      $GOPATH/pkg/darwin_amd64
      目录下的
      golang.org/x/net/html.a
      文件。
    • 只需要设置好目标对应的
      GOOS
      GOARCH
      ,就可以针对不同操作系统或CPU进行交叉构建。
      runtime.GOOS
      ,
      runtime.GOARCH
      可以打印所对应的操作系统和CPU。

    10.7.4包文档

    划重点

    • Go语言的编码风格鼓励为每个包提供良好的文档。包中每个导出的成员和包声明前都应该包含目的和用法说明的注释。
    • 每个包源文件的第一行是包的摘要说明,注释后仅跟着包声明语句。
    • 如果注释后仅跟着包声明语句,那注释对应整个包的文档。包文档对应的注释只能有一个(译注:其实可以有多个,它们会组合成一个包文档注释),包注释可以出现在任何一个源文件中。如果包的注释内容比较长,一般会放到一个独立的源文件中;
      fmt
      包注释就有300行之多。这个专门用于保存包文档的源文件通常叫
      doc.go
    • 注释中函数的参数或其它的标识符并不需要额外的引号或其它标记注明
    // Fprintf formats according to a format specifier and writes to >w.
    // It returns the number of bytes written and any write error >encountered.
    func Fprintf(w io.Writer, format string, a ...interface{}) (int, >error)
    • go doc
      命令,该命令打印包的声明和每个成员的文档注释,该命令并不需要输入完整的包导入路径或正确的大小写
    // 获取整个包的文档:
    $ go doc time
    // 获取某个具体包成员的注释文档
    $ go doc time.Since
    // 某个具体包的一个方法的注释文档
    $ go doc time.Duration.Seconds
    // 获取encoding/json包的 (*json.Decoder).Decode 方法的文档
    $ go doc json.decode
    • 第二个工具,名字也叫
      godoc
      ,它提供可以相互交叉引用的HTML页面,但是包含和
      go doc
      命令相同以及更多的信息。godoc的在线服务 ,包含了成千上万的开源包的检索工具。也可以在本地运行godoc服务。其中
      -analysis=type
      -analysis=pointer
      命令行标志参数用于打开文档和代码中关于静态分析的结果。
    $ godoc -http :8000
    // 通过http://localhost:8000/pkg进行查看

    10.7.5内部包

    划重点

    • internal包,一个internal包只能被和internal目录有同一个父目录的包所导入。例如,net/http/internal/chunked内部包只能被net/http/httputil或net/http包导入,但是不能被net/url包导入。不过net/url包却可以导入net/http/httputil包。

    10.7.6查询包

    划重点

    • go list
      命令可以查询可用包的信息。
    • 测试包是否在工作区并打印它的导入路径
    $ go list github.com/go-sql-driver/mysql
    github.com/go-sql-driver/mysql
    • go list
      命令的参数还可以用
      "..."
      表示匹配任意的包的导入路径, 可以用它来列出工作区中的所有包
      go list ...
    • 或者指定目录下的所有包
      go list gopl.io/ch3/...
      ,
    • 某个主题相关的所有
      go list ...xml...
  • go list
    命令还可以获取每个包完整的元信息,
    -json
    命令行参数表示用JSON格式打印每个包的元信息,比如:
  • $ go list -json hash
    {
    "Dir": "/home/gopher/go/src/hash",
    "ImportPath": "hash",
    "Name": "hash",
    "Doc": "Package hash provides interfaces for hash functions.",
    "Target": "/home/gopher/go/pkg/darwin_amd64/hash.a",
    "Goroot": true,
    "Standard": true,
    "Root": "/home/gopher/go",
    "GoFiles": [
    "hash.go"
    ],
    "Imports": [
    "io"
    ],
    "Deps": [
    "errors",
    "io",
    "runtime",
    "sync",
    "sync/atomic",
    "unsafe"
    ]
    }
    • 命令行参数
      -f
      则允许用户使用
      text/template包(§4.6)
      的模板语言定义输出文本的格式。
    $ go list -f '{{join .Deps " "}}' strconv
    errors math runtime unicode/utf8 unsafe
    -----------------------------------------
    $ go list -f '{{.ImportPath}} -> {{join .Imports " "}}' >compress/...
    compress/bzip2 -> bufio io sort
    compress/flate -> bufio fmt io math sort strconv
    compress/gzip -> bufio compress/flate errors fmt hash hash/crc32
    • go list
      命令对于一次性的交互式查询或自动化构建或测试脚本都很有帮助,可以用
      go help list
      命令查看可设置的字段和意义
    • 点赞
    • 收藏
    • 分享
    • 文章举报
    rabbit0206 发布了13 篇原创文章 · 获赞 3 · 访问量 127 私信 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: