原文地址: https://www.goinggo.net/2013/07/how-packages-work-in-go-language.html
自从开始用Go编程,如何最好的组织我的代码和使用package这个关键词对我来说很神秘。package关键字类似于C#中使用的namespace,通常将包名和目录结构绑定。
Go有如下web页面尝试解释如何编写Go代码:http://golang.org/doc/code.html
当我开始用Go进行编程时,这是我阅读的第一份文档。它被放在我的手头,主要因为我一直使用Visual Studio开发,代码以solution和工程文件的形式为你打包。
"如何写Go代码"开始于工作空间的概念。想一下你项目的根目录。如果你使用Visual Studio进行开发,这就是你的solution和工程文件被放置的地方。然后在你的工作空间你需要创建一个单一的叫做src的子目录。如果你想使用Go tools正确的运行代码这是必须的。在src目录开始,你可以以你想要的方式自由的组织你的代码。你需要理解Go团队展示的关于包和源码组织的方式,否则你可能基于该原则重构你的代码。
- 在我的电脑上创建了一个叫做Test的工作空间,并且需要一个src子目录。这是创建你的工程的第一步。
- 接着使用LiteIDE打开Test目录,创建子目录和空的Go源码文件。
首先我们为即将创建的应用创建了一个子目录。main函数所在目录名为可执行文件的名字。在我们的例子中main.go包含main函数并且被置于myprogram目录。这意味着我们的可执行文件将被命名为myprogram.
src中的其他子目录将包含我们工程的包。按照惯例,置于那些目录中的源文件应以目录名作为包名。在我们的例子中,新包被命名为samplepkg和subpkg。源码文件可命名为任何你喜欢的名字。
创建相同的包文件夹,随后创建空的GO源码文件。
如果你不把工作空间文件夹添加到GOPATH路径将会出现问题。
意识到自定义目录窗口是个文本框花了我一点时间。因此你可以直接编辑哪些文件夹。系统GOPATH是只读的。
Go设计者已经做了很多事情当命名他们的包和源码文件的时候。所有的文件和目录名均为小写,并且不使用下划线分割目录名中的单词。并且,包名和目录名相匹配。目录中的代码文件属于以目录命名的包。
看下Go源码目录下的标准库包。
bufio和builtin包目录时目录命名规范的最好示例。它们也可称作buf_io和built_in。
再看下Go源码目录,核验下源码文件的名字。
注意一些文件名中下划线的使用。当文件为测试代码或者被指定为特殊平台时,下划线将被使用。
通常的规范是将其中一个源码文件命名为与包名相同。bufio遵从此规范。然而,这是不太被严格遵从的规范。
在fmt包你将发现没有源码文件命名为fmt.go。我个人比较喜欢将我的包和源码文件不同命名。
最后,打开doc.go,format.go,print.go和scan.go文件。它们都在fmt包下声明。
让我们看下sample.go中的代码:
package samplepkg
import (
"fmt"
)
type Sample struct {
Name string
}
func New(name string) (sample * Sample) {
return &Sample{
Name: name,
}
}
func (sample * Sample) Print() {
fmt.Println("Sample Name:", sample.Name)
}
代码没有什么实际意义,仅使我们关注于两个重要的规范。
- 注意包名和子目录名相同。
- 有一个叫做New的函数。
New函数是一个Go包规范,用于创建一个核心类型或者为其他应用开发者使用而创建的不同类型。看下New是如何定义并在log.go,bufio.go和cypto.go中被实现的。
log.go
// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) * Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
bufio.go
// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) * Reader {
return NewReaderSize(rd, defaultBufSize)
}
crypto.go
// New returns a new hash.Hash calculating the given hash function. New panics
// if the hash function is not linked into the binary.
func (h Hash) New() hash.Hash {
if h > 0 && h < maxHash {
f := hashes[h]
if f != nil {
return f()
}
}
panic("crypto: requested hash function is unavailable")
}
自从各个包充当命名空间,每个包都可以有自己版本的New.在bufio.go中多个类型能够被创建,因此没有独立的New函数。这里你将会发现像NewReader和NewWriter这样的函数。
回看下sample.go.在我们的代码中核心类型是Sample,因此我们的New函数返回了一个Sample类型的引用。然后我们添加一个成员函数来输出New中提供的名称。
现在让我们看下sub.go的代码:
package subpkg
import (
"fmt"
)
type Sub struct {
Name string
}
func New(name string) (sub * Sub) {
return &Sub{
Name: name,
}
}
func (sub * Sub) Print() {
fmt.Println("Sub Name:", sub.Name)
}
除了我们命名类型为Sub代码是一样的。包名和子目录名相匹配,并且New返回了一个Sub类型的引用。
既然我们的包被正确定义并且编码正确,我们可以运行它们。
看下main.go的代码:
package main
import (
"samplepkg"
"samplepkg/subpkg"
)
func main() {
sample := samplepkg.New("Test Sample Package")
sample.Print()
sub := subpkg.New("Test Sub Package")
sub.Print()
}
我们的GOPATH指向工作空间目录,在我的实例中为/Users/bill/Spaces/Test,我们的导入引用从这点开始。这里我们基于目录结构对包进行引用。
接下来我们在每个需要的包中调用New函数并且创建那些核心类型的变量。
现在构建并且运行程序。你应该可以看见一个叫做myprogram的可执行程序被创建。
一旦你的程序准备好发布你就可以执行安装命令了。
安装命令会在你的工作空间创建bin和pkg文件夹。注意最后的可执行文件被放在bin目录下。
被编译的包被放置在pkg目录下。该目录中会创建一个描述目标结构并且映射源码目录的子目录。
这些被编译的包的存在能够避免go tool不必要的源码重新编译。
最后一个问题,在“如何运行Go代码”发布时发现,在之后构建你的代码时,.a文件被Go tool忽略。没有源码文件你将不能构建你的程序。我还没有在任何文档中发现关于这些.a文件在你构建Go程序时是如何被直接使用的真正解释。如果有人能分享一些关于这个主题的信息我将非常感激。
最后,最好遵从Go设计者的规范开发。看下他们写的源码,提供了如何进行开发的最好的文档。我们中的很多人为公司写代码。如果我们都遵从相同的规范,我们能够保证兼容性和可读性。当有疑问的时候打开/usr/local/go/src/pkg文件夹然后寻找解决办法。
一如往常,我希望这能对你理解Go编程语言有一点点帮助。
---------------------个人翻译,不喜勿喷--------------------!!
有疑问加站长微信联系(非本文作者)