forked from mikespook/Learning-Go-zh-cn
-
Notifications
You must be signed in to change notification settings - Fork 0
/
go-packages.tex
321 lines (275 loc) · 14.1 KB
/
go-packages.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
\epi{``\lstinline{^}''}{\textit{对是否有按位非的运算符的回答。}\\\textsc{KEN THOMPSON}}
\noindent{}
包是函数和数据的集合。
用 \first{\key{package}}{keyword!package} 关键字定义一个包。
文件名不需要与包名一致。包名的约定是使用小写字符。
Go 包可以由多个文件组成,但是使用相同的 \lstinline{package <name>} 这一行。
让我们在文件 \prog{even.go} 中定义一个叫做 \package{even}\index{package!even} 的包。
\lstinputlisting[label=src:even,caption=一个小包]{src/even.go}
名称以大写字母起始的是\emph{可导出}的,可以在包的外部调用(稍候会对此进行讨论)。
现在只需要构建这个包。在 \var{\$GOPATH} 下建立一个目录,复制
\file{even.go} 到这个目录(参阅第 \ref{chap:intro} 章的``\titleref{sec:building a program}'')。
\begin{display}
\pr \user{mkdir $GOPATH/src/even}
\pr \user{cp even.go $GOPATH/src/even}
\pr \user{go build}
\pr \user{go install}
\end{display}
现在就可以在程序 \prog{myeven.go} 中使用这个包:
\lstinputlisting[label=src:myeven,caption=even 包的使用]{src/myeven.go}
\showremarks
\begin{display}
\pr \user{go build myeven.go}
\pr \user{./myeven}
Is 5 even? false
\end{display}
在 Go 中,当函数的首字母大写的时候,函数会被从包中导出(在包外部可见,或者说公有的),
因此函数名是 \func{\emph{E}ven}。如果修改 \prog{myeven.go} 的第 10 行,使用未导出的函数
\func{even.odd}:
\noindent\lstinline{fmt.Printf("Is %d even? %v\n", i, even.odd(i))}
由于使用了\emph{私有}的函数,会得到一个编译错误:
\begin{display}
myeven.go:10: cannot refer to unexported name even.odd
\end{display}
\noindent{}概括来说:
\begin{itemize}
\item 公有函数的名字以\emph{大写}字母开头;
\index{public}
\item 私有函数的名字以\emph{小写}字母开头。
\index{private}
\end{itemize}
这个规则同样适用于定义在包中的其他名字(新类型、全局变量)。
注意,``大写''的含义并不仅限于 US ASCII,它被扩展到了所有大小写字母表
(拉丁文、希腊文、斯拉夫文、亚美尼亚文和埃及古文)。
\section{标识符}
像在其他语言中一样,Go 的命名是很重要的。在某些情况下,它们甚至有语义上的作用:
例如,在包外是否可见决定于首字母是不是大写。因此有必要花点时间讨论一下 Go 程序的命名规则。
使用的规则是让众所周知的缩写保持原样,而不是去尝试到底哪里应该大写。
\lstinline{Atoi},\lstinline{Getwd},\lstinline{Chmod}。
驼峰式对那些有完整单词的会很好:\lstinline{ReadFile},\lstinline{NewWriter},\lstinline{MakeSlice}。
\subsection{包名}
当包导入(通过 \first{\key{import}}{keyword!import})时,包名成为了内容的入口。
在\index{package!bytes}
\begin{lstlisting}
import "bytes"
\end{lstlisting}
之后,导入包的可以调用函数\func{bytes.Buffer}。任何使用这个包的人,
可以使用同样的名字访问到它的内容,因此这样的包名是好的:短的、简洁的、好记的。
根据规则,包名是小写的一个单词;不应当有下划线或混合大小写。
保持简洁(由于每个人都可能需要录入这个名字),不要过早考虑命名冲突。
包名是导入的默认名称。可以通过在导入语句指定其他名称来覆盖默认名称:
\begin{lstlisting}
import bar "bytes"
\end{lstlisting}
函数 \func{Buffer} 现在可以通过 \lstinline{bar.Buffer} 来访问。
这意味着,包名无需全局唯一;在少有的冲突中,可以给导入的包选择另一个名字在局部使用。
在任何时候,冲突都是很少见的,因为导入的文件名会用来做判断,到底是哪个包使用了。
另一个规则是包名就是代码的根目录名;在 \package{src/pkg/compress/gzip} 的包,
作为 \var{compress/gzip} 导入,但名字是 \package{gzip},
不是 \package{compress\_gzip} 也不是 \package{compressGzip}。\index{package!compress/gzip}
导入包将使用其名字引用到内容上,所以导入的包可以利用这个避免罗嗦。
例如,缓冲类型 \package{bufio}\index{package!bufio} 包的读取方法,叫做 \func{Reader},而不是
\func{BufReader},因为用户看到的是 \func{bufio.Reader} 这个清晰、简洁的名字。
更进一步说,由于导入的实例总是它们包名指向的地址,\func{bufio.Reader} 不会与
\func{io.Reader} 冲突。类似的,\func{ring.Ring}(包 \package{container/ring})创建新实例的函数——在 Go 中定义的构造函数——通常叫做
\func{NewRing},但是由于 \type{Ring} 是这个包唯一的一个导出的类型,同时,这个包也叫做
\package{ring}\index{package!ring},所以它可以只称作 \func{New}。
包的客户看到的是 \func{ring.New}。用包的结构帮助你选择更好的名字。
另外一个简短的例子是 \func{once.Do}(参看 \package{sync});\func{once.Do(setup)} 读起来很不错,并且
命名为 \lstinline{once.DoOrWaitUntilDone(setup)} 不会有任何帮助。长的名字不会让其变得容易阅读。
如果名字表达了一些复杂并且微妙的内容,更好的办法是编写一些有帮助的注释,而不是将所有信息都放入名字里。
最后,在 Go 中使用\first{混合大小写}{MixedCaps} MixedCaps 或者 mixedCaps,
而不是下划线区分含有多个单词的名字。
%% Advanced Go, leave it
%%\section{Initialization}
%%Every source file in a package can define an \func{init()} function. This function is
%%called after the variables in the package have gotten their value. The
%%\func{init()} function can be used to setup state before the execution
%%begins.
\section{包的文档}
\gomarginpar{这段复制于 \cite{effective_go}。}
每个包都应该有\emph{包注释},在 \key{package} 前的一个注释块。
对于多文件包,包注释只需要出现在一个文件前,任意一个文件都可以。
包注释应当对包进行介绍,并提供相关于包的整体信息。
这会出现在 \prog{go doc} 生成的关于包的页面上,并且相关的细节会一并显示。
来自官方 \package{regexp} 包的例子:
\begin{display}
/*
The regexp package implements a simple library for
regular expressions.
The syntax of the regular expressions accepted is:
regexp:
concatenation { '|' concatenation }
*/
package regexp
\end{display}
每个定义(并且导出)的函数应当有一小段文字描述该函数的行为。来自于
\package{fmt} 包的例子:
\begin{display}
// Printf formats according to a format specifier and writes to standard
// output. It returns the number of bytes written and any write error
// encountered.
func Printf(format string, a ...interface{}) (n int, err error)
\end{display}
\section{测试包}
在 Go 中为包编写单元测试应当是一种习惯。编写测试需要包含
\package{testing} 包和程序 \first{\prog{go test}}{toolin!go!test}。
两者都有良好的文档。
\prog{go test} 程序调用了所有的测试函数。
\package{even} 包没有定义任何测试函数,执行 \prog{go test},这样:
\begin{display}
\pr \user{go test}
? even [no test files]
\end{display}
在测试文件中定义一个测试来修复这个。测试文件也在包目录中,被命名为 \file{*\_test.go}。
这些测试文件同 Go 程序中的其他文件一样,但是 \prog{go test} 只会执行测试函数。
每个测试函数都有相同的标识,它的名字以 \lstinline{Test} 开头:
\begin{lstlisting}
func TestXxx(t *testing.T)
\end{lstlisting}
编写测试时,需要告诉 \prog{go test} 测试是失败还是成功。测试成功则直接返回。
当测试失败可以用下面的函数标记 \cite{go_doc}。这是非常重要的(参阅 \prog{go doc testing} 或
\prog{go help testfunc} 了解更多):
\begin{lstlisting}[numbers=none]
func (t *T) Fail()
\end{lstlisting}
\func{Fail} 标记测试函数失败,但仍然继续执行。
\begin{lstlisting}[numbers=none]
func (t *T) FailNow()
\end{lstlisting}
\func{FailNow} 标记测试函数失败,并且中断其执行。
当前文件中的其余的测试将被跳过,然后执行下一个文件中的测试。
\begin{lstlisting}[numbers=none]
func (t *T) Log(args ...interface{})
\end{lstlisting}
\func{Log} 用默认格式对其参数进行格式化,与
\func{Print()} 类似,并且记录文本到错误日志。
\begin{lstlisting}[numbers=none]
func (t *T) Fatal(args ...interface{})
\end{lstlisting}
\func{Fatal} 等价于 \func{Log()} 后跟随 \func{FailNow()}。
将这些凑到一起,就可以编写测试了。首先,选择名字 \file{even\_test.go}。
然后添加下面的内容:
\lstinputlisting[label=src:eventest,caption=even
包的测试,numbers=right]{src/even_test.go}
注意在第一行使用了 \lstinline{package even},测试使用与被测试的包使用相同的名字空间。
这不仅仅是为了方便,也允许了测试未导出的函数和结构。然后导入
\package{testing} 包,并且在第 5 行定义了这个文件中唯一的测试函数。
展示的 Go 代码应当没有任何惊异的地方:
检查了 \func{Even} 函数是否工作正常。
现在等待了好久的时刻到了,执行测试:
\begin{display}
\pr \user{go test}
ok even 0.001s
\end{display}
\noindent{}测试执行并且报告 \texttt{ok}。成功了!
如果重新定义测试函数,就可以看到一个失败的测试:
\begin{lstlisting}
// Entering the twilight zone
func TestEven(t *testing.T) {
if Even(2) {
t.Log("2 should be odd!")
t.Fail()
}
}
\end{lstlisting}
然后得到:
\begin{display}
FAIL even 0.004s
--- FAIL: TestEven (0.00 seconds)
\qquad\qquad2 should be odd!
FAIL
\end{display}
\noindent{}然后你可以以此行事(修复测试的实例)
\begin{lbar}{}
在编写包的时候应当一边写代码,一边写(一些)文档和测试函数。
这可以让你的程序更好,并且它展示了你的努力。
\end{lbar}
The Go test suite also allows you to incorperate example functions which
serve as documentation \emph{and} as tests. These functions need to
start with \texttt{Example}.
\begin{lstlisting}
func ExampleEven() {
if Even(2) {
fmt.Printf("Is even\n")
}
// Output:
// Is even
}
\end{lstlisting}
Those last two comments lines are part of the example, \prog{go test} uses those
to check the \emph{generated} output with the text in the comments. If there is
a mismatch the test fails.
\section{常用的包}
标准的 Go 代码库中包含了大量的包,并且在安装 Go 的时候多数会伴随一起安装。
浏览 \file{\$GOROOT/src/pkg} 目录并且查看那些包会非常有启发。
无法对每个包就加以解说,不过下面的这些值得讨论:
\footnote{描述来自包的 \prog{go doc}。额外的解释用斜体。}
\begin{description}
\item[\package{fmt}]{\index{package!fmt}
包 \package{fmt} 实现了格式化的 I/O 函数,这与 C 的 \func{printf} 和 \func{scanf} 类似。
格式化短语派生于 C 。一些短语(\%-序列)这样使用:
\begin{description}
\item[\%v]{默认格式的值。当打印结构时,加号(\%+v)会增加字段名;}
\item[\%\#v]{Go 样式的值表达;}
\item[\%T]{带有类型的 Go 样式的值表达;}
\end{description}
}
\item[\package{io}]{\index{package!io}
这个包提供了原始的 I/O 操作界面。它主要的任务是对 os 包这样的原始的 I/O
进行封装,增加一些其他相关,使其具有抽象功能用在公共的接口上。
}
\item[\package{bufio}]{\index{package!bufio}
这个包实现了缓冲的 I/O。它封装于
\lstinline{io.Reader}
和
\lstinline{io.Writer}
对象,创建了另一个对象(Reader 和 Writer)在提供缓冲的同时实现了一些文本 I/O 的功能。
}
\item[\package{sort}]{\index{package!sort}
\package{sort} 包提供了对数组和用户定义集合的原始的排序功能。
}
\item[\package{strconv}]{\index{package!strconv}
\package{strconv} 包提供了将字符串转换成基本数据类型,或者从基本数据类型转换为字符串的功能。
}
\item[\package{os}]{\index{package!os}
\package{os} 包提供了与平台无关的操作系统功能接口。其设计是 Unix 形式的。
}
\item[\package{sync}]{\index{package!sync}
\package{sync} 包提供了基本的同步原语,例如互斥锁。
}
\item[\package{flag}]{\index{package!flag}
\package{flag} 包实现了命令行解析。
\emph{参阅 ``\titleref{sec:option parsing}'' 在第 \pageref{sec:option parsing} 页。}
}
\item[\package{encoding/json}]{\index{package!encoding/json}
\package{encoding/json} 包实现了编码与解码 RFC 4627 \cite{RFC4627} 定义的 JSON 对象。
}
\item[\package{html/template}]{\index{package!html/template}
数据驱动的模板,用于生成文本输出,例如 HTML。
将模板关联到某个数据结构上进行解析。模板内容指向数据结构的元素(通常结构的字段或者 map 的键)
控制解析并且决定某个值会被显示。模板扫描结构以便解析,而``游标'' @ 决定了当前位置在结构中的值。
}
\item[\package{net/http}]{\index{package!net/http}
\package{net/http} 实现了 HTTP 请求、响应和 URL 的解析,并且提供了可扩展的 HTTP 服务和基本的 HTTP 客户端。
}
\item[\package{unsafe}]{\index{package!unsafe}
\package{unsafe} 包包含了 Go 程序中数据类型上所有不安全的操作。
\emph{通常无须使用这个。}
}
\item[\package{reflect}]{\index{package!reflect}
\package{reflect} 包实现了运行时反射,允许程序通过抽象类型操作对象。
通常用于处理静态类型 \lstinline|interface{}| 的值,并且通过 \func{Typeof}
解析出其动态类型信息,通常会返回一个有接口类型 \type{Type} 的对象。
\emph{参阅 \ref{chap:interfaces},第 ``\titleref{sec:introspection and reflection}'' 节}。
}
\item[\package{os/exec}]{\index{package!os/exec}
\package{os/exec} 包执行外部命令。
}
\end{description}
\section{练习}
\input{ex-packages/ex-stack-package.tex}
\input{ex-packages/ex-calc.tex}
\cleardoublepage
\section{答案}
\shipoutAnswer