<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Google Go语言编码风格规范 on Tony Bai</title><link>https://tonybai.com/google-go-style/</link><description>Recent content in Google Go语言编码风格规范 on Tony Bai</description><generator>Hugo</generator><language>zh-cn</language><copyright>2004-2026 Tony Bai. 版权所有.</copyright><atom:link href="https://tonybai.com/google-go-style/index.xml" rel="self" type="application/rss+xml"/><item><title>Google Go语言编码风格规范：决定篇</title><link>https://tonybai.com/google-go-style/google-go-style-decisions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://tonybai.com/google-go-style/google-go-style-decisions/</guid><description>&lt;p&gt;&lt;img alt="Image 1" loading="lazy" src="https://tonybai.com/images/wp-content/uploads/google-go-style/google-go-style-1.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tonybai.com/google-go-style/google-go-style-decisions"&gt;本文永久链接&lt;/a&gt; – &lt;a href="https://tonybai.com/google-go-style/google-go-style-decisions"&gt;https://tonybai.com/google-go-style/google-go-style-decisions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本页面是2022年11月中旬Google发布的&lt;a href="https://google.github.io/styleguide/go/index"&gt;Go语言编码风格规范&lt;/a&gt;系列文档的&lt;a href="https://google.github.io/styleguide/go/decisions"&gt;决定篇&lt;/a&gt;的中译版。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tonybai.com/google-go-style"&gt;概述&lt;/a&gt; | &lt;a href="https://tonybai.com/google-go-style/google-go-style-guide"&gt;指南&lt;/a&gt; | &lt;a href="https://tonybai.com/google-go-style/google-go-style-decisions"&gt;决定&lt;/a&gt; | &lt;a href="https://tonybai.com/google-go-style/google-go-style-best-practices"&gt;最佳实践&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;注意：这是介绍&lt;a href="https://tonybai.com/google-go-style"&gt;Google Go编码风格的系列文档&lt;/a&gt;的一部分。本文档是规范性的，但不具备权威性。本篇级别要低于&lt;a href="https://tonybai.com/google-go-style/google-go-style-guide"&gt;指南篇&lt;/a&gt;，更多信息请参见&lt;a href="https://tonybai.com/google-go-style"&gt;概述篇&lt;/a&gt;。&lt;/p&gt;</description><content:encoded><![CDATA[<p><img alt="Image 1" loading="lazy" src="/images/wp-content/uploads/google-go-style/google-go-style-1.png"></p>
<p><a href="https://tonybai.com/google-go-style/google-go-style-decisions">本文永久链接</a> – <a href="https://tonybai.com/google-go-style/google-go-style-decisions">https://tonybai.com/google-go-style/google-go-style-decisions</a></p>
<p>本页面是2022年11月中旬Google发布的<a href="https://google.github.io/styleguide/go/index">Go语言编码风格规范</a>系列文档的<a href="https://google.github.io/styleguide/go/decisions">决定篇</a>的中译版。</p>
<p><a href="https://tonybai.com/google-go-style">概述</a> | <a href="https://tonybai.com/google-go-style/google-go-style-guide">指南</a> | <a href="https://tonybai.com/google-go-style/google-go-style-decisions">决定</a> | <a href="https://tonybai.com/google-go-style/google-go-style-best-practices">最佳实践</a></p>
<p>注意：这是介绍<a href="https://tonybai.com/google-go-style">Google Go编码风格的系列文档</a>的一部分。本文档是规范性的，但不具备权威性。本篇级别要低于<a href="https://tonybai.com/google-go-style/google-go-style-guide">指南篇</a>，更多信息请参见<a href="https://tonybai.com/google-go-style">概述篇</a>。</p>
<hr>
<h2 id="关于">关于</h2>
<p>本文档包含了Go编码风格的决定，旨在整合统一Go可读性导师给出的建议，并提供标准的指导、解释和例子。</p>
<p>本文档并不是完全版，而是会随着时间的推移而增加内容。如果<a href="https://tonybai.com/google-go-style-guide">核心风格指南</a>与这里给出的建议相矛盾，则以风格指南为准，本文档也应进行相应更新。</p>
<p>请参阅<a href="https://tonybai.com/google-go-style">“概述篇”</a>中的全套Go编码风格文档。</p>
<p>以下部分已从本风格决定篇中移至<a href="https://tonybai.com/google-go-style/google-go-style-guide">指南篇</a>了：</p>
<ul>
<li>驼峰命名</li>
<li>格式化</li>
<li>行长</li>
</ul>
<h2 id="命名">命名</h2>
<p>关于命名的总体指导，见<a href="https://tonybai.com/google-go-style/google-go-style-guide">核心风格指南</a>中的命名部分。以下各节将对命名进行细分场景的进一步的说明。</p>
<h3 id="下划线">下划线</h3>
<p>Go语言中的名称一般不应包含下划线，不过这一原则有三个例外：</p>
<ul>
<li>仅由生成的代码导入的包名可以包含下划线。关于如何选择由多个单词组成的包名的更多细节，请参阅下面<strong>包名</strong>一节。</li>
<li>在*_test.go文件中的Test、Benchmark和Example函数名称可以包含下划线。</li>
<li>与操作系统或cgo互操作的低级库可以重复使用标识符，就像在<a href="https://pkg.go.dev/syscall#pkg-constants">syscall</a>中那样。但这在大多数代码库中是非常罕见的。</li>
</ul>
<h3 id="包名">包名</h3>
<p>Go包的名称应该是短小的，并且只包含小写字母。由多个单词组成的包名的各个单词之间应该没有间断。例如，我们使用<a href="https://pkg.go.dev/text/tabwriter">tabwriter</a>作为包名，而不是tabWriter，TabWriter或者tab_writer。</p>
<p>避免选择那些可能被常用的局部变量名称<a href="https://google.github.io/styleguide/go/best-practices#shadowing">遮蔽</a>的包名。例如，usercount是一个比count更好的包名，因为count是一个常用的变量名。</p>
<p>Go包的名字不应该有下划线。如果您需要导入一个名称中含有下划线的包（通常来自自动生成的代码或第三方代码），必须<strong>在导入时将其重命名为适合在Go代码中使用的名称</strong>。</p>
<p>这方面的一个例外是，仅由生成的代码导入的包名可以包含下划线，具体的例子包括：</p>
<ul>
<li>在外部测试包的包名中使用_test后缀，例如集成测试。</li>
<li>在<a href="https://go.dev/blog/examples">包级的文档示例</a>中使用_test后缀。</li>
</ul>
<p>避免使用诸如util、utility、common、helper等信息量不足的包名。更多信息参见所谓的<a href="https://google.github.io/styleguide/go/best-practices#util-packages">“实用程序包”</a>。</p>
<p>当一个导入的包被重命名时（例如：import foopb “path/to/foo_go_proto”），该包的本地名称必须符合上述规则，因为本地名称决定了包中的符号在文件中的引用方式。如果一个包在多个文件中被导入时都做了重命名，特别是在相同或邻近的包中，为了保持一致性，应尽可能使用相同的本地名称。</p>
<p>也请参见：<a href="https://go.dev/blog/package-names">关于包名的Go博文</a>。</p>
<h3 id="receiver命名">Receiver命名</h3>
<p><a href="https://golang.org/ref/spec#Method_declarations">Receiver</a>变量的名称必须满足下面要求：</p>
<ul>
<li>短（通常为一或两个字母的长度）。</li>
<li>类型本身的缩略语。</li>
<li>统一应用于该类型的每一个Receiver。</li>
</ul>
<p><img alt="Image 2" loading="lazy" src="/images/wp-content/uploads/google-go-style/google-go-style-decisions-2.png"></p>
<table>
	<thead>
			<tr>
					<th>长名字</th>
					<th>更好的名字</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>func (tray Tray)</code></td>
					<td><code>func (t Tray)</code></td>
			</tr>
			<tr>
					<td><code>func (info *ResearchInfo)</code></td>
					<td><code>func (ri *ResearchInfo)</code></td>
			</tr>
			<tr>
					<td><code>func (this *ReportWriter)</code></td>
					<td><code>func (w *ReportWriter)</code></td>
			</tr>
			<tr>
					<td><code>func (self *Scanner)</code></td>
					<td><code>func (s *Scanner)</code></td>
			</tr>
	</tbody>
</table>
<h3 id="常量命名">常量命名</h3>
<p>常量名称必须像Go中的其他名称一样使用驼峰命名法(MixedCaps)(导出的常量以大写字母开始，而未导出的常量以小写字母开始)。即便这与其他语言中的惯例有悖。常量名称不应该是其值的衍生物，而应该解释其值所表示的内容：</p>
<pre><code>// Good:
const MaxPacketSize = 512 // 译注：不应命名为FiveHundredTwelve

const (
    ExecuteBit = 1 &lt;&lt; iota
    WriteBit
    ReadBit
)
</code></pre>
<p>不要使用非驼峰命名的常量名称或带有K前缀的常量：</p>
<pre><code>// Bad:
const MAX_PACKET_SIZE = 512
const kMaxBufferSize = 1024
const KMaxUsersPergroup = 500
</code></pre>
<p>根据常量的作用来命名常量，而不是根据它们的值。如果一个常量除了它的值之外没有作用，那么就没有必要把它定义为一个常量。</p>
<pre><code>// Bad:
const Twelve = 12

const (
    UserNameColumn = &quot;username&quot;
    GroupColumn    = &quot;group&quot;
)
</code></pre>
<h3 id="首字母缩略词initialisms">首字母缩略词(Initialisms)</h3>
<p>名称中的单词如果是首字母缩略词或缩略语（例如，URL和NATO）应该使用相同的大小写命名。URL应该使用URL或url（如urlPony，或URLPony），而不是Url。这也适用于ID，当它是“标识符”的缩写时使用appID而不是appId。</p>
<ul>
<li>在有多个首字母缩略词的名字中（例如XMLAPI，它包含XML和API两个首字母缩略词），每个首字母缩略词中的字母都应该具有一致的大小写，但名字中的多个首字母缩略词不需要有相同的大小写。</li>
<li>在包含小写字母的首字母缩略词名称中（例如DDoS、iOS、gRPC），首字母应该保持其在缩略词中原有的样子，除非你需要为了导出该名称而改变第一个字母。在这些情况下，整个首字母缩略词中的字母应该采用相同的大小写（例如，ddos, IOS, GRPC）。</li>
</ul>
<p><img alt="Image 3" loading="lazy" src="/images/wp-content/uploads/google-go-style/google-go-style-decisions-3.png"></p>
<table>
	<thead>
			<tr>
					<th>首字母缩略词</th>
					<th>作用域</th>
					<th>正确的</th>
					<th>不正确的</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>XML API</td>
					<td>导出的</td>
					<td><code>XMLAPI</code></td>
					<td><code>XmlApi</code>, <code>XMLApi</code>, <code>XmlAPI</code>, <code>XMLapi</code></td>
			</tr>
			<tr>
					<td>XML API</td>
					<td>非导出的</td>
					<td><code>xmlAPI</code></td>
					<td><code>xmlapi</code>, <code>xmlApi</code></td>
			</tr>
			<tr>
					<td>iOS</td>
					<td>导出的</td>
					<td><code>IOS</code></td>
					<td><code>Ios</code>, <code>IoS</code></td>
			</tr>
			<tr>
					<td>iOS</td>
					<td>非导出的</td>
					<td><code>iOS</code></td>
					<td><code>ios</code></td>
			</tr>
			<tr>
					<td>gRPC</td>
					<td>导出的</td>
					<td><code>GRPC</code></td>
					<td><code>Grpc</code></td>
			</tr>
			<tr>
					<td>gRPC</td>
					<td>非导出的</td>
					<td><code>gRPC</code></td>
					<td><code>grpc</code></td>
			</tr>
			<tr>
					<td>DDoS</td>
					<td>导出的</td>
					<td><code>DDoS</code></td>
					<td><code>DDOS</code>, <code>Ddos</code></td>
			</tr>
			<tr>
					<td>DDoS</td>
					<td>非导出的</td>
					<td><code>ddos</code></td>
					<td><code>dDoS</code>, <code>dDOS</code></td>
			</tr>
	</tbody>
</table>
<h3 id="getter命名">Getter命名</h3>
<p>函数和方法名称不应该使用Get或get前缀，除非底层概念使用“get”一词（例如HTTP GET）。我们倾向于直接用那个要Get的事物名词进行命名，例如使用Counts而不是GetCounts。</p>
<p>如果函数涉及到执行复杂的计算或执行远程调用，可以使用不同的词，如Compute或Fetch来代替Get，以使读者清楚地知道函数调用可能需要时间，并可能阻塞或失败。</p>
<h3 id="变量命名">变量命名</h3>
<p>一般的经验法则是，名字的长度应该与它使用的范围大小成正比，与它在该范围内使用的次数成反比。一个在文件范围内创建的变量，其名称可能需要由多个单词组成，而一个在单个内部代码块范围内的变量可能只需要用一个单词命名，甚至只有一两个字符，以保持代码的清晰和避免无关的信息。</p>
<p>这里有一个粗略的基线。这些数字准则并不是严格的规则。根据上下文、<a href="https://google.github.io/styleguide/go/guide#clarity">清晰度</a>和<a href="https://google.github.io/styleguide/go/guide#concision">简明度</a>来应用判断：</p>
<ul>
<li>小范围是指执行一个或两个小操作的范围，例如1-7行。</li>
<li>中等范围是指几个小的或一个大的操作，例如8-15行。</li>
<li>大范围是指一个或几个大的操作，例如15-25行。</li>
<li>一个非常大的范围是任何跨越一页以上的范围（比如，超过25行）。</li>
</ul>
<p>一个在小范围内可能非常清楚的名字（例如，c代表一个计数器）在大范围内可能是不足以胜任的，需要在代码中进一步澄清以提醒读者其目。当一个范围内有许多变量，或者有表示类似的值或概念的变量时，我们可能需要比范围建议的更长的变量名。</p>
<p>概念的特殊性也可以帮助保持变量名称的简明性。例如，假设只有一个单一的数据库在使用，那么像db这样的短小的变量名称，通常可能是为非常小的范围保留的，即使范围非常大，也可能保持完全清晰。在这种情况下，根据作用域的大小，以database命名可以接受的，但并不是必须的，因为db是一个非常常见的词的简称，几乎没有其他的解释。</p>
<p>局部变量的名称应该反映它所包含的内容以及它在当前上下文环境中的使用方式，而不是数值的来源。例如，通常情况下，最好的局部变量名称与结构体字段或protocol buffer的字段名称不一样。</p>
<p>一般来说：</p>
<ul>
<li>像count或options这样的单个单词名称是一个好的起点。</li>
<li>可以添加额外的词来区分类似的名字，例如userCount和projectCount。</li>
<li>不要为了节省几次打字而简单地删除字母。例如，Sandbox比Sbx更受欢迎，特别是对于导出的名字。</li>
<li>在大多数变量名称中省略<a href="https://google.github.io/styleguide/go/decisions#repetitive-with-type">类型和类似类型</a>的词。
<ul>
<li>对于一个数字来说，userCount是一个比numUsers或usersInt更好的名字。</li>
<li>对于一个切片来说，users是一个比userSlice更好的名字。</li>
<li>如果一个值在范围内有两个版本，那么名字中包含一个类似类型的修饰词是可以接受的，例如，你可能将命令行输入的内容存储在ageString中，并使用age作为解析后的值。</li>
</ul>
</li>
<li>省略那些从<a href="https://google.github.io/styleguide/go/decisions#repetitive-in-context">周围上下文</a>中可以清楚看出的词。例如，在一个UserCount方法的实现中，一个叫做userCount的局部变量可能是多余的；count、users、甚至c都具备一样的可读性。</li>
</ul>
<h4 id="单字母变量名">单字母变量名</h4>
<p>单字母变量名可以是一个有用的工具，可以最大限度地减少<a href="https://google.github.io/styleguide/go/decisions#repetition">重复</a>，但这类变量名也可能使代码出现不必要地不透明。把它们的使用限制在其全词含义很明显的情况下，而且如果用全词来代替单字母变量，就会出现重复的情况。</p>
<p>一般来说：</p>
<ul>
<li>对于一个方法接收器变量，最好使用一个或两个字母的名字。</li>
<li>对常见的类型使用熟悉的变量名通常是有帮助的。
<ul>
<li>r代表io.Reader或*http.Request</li>
<li>w代表io.Writer或http.ResponseWriter。</li>
</ul>
</li>
<li>单字母标识符作为整数循环变量是可以接受的，特别是对于索引（如i）和坐标（如x和y）。</li>
<li>当范围很小时，缩写可以成为可接受的循环标识符，例如，for _, n := range nodes { … }。</li>
</ul>
<h3 id="重复">重复</h3>
<p>Go源代码应该避免不必要的重复。这方面的一个常见来源是重复的名称，它往往包括不必要的单词或重复其上下文或类型。如果相同或类似的代码段在很近的地方多次出现，代码本身也会出现不必要的重复。</p>
<p>重复性命名有多种形式，包括：</p>
<h4 id="包-vs-导出符号的名称">包 vs. 导出符号的名称</h4>
<p>当命名导出的符号时，包的名称在你的包外总是可见的，所以这两者之间的冗余信息应该减少或消除。如果一个包只导出了一个类型，并且是以包本身的名字命名的，如果需要一个构造函数，那么构造函数的规范名称就是New。</p>
<p>例子：</p>
<p><img alt="Image 4" loading="lazy" src="/images/wp-content/uploads/google-go-style/google-go-style-decisions-5.png"></p>
<table>
	<thead>
			<tr>
					<th>重复的名字</th>
					<th>更好的名字</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>widget.NewWidget</code></td>
					<td><code>widget.New</code></td>
			</tr>
			<tr>
					<td><code>widget.NewWidgetWithName</code></td>
					<td><code>widget.NewWithName</code></td>
			</tr>
			<tr>
					<td><code>db.LoadFromDatabase</code></td>
					<td><code>db.Load</code></td>
			</tr>
			<tr>
					<td><code>goatteleportutil.CountGoatsTeleported</code></td>
					<td><code>gtutil.CountGoatsTeleported</code> 或 <code>goatteleport.Count</code></td>
			</tr>
			<tr>
					<td><code>myteampb.MyTeamMethodRequest</code></td>
					<td><code>mtpb.MyTeamMethodRequest</code> 或 <code>myteampb.MethodRequest</code></td>
			</tr>
	</tbody>
</table>
<h4 id="变量名称-vs-类型">变量名称 vs. 类型</h4>
<p>编译器总是知道变量的类型，而且在大多数情况下，读者也可以通过变量的使用方式清楚地知道它是什么类型。只有当一个变量的值在同一范围内出现两次时，才有必要澄清它的类型。</p>
<p><img alt="Image 5" loading="lazy" src="/images/wp-content/uploads/google-go-style/google-go-style-decisions-4.png"></p>
<table>
	<thead>
			<tr>
					<th>重复的名字</th>
					<th>更好的名字</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>var numUsers int</code></td>
					<td><code>var users int</code></td>
			</tr>
			<tr>
					<td><code>var nameString string</code></td>
					<td><code>var name string</code></td>
			</tr>
			<tr>
					<td><code>var primaryProject *Project</code></td>
					<td><code>var primary *Project</code></td>
			</tr>
	</tbody>
</table>
<p>如果该值以多种形式出现，可以用一个额外的词来澄清，如raw和parsed，或者用底层表示法：</p>
<pre><code>// Good:
limitStr := r.FormValue(&quot;limit&quot;)
limit, err := strconv.Atoi(limitStr)


// Good:
limitRaw := r.FormValue(&quot;limit&quot;)
limit, err := strconv.Atoi(limitRaw)
</code></pre>
<h4 id="外部上下文-vs-本地名称">外部上下文 vs. 本地名称</h4>
<p>包含周围上下文信息的名字往往不仅没有带来好处，还会产生额外的噪音。包名、方法名、类型名、函数名、导入路径、甚至文件名都可以提供自动限定其中所有名称的上下文信息。</p>
<pre><code>// Bad:
// In package &quot;ads/targeting/revenue/reporting&quot;
type AdsTargetingRevenueReport struct{}

func (p *Project) ProjectName() string


// Good:
// In package &quot;ads/targeting/revenue/reporting&quot;
type Report struct{}

func (p *Project) Name() string


// Bad:
// In package &quot;sqldb&quot;
type DBConnection struct{}


// Good:
// In package &quot;sqldb&quot;
type Connection struct{}


// Bad:
// In package &quot;ads/targeting&quot;
func Process(in *pb.FooProto) *Report {
    adsTargetingID := in.GetAdsTargetingID()
}


// Good:
// In package &quot;ads/targeting&quot;
func Process(in *pb.FooProto) *Report {
    id := in.GetAdsTargetingID()
}
</code></pre>
<p>重复一般应在符号使用者的上下文中进行评估，而不是孤立地进行评估。例如，下面的代码有很多名字，在某些情况下可能是好的，但在上下文中是多余的。</p>
<pre><code>// Bad:
func (db *DB) UserCount() (userCount int, err error) {
    var userCountInt64 int64
    if dbLoadError := db.LoadFromDatabase(&quot;count(distinct users)&quot;, &amp;userCountInt64); dbLoadError != nil {
        return 0, fmt.Errorf(&quot;failed to load user count: %s&quot;, dbLoadError)
    }
    userCount = int(userCountInt64)
    return userCount, nil
}
</code></pre>
<p>相反，那些上下文或用法中明确的名称信息往往可以被省略：</p>
<pre><code>// Good:
func (db *DB) UserCount() (int, error) {
    var count int64
    if err := db.Load(&quot;count(distinct users)&quot;, &amp;count); err != nil {
        return 0, fmt.Errorf(&quot;failed to load user count: %s&quot;, err)
    }
    return int(count), nil
}
</code></pre>
<h2 id="注释">注释</h2>
<p>对注释的惯例（包括注释的内容、使用的风格、如何提供可运行的例子等）进行说明是为了更好地提升阅读公共API文档的体验。更多信息请参见<a href="http://golang.org/doc/effective_go.html#commentary">Effective Go</a>。</p>
<p><a href="https://tonybai.com/google-go-style-best-practices">最佳实践文档</a>中关于<a href="https://google.github.io/styleguide/go/best-practices#documentation-conventions">文档惯例</a>的部分将进一步讨论了这个问题。</p>
<p><strong>最佳实践</strong>：在开发和代码审查过程中使用<a href="https://google.github.io/styleguide/go/best-practices#documentation-preview">文档预览</a>，看看文档和可运行的例子是否有用，是否按照你期望的方式呈现。</p>
<p><strong>提示</strong>：Godoc很少使用特殊格式；列表和代码片段通常应该缩进以避免换行。除缩进外，一般应避免使用其他修饰方法。</p>
<h3 id="注释行的长度">注释行的长度</h3>
<p>确保注释即使在狭窄的屏幕上也能从源码中读到。</p>
<p>当一个注释变得太长时，建议将它拆成多个单行注释。在可能的情况下，争取使注释在80列宽的终端上也能很好阅读，但这并不是一个硬性规定；Go中的注释没有固定的行长限制。例如，标准库经常选择根据标点符号来中断注释，这有时会使个别行更接近60-70个字符。</p>
<p>有很多现有的代码中，注释的长度超过了80个字符。本指南不应作为修改这些代码的理由，作为可读性审查的一部分（见<a href="https://google.github.io/styleguide/go/guide#consistency">一致性</a>），尽管我们鼓励团队适时地更新注释以遵循本指南并作为其他重构的一部分。本指南的主要目标是确保所有Go可读性导师在提出建议时都能做出相同的建议。</p>
<p>关于注释的更多内容，请参见<a href="https://blog.golang.org/godoc-documenting-go-code">The Go Blog关于文档的这篇文章</a>。</p>
<pre><code># Good:
// This is a comment paragraph.
// The length of individual lines doesn't matter in Godoc;
// but the choice of wrapping makes it easy to read on narrow screens.
//
// Don't worry too much about the long URL:
// https://supercalifragilisticexpialidocious.example.com:8080/Animalia/Chordata/Mammalia/Rodentia/Geomyoidea/Geomyidae/
//
// Similarly, if you have other information that is made awkward
// by too many line breaks, use your judgment and include a long line
// if it helps rather than hinders.
</code></pre>
<p>避免在小屏幕上重复折行的注释，这是一种糟糕的读者体验：</p>
<pre><code># Bad:
// This is a comment paragraph. The length of individual lines doesn't matter in
Godoc;
// but the choice of wrapping causes jagged lines on narrow screens or in
Critique,
// which can be annoying, especially when in a comment block that will wrap
repeatedly.
//
// Don't worry too much about the long URL:
// https://supercalifragilisticexpialidocious.example.com:8080/Animalia/Chordata/Mammalia/Rodentia/Geomyoidea/Geomyidae/
</code></pre>
<h3 id="文档注释">文档注释</h3>
<p>所有顶层导出的名字都必须有文档注释，具有不明显的行为或意义的未导出的类型或函数声明也应该如此。这些注释应该是以被描述对象的名称开始的<a href="https://google.github.io/styleguide/go/decisions#comment-sentences">完整句子</a>。冠词（”a”、”an”、”the”）可以放在名字前面，使其读起来更自然。</p>
<pre><code>// Good:
// A Request represents a request to run a command.
type Request struct { ...

// Encode writes the JSON encoding of req to w.
func Encode(w io.Writer, req *Request) { ...
</code></pre>
<p>文档注释出现在<a href="https://pkg.go.dev/">Godoc</a>中，并被IDE显示出来，因此应该为使用该包的任何人编写文档注释。</p>
<p>文档注释适用于以下符号，如果它出现在一个结构体中，则适用于该组字段。</p>
<pre><code>// Good:
// Options configure the group management service.
type Options struct {
    // General setup:
    Name  string
    Group *FooGroup

    // Dependencies:
    DB *sql.DB

    // Customization:
    LargeGroupThreshold int // optional; default: 10
    MinimumMembers      int // optional; default: 2
}
</code></pre>
<p><strong>最佳实践</strong>：如果你有针对未导出代码的文档注释，请遵循与导出代码相同的习惯（即以未导出的名称开始注释）。这使得以后导出时很容易，只需在注释和代码中用新导出的名字替换未导出的名字即可。</p>
<h3 id="注释句子">注释句子</h3>
<p>作为完整的句子的注释应该像标准英语句子一样首词头字母大写并使用标点符号。(作为一个例外，在一个句子的开头使用非头母大写的标识符是可以的。这种情况可能最好只在段落的开头进行）。</p>
<p>作为句子片段的注释对标点符号和大写字母没有这样的要求。</p>
<p>文档注释应该始终是完整的句子，因此应该始终首词头字母大写和使用标点。简单的行末注释（特别是对结构体字段）可以是简单的短语，并假定字段名是短语的主语。</p>
<pre><code>// Good:
// A Server handles serving quotes from the collected works of Shakespeare.
type Server struct {
    // BaseDir points to the base directory under which Shakespeare's works are stored.
    //
    // The directory structure is expected to be the following:
    //   {BaseDir}/manifest.json
    //   {BaseDir}/{name}/{name}-part{number}.txt
    BaseDir string

    WelcomeMessage  string // displayed when user logs in
    ProtocolVersion string // checked against incoming requests
    PageLength      int    // lines per page when printing (optional; default: 20)
}
</code></pre>
<h3 id="例子">例子</h3>
<p>软件包应该清楚地记录它们的预期用途。尽量提供一个<a href="http://blog.golang.org/examples">可运行的例子</a>；例子将在Godoc中显示出来。可运行的例子属于测试文件，而不属于用于生产环境的源文件。请看这个例子（<a href="https://pkg.go.dev/time#example-Duration">Godoc</a>, <a href="https://cs.opensource.google/go/go/+/HEAD:src/time/example_test.go">source</a>）。</p>
<p>如果无法提供一个可运行的例子，也可以在代码注释中提供例子代码。与其他代码和命令行片段的注释一样，它应该遵循标准的格式化惯例。</p>
<h3 id="具名返回值参数">具名返回值参数</h3>
<p>在命名函数参数时，要考虑函数签名在Godoc中的呈现方式。函数本身的名称和返回值参数的类型通常足够清楚。</p>
<pre><code>// Good:
func (n *Node) Parent1() *Node
func (n *Node) Parent2() (*Node, error)
</code></pre>
<p>如果一个函数返回两个或更多相同类型的参数，为返回值参数添加名称可能会很有用：</p>
<pre><code>// Good:
func (n *Node) Children() (left, right *Node, err error)
</code></pre>
<p>如果调用者必须对特定的返回值参数采取行动，对它们的命名可以帮助提示行动是什么。</p>
<pre><code>// Good:
// WithTimeout returns a context that will be canceled no later than d duration
// from now.
//
// The caller must arrange for the returned cancel function to be called when
// the context is no longer needed to prevent a resource leak.
func WithTimeout(parent Context, d time.Duration) (ctx Context, cancel func())
</code></pre>
<p>在上面的代码中，“取消”是一个调用者必须采取的特殊行动。然而，如果把结果参数单独写成（Context, func()），就会不清楚“取消函数”是什么意思。</p>
<p>当名称产生<a href="https://google.github.io/styleguide/go/decisions#repetitive-with-type">不必要的重复</a>时，不要使用命名的返回值参数。</p>
<pre><code>// Bad:
func (n *Node) Parent1() (node *Node)
func (n *Node) Parent2() (node *Node, err error)
</code></pre>
<p>不要为了避免在函数中声明一个变量而给返回值参数命名，这种做法收获的仅仅是很小的实现简洁性，但却会导致不必要的API冗长。</p>
<p>只有在小型函数中才可以<a href="https://go.dev/tour/basics/7">接受<strong>裸返回(naked return)</strong></a>。一旦是一个中等规模的函数，就要显式地带着返回值一起返回。同样地，不要因为可以使用裸返回就给返回值参数命名。<a href="https://google.github.io/styleguide/go/guide#clarity">清晰性</a>总是比在你的函数中节省几行字更重要。</p>
<p>如果一个返回值参数的值必须在deferred闭包中改变，命名它则总是可以接受的。</p>
<pre><code>提示：在函数签名中，类型往往比名称更清晰。GoTip #38：作为具名类型的函数 说明了这一点。

在上面的WithTimeout中，真正的代码在返回值参数列表中使用了CancelFunc而不是func()，这样做(译注：使用表意的类型)可以省下来很多编写文档的工作。
</code></pre>
<h3 id="包注释">包注释</h3>
<p>包的注释必须紧挨着package子句出现，在注释和包名之间没有空行。例如。</p>
<pre><code>// Good:
// Package math provides basic constants and mathematical functions.
//
// This package does not guarantee bit-identical results across architectures.
package math
</code></pre>
<p>每个包必须有一个包的注释。如果一个包是由多个文件组成的，则其中一个文件应该有一个包注释。</p>
<p>main包的注释有一个稍微不同的形式，BUILD文件中go_binary规则的名称代替了包名。</p>
<pre><code>// Good:
// The seed_generator command is a utility that generates a Finch seed file
// from a set of JSON study configs.
package main
</code></pre>
<p>只要二进制文件的名称与BUILD文件中写的一模一样，其他样式的注释也可以。当二进制名称是第一个词时，需要将其大写，即使它与命令行调用的拼写不严格一致。</p>
<pre><code>// Good:
// Binary seed_generator ...
// Command seed_generator ...
// Program seed_generator ...
// The seed_generator command ...
// The seed_generator program ...
// Seed_generator ...
</code></pre>
<p>提示：</p>
<ul>
<li>
<p>命令行调用和API使用的例子可以成为有用的文档。考虑Godoc的格式，请缩进包含代码的注释行。</p>
</li>
<li>
<p>如果没有明显的主文件，或者包的注释特别长，把文档注释单独放在一个名为doc.go的文件中，只写上注释和包声明句也是可以接受的。</p>
</li>
<li>
<p>可以用多行注释来代替多个单行注释。这有利于在源文件中对部分内容的复制和粘贴操作，比如二进制文件的命令行说明或模板示例。</p>
<p>// Good:
/*
The seed_generator command is a utility that generates a Finch seed file
from a set of JSON study configs.</p>
<pre><code>seed_generator *.json | base64 &gt; finch-seed.base64
</code></pre>
<p>*/
package template</p>
</li>
<li>
<p>为维护者准备的、适用于整个文件的注释，通常放在import声明语句的后面。这些注释不在Godoc中显示，不受上述包注释规则的约束。</p>
</li>
</ul>
<h2 id="导入">导入</h2>
<h3 id="重命名导入包">重命名导入包</h3>
<p>import只应该在为避免与其他import的名称冲突时才进行重命名(一个推论是，<a href="https://google.github.io/styleguide/go/decisions#package-names">好的包名</a>不应该需要重命名)。在名字冲突的情况下，最好对本地的或项目特定的包进行重命名。包的本地名称（别名）必须遵循<a href="https://google.github.io/styleguide/go/decisions#package-names">包命名的指导</a>，包括禁止使用下划线和大写字母。</p>
<p>生成的protobuf协议包必须被重新命名，以去除其名称中的下划线，其别名必须有一个pb后缀。更多信息请参见<a href="https://google.github.io/styleguide/go/best-practices#import-protos">proto和stub的最佳实践</a>。</p>
<pre><code>// Good:
import (
    fspb &quot;path/to/package/foo_service_go_proto&quot;
)
</code></pre>
<p>如果导入的软件包名称没有任何有用的标识信息（例如，package v1），应该将其重新命名为包括之前路径成分的名字。重命名必须与其他导入相同软件包的本地文件一致，包括版本号。</p>
<p>注意：最好是重命名包以符合<a href="https://google.github.io/styleguide/go/decisions#package-names">好的包名称</a>，但这对于在vendor目录中的软件包往往是不可行的。</p>
<pre><code>// Good:
import (
    core &quot;github.com/kubernetes/api/core/v1&quot;
    meta &quot;github.com/kubernetes/apimachinery/pkg/apis/meta/v1beta1&quot;
)
</code></pre>
<p>如果您需要导入一个包，其名称与你想使用的常见局部变量名称相冲突（例如 url, ssh），并且你希望重命名该包，首选的方法是使用pkg后缀（例如urlpkg）。注意，一个本地变量可以遮蔽一个包；但只有当这样的变量在同一范围内时，且该包仍然需要被使用时，这种重命名才是必要的。</p>
<h3 id="分组导入">分组导入</h3>
<p>包应分为两组导入：</p>
<ul>
<li>
<p>标准库包</p>
</li>
<li>
<p>其他包（项目和vendor包）</p>
<p>// Good:
package main</p>
<p>import (
&ldquo;fmt&rdquo;
&ldquo;hash/adler32&rdquo;
&ldquo;os&rdquo;</p>
<pre><code>&quot;github.com/dsnet/compress/flate&quot;
&quot;golang.org/x/text/encoding&quot;
&quot;google.golang.org/protobuf/proto&quot;
foopb &quot;myproj/foo/proto/proto&quot;
_ &quot;myproj/rpc/protocols/dial&quot;
_ &quot;myproj/security/auth/authhooks&quot;
</code></pre>
<p>)</p>
</li>
</ul>
<p>将项目包分成多个组是可以接受的，例如，如果你想为重命名的、只为副作用效果而导入的，或其他特殊的包单独设一个组。</p>
<pre><code>// Good:
package main

import (
    &quot;fmt&quot;
    &quot;hash/adler32&quot;
    &quot;os&quot;

    &quot;github.com/dsnet/compress/flate&quot;
    &quot;golang.org/x/text/encoding&quot;
    &quot;google.golang.org/protobuf/proto&quot;

    foopb &quot;myproj/foo/proto/proto&quot;

    _ &quot;myproj/rpc/protocols/dial&quot;
    _ &quot;myproj/security/auth/authhooks&quot;
)
</code></pre>
<p>注意: <a href="https://google.github.io/styleguide/go/golang.org/x/tools/cmd/goimports">goimports</a>工具不支持维护在标准库和Google导入包之间强制分出的可选组。额外的导入子组需要作者和审核者的关注，以保证其符合要求。</p>
<p>同是AppEngine应用程序的Google程序应该有一个单独的AppEngine导入组。</p>
<p>Gofmt负责按导入路径对每个组进行排序。然而，它并不会自动将导入的内容分成组。goimports工具结合了Gofmt的功能和导入管理，根据上面的决定将导入包分离成组。让goimports全权负责管理包导入是ok的，但当一个文件被修改时，其导入列表必须保持内部一致。</p>
<h3 id="空导入import-_">空导入(import _)</h3>
<p>只为其副作用而导入的包（使用 import _ “package” 语法）只能在main包中，或在需要它们的测试中导入。</p>
<p>这种包的一些例子包括：</p>
<ul>
<li><a href="https://pkg.go.dev/time/tzdata">time/tzdataa</a></li>
<li>图像处理代码中的<a href="https://pkg.go.dev/image/jpeg">image/jpeg</a></li>
</ul>
<p>避免在library包中进行空导入，即使library间接依赖于它们。将副作用导入限制在main包中有助于控制依赖关系，并使编写依赖不同导入的测试成为可能，而不会产生冲突或浪费构建成本。</p>
<p>以下是这个规则的唯一例外：</p>
<ul>
<li>你可以使用一个空导入来绕过<a href="https://github.com/bazelbuild/rules_go/blob/master/go/nogo.rst">nogo静态检查器</a>中不允许的导入检查。</li>
<li>你可以在使用//go:embed编译器指令的源文件中使用一个<a href="https://pkg.go.dev/embed">embed包</a>的空导入。</li>
</ul>
<p><strong>提示</strong>：如果你在生产中创建了一个间接依赖副作用导入的library包，请写明预期的用法。</p>
<h3 id="import-dotimport-">import dot(import .)</h3>
<p>“import .”形式是一种语言特性，它允许将从另一个包中导出的标识符带到当前的包中，而无需在使用它们时使用包限定符。更多信息请参见<a href="https://go.dev/ref/spec#Import_declarations">语言规范</a>。</p>
<p>不要在谷歌代码库中使用这个功能特性；它使人们更难分辨功能的来源。</p>
<pre><code>// Bad:
package foo_test

import (
    &quot;bar/testutil&quot; // also imports &quot;foo&quot;
    . &quot;foo&quot;
)

var myThing = Bar() // Bar defined in package foo; no qualification needed.


// Good:
package foo_test

import (
    &quot;bar/testutil&quot; // also imports &quot;foo&quot;
    &quot;foo&quot;
)

var myThing = foo.Bar()
</code></pre>
<h2 id="错误">错误</h2>
<h3 id="返回错误">返回错误</h3>
<p>使用error来表示一个函数可能失败。按照惯例，error应作为最后一个返回值参数。</p>
<pre><code>// Good:
func Good() error { /* ... */ }
</code></pre>
<p>返回一个nil错误是提示成功操作的惯用方法，否则就代表失败。如果一个函数返回一个错误，调用者必须将所有非错误类型的返回值视为未指定的，除非有明确的文档说明。通常情况下，这些非错误类型的返回值是它们的零值，但这不能被假定。</p>
<pre><code>// Good:
func GoodLookup() (*Result, error) {
    // ...
    if err != nil {
        return nil, err
    }
    return res, nil
}
</code></pre>
<p>返回错误的导出函数应该使用error类型来返回它们。而使用具体的错误类型容易受到微妙的错误影响：一个具体的nil指针可以被包装成一个接口，从而成为一个非nil值（见<a href="https://golang.org/doc/faq#nil_error">Go FAQ中关于这个主题的条目</a>）。</p>
<pre><code>// Bad:
func Bad() *os.PathError { /*...*/ }
</code></pre>
<p>提示：一个接受context.Context参数的函数通常应该返回一个错误，以便调用者可以确定在函数运行时是否取消了context。</p>
<h3 id="错误字符串">错误字符串</h3>
<p>错误字符串不应大写（除非是以导出名称、专有名词或首字母缩略词开始），也不应以标点符号结束。这是因为错误字符串在打印给用户之前，通常出现在其他环境中。</p>
<pre><code>// Bad:
err := fmt.Errorf(&quot;Something bad happened.&quot;)


// Good:
err := fmt.Errorf(&quot;something bad happened&quot;)
</code></pre>
<p>另一方面，完整显示的消息（日志、测试失败、API响应或其他用户界面）的风格视情况而定，但通常应该以大写开头。</p>
<pre><code>// Good:
log.Infof(&quot;Operation aborted: %v&quot;, err)
log.Errorf(&quot;Operation aborted: %v&quot;, err)
t.Errorf(&quot;Op(%q) failed unexpectedly; err=%v&quot;, args, err)
</code></pre>
<h3 id="错误处理">错误处理</h3>
<p>在代码中遇到错误时应该慎重选择如何处理它。通常情况下，使用“_”空变量来丢弃错误是不合适的。如果一个函数返回一个错误，请做以下其中一个：</p>
<ul>
<li>立即处理并解决该错误。</li>
<li>将错误返回给调用者。</li>
<li>在特殊情况下，调用log.Fatal或（如果绝对必要）panic。</li>
</ul>
<p>注意：log.Fatalf不是标准库中的日志。参见#logging。</p>
<p>在极少数情况下，忽略或丢弃一个错误是合适的（例如对(*bytes.Buffer).Write的调用被记录为永不失败），附带的注释应该解释为什么这是安全的。</p>
<pre><code>// Good:
var b *bytes.Buffer

n, _ := b.Write(p) // never returns a non-nil error
</code></pre>
<p>关于错误处理的更多讨论和例子，请参见<a href="http://golang.org/doc/effective_go.html#errors">《Effective Go》</a>和<a href="https://google.github.io/styleguide/go/best-practices.html#error-handling">最佳实践</a>。</p>
<h3 id="带内in-band错误">带内(in-band)错误</h3>
<p>在C语言和类似语言中，函数通常会返回-1、null或空字符串等值，以示错误或丢失结果。这就是所谓的带内错误处理。</p>
<blockquote>
<p>译注：所谓带内(in-band)是指将错误值与普通返回值混在一起。</p>
</blockquote>
<pre><code>// Bad:
// Lookup returns the value for key or -1 if there is no mapping for key.
func Lookup(key string) int
</code></pre>
<p>未能检查带内错误值会导致错误，并可能将错误归于出错的函数。</p>
<pre><code>// Bad:
// The following line returns an error that Parse failed for the input value,
// whereas the failure was that there is no mapping for missingKey.
return Parse(Lookup(missingKey))
</code></pre>
<p>Go对多返回值的支持为此提供了一个更好的解决方案（见<a href="http://golang.org/doc/effective_go.html#multiple-returns">Effective Go中关于多个返回值的部分</a>）。与其要求客户检查带内的错误值，一个函数应该返回一个额外的值来表明它的其他返回值是否有效。这个返回值可以是一个错误或在不需要解释时是一个布尔值，并且应该是最终的返回值。</p>
<pre><code>// Good:
// Lookup returns the value for key or ok=false if there is no mapping for key.
func Lookup(key string) (value string, ok bool)
</code></pre>
<p>这个API可以防止调用者错误地将代码写成Parse(Lookup(key))，因为Lookup(key)有两个返回值，所以会导致编译错误。</p>
<p>以这种方式返回错误，可以鼓励更强大和明确的错误处理。</p>
<pre><code>// Good:
value, ok := Lookup(key)
if !ok {
    return fmt.Errorf(&quot;no value for %q&quot;, key)
}
return Parse(value)
</code></pre>
<p>一些标准库函数，如包strings中的函数，返回带内错误值。这大大简化了字符串处理代码，但代价是要求程序员更加勤奋。一般来说，Google代码库中的Go代码应该为错误返回额外的值。</p>
<h3 id="缩进错误流程">缩进错误流程</h3>
<p>在继续进行你的代码的其余部分之前，先处理错误。这可以提高代码的可读性，使读者能够迅速找到正常的路径。这个逻辑同样适用于任何测试一个条件是否为终止条件的代码块（例如，return、panic、log.Fatal）。</p>
<p>如果终止条件没有得到满足，后续运行的代码应该出现在if块之后，而不应该放入缩进的else子句中。</p>
<pre><code>// Good:
if err != nil {
    // error handling
    return // or continue, etc.
}
// normal code


// Bad:
if err != nil {
    // error handling
} else {
    // normal code that looks abnormal due to indentation
}
</code></pre>
<p>提示：如果你在多行代码中使用了一个变量，通常不值得使用if-with-initializer风格。在这种情况下，通常最好将声明移出，使用标准的if语句。</p>
<pre><code>// Good:
x, err := f()
if err != nil {
  // error handling
  return
}
// lots of code that uses x
// across multiple lines

// Bad:
if x, err := f(); err != nil {
  // error handling
  return
} else {
  // lots of code that uses x
  // across multiple lines
}
</code></pre>
<p>详情见Go技巧1：Line of Sight和<a href="https://testing.googleblog.com/2017/06/code-health-reduce-nesting-reduce.html">TotT：通过减少嵌套降低代码的复杂性</a>。</p>
<h2 id="语言">语言</h2>
<h3 id="字面值格式">字面值格式</h3>
<p>Go有一个非常强大的<a href="https://go.dev/ref/spec#Composite_literals">复合字面值语法</a>，可以用一个表达式来表达深度嵌套的复杂值。在可能的情况下，应该使用这种字面值语法，而不是逐个字段地赋值。一般来说，gofmt对字面值的格式化是非常好的，但是还有一些额外的规则来保持这些字面值的可读性和可维护性。</p>
<h4 id="字段名">字段名</h4>
<p>结构体字面值通常应该为当前包之外定义的类型指定字段名。</p>
<ul>
<li>
<p>包括来自其他软件包的类型的字段名</p>
<p>// Good:
good := otherpkg.Type{A: 42}</p>
</li>
</ul>
<p>结构体中字段的位置和字段的完整集合（当字段名被省略时，这两者都是有必要搞清楚的）通常不被认为是结构体的公共API的一部分；需要指定字段名以避免不必要的耦合。</p>
<pre><code>// Bad:
// https://pkg.go.dev/encoding/csv#Reader
r := csv.Reader{',', '#', 4, false, false, false, false}
</code></pre>
<p>在小型、简单的结构体中可以省略字段名，这些结构体的组成和顺序都是稳定的，都有对应的文档记录。</p>
<pre><code>// Good:
okay := image.Point{42, 54}
also := image.Point{X: 42, Y: 54}
</code></pre>
<ul>
<li>
<p>对于包的本地类型，字段名可选</p>
<p>// Good:
okay := Type{42}
also := internalType{4, 2}</p>
</li>
</ul>
<p>如果要使代码更清晰，还是应该使用字段名，而且这样做是非常普遍的。例如，一个有大量字段的结构体几乎都应该用字段名来初始化。</p>
<pre><code>// Good:
okay := StructWithLotsOfFields{
  field1: 1,
  field2: &quot;two&quot;,
  field3: 3.14,
  field4: true,
}
</code></pre>
<h4 id="匹配括号">匹配括号</h4>
<p>一对大括号的最后一半应该总是应该出现在缩进量与开头的大括号相同的一行中。单行字面值也必须有这个属性。当字面值跨越多行时，保持这一属性可以使字面值的大括号匹配与函数和if语句等常见Go语法结构的大括号匹配相同。</p>
<p>这方面最常见的错误是在多行结构体字面值中把收尾括号和值放在同一行。在这种情况下，该行应以逗号结束，收尾括号应出现在下一行。</p>
<pre><code>// Good:
good := []*Type{{Key: &quot;value&quot;}}


// Good:
good := []*Type{
    {Key: &quot;multi&quot;},
    {Key: &quot;line&quot;},
}


// Bad:
bad := []*Type{
    {Key: &quot;multi&quot;},
    {Key: &quot;line&quot;}}


// Bad:
bad := []*Type{
    {
        Key: &quot;value&quot;},
}
</code></pre>
<h4 id="拥抱式大括号">拥抱式大括号</h4>
<p>只有在以下两种情况下，才允许在切片和数组字面值的大括号之间丢弃空格（又称 “拥抱”）。</p>
<ul>
<li>
<p><a href="https://google.github.io/styleguide/go/decisions#literal-matching-braces">缩进匹配</a></p>
</li>
<li>
<p>内部值也是字面值或proto构建器（即不是变量或其他表达式）。</p>
<p>// Good:
good := []*Type{
{ // Not cuddled
Field: &ldquo;value&rdquo;,
},
{
Field: &ldquo;value&rdquo;,
},
}</p>
<p>// Good:
good := []*Type{{ // Cuddled correctly
Field: &ldquo;value&rdquo;,
}, {
Field: &ldquo;value&rdquo;,
}}</p>
<p>// Good:
good := []*Type{
first, // Can&rsquo;t be cuddled
{Field: &ldquo;second&rdquo;},
}</p>
<p>// Good:
okay := []*pb.Type{pb.Type_builder{
Field: &ldquo;first&rdquo;, // Proto Builders may be cuddled to save vertical space
}.Build(), pb.Type_builder{
Field: &ldquo;second&rdquo;,
}.Build()}</p>
<p>// Bad:
bad := []*Type{
first,
{
Field: &ldquo;second&rdquo;,
}}</p>
</li>
</ul>
<h4 id="重复的类型名">重复的类型名</h4>
<p>重复的类型名可以从切片和map字面值中省略。这有助于减少混乱。明确的使用重复类型名的一个合理场合是当处理一个在你的项目中不常见的复杂类型时，当重复的类型名在相隔很远的行上时，可以提醒读者的上下文。</p>
<pre><code>// Good:
good := []*Type{
    {A: 42},
    {A: 43},
}


// Bad:
repetitive := []*Type{
    &amp;Type{A: 42},
    &amp;Type{A: 43},
}


// Good:
good := map[Type1]*Type2{
    {A: 1}: {B: 2},
    {A: 3}: {B: 4},
}


// Bad:
repetitive := map[Type1]*Type2{
    Type1{A: 1}: &amp;Type2{B: 2},
    Type1{A: 3}: &amp;Type2{B: 4},
}
</code></pre>
<p>提示：如果你想删除结构体字面值中重复的类型名称，你可以运行gofmt -s。</p>
<h4 id="零值字段">零值字段</h4>
<p>当清晰度不会因此而降低时，零值字段可以从结构体字面值中省略。</p>
<p>设计良好的API经常采用零值结构来提高可读性。例如，从下面的结构体中省略三个零值字段，可以使人们将注意力集中到正在赋值的option字段上。</p>
<pre><code>// Bad:
import (
  &quot;github.com/golang/leveldb&quot;
  &quot;github.com/golang/leveldb/db&quot;
)

ldb := leveldb.Open(&quot;/my/table&quot;, &amp;db.Options{
    BlockSize: 1&lt;&lt;16,
    ErrorIfDBExists: true,

    // These fields all have their zero values.
    BlockRestartInterval: 0,
    Comparer: nil,
    Compression: nil,
    FileSystem: nil,
    FilterPolicy: nil,
    MaxOpenFiles: 0,
    WriteBufferSize: 0,
    VerifyChecksums: false,
})


// Good:
import (
  &quot;github.com/golang/leveldb&quot;
  &quot;github.com/golang/leveldb/db&quot;
)

ldb := leveldb.Open(&quot;/my/table&quot;, &amp;db.Options{
    BlockSize: 1&lt;&lt;16,
    ErrorIfDBExists: true,
})
</code></pre>
<p>表驱动测试中的结构体经常受益于<a href="https://google.github.io/styleguide/go/decisions#literal-field-names">显式的字段名</a>，特别是当测试结构体十分重要的时候。这允许作者在有关字段与测试用例无关时完全省略零值字段。例如，成功的测试用例应该省略任何与错误相关或失败相关的字段。在零值对于理解测试用例是必要的情况下，如测试零或零输入，应指定字段名。</p>
<p><strong>简明</strong></p>
<pre><code>tests := []struct {
    input      string
    wantPieces []string
    wantErr    error
}{
    {
        input:      &quot;1.2.3.4&quot;,
        wantPieces: []string{&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;},
    },
    {
        input:   &quot;hostname&quot;,
        wantErr: ErrBadHostname,
    },
}
</code></pre>
<p><strong>显式</strong></p>
<pre><code>tests := []struct {
    input    string
    wantIPv4 bool
    wantIPv6 bool
    wantErr  bool
}{
    {
        input:    &quot;1.2.3.4&quot;,
        wantIPv4: true,
        wantIPv6: false,
    },
    {
        input:    &quot;1:2::3:4&quot;,
        wantIPv4: false,
        wantIPv6: true,
    },
    {
        input:    &quot;hostname&quot;,
        wantIPv4: false,
        wantIPv6: false,
        wantErr:  true,
    },
}
</code></pre>
<h3 id="空切片">空切片</h3>
<p>在大多数情况下，nil和空切片之间没有功能上的区别。像len和cap这样的内置函数在nil切片上的表现与预期一致。</p>
<pre><code>// Good:
import &quot;fmt&quot;

var s []int         // nil

fmt.Println(s)      // []
fmt.Println(len(s)) // 0
fmt.Println(cap(s)) // 0
for range s {...}   // no-op

s = append(s, 42)
fmt.Println(s)      // [42]
</code></pre>
<p>如果你声明一个空切片作为局部变量（特别是如果它可以成为返回值的来源），最好选择nil初始化以减少调用者的bug风险。</p>
<pre><code>// Good:
var t []string


// Bad:
t := []string{}
</code></pre>
<p>不要创建强迫其客户区分nil和空切片的API：</p>
<pre><code>// Good:
// Ping pings its targets.
// Returns hosts that successfully responded.
func Ping(hosts []string) ([]string, error) { ... }


// Bad:
// Ping pings its targets and returns a list of hosts
// that successfully responded. Can be empty if the input was empty.
// nil signifies that a system error occurred.
func Ping(hosts []string) []string { ... }
</code></pre>
<p>在设计接口时，要避免区分nil切片和非nil的零长度切片，因为这可能导致微妙的编程错误。这通常需要我们使用len来检查是否为空，而不是与nil比较来实现。</p>
<p>这个实现接受nil和零长度的切片作为”空”：</p>
<pre><code>// Good:
// describeInts describes s with the given prefix, unless s is empty.
func describeInts(prefix string, s []int) {
    if len(s) == 0 {
        return
    }
    fmt.Println(prefix, s)
}
</code></pre>
<p>而不是依靠区别nil和零长度的切片来作为API的一部分：</p>
<pre><code>// Bad:
func maybeInts() []int { /* ... */ }

// describeInts describes s with the given prefix; pass nil to skip completely.
func describeInts(prefix string, s []int) {
  // The behavior of this function unintentionally changes depending on what
  // maybeInts() returns in 'empty' cases (nil or []int{}).
  if s == nil {
    return
  }
  fmt.Println(prefix, s)
}

describeInts(&quot;Here are some ints:&quot;, maybeInts())
</code></pre>
<p>进一步讨论见<a href="https://google.github.io/styleguide/go/decisions#in-band-errors">带内错误</a>。</p>
<h3 id="缩进的混乱">缩进的混乱</h3>
<p>避免引入断行，如果它将使其余的行与缩进的代码块对齐。如果这是不可避免的，请留出一个空格，将代码块中的代码与被包裹的行分开。</p>
<pre><code>// Bad:
if longCondition1 &amp;&amp; longCondition2 &amp;&amp;
    // Conditions 3 and 4 have the same indentation as the code within the if.
    longCondition3 &amp;&amp; longCondition4 {
    log.Info(&quot;all conditions met&quot;)
}
</code></pre>
<p>具体准则和例子见以下章节：</p>
<ul>
<li><a href="https://google.github.io/styleguide/go/decisions#func-formatting">函数格式化</a></li>
<li><a href="https://google.github.io/styleguide/go/decisions#conditional-formatting">条件语句和循环</a></li>
<li><a href="https://google.github.io/styleguide/go/decisions#literal-formatting">字面值格式化</a></li>
</ul>
<h3 id="函数格式化">函数格式化</h3>
<p>函数或方法声明的签名应该保持在一行，以避免缩进的混乱。</p>
<p>函数参数列表可能成为Go源文件中最长的几行。然而，它们在缩进的变化之前，因此很难以一种不使后续行看起来像函数体的一部分的混乱方式来断行。</p>
<pre><code>// Bad:
func (r *SomeType) SomeLongFunctionName(foo1, foo2, foo3 string,
    foo4, foo5, foo6 int) {
    foo7 := bar(foo1)
    // ...
}
</code></pre>
<p>参见<a href="https://google.github.io/styleguide/go/best-practices#funcargs">最佳实践</a>，了解一些缩短函数调用的选项，否则这些函数会有很多参数。</p>
<pre><code>// Good:
good := foo.Call(long, CallOptions{
    Names:   list,
    Of:      of,
    The:     parameters,
    Func:    all,
    Args:    on,
    Now:     separate,
    Visible: lines,
})


// Bad:
bad := foo.Call(
    long,
    list,
    of,
    parameters,
    all,
    on,
    separate,
    lines,
)
</code></pre>
<p>通过析出局部变量，通常可以缩短行数。</p>
<pre><code>// Good:
local := helper(some, parameters, here)
good := foo.Call(list, of, parameters, local)
</code></pre>
<p>同样地，函数和方法的调用也不应该仅仅根据行的长度来区分。</p>
<pre><code>// Good:
good := foo.Call(long, list, of, parameters, all, on, one, line)


// Bad:
bad := foo.Call(long, list, of, parameters,
    with, arbitrary, line, breaks)
</code></pre>
<p>不要给特定的函数参数添加注释。相反，使用选项结构或在函数文档中添加更多细节。</p>
<pre><code>// Good:
good := server.New(ctx, server.Options{Port: 42})


// Bad:
bad := server.New(
    ctx,
    42, // Port
)
</code></pre>
<p>如果调用函数时的语句长得让人不舒服，请考虑重构。</p>
<pre><code>// Good:
// Sometimes variadic arguments can be factored out
replacements := []string{
    &quot;from&quot;, &quot;to&quot;, // related values can be formatted adjacent to one another
    &quot;source&quot;, &quot;dest&quot;,
    &quot;original&quot;, &quot;new&quot;,
}

// Use the replacement struct as inputs to NewReplacer.
replacer := strings.NewReplacer(replacements...)
</code></pre>
<p>如果不能改变API，或者本地不常调用（无论调用是否太长），如果有助于理解调用，添加换行符总是允许的。</p>
<pre><code>// Good:
canvas.RenderCube(cube,
    x0, y0, z0,
    x0, y0, z1,
    x0, y1, z0,
    x0, y1, z1,
    x1, y0, z0,
    x1, y0, z1,
    x1, y1, z0,
    x1, y1, z1,
)
</code></pre>
<p>请注意，上述例子中的行没有在特定的列边界处被换行，而是根据坐标三要素进行分组。</p>
<p>在函数中的长字符串字数不应该因为行长的原因而被打断。对于包含此类字符串的函数，可以在字符串格式之后添加一个换行符，参数可以在下一行或后续行提供。关于断行的位置，最好是根据输入的语义分组来决定，而不是单纯地根据行的长度。</p>
<pre><code>// Good:
log.Warningf(&quot;Database key (%q, %d, %q) incompatible in transaction started by (%q, %d, %q)&quot;,
    currentCustomer, currentOffset, currentKey,
    txCustomer, txOffset, txKey)


// Bad:
log.Warningf(&quot;Database key (%q, %d, %q) incompatible in&quot;+
    &quot; transaction started by (%q, %d, %q)&quot;,
    currentCustomer, currentOffset, currentKey, txCustomer,
    txOffset, txKey)
</code></pre>
<h3 id="条件与循环">条件与循环</h3>
<p>一个if语句不应断行；多行if子句会导致缩进混乱。</p>
<pre><code>// Bad:
// The second if statement is aligned with the code within the if block, causing
// indentation confusion.
if db.CurrentStatusIs(db.InTransaction) &amp;&amp;
    db.ValuesEqual(db.TransactionKey(), row.Key()) {
    return db.Errorf(db.TransactionError, &quot;query failed: row (%v): key does not match transaction key&quot;, row)
}
</code></pre>
<p>如果不需要短路行为，可以直接提取布尔操作数：</p>
<pre><code>// Good:
inTransaction := db.CurrentStatusIs(db.InTransaction)
keysMatch := db.ValuesEqual(db.TransactionKey(), row.Key())
if inTransaction &amp;&amp; keysMatch {
    return db.Error(db.TransactionError, &quot;query failed: row (%v): key does not match transaction key&quot;, row)
}
</code></pre>
<p>也可能有其他的局部变量可以被提取出来，特别是如果条件已经是重复的。</p>
<pre><code>// Good:
uid := user.GetUniqueUserID()
if db.UserIsAdmin(uid) || db.UserHasPermission(uid, perms.ViewServerConfig) || db.UserHasPermission(uid, perms.CreateGroup) {
    // ...
}


// Bad:
if db.UserIsAdmin(user.GetUniqueUserID()) || db.UserHasPermission(user.GetUniqueUserID(), perms.ViewServerConfig) || db.UserHasPermission(user.GetUniqueUserID(), perms.CreateGroup) {
    // ...
}
</code></pre>
<p>包含闭包或多行结构体字面值的if语句应确保大括号的匹配，以避免缩进的混乱。</p>
<pre><code>// Good:
if err := db.RunInTransaction(func(tx *db.TX) error {
    return tx.Execute(userUpdate, x, y, z)
}); err != nil {
    return fmt.Errorf(&quot;user update failed: %s&quot;, err)
}


// Good:
if _, err := client.Update(ctx, &amp;upb.UserUpdateRequest{
    ID:   userID,
    User: user,
}); err != nil {
    return fmt.Errorf(&quot;user update failed: %s&quot;, err)
}
</code></pre>
<p>同样地，不要试图在for语句中人为的插入换行。如果没有优雅的方法来重构它，你总是可以让这一行简单地变长。</p>
<pre><code>// Good:
for i, max := 0, collection.Size(); i &lt; max &amp;&amp; !collection.HasPendingWriters(); i++ {
    // ...
}
</code></pre>
<p>不过，往往是有方法的：</p>
<pre><code>// Good:
for i, max := 0, collection.Size(); i &lt; max; i++ {
    if collection.HasPendingWriters() {
        break
    }
    // ...
}
</code></pre>
<p>switch和case语句也应该保持在一行：</p>
<pre><code>// Good:
switch good := db.TransactionStatus(); good {
case db.TransactionStarting, db.TransactionActive, db.TransactionWaiting:
    // ...
case db.TransactionCommitted, db.NoTransaction:
    // ...
default:
    // ...
}


// Bad:
switch bad := db.TransactionStatus(); bad {
case db.TransactionStarting,
    db.TransactionActive,
    db.TransactionWaiting:
    // ...
case db.TransactionCommitted,
    db.NoTransaction:
    // ...
default:
    // ...
}
</code></pre>
<p>如果行过长，请缩进所有case，并用空行隔开，以避免缩进的混乱。</p>
<pre><code>// Good:
switch db.TransactionStatus() {
case
    db.TransactionStarting,
    db.TransactionActive,
    db.TransactionWaiting,
    db.TransactionCommitted:

    // ...
case db.NoTransaction:
    // ...
default:
    // ...
}
</code></pre>
<p>在将变量与常数进行比较的条件语句中，将变量值放在判等运算符的左侧：</p>
<pre><code>// Good:
if result == &quot;foo&quot; {
  // ...
}
</code></pre>
<p>而不是采用常量在先（”<a href="https://en.wikipedia.org/wiki/Yoda_conditions">尤达式条件语句</a>“）。</p>
<pre><code>// Bad:
if &quot;foo&quot; == result {
  // ...
}
</code></pre>
<h3 id="拷贝">拷贝</h3>
<p>为了避免意外的别名和类似的错误，在从其他包中复制结构体时要小心。例如，同步对象（如sync.Mutex）不能被复制。</p>
<p>bytes.Buffer类型包含一个[]byte切片，作为对小字符串的优化，该切片可以引用一个小的字节数组。如果你拷贝一个Buffer，拷贝中的切片可能会建立原始数组的别名，导致后续的方法调用产生意外的结果。</p>
<p>一般来说，如果一个T类型的值的方法与指针类型*T有关，就不要复制它。</p>
<pre><code>// Bad:
b1 := bytes.Buffer{}
b2 := b1
</code></pre>
<p>调用一个值类型接收器的方法可以隐藏复制。当你编写API时，如果你的结构体包含不应该被复制的字段，你一般应该使用指针类型和返回指针类型。</p>
<p>下面这些是可以接受的。</p>
<pre><code>// Good:
type Record struct {
  buf bytes.Buffer
  // other fields omitted
}

func New() *Record {...}

func (r *Record) Process(...) {...}

func Consumer(r *Record) {...}
</code></pre>
<p>但是如下这些通常是错误的：</p>
<pre><code>// Bad:
type Record struct {
  buf bytes.Buffer
  // other fields omitted
}

func (r Record) Process(...) {...} // Makes a copy of r.buf

func Consumer(r Record) {...} // Makes a copy of r.buf
</code></pre>
<p>本指南也适用于复制sync.Mutex。</p>
<h3 id="不要panic">不要panic</h3>
<p>不要在正常的错误处理中使用panic。相反，使用错误和多个返回值。参见<a href="http://go.dev/doc/effective_go.html#errors">Effective Go中关于错误的部分</a>。</p>
<p>在main包和初始化代码中，考虑用log.Exit来处理应该终止程序的错误（例如，无效的配置），因为在许多这种情况下，堆栈跟踪不会帮助到读者。<strong>请注意：log.Exit调用os.Exit，任何defer函数都不会被运行</strong>。</p>
<p>对于表明”不可能”的条件的错误，即应该总是在代码审查和/或测试期间捕获的错误，一个函数可以合理地返回一个错误或调用log.Fatal。</p>
<p>注意：log.Fatalf不是标准库中的日志。参见#logging。</p>
<h3 id="must函数">Must函数</h3>
<p>在失败时停止程序的辅助函数遵循命名惯例MustXYZ（或mustXYZ）。一般来说，它们应该只在程序启动初期被调用，而不是在像用户输入这样的事情上被调用，因为在这种情况下，正常的Go错误处理是首选。</p>
<p>这经常出现在专门在包初始化时调用的初始化包级变量的函数中（如template.Must和regexp.MustCompile）。</p>
<pre><code>// Good:
func MustParse(version string) *Version {
    v, err := Parse(version)
    if err != nil {
        log.Fatalf(&quot;MustParse(%q) = _, %v&quot;, version, err)
    }
    return v
}

// Package level &quot;constant&quot;. If we wanted to use `Parse`, we would have had to
// set the value in `init`.
var DefaultVersion = MustParse(&quot;1.2.3&quot;)
</code></pre>
<p>注意：log.Fatalf不是标准库中的日志。参见#logging。</p>
<p>同样的约定可以用在只停止当前测试的测试helper函数中（使用t.Fatal）。这样的helper函数在创建测试值时往往很方便，例如在表驱动测试的结构体字段中，因为返回错误的函数不能直接赋值给结构体字段。</p>
<pre><code>// Good:
func mustMarshalAny(t *testing.T, m proto.Message) *anypb.Any {
  t.Helper()
  any, err := anypb.New(m)
  if err != nil {
    t.Fatalf(&quot;MustMarshalAny(t, m) = %v; want %v&quot;, err, nil)
  }
  return any
}

func TestCreateObject(t *testing.T) {
  tests := []struct{
    desc string
    data *anypb.Any
  }{
    {
      desc: &quot;my test case&quot;,
      // Creating values directly within table driven test cases.
      data: mustMarshalAny(t, mypb.Object{}),
    },
    // ...
  }
  // ...
}
</code></pre>
<p>在这两种情况下，这种模式的价值在于helper函数可以在”值”的上下文中被调用。这些helper函数不应该在难以确保错误被捕获的地方或在应该<a href="https://google.github.io/styleguide/go/decisions#handle-errors">检查错误</a>的上下文中被调用（例如，在许多请求处理程序中）。对于常量输入，这让测试可以轻松确保Must参数是格式良好的，而对于非常量输入，它允许测试验证<a href="https://google.github.io/styleguide/go/best-practices#error-handling">错误可以被正确处理或传播</a>。</p>
<p>在测试中使用Must函数时，它们通常应该被标记为<a href="https://google.github.io/styleguide/go/decisions#mark-test-helpers">测试助手</a>，并在出错时调用t.Fatal（如果要了解更多，可参见测试助手中的<a href="https://google.github.io/styleguide/go/best-practices#test-helper-error-handling">错误处理</a>）。</p>
<p>当<a href="https://google.github.io/styleguide/go/best-practices#error-handling">常规错误处理</a>可行时（包括一些重构），Must函数就不应该被使用。</p>
<pre><code>// Bad:
func Version(o *servicepb.Object) (*version.Version, error) {
    // Return error instead of using Must functions.
    v := version.MustParse(o.GetVersionString())
    return dealiasVersion(v)
}
</code></pre>
<h3 id="goroutine的生命周期">goroutine的生命周期</h3>
<p>当你创建新的goroutines时，要明确它们何时或是否会退出。</p>
<p>goroutine可能因阻塞在channel的发送或接收操作上而导致泄漏。垃圾收集器不会终止一个goroutine，即使阻塞它的channel已经是不可到达的了。</p>
<p>即使goroutine没有泄漏，当它们不再被需要时，让它们继续存活也会导致其他微妙的、难以诊断的问题。在一个已经关闭的channel上执行发送操作会导致panic。</p>
<pre><code>// Bad:
ch := make(chan int)
ch &lt;- 42
close(ch)
ch &lt;- 13 // panic
</code></pre>
<p>在“不需要结果之后”修改仍在使用的输入，会导致数据竞争。让goroutine存活任意长的时间会导致不可预知的内存使用。</p>
<p>编写并发代码时应该明确goroutine的生命周期。通常情况下，这意味着将同步相关的代码限制在一个函数的范围内，并将逻辑分解到<a href="https://google.github.io/styleguide/go/decisions#synchronous-functions">同步函数</a>中。如果并发性仍然不明显，那么记录下goroutine退出的时间和原因是很重要的。</p>
<p>遵循围绕Context使用的最佳实践的代码通常有助于明确这一点。传统上，它是用context.Context来管理的。</p>
<pre><code>// Good:
func (w *Worker) Run(ctx context.Context) error {
    // ...
    for item := range w.q {
        // process returns at latest when the context is cancelled.
        go process(ctx, item)
    }
    // ...
}
</code></pre>
<p>以上还有其他变种，使用原始信号channel，如chan struct{}、同步变量、条件变量等。重要的是，goroutine的结束对后续维护者来说是显而易见的。</p>
<p>相比之下，下面的代码对其生成的goroutine的结束时间很不在意：</p>
<pre><code>// Bad:
func (w *Worker) Run() {
    // ...
    for item := range w.q {
        // process returns when it finishes, if ever, possibly not cleanly
        // handling a state transition or termination of the Go program itself.
        go process(item)
    }
    // ...
}
</code></pre>
<p>这段代码可能看起来很正常，但有几个潜在的问题：</p>
<ul>
<li>这段代码在生产中可能有未定义的行为，程序可能不会干净地终止，即使操作系统释放了资源。</li>
<li>由于代码的生命周期不确定，该代码很难进行有意义的测试。</li>
<li>该代码可能会像上面描述的那样泄漏资源。</li>
</ul>
<p>也请参见：</p>
<ul>
<li><a href="https://dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop">不要在不知道如何停止的情况下启动一个goroutine</a></li>
<li>反思经典的并发模式：<a href="https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view">幻灯片</a>，<a href="https://www.youtube.com/watch?v=5zXAHh5tJqQ">视频</a></li>
<li><a href="https://changelog.com/gotime/165">Go程序何时结束</a></li>
</ul>
<h3 id="接口">接口</h3>
<p>Go接口类型定义一般放在使用接口类型值的包中(如下面示例中的consumer包)，而不是实现接口类型的包中。实现包应该返回具体的（通常是指针或结构体）类型。这样一来，新的方法可以被添加到实现中，而不需要大量的重构。参见<a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #49: 接受接口，返回具体类型</a>以了解更多细节。</p>
<p>不要从消费接口的API中导出接口的<a href="https://abseil.io/resources/swe-book/html/ch13.html#techniques_for_using_test_doubles">测试替身</a>实现。相反，设计API，使其可以使用真正实现的公共API进行测试。请参阅<a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #42: 授权测试存根</a>以了解更多细节。即使使用真实实现不可行，也没有必要引入一个完全覆盖真实类型中所有方法的接口；消费者可以创建一个只包含它所需要的方法的接口，正如<a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #78: 最小可行接口</a>中所展示的。</p>
<p>要测试使用Stubby RPC客户端的包，请使用真实的客户端连接。如果在测试中不能运行真正的服务器，Google的内部做法是使用内部的rpctest包（即将推出！）获得一个真正的客户端连接到本地[测试替身]。</p>
<p>在使用之前不要定义接口（见<a href="https://testing.googleblog.com/2017/08/code-health-eliminate-yagni-smells.html">TotT: Code Health: Eliminate YAGNI Smells</a> ）。如果没有一个真实的使用例子，就很难看出一个接口是否有必要，更不用说它应该包含哪些方法。</p>
<p>如果包的用户不需要为它们传递不同类型的参数，就不要使用接口类型的参数。</p>
<p>不要导出包用户不需要的接口。</p>
<p>TODO: 写一份关于接口的更深入的文档，并在此链接。</p>
<pre><code>// Good:
package consumer // consumer.go

type Thinger interface { Thing() bool }

func Foo(t Thinger) string { ... }


// Good:
package consumer // consumer_test.go

type fakeThinger struct{ ... }
func (t fakeThinger) Thing() bool { ... }
...
if Foo(fakeThinger{...}) == &quot;x&quot; { ... }


// Bad:
package producer

type Thinger interface { Thing() bool }

type defaultThinger struct{ ... }
func (t defaultThinger) Thing() bool { ... }

func NewThinger() Thinger { return defaultThinger{ ... } }


// Good:
package producer

type Thinger struct{ ... }
func (t Thinger) Thing() bool { ... }

func NewThinger() Thinger { return Thinger{ ... } }
</code></pre>
<h3 id="泛型">泛型</h3>
<p>在满足你的业务需求时，泛型（正式名称是<a href="https://go.dev/design/43651-type-parameters">“类型参数”</a>是允许被使用的。在许多应用中，使用现有的语言特性（切片、map、接口等）的传统方法也能很好地工作，并且不会增加复杂性，所以要警惕过早地使用泛型。见关于<a href="https://google.github.io/styleguide/go/guide#least-mechanism">最小机制</a>的讨论。</p>
<p>当引入一个使用泛型的导出API时，要确保它有适当的文档。强烈建议包括可运行的<a href="https://google.github.io/styleguide/go/decisions#examples">例子</a>。</p>
<p>不要因为你正在实现一个不关心其成员元素类型的算法或数据结构，就使用泛型。如果在实践中只有一种类型被实例化，那就从让你的代码从让这种类型可以工作开始，而完全不需要使用泛型。与删除那些被认为是不必要的抽象相比，以后再添加多态性将更为直接。</p>
<p>不要使用泛型来发明领域特定语言（DSL）。特别是，不要引入可能给读者带来巨大负担的错误处理框架。相反，应该选择既定的<a href="https://google.github.io/styleguide/go/decisions#errors">错误处理实践</a>。对于测试，要特别警惕引入<a href="https://google.github.io/styleguide/go/decisions#assert">断言库</a>或框架，因为它们会导致不太有用的<a href="https://google.github.io/styleguide/go/decisions#useful-test-failures">测试失败</a>。</p>
<p>一般来说：</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=Pa_e9EeCdy8&amp;t=1250s">写代码，不要设计类型</a>。来自Robert Griesemer和Ian Lance Taylor的GopherCon演讲。</li>
<li>如果你有几个类型共享一个有用的统一接口，可以考虑使用该接口对解决方案进行建模。可能不需要泛型。</li>
<li>否则，与其依赖任何类型和过度的类型转换，不如考虑泛型。</li>
</ul>
<p>也请参见。</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=nr8EpUO9jhw">在Go中使用泛型</a>，Ian Lance Taylor 的演讲</li>
<li><a href="https://go.dev/doc/tutorial/generics">Go官方的泛型教程</a></li>
</ul>
<h3 id="传值">传值</h3>
<p>不要仅仅为了节省几个字节而把指针作为函数参数传递。如果一个函数自始至终只是以*x的形式对它的参数x进行了读操作，那么这个参数就不应该被设计成一个指针。常见的例子包括传递一个字符串的指针（_string）或一个接口值的指针（_io.Reader）。在这两种情况下，值本身是一个固定的大小，可以直接传递。</p>
<blockquote>
<p>译注：string类型是一个二元组，接口类型也是一个二元组，它们都是固定大小的。</p>
</blockquote>
<p>这个建议并不适用于大型结构体，甚至是可能增大的小型结构体。特别是，protocol buffer消息一般应通过指针而不是值来处理。指针类型满足proto.Message接口（由proto.Marshal、protocmp.Transform等接受），而protocol buffer消息可能相当大，并且经常随着时间的推移而变大。</p>
<h3 id="receiver类型">Receiver类型</h3>
<p>一个<a href="https://go.dev/ref/spec#Method_declarations">方法接收器(Receiver)</a>可以作为一个值或者一个指针来传递，就像它是一个普通的函数参数一样。选择哪种方式应该基于该方法应该是哪一个（几个）<a href="https://golang.org/ref/spec#Method_sets">方法集合</a>的一部分。</p>
<p><strong>正确性胜过速度或简单性</strong>。有些情况下，你必须使用一个指针值。在其他情况下，如果你对代码的发展没有很好的认识，可以为大的类型选择指针，或者作为对未来的保护，而对<a href="https://en.wikipedia.org/wiki/Passive_data_structure">简单的普通数据</a>使用值类型。</p>
<p>下面的列表进一步详细说明了每种情况：</p>
<ul>
<li>
<p>如果接收器是一个切片，并且该方法没有做reslice操作或重新分配切片，则使用一个值而不是一个指针。</p>
<p>// Good:
type Buffer []byte</p>
<p>func (b Buffer) Len() int { return len(b) }</p>
</li>
<li>
<p>如果方法需要修改receiver参数，那么receiver必须用指针类型</p>
<p>// Good:
type Counter int</p>
<p>func (c *Counter) Inc() { *c++ }</p>
<p>// See <a href="https://pkg.go.dev/container/heap">https://pkg.go.dev/container/heap</a>.
type Queue []Item</p>
<p>func (q *Queue) Push(x Item) { *q = append([]Item{x}, *q&hellip;) }</p>
</li>
<li>
<p>如果receiver是一个包含不能安全复制的字段的结构体，请使用一个指针类型receiver。常见的例子是sync.Mutex和其他同步类型。</p>
<p>// Good:
type Counter struct {
mu    sync.Mutex
total int
}</p>
<p>func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.total++
}</p>
</li>
</ul>
<p>提示：检查该类型的Godoc，了解它是否可以安全地复制。</p>
<ul>
<li>
<p>如果receiver是一个”大”结构体或数组，指针类型接收器可能更有效率。传递一个结构体相当于把它的所有字段或元素作为参数传递给方法。如果这看起来太大，无法通过数值传递，那么指针是一个不错的选择。</p>
</li>
<li>
<p>对于将调用或与其他修改receiver的函数并发运行的方法，如果这些修改不应该对你的方法可见，则使用一个值；否则使用一个指针。</p>
</li>
<li>
<p>如果receiver是一个结构体或数组，其任何元素都是指向可能被修改的东西的指针，那么最好使用指针类型接收器，以使读者清楚地了解可修改的意图。</p>
<p>// Good:
type Counter struct {
m *Metric
}</p>
<p>func (c *Counter) Inc() {
c.m.Add(1)
}</p>
</li>
<li>
<p>如果接收器是一个Go内置的类型，如整数或字符串，不需要被修改，则使用一个值类型。</p>
<p>// Good:
type User string</p>
<p>func (u User) String() { return string(u) }</p>
</li>
<li>
<p>如果接收器是一个map、函数或channel，使用一个值而不是一个指针。</p>
<p>// Good:
// See <a href="https://pkg.go.dev/net/http#Header">https://pkg.go.dev/net/http#Header</a>.
type Header map[string][]string</p>
<p>func (h Header) Add(key, value string) { /* omitted */ }</p>
</li>
<li>
<p>如果接收器是一个”小”数组或结构体，并且元素是没有可变字段和指针的值类型，值类型接收器通常是正确的选择。</p>
<p>// Good:
// See <a href="https://pkg.go.dev/time#Time">https://pkg.go.dev/time#Time</a>.
type Time struct { /* omitted */ }</p>
<p>func (t Time) Add(d Duration) Time { /* omitted */ }</p>
</li>
<li>
<p>如果不确定，那就使用指针类型receiver</p>
</li>
</ul>
<p>作为一般的指导原则，最好使一个类型的方法要么所有都是指针方法，要么所有都是值方法。</p>
<p>注意：关于向函数传递值或指针是否会影响性能，有很多错误的信息。编译器可以选择向堆栈中的值传递指针以及复制堆栈中的值，但在大多数情况下，这些考虑的优先级不应该超过代码的可读性和正确性。当性能确实重要时，在决定一种方法优于另一种方法之前，用一个实际的基准测试对两种方法进行分析是很重要的。</p>
<h3 id="switch和break">switch和break</h3>
<p>不要在switch子句的末尾使用没有目标标签的break语句，它们是多余的。与C和Java不同，Go中的switch子句会自动跳出，我们需要显式使用fallthrough语句来实现C风格的行为。如果你想澄清一个空case子句的目的，请使用注释而不是break。</p>
<pre><code>// Good:
switch x {
case &quot;A&quot;, &quot;B&quot;:
    buf.WriteString(x)
case &quot;C&quot;:
    // handled outside of the switch statement
default:
    return fmt.Errorf(&quot;unknown value: %q&quot;, x)
}


// Bad:
switch x {
case &quot;A&quot;, &quot;B&quot;:
    buf.WriteString(x)
    break // this break is redundant
case &quot;C&quot;:
    break // this break is redundant
default:
    return fmt.Errorf(&quot;unknown value: %q&quot;, x)
}
</code></pre>
<p>注意：如果switch子句位于for循环中，在switch中使用break并不能退出外围的for循环。</p>
<pre><code>for {
  switch x {
  case &quot;A&quot;:
     break // exits the switch, not the loop
  }
}
</code></pre>
<p>为了跳出外围的循环，请在for语句上使用一个标签。</p>
<pre><code>loop:
  for {
    switch x {
    case &quot;A&quot;:
       break loop // exits the loop
    }
  }
</code></pre>
<h3 id="同步函数">同步函数</h3>
<p>同步函数直接返回其结果，并在返回前完成所有回调或channel操作。比起异步函数，我们更喜欢同步函数。</p>
<p>同步函数在调用中保持goroutines的本地化。这有助于推断它们的生命周期，并避免泄漏和数据竞争。同步函数也更容易测试，因为调用者可以传递一个输入并检查输出，而不需要轮询或使用同步原语。</p>
<p>如果有必要，调用者可以通过在一个单独的goroutine中调用该函数来增加并发性。然而，在调用者一方删除不必要的并发是相当困难的（有时是不可能的）。</p>
<ul>
<li>另见”重新思考经典的并发模式”，Bryan Mills的演讲：<a href="https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view">幻灯片</a>，<a href="https://www.youtube.com/watch?v=5zXAHh5tJqQ">视频</a></li>
</ul>
<h3 id="类型别名">类型别名</h3>
<p>使用类型定义：type T1 T2 来定义一个新的类型。使用<a href="http://go.dev/ref/spec#Type_declarations">类型别名</a>：type T1 = T2 来引用一个现有的类型，而不用定义一个新的类型。类型别名是罕见的；它们的主要用途是帮助软件包迁移到新的源代码位置。在不需要时不要使用类型别名。</p>
<h3 id="使用q">使用%q</h3>
<p>Go的格式函数（fmt.Printf等）有一个%q的动词，可以打印双引号内的字符串。</p>
<pre><code>// Good:
fmt.Printf(&quot;value %q looks like English text&quot;, someText)
</code></pre>
<p>尽量使用%q，而不是使用%s来手动操作：</p>
<pre><code>// Bad:
fmt.Printf(&quot;value \&quot;%s\&quot; looks like English text&quot;, someText)
// Avoid manually wrapping strings with single-quotes too:
fmt.Printf(&quot;value '%s' looks like English text&quot;, someText)
</code></pre>
<p>当输入值可能为空或包含控制字符时，建议在面向人类的输出中使用%q。很难注意到一个空字符串，但%q输出的”&ldquo;会很明显地表现出来。</p>
<h3 id="使用any">使用any</h3>
<p>Go 1.18引入了一个any类型作为interface{}的别名。因为它是一个别名，所以any在很多情况下等同于interface{}，而在其他情况下，它可以通过显式转换轻松地进行互换。倾向于在新代码中使用any。</p>
<h2 id="常用库">常用库</h2>
<h3 id="flags">Flags</h3>
<p>Google代码库中的Go程序使用标准库flag包的内部变体。它有一个和标准库flag包相似的接口，但与谷歌内部系统有更为良好的互操作性。Go二进制文件中的标志名称应该倾向于使用下划线来分隔单词，不过保存标志值的变量应该遵循标准的Go名称风格（驼峰命名）。具体来说，标志名应该使用蛇形命名，而变量名应该是驼峰命名的对应名称。</p>
<pre><code>// Good:
var (
    pollInterval = flag.Duration(&quot;poll_interval&quot;, time.Minute, &quot;Interval to use for polling.&quot;)
)


// Bad:
var (
    poll_interval = flag.Int(&quot;pollIntervalSeconds&quot;, 60, &quot;Interval to use for polling in seconds.&quot;)
)
</code></pre>
<p>flag必须只在main包或等价包中定义。</p>
<p>通用包应该使用Go的API来配置，而不是通过命令行接口来配置；不要在导入一个库时附带导出新的标志的副作用。也就是说，最好是使用明确的函数参数或结构体字段赋值，或者在最严格的审查下，不那么频繁地导出全局变量。在极其罕见的情况下，如果有必要打破这一规则，标志的名称必须明确指出它所配置的包。</p>
<p>如果你的标志是全局变量，请将它们放在自己的var组中，遵循导入部分。</p>
<p>围绕创建带有子命令的复杂CLI的最佳实践，还有一些讨论。</p>
<p>也请参见：</p>
<ul>
<li>[Tip of the Week #45: Avoid Flags, Especially in Library Code][totw-45]</li>
<li><a href="https://google.github.io/styleguide/go/index.html#gotip">Go Tip #10: Configuration Structs and Flags</a></li>
<li><a href="https://google.github.io/styleguide/go/index.html#gotip">Go Tip #80: Dependency Injection Principles</a></li>
</ul>
<h3 id="日志包">日志包</h3>
<p>Google代码库中的Go程序使用了标准库日志包的一个变种。它有一个类似的但更强大的接口，并能与谷歌内部系统很好地互操作。这个库的开源版本是作为软件包<a href="https://pkg.go.dev/github.com/golang/glog">glog</a>提供的，开源的Google项目可以使用它，但本指南自始至终将其称为log。</p>
<p>注意：对于异常的程序退出，这个库使用log.Fatal来终止，并有堆栈跟踪，使用log.Exit来停止，没有堆栈跟踪。没有像标准库中的log.Panic函数。</p>
<p>提示：log.Info(v)等同于log.Infof(“%v”, v)，对于其他日志级别也是如此。当你没有格式化工作时，更倾向于非格式化版本。</p>
<p>另请参见：</p>
<ul>
<li>关于<a href="https://google.github.io/styleguide/go/best-practices#error-logging">记录错误</a>和<a href="https://google.github.io/styleguide/go/best-practices#vlog">自定义verbosily级别</a>的最佳实践</li>
<li>何时以及如何使用日志包来<a href="https://google.github.io/styleguide/go/best-practices#checks-and-panics">停止程序</a></li>
</ul>
<h3 id="contexts">Contexts</h3>
<p>context.Context类型的值携带安全凭证、跟踪信息、截止日期和取消信号，跨越API和进程边界。与C++和Java在Google代码库中使用线程本地存储不同，Go程序在整个函数调用链中明确地传递上下文，从传入的RPC和HTTP请求到传出的请求。</p>
<p>当被传递到一个函数或方法时，context.Context总是作为第一个参数。</p>
<pre><code>func F(ctx context.Context /* other arguments */) {}
</code></pre>
<p>例外情况是：</p>
<ul>
<li>在HTTP处理程序中，其上下文来自req.Context()。</li>
<li>在流式RPC方法中，上下文来自于流。</li>
</ul>
<p>使用gRPC流的代码从生成的服务器类型中的Context()方法访问上下文，该类型实现了grpc.ServerStream。参见<a href="https://grpc.io/docs/languages/go/generated-code/">gRPC生成的代码文档</a>。</p>
<ul>
<li>
<p>在入口函数中（此类函数的例子见下文），使用context.background()。</p>
<ul>
<li>在二进制目标中：main</li>
<li>在通用代码和库中：init</li>
<li>在测试中：TestXXX, BenchmarkXXX, FuzzXXX</li>
</ul>
<p>注意：在调用链中间的代码需要使用context.background()来创建自己的基础上下文，这非常罕见。总是优先从你的调用者那里获取一个上下文，除非它是错误的上下文。</p>
<p>你可能会遇到一些服务器库（Stubby、gRPC或Google的Go服务器框架中的HTTP的实现），它们为每个请求构建一个新的上下文对象。这些上下文被立即填充了来自传入请求的信息，因此当传递给请求处理程序时，上下文的附加值已经从客户端调用者那里通过网络边界传播给了它。此外，这些上下文的生命期与请求的生命期是一致的：当请求完成时，上下文就被取消了。</p>
<p>除非你正在实现一个服务器框架，否则你不应该在库代码中用context.Background()创建上下文。相反，如果有一个现有的上下文可用的话，最好使用下面提到的context detachment。如果你认为你确实需要入口函数之外的context.Background()，请在承诺实现之前使用Google Go风格邮件列表讨论。</p>
</li>
</ul>
<p>在函数中context.Context优先的惯例也适用于测试helper。</p>
<pre><code>// Good:
func readTestFile(ctx context.Context, t *testing.T, path string) string {}
</code></pre>
<p>不要给结构体类型添加上下文成员。相反，在需要传递上下文的类型上的每个方法中添加一个上下文参数。唯一的例外是那些签名必须与标准库或谷歌控制之外的第三方库中的接口匹配的方法。这种情况非常罕见，应该在实施和可读性审查之前使用Google Go风格邮件列表讨论。</p>
<p>谷歌代码库中必须催生后台操作的代码，这些后台操作可以在父级上下文被取消后运行，可以使用内部包进行分离。请关注<a href="https://github.com/golang/go/issues/40221">问题#40221</a>，了解关于开源替代方案的讨论。</p>
<p>由于上下文是不可改变的，因此可以将同一个上下文传递给共享相同的deadline、取消信号、凭证、父级跟踪等的多个调用。</p>
<p>也请参见：</p>
<ul>
<li><a href="https://go.dev/blog/context-and-structs">Context和structs</a></li>
</ul>
<h4 id="自定义上下文">自定义上下文</h4>
<p>不要创建自定义的上下文类型，也不要在函数签名中使用上下文以外的接口。这条规则没有例外。</p>
<p>想象一下，如果每个团队都有一个自定义的上下文。每个从包P到包Q的函数调用都必须确定如何将PContext转换为QContext，对于所有的包P和Q对来说都是不切实际的，而且容易出错，这使得增加上下文参数的自动化重构几乎不可能。</p>
<p>如果你有应用数据需要传递，请把它放在一个参数中，放在接收器中，放在globals中，或者放在Context值中，如果它真的属于那里。创建自己的Context类型是不可接受的，因为它破坏了Go团队使Go程序在生产中正常工作的能力。</p>
<h3 id="cryptorand">crypto/rand</h3>
<p>不要使用软件包math/rand来生成key，即使是丢弃的key。如果不加种子，生成器是完全可预测的。用time.Nanoseconds()做种子，就只有几个比特的熵了。相反，如果你需要文本，打印成十六进制或base64，请使用crypto/rand的Reader。</p>
<pre><code>// Good:
import (
    &quot;crypto/rand&quot;
    // &quot;encoding/base64&quot;
    // &quot;encoding/hex&quot;
    &quot;fmt&quot;

    // ...
)

func Key() string {
    buf := make([]byte, 16)
    if _, err := rand.Read(buf); err != nil {
        log.Fatalf(&quot;Out of randomness, should never happen: %v&quot;, err)
    }
    return fmt.Sprintf(&quot;%x&quot;, buf)
    // or hex.EncodeToString(buf)
    // or base64.StdEncoding.EncodeToString(buf)
}
</code></pre>
<p>注意：log.Fatalf不是标准库中的日志。参见[#logging]。</p>
<h2 id="有用的测试失败">有用的测试失败</h2>
<p>不需要阅读测试的源代码就可以诊断出测试的失败。测试失败时应该有有用的信息详细说明。</p>
<ul>
<li>是什么导致了失败</li>
<li>哪些输入导致了错误</li>
<li>实际结果</li>
<li>预期结果是什么</li>
</ul>
<p>以下是实现这一目标的具体约定。</p>
<h3 id="断言库">断言库</h3>
<p>不要创建 “断言库 “作为测试的辅助工具。</p>
<p>断言库是试图在测试中结合验证和生产失败信息的库（尽管同样的陷阱也可以适用于其他测试助手）。关于测试助手和断言库之间的区别的更多信息，请参见<a href="https://google.github.io/styleguide/go/best-practices#test-functions">最佳实践</a>。</p>
<pre><code>// Bad:
var obj BlogPost

assert.IsNotNil(t, &quot;obj&quot;, obj)
assert.StringEq(t, &quot;obj.Type&quot;, obj.Type, &quot;blogPost&quot;)
assert.IntEq(t, &quot;obj.Comments&quot;, obj.Comments, 2)
assert.StringNotEq(t, &quot;obj.Body&quot;, obj.Body, &quot;&quot;)
</code></pre>
<p>断言库往往要么提前停止测试（如果断言调用t.Fatalf或panic），要么省略关于测试正确的相关信息。</p>
<pre><code>// Bad:
package assert

func IsNotNil(t *testing.T, name string, val interface{}) {
    if val == nil {
        t.Fatalf(&quot;data %s = nil, want not nil&quot;, name)
    }
}

func StringEq(t *testing.T, name, got, want string) {
    if got != want {
        t.Fatalf(&quot;data %s = %q, want %q&quot;, name, got, want)
    }
}
</code></pre>
<p>复杂的断言函数通常不提供<a href="https://google.github.io/styleguide/go/decisions#useful-test-failures">有用的失败信息</a>和存在于测试函数中的上下文。太多的断言函数和库会导致开发人员的经验碎片化：我应该使用哪个断言库，它应该发出什么风格的输出格式，等等。碎片化产生了不必要的混乱，特别是对于库的维护者和大规模修改的作者，他们负责修复潜在的下游故障。与其创建一个特定领域的测试语言，不如使用Go本身。</p>
<p>断言库通常会把比较和相等性检查的因素排除在外。尽量使用标准库，如<a href="https://pkg.go.dev/github.com/google/go-cmp/cmp">cmp</a>和fmt来代替。</p>
<pre><code>// Good:
var got BlogPost

want := BlogPost{
    Comments: 2,
    Body:     &quot;Hello, world!&quot;,
}

if !cmp.Equal(got, want) {
    t.Errorf(&quot;blog post = %v, want = %v&quot;, got, want)
}
</code></pre>
<p>对于更多特定领域的比较帮助器，倾向于返回一个值或一个可以在测试的失败消息中使用的错误，而不是传递*testing.T并调用其错误报告方法。</p>
<pre><code>// Good:
func postLength(p BlogPost) int { return len(p.Body) }

func TestBlogPost_VeritableRant(t *testing.T) {
    post := BlogPost{Body: &quot;I am Gunnery Sergeant Hartman, your senior drill instructor.&quot;}

    if got, want := postLength(post), 60; got != want {
        t.Errorf(&quot;length of post = %v, want %v&quot;, got, want)
    }
}
</code></pre>
<p>最佳实践：如果postLength是非琐碎的，那么直接测试它是有意义的，独立于任何使用它的测试。</p>
<p>另见：</p>
<ul>
<li><a href="https://google.github.io/styleguide/go/decisions#types-of-equality">相等性比较和差异</a></li>
<li><a href="https://google.github.io/styleguide/go/decisions#print-diffs">打印差异</a></li>
<li>更多关于测试助手和断言助手的区别，请参见<a href="https://google.github.io/styleguide/go/best-practices#test-functions">最佳实践</a></li>
</ul>
<h3 id="识别函数">识别函数</h3>
<p>在大多数测试中，失败信息应该包括失败的函数名称，即使它从测试函数的名称中看起来很明显。具体来说，你的失败信息应该是“YourFunc(%v) = %v, want %v”，而不是仅仅“got %v, want %v”。</p>
<h3 id="识别输入">识别输入</h3>
<p>在大多数测试中，如果函数输入很短，失败信息应该包括函数输入。如果输入的相关属性不明显（例如，因为输入很大或不透明），你应该在测试用例的名称中加入被测试内容的描述，并将该描述作为错误信息的一部分打印出来。</p>
<h3 id="got在want之前">got在want之前</h3>
<p>测试输出应该包括函数返回的实际值，然后再打印预期的值。打印测试输出的标准格式是“YourFunc(%v) = %v, want %v”。在你写”actual”和”expected”的地方，最好分别使用”got “和”want”。</p>
<p>对于差异来说，方向性不那么明显，因此，重要的是包括一个键来帮助解释失败。参见<a href="https://google.github.io/styleguide/go/decisions#print-diffs">打印差异的章节</a>。无论你在故障信息中使用哪种差异顺序，你都应该明确指出它是故障信息的一部分，因为现有的代码在顺序上是不一致的。</p>
<h3 id="结构体整体比较">结构体整体比较</h3>
<p>如果你的函数返回一个结构体（或任何有多个字段的数据类型，如切片、数组和map），避免编写测试代码，对结构体进行手工编码的逐字段比较。相反，构建你期望你的函数返回的数据，并直接使用<a href="https://google.github.io/styleguide/go/decisions#types-of-equality">深度比较法</a>进行比较。</p>
<p>注意：如果您的数据包含不相关的字段，从而掩盖了测试的意图，那么这一点就不适用了。</p>
<p>如果你的结构体需要进行近似（或同等种类的语义）的相等性比较，或者它包含不能进行相等性比较的字段（例如，如果其中一个字段是io.Reader），用<a href="https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts">cmpopts</a>选项（如cmpopts.IgnoreInterfaces）调整<a href="https://pkg.go.dev/github.com/google/go-cmp/cmp/cmp#Diff">cmp.Diff</a>或cmp.Equal比较可能满足你的需要（示例）。</p>
<p>如果你的函数返回多个返回值，你不需要在比较之前将这些返回值包裹在一个结构中。只需单独比较返回值并打印它们：</p>
<pre><code>// Good:
val, multi, tail, err := strconv.UnquoteChar(`\&quot;Fran &amp; Freddie's Diner\&quot;`, '&quot;')
if err != nil {
  t.Fatalf(...)
}
if val != `&quot;` {
  t.Errorf(...)
}
if multi {
  t.Errorf(...)
}
if tail != `Fran &amp; Freddie's Diner&quot;` {
  t.Errorf(...)
}
</code></pre>
<h3 id="比较稳定的结果">比较稳定的结果</h3>
<p>避免比较可能依赖于你不拥有的包的输出稳定性的结果。相反，测试应该在语义相关的信息上进行比较，这些信息是稳定的，可以抵抗依赖关系的变化。对于返回格式化字符串或序列化字节的功能，一般来说，假设输出是稳定的并不安全。</p>
<p>例如，json.Marshal可以改变（而且在过去已经改变过）它所产生的特定字节。如果json包改变了它序列化字节的方式，在JSON字符串上执行字符串相等的测试可能会失败。相反，一个更健壮的测试将解析JSON字符串的内容，并确保它在语义上等同于一些预期的数据结构。</p>
<h3 id="持续进行">持续进行</h3>
<p>测试应该尽可能地持续下去，即使在失败之后，以便在一次测试运行中打印出所有失败的检查。这样一来，正在修复失败测试的开发者就不必在修复每个错误后重新运行测试来发现下一个错误。</p>
<p>更倾向于调用t.Error而不是t.Fatal来报告一个不匹配。当比较一个函数输出的几个不同属性时，对每一个比较都使用t.Error。</p>
<p>调用t.Fatal主要用于报告一个意外的错误情况，当后续的比较失败不会有什么意义时。</p>
<p>对于表驱动的测试，考虑使用子测试(subtests)，使用t.Fatal而不是t.Error和continue。参见<a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #25: Subtests: 让你的测试更精简</a>。</p>
<p>最佳实践：关于什么时候应该使用t.Fatal的更多讨论，见<a href="https://google.github.io/styleguide/go/best-practices#t-fatal">最佳实践</a>。</p>
<h3 id="相等性比较和差异">相等性比较和差异</h3>
<p>“==”操作符使用<a href="http://go.dev/ref/spec#Comparison_operators">语言定义的比较法</a>来评估相等性。标量值（数字、布尔运算等）根据其值进行比较，但只有一些结构和接口可以用这种方式进行比较。指针的比较是基于它们是否指向同一个变量，而不是基于它们所指向的值是否相等。</p>
<p><a href="https://pkg.go.dev/github.com/google/go-cmp/cmp">cmp包</a>可以比较不适合由==处理的更复杂的数据结构，例如切片。使用cmp.Equal来进行相等性比较，使用cmp.Diff来获得对象之间可供人类阅读的差异。</p>
<pre><code>// Good:
want := &amp;Doc{
    Type:     &quot;blogPost&quot;,
    Comments: 2,
    Body:     &quot;This is the post body.&quot;,
    Authors:  []string{&quot;isaac&quot;, &quot;albert&quot;, &quot;emmy&quot;},
}
if !cmp.Equal(got, want) {
    t.Errorf(&quot;AddPost() = %+v, want %+v&quot;, got, want)
}
</code></pre>
<p>虽然cmp包不是Go标准库的一部分，但它是由Go团队维护的，随着时间的推移应该会产生稳定的相等性结果。它是用户可配置的，应该可以满足大多数的比较需求。</p>
<p>现有的代码可能会使用以下较早的库，并且可以继续使用它们以保持一致性。</p>
<ul>
<li><a href="https://pkg.go.dev/github.com/kylelemons/godebug/pretty">pretty</a>产生美观的差异报告。然而，它非常谨慎地认为具有相同视觉表现的值是相等的。特别是，pretty不捕捉nil切片和空切片之间的差异，对具有相同字段的不同接口实现不敏感，并且可以使用嵌套map作为与结构值比较的基础。在产生差异之前，它还会将整个值序列化为一个字符串，因此对于比较大的值来说不是一个好的选择。默认情况下，它比较的是未导出的字段，这使得它对你的依赖关系中实现细节的变化很敏感。出于这个原因，在protobuf信息上使用pretty是不合适的。</li>
</ul>
<p>在新的代码中更倾向于使用cmp，值得考虑更新旧的代码，以便在实际情况下使用cmp。</p>
<p>旧的代码可能使用标准库reflect.DeepEqual函数来比较复杂的结构。reflect.DeepEqual不应该被用来检查相等性，因为它对未导出的字段和其他实现细节的变化很敏感。使用reflect.DeepEqual的代码应该更新为上述库中的一个。</p>
<p>注意：cmp包是为测试而设计的，而不是为生产使用。因此，当它怀疑一个比较被错误地执行时，它可能会panic，以向用户提供关于如何改进测试以减少脆性的指导。鉴于cmp具有报panic的倾向，它不适合在生产中使用的代码，因为虚假的panic可能是致命的。</p>
<h3 id="详细程度">详细程度</h3>
<p>传统的失败信息是“YourFunc(%v) = %v, want %v”，适用于大多数Go测试。然而，有些情况下可能需要更多或更少的细节：</p>
<ul>
<li>进行复杂交互的测试也应该描述交互。例如，如果同一个YourFunc被调用了好几次，要确定哪个调用没有通过测试。如果知道系统的任何额外状态是很重要的，那么在失败输出中包括这些（或者至少在日志中）。</li>
<li>如果数据是一个复杂的结构，有大量的模板，在消息中只描述重要的部分是可以接受的，但不要过分地掩盖数据。</li>
<li>设置失败不需要同样水平的细节。如果一个测试助手填充了一个Spanner表，但Spanner是关闭的，你可能不需要包括你要存储在数据库中的测试输入。t.Fatalf(“Setup: Failed to set up test database: %s”, err)通常足以帮助解决这个问题。</li>
</ul>
<p>提示：在开发过程中触发你的失败模式。审查失败信息是什么样子的，维护者是否能有效地处理失败。</p>
<p>有一些技术可以清晰地再现测试输入和输出。</p>
<ul>
<li>当打印字符串数据时，%q通常是有用的，可以强调该值的重要性，并更容易发现坏值。</li>
<li>当打印（小）结构时，%+v可能比%v更有用。</li>
<li>当验证较大的数值失败时，打印一个差异可以使人们更容易理解失败的原因。</li>
</ul>
<h3 id="打印差异">打印差异</h3>
<p>如果你的函数返回大量的输出，那么当你的测试失败时，阅读失败信息的人很难发现其中的差异。与其同时打印返回值和想要的值，不如做一个差异。</p>
<p>要计算这些值的差异，首选cmp.Diff，特别是对于新的测试和新的代码，但也可以使用其他工具。关于每个函数的优势和劣势的指导，请看类型的相等性。</p>
<ul>
<li><a href="https://pkg.go.dev/github.com/google/go-cmp/cmp/cmp#Diff">cmp.Diff</a></li>
<li><a href="https://pkg.go.dev/github.com/kylelemons/godebug/pretty#Compare">pretty.Compare</a></li>
</ul>
<p>你可以使用diff包来比较多行字符串或字符串的列表。你可以把它作为其他类型的差异的构建模块。</p>
<p>在你的失败信息中添加一些文字，解释差异的方向。</p>
<ul>
<li>当你使用cmp、pretty和diff包时，类似diff(-want +got)的东西很好（如果你向函数传递(want, got)），因为你添加到格式字符串中的-和+将与实际出现在diff行开头的-和+相匹配。如果你把(got, want)传给你的函数，正确的键将是(-got +want)。</li>
<li>messagediff包使用不同的输出格式，所以当你使用它时，消息diff（want -&gt; got）是合适的（如果你向函数传递（want，got）），因为箭头的方向将与”修改”行中的箭头方向一致。</li>
</ul>
<p>diff将跨越多行，所以你应该在打印diff之前打印一个新行。</p>
<h3 id="测试错误语义">测试错误语义</h3>
<p>当一个单元测试执行字符串比较或使用cmp包来检查针对特定输入返回的特定类型的错误时，你可能会发现，如果这些错误信息中的任何一个在未来被重新措辞，你的测试将是脆弱的。因为这有可能把你的单元测试变成一个变化检测器（<a href="https://testing.googleblog.com/2015/01/testing-on-toilet-change-detector-tests.html">见TotT: Change-Detector Tests Considered Harmful</a> ），所以不要使用字符串比较来检查你的函数返回什么类型的错误。然而，允许使用字符串比较来检查来自被测包的错误信息是否满足某些属性，例如，它是否包括参数名称。</p>
<p>Go中的错误值通常有一个用于人眼的部分和一个用于语义控制流的部分。测试应该设法只测试可以可靠地观察到的语义信息，而不是显示用于人类调试的信息，因为这通常会在未来发生变化。关于构建具有语义的错误的指导，请参见<a href="https://google.github.io/styleguide/go/best-practices#error-handling">有关错误的最佳实践</a>。如果来自于你控制之外的依赖关系的错误的语义信息不充分，请考虑向所有者提交一个错误，以帮助改进API，而不是依靠解析错误信息。</p>
<p>在单元测试中，通常只关心一个错误是否发生。如果是这样，那么在你预期发生错误时，只测试错误是否为非零就足够了。如果你想测试错误在语义上是否与其他错误匹配，那么可以考虑使用cmp与<a href="https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts#EquateErrors">cmpopts.EquateErrors</a>。</p>
<p>注意：如果一个测试使用了cmpopts.EquateErrors，但是它所有的wantErr值都是nil或者cmpopts.AnyError，那么使用cmp是不必要的机制。简化代码，使want字段成为一个bool。然后，你可以使用一个简单的比较法，即!=。</p>
<pre><code>// Good:
gotErr := f(test.input) != nil
if gotErr != test.wantErr {
    t.Errorf(&quot;f(%q) returned err = %v, want error presence = %v&quot;, test.input, gotErr, test.wantErr)
}
</code></pre>
<p>另见<a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #13。设计用于检查的错误</a>。</p>
<h2 id="测试的结构组织">测试的结构组织</h2>
<h3 id="子测试subtests">子测试(subtests)</h3>
<p>标准Go测试库提供了<a href="https://pkg.go.dev/testing#hdr-Subtests_and_Sub_benchmarks">定义子测试的功能</a>。这使得设置和清理、控制并行性和测试过滤变得灵活。子测试可能很有用（特别是对于表驱动的测试），但不是必须使用它们。请参阅<a href="https://blog.golang.org/subtests">Go博客中关于子测试的文章</a>。</p>
<p>子测试不应该依赖其他case的执行来获得成功或初始状态，因为子测试应该能够通过使用go test -run标志或使用Bazel测试过滤表达式来单独运行。</p>
<h4 id="子测试命名">子测试命名</h4>
<p>命名你的子测试，使其在测试输出中可读，并在命令行中对测试过滤的用户有用。当你使用t.Run来创建一个子测试时，第一个参数被用作测试的描述性名称。为了确保测试结果对阅读日志的人来说是可读的，选择在转义后仍然有用和可读的子测试名称。把子测试名称想得更像一个函数标识符，而不是一个散文描述。test runner用下划线代替空格，并转义非打印字符。如果你的测试数据受益于较长的描述，可以考虑将描述放在一个单独的字段中（也许可以用t.Log打印，或者与失败信息一起打印）。</p>
<p>子测试可以使用Go test runner或Bazel测试过滤器的标志单独运行，所以选择描述性的名字，同时也要容易输入。</p>
<p>警告：斜杠”/”在子测试名称中是特别不友好的，因为它们对测试过滤器有特殊意义。</p>
<pre><code># Bad:
# Assuming TestTime and t.Run(&quot;America/New_York&quot;, ...)
bazel test :mytest --test_filter=&quot;Time/New_York&quot;    # Runs nothing!
bazel test :mytest --test_filter=&quot;Time//New_York&quot;   # Correct, but awkward.
</code></pre>
<p>为了识别函数的输入，把函数名包括在测试的失败信息中，在那里它们不会被test runner转义。</p>
<pre><code>// Good:
func TestTranslate(t *testing.T) {
    data := []struct {
        name, desc, srcLang, dstLang, srcText, wantDstText string
    }{
        {
            name:        &quot;hu=en_bug-1234&quot;,
            desc:        &quot;regression test following bug 1234. contact: cleese&quot;,
            srcLang:     &quot;hu&quot;,
            srcText:     &quot;cigarettát és egy öngyújtót kérek&quot;,
            dstLang:     &quot;en&quot;,
            wantDstText: &quot;cigarettes and a lighter please&quot;,
        }, // ...
    }
    for _, d := range data {
        t.Run(d.name, func(t *testing.T) {
            got := Translate(d.srcLang, d.dstLang, d.srcText)
            if got != d.wantDstText {
                t.Errorf(&quot;%s\nTranslate(%q, %q, %q) = %q, want %q&quot;,
                    d.desc, d.srcLang, d.dstLang, d.srcText, got, d.wantDstText)
            }
        })
    }
}
</code></pre>
<p>下面是一些要避免的例子：</p>
<pre><code>// Bad:
// Too wordy.
t.Run(&quot;check that there is no mention of scratched records or hovercrafts&quot;, ...)
// Slashes cause problems on the command line.
t.Run(&quot;AM/PM confusion&quot;, ...)
</code></pre>
<h3 id="表驱动的测试">表驱动的测试</h3>
<p>当许多不同的测试用例可以使用类似的测试逻辑进行测试时，使用表驱动的测试。</p>
<ul>
<li>当测试一个函数的实际输出是否等于预期输出。例如，fmt.Sprintf的许多测试或下面的最小片段。</li>
<li>当测试一个函数的输出是否总是符合同一组不变量时。例如，net.Dial的测试。</li>
</ul>
<p>下面是一个表格驱动的测试的最小结构，从标准库strings包中复制出来的。如果需要，你可以使用不同的名字，把测试切片移到测试函数中，或者添加额外的设施，如子测试或设置和清理函数。始终牢记<a href="https://google.github.io/styleguide/go/decisions#useful-test-failures">有用的测试失败</a>：</p>
<pre><code>// Good:
var compareTests = []struct {
    a, b string
    i    int
}{
    {&quot;&quot;, &quot;&quot;, 0},
    {&quot;a&quot;, &quot;&quot;, 1},
    {&quot;&quot;, &quot;a&quot;, -1},
    {&quot;abc&quot;, &quot;abc&quot;, 0},
    {&quot;ab&quot;, &quot;abc&quot;, -1},
    {&quot;abc&quot;, &quot;ab&quot;, 1},
    {&quot;x&quot;, &quot;ab&quot;, 1},
    {&quot;ab&quot;, &quot;x&quot;, -1},
    {&quot;x&quot;, &quot;a&quot;, 1},
    {&quot;b&quot;, &quot;x&quot;, -1},
    // test runtime·memeq's chunked implementation
    {&quot;abcdefgh&quot;, &quot;abcdefgh&quot;, 0},
    {&quot;abcdefghi&quot;, &quot;abcdefghi&quot;, 0},
    {&quot;abcdefghi&quot;, &quot;abcdefghj&quot;, -1},
}

func TestCompare(t *testing.T) {
    for _, tt := range compareTests {
        cmp := Compare(tt.a, tt.b)
        if cmp != tt.i {
            t.Errorf(`Compare(%q, %q) = %v`, tt.a, tt.b, cmp)
        }
    }
}
</code></pre>
<p>注意：上面这个例子中的失败信息满足了识别函数和识别输入的要求。没有必要再<a href="https://google.github.io/styleguide/go/decisions#table-tests-identifying-the-row">用数字来识别行</a>。</p>
<p>当一些测试用例需要使用与其他测试用例不同的逻辑进行检查时，编写多个测试函数是比较合适的，正如<a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #50: Disjoint Table Tests</a>中解释的那样。当一个表中的每个条目都有自己不同的条件逻辑来检查其输入的每个输出时，你的测试代码的逻辑会变得难以理解。如果测试用例有不同的逻辑，但有相同的设置，在一个单一的测试函数中使用子测试序列可能是更有意义的。</p>
<p>你可以将表驱动的测试与多个测试函数结合起来。例如，当测试一个函数的输出与预期的输出完全一致，并且函数对无效的输入返回一个非nil的错误时，那么编写两个单独的表驱动的测试函数是最好的方法：一个用于正常的非错误输出，一个用于错误输出。</p>
<h4 id="数据驱动的测试用例">数据驱动的测试用例</h4>
<p>表测试行有时会变得很复杂，行的值决定了测试用例内的条件行为。测试用例之间的重复所带来的额外清晰度对可读性是必要的。</p>
<pre><code>// Good:
type decodeCase struct {
    name   string
    input  string
    output string
    err    error
}

func TestDecode(t *testing.T) {
    // setupCodex is slow as it creates a real Codex for the test.
    codex := setupCodex(t)

    var tests []decodeCase // rows omitted for brevity

    for _, test := range tests {
        t.Run(test.name, func(t *testing.T) {
            output, err := Decode(test.input, codex)
            if got, want := output, test.output; got != want {
                t.Errorf(&quot;Decode(%q) = %v, want %v&quot;, test.input, got, want)
            }
            if got, want := err, test.err; !cmp.Equal(got, want) {
                t.Errorf(&quot;Decode(%q) err %q, want %q&quot;, test.input, got, want)
            }
        })
    }
}

func TestDecodeWithFake(t *testing.T) {
    // A fakeCodex is a fast approximation of a real Codex.
    codex := newFakeCodex()

    var tests []decodeCase // rows omitted for brevity

    for _, test := range tests {
        t.Run(test.name, func(t *testing.T) {
            output, err := Decode(test.input, codex)
            if got, want := output, test.output; got != want {
                t.Errorf(&quot;Decode(%q) = %v, want %v&quot;, test.input, got, want)
            }
            if got, want := err, test.err; !cmp.Equal(got, want) {
                t.Errorf(&quot;Decode(%q) err %q, want %q&quot;, test.input, got, want)
            }
        })
    }
}
</code></pre>
<p>在下面的反例中，注意在case设置中很难区分每个测试case使用哪种类型的Codex。(突出显示的部分违反了<a href="https://testing.googleblog.com/2008/09/tott-data-driven-traps.html">TotT的建议：数据驱动的陷阱</a>！) .)</p>
<pre><code>// Bad:
type decodeCase struct {
  name   string
  input  string
  codex  testCodex
  output string
  err    error
}

type testCodex int

const (
  fake testCodex = iota
  prod
)

func TestDecode(t *testing.T) {
  var tests []decodeCase // rows omitted for brevity

  for _, test := tests {
    t.Run(test.name, func(t *testing.T) {
      var codex Codex
      switch test.codex {
      case fake:
        codex = newFakeCodex()
      case prod:
        codex = setupCodex(t)
      default:
        t.Fatalf(&quot;unknown codex type: %v&quot;, codex)
      }
      output, err := Decode(test.input, codex)
      if got, want := output, test.output; got != want {
        t.Errorf(&quot;Decode(%q) = %q, want %q&quot;, test.input, got, want)
      }
      if got, want := err, test.err; !cmp.Equal(got, want) {
        t.Errorf(&quot;Decode(%q) err %q, want %q&quot;, test.input, got, want)
      }
    })
  }
}
</code></pre>
<h4 id="识别行">识别行</h4>
<p>不要用测试表中的测试索引来代替命名你的测试或打印输入。没有人愿意去看你的测试表，为了弄清楚哪个测试用例失败而去数条目。</p>
<pre><code>// Bad:
tests := []struct {
    input, want string
}{
    {&quot;hello&quot;, &quot;HELLO&quot;},
    {&quot;wORld&quot;, &quot;WORLD&quot;},
}
for i, d := range tests {
    if strings.ToUpper(d.input) != d.want {
        t.Errorf(&quot;failed on case #%d&quot;, i)
    }
}
</code></pre>
<p>在你的测试结构中添加测试描述，并将其与失败信息一起打印。当使用子测试时，你的子测试名称应能有效识别行。</p>
<p>重要的是：即使t.Run限定了输出和执行的范围，你也必须始终识别输入。表的测试行名称必须遵循子测试的命名指导。</p>
<h3 id="测试助手">测试助手</h3>
<p>一个测试助手(test helper)是一个执行设置或清理任务的函数。所有发生在测试助手中的故障都被认为是环境的故障（而不是被测代码的故障）–例如，当一个测试数据库不能被启动，因为在这台机器上没有更多的空闲端口。</p>
<p>如果你传递一个*testing.T，调用t.Helper，将测试助手中的失败归因于调用该助手的那一行。如果有的话，这个参数应该在上下文参数之后，在任何其他参数之前。</p>
<pre><code>// Good:
func TestSomeFunction(t *testing.T) {
    golden := readFile(t, &quot;testdata/golden-result.txt&quot;)
    // ... tests against golden ...
}

// readFile returns the contents of a data file.
// It must only be called from the same goroutine as started the test.
func readFile(t *testing.T, filename string) string {
    t.Helper()
    contents, err := runfiles.ReadFile(filename)
    if err != nil {
        t.Fatal(err)
    }
    return string(contents)
}
</code></pre>
<p>当这种模式掩盖了测试失败和导致失败的条件之间的联系时，请不要使用这种模式。特别是，关于断言库的指导仍然适用，t.Helper不应该被用来实现这种库。</p>
<p>提示：更多关于测试助手和断言助手的区别，请参见<a href="https://google.github.io/styleguide/go/best-practices#test-functions">最佳实践</a>。</p>
<p>虽然上面提到的是*testing.T，但很多建议对基准和模糊测试助手来说是一样的。</p>
<h3 id="测试包">测试包</h3>
<h4 id="同一包内的测试">同一包内的测试</h4>
<p>测试可以定义在与被测试代码相同的包中。</p>
<p>要在同一个包中编写测试：</p>
<ul>
<li>
<p>将测试放在foo_test.go文件中</p>
</li>
<li>
<p>在测试文件中使用包foo</p>
</li>
<li>
<p>不要明确地导入要测试的包</p>
<h1 id="good">Good:</h1>
<p>go_library(
name = &ldquo;foo&rdquo;,
srcs = [&ldquo;foo.go&rdquo;],
deps = [
&hellip;
],
)</p>
<p>go_test(
name = &ldquo;foo_test&rdquo;,
size = &ldquo;small&rdquo;,
srcs = [&ldquo;foo_test.go&rdquo;],
library = &ldquo;:foo&rdquo;,
deps = [
&hellip;
],
)</p>
</li>
</ul>
<p>同一包中的测试可以访问包中未导出的标识符。这可以实现更好的测试覆盖率和更简洁的测试。请注意，在测试中声明的任何例子都不会有用户在其代码中需要的包名。</p>
<h4 id="不同软件包中的测试">不同软件包中的测试</h4>
<p>将测试定义在与被测代码相同的包中并不总是合适的，甚至不可能。在这种情况下，使用带有 _test 后缀的包名。这是包名的”无下划线”规则的一个例外。比如说：</p>
<ul>
<li>
<p>如果一个集成测试没有一个明显它归属的库：</p>
<p>// Good:
package gmailintegration_test</p>
<p>import &ldquo;testing&rdquo;</p>
</li>
<li>
<p>如果在同一软件包中定义测试会导致循环依赖性</p>
<p>// Good:
package fireworks_test</p>
<p>import (
&ldquo;fireworks&rdquo;
&ldquo;fireworkstestutil&rdquo; // fireworkstestutil also imports fireworks
)</p>
</li>
</ul>
<h3 id="使用testing包">使用testing包</h3>
<p>Go标准库提供了testing包。这是谷歌代码库中唯一允许用于Go代码的测试框架。特别是，断言库和第三方测试框架是不允许的。</p>
<p>testing包为编写好的测试提供了一个最小但完整的功能集：</p>
<ul>
<li>顶层测试</li>
<li>性能基准测试</li>
<li><a href="https://blog.golang.org/examples">可运行的例子</a></li>
<li>子测试</li>
<li>logging</li>
<li>失败和致命的失败</li>
</ul>
<p>这些都是为了与核心语言功能协同工作，如复合字面值和if-with-initializer语法，使测试作者能够编写清晰、可读、可维护的测试。</p>
<h2 id="非决定性">非决定性</h2>
<p>风格指南不可能列举出所有事项的正面规定，也不可能列举出所有它不提供意见的事项。也就是说，这里有几件可读性社区以前争论过但没有达成共识的事情。</p>
<ul>
<li>局部变量的零值初始化。var i int和i := 0是等价的。请参见<a href="https://google.github.io/styleguide/go/best-practices#vardeclinitialization">初始化的最佳实践</a>。</li>
<li>空复合字面值 vs new 或make。&amp;File{}和new(File)是等同的。map[string]bool{}和make(map[string]bool)也是如此。请参见<a href="https://google.github.io/styleguide/go/best-practices#vardeclcomposite">复合类型声明的最佳实践</a>。</li>
<li>在cmp.Diff调用中，got, want的参数排序。要有本地一致性，并在你的失败信息中<a href="https://google.github.io/styleguide/go/decisions#print-diffs">包含一个示例说明</a>。</li>
<li>errors.New vs fmt.Errorf针对非格式化字符串。errors.New(“foo”) 和 fmt.Errorf(“foo”) 可以互换使用。</li>
</ul>
<p>如果有特殊情况再次出现，可读性导师可能会做一个可选的注释，但一般来说，作者可以自由选择他们在特定情况下喜欢的风格。</p>
<p>当然，如果风格指南中没有涉及的东西确实需要更多的讨论，欢迎作者提出–在具体的审查中，或者在内部留言板上。</p>
]]></content:encoded></item><item><title>Google Go语言编码风格规范：指南篇</title><link>https://tonybai.com/google-go-style/google-go-style-guide/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://tonybai.com/google-go-style/google-go-style-guide/</guid><description>&lt;p&gt;&lt;img alt="Image 1" loading="lazy" src="https://tonybai.com/images/wp-content/uploads/google-go-style/google-go-style-1.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tonybai.com/google-go-style/google-go-style-guide"&gt;本文永久链接&lt;/a&gt; – &lt;a href="https://tonybai.com/google-go-style/google-go-style-guide"&gt;https://tonybai.com/google-go-style/google-go-style-guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本页面是2022年11月中旬Google发布的&lt;a href="https://google.github.io/styleguide/go/index"&gt;Go语言编码风格规范&lt;/a&gt;系列文档的&lt;a href="https://google.github.io/styleguide/go/guide"&gt;指南文档&lt;/a&gt;的中译版。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tonybai.com/google-go-style"&gt;概述&lt;/a&gt; | &lt;a href="https://tonybai.com/google-go-style/google-go-style-guide"&gt;指南&lt;/a&gt; | &lt;a href="https://tonybai.com/google-go-style/google-go-style-decisions"&gt;决定&lt;/a&gt; | &lt;a href="https://tonybai.com/google-go-style/google-go-style-best-practices"&gt;最佳实践&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;注意：这是介绍Google Go编码风格的系列文档的一部分。本文档具备权威性和规范性。更多信息请参见&lt;a href="https://tonybai.com/google-go-style"&gt;概述&lt;/a&gt;。&lt;/p&gt;</description><content:encoded><![CDATA[<p><img alt="Image 1" loading="lazy" src="/images/wp-content/uploads/google-go-style/google-go-style-1.png"></p>
<p><a href="https://tonybai.com/google-go-style/google-go-style-guide">本文永久链接</a> – <a href="https://tonybai.com/google-go-style/google-go-style-guide">https://tonybai.com/google-go-style/google-go-style-guide</a></p>
<p>本页面是2022年11月中旬Google发布的<a href="https://google.github.io/styleguide/go/index">Go语言编码风格规范</a>系列文档的<a href="https://google.github.io/styleguide/go/guide">指南文档</a>的中译版。</p>
<p><a href="https://tonybai.com/google-go-style">概述</a> | <a href="https://tonybai.com/google-go-style/google-go-style-guide">指南</a> | <a href="https://tonybai.com/google-go-style/google-go-style-decisions">决定</a> | <a href="https://tonybai.com/google-go-style/google-go-style-best-practices">最佳实践</a></p>
<p>注意：这是介绍Google Go编码风格的系列文档的一部分。本文档具备权威性和规范性。更多信息请参见<a href="https://tonybai.com/google-go-style">概述</a>。</p>
<hr>
<h2 id="编码风格原则">编码风格原则</h2>
<hr>
<p>这里列举了几条有关思考如何编写可读Go代码的总体原则。以下是可读代码的属性，按重要性排序：</p>
<ul>
<li>清晰：代码的目的和原理对读者来说是清晰的。</li>
<li>简单：代码以最简单的方式完成其目标。</li>
<li>简明：代码具有较高的信噪比。</li>
<li>可维护：编写的代码可以很容易维护。</li>
<li>一致：代码与更广泛的谷歌代码库风格一致。</li>
</ul>
<h3 id="清晰clarity">清晰(Clarity)</h3>
<p>可读性的核心目标是生产对读者清晰的代码。</p>
<p>清晰主要是通过有效的命名、有用的注释和有效的代码组织来实现的。</p>
<p>清晰与否要从代码的读者角度来看，而不是从代码的作者的角度来看。代码的易读性比易写性更重要。代码的清晰性有两个不同的方面：</p>
<ul>
<li>代码实际上在做什么？</li>
<li>为什么代码在做它所做的事？</li>
</ul>
<h4 id="代码实际上在做什么">代码实际上在做什么？</h4>
<p>Go的设计是这样的，它应该是可以相对直接地看到代码在做什么。在不确定的情况下，或者在读者可能需要先验知识才能理解代码的情况下，值得投入时间让代码的目的对未来的读者更加清晰。比如，下面这些措施可能会对清晰描述代码目的有帮助：</p>
<ul>
<li>使用更具描述性的变量名</li>
<li>添加额外的注释</li>
<li>用空白和注释来分隔代码</li>
<li>将代码重构为独立的函数/方法，使其更加模块化。</li>
</ul>
<p>这里没有一个放之四海而皆准的方法，但在开发Go代码时，优先考虑清晰性是很重要的。</p>
<h4 id="为什么代码要做它所做的事">为什么代码要做它所做的事？</h4>
<p>代码的原理往往是通过变量、函数、方法或包的名称来充分传达的。如果这些元素名称无法做到这点，那么添加注释就会变得很重要。当代码包含读者可能不熟悉的细微差别时，解释“为什么”将变得尤其重要，例如：</p>
<ul>
<li>语言上的细微差别，例如，一个闭包将捕获一个循环变量，但闭包写在很多行之外；</li>
<li>业务逻辑的细微差别，例如，访问控制检查需要区分实际用户和冒充用户的人。</li>
</ul>
<p>一个API可能需要额外注意才能正确使用。例如，由于性能原因，一段代码可能是错综复杂的，导致很难理解，或者一连串复杂的数学运算可能以一种意想不到的方式使用类型了转换。在这些情况以及更多类似的情况下，使用附带注释和文档来解释这些方面就变得十分重要了，这样未来的维护者才不会犯错，读者也无需进行反向工程就可以理解代码。</p>
<p>同样重要的是要意识到，一些试图提升代码清晰度的尝试（如添加额外的注释）实际上会让代码的目的变得更加模糊，比如增加杂乱无章的内容、用注释重述代码逻辑、注释与代码逻辑自相矛盾或为保持注释同步而增加维护负担。让代码自己说话（例如，通过使用可自描述的符号名称），而不是添加多余的注释。通常情况下，注释最好是解释为什么要做某事，而不是解释代码在做什么。</p>
<p>谷歌的代码库在很大程度上是统一和一致的。通常情况下，那些“另类”的代码（例如，通过使用不熟悉的模式）也是有充分理由的，通常是为了性能。保持这一特性对于让读者在阅读一段新的代码时清楚地知道他们应该把注意力放在哪里是很重要的。</p>
<p>Go标准库中包含了许多践行这一原则的实例，其中包括：</p>
<ul>
<li>在<a href="https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/sort/sort.go">sort包</a>中的维护者注释；</li>
<li>在同一个包中有可读性好的可运行的例子，这对用户（例子可以<a href="https://pkg.go.dev/sort#pkg-examples">在godoc中显示</a>）和维护者（例子作为测试的一部分运行）都有好处。</li>
<li>strings.Cut函数只有四行代码，但从调用者角度来看，该函数的存在<a href="https://github.com/golang/go/issues/46336">提高了代码的清晰度和正确性</a>。</li>
</ul>
<h3 id="简单simplicity">简单(Simplicity)</h3>
<p>对于使用、阅读和维护它的人来说，你的Go代码应该是简单的。</p>
<p>Go代码应该以能实现其目标的最简单的方式编写，无论在行为还是性能方面。在GoogleGo代码库中，简单的代码具有如下属性：</p>
<ul>
<li>从上到下都易于阅读</li>
<li>不假设你已经知道它的工作原理</li>
<li>不假设你能记住前面所有的代码</li>
<li>没有不必要的抽象层次</li>
<li>在平凡代码中没有引起人们注意的名字</li>
<li>让读者清楚地了解价值和决策的传播情况</li>
<li>有注释，解释为什么，而不是代码在做什么，以避免将来出现偏差</li>
<li>有独立的文档</li>
<li>拥有有用的错误和有用的失败测试用例</li>
<li>通常与“故作聪明的”代码相互排斥</li>
</ul>
<p>在代码的简单性和API使用的简单性之间可能会产生权衡。例如，让代码更复杂可能是值得的，这样API的终端用户可能更容易正确地调用API。相反，把一些额外的工作留给API的终端用户也是值得的，这样代码就会保持简单和容易理解。</p>
<p>当代码需要复杂性时，应该有意地增加复杂性。如果需要额外的性能，或者一个特定的库或服务有多个不同的客户，这通常是必要的。复杂性可能是合理的，但它应该有相应的文档，以便客户和未来的维护者能够理解和驾驭这种复杂性。这应该用测试和例子来补充说明其正确的用法，特别是要在例子中既包含使用代码的“简单”方法，也包含“复杂”的使用方法。</p>
<p>这一原则并不意味着复杂的代码不能或不应该用Go编写，也不意味着Go代码不允许复杂。我们努力使代码库避免不必要的复杂性，这样当复杂性出现时，就表明有关的代码需要认真理解和维护。理想情况下，应该有相应的注释来解释其中的道理，并指出应该注意的地方。在优化代码以提高性能时，经常会出现这种情况；这样做往往需要更复杂的方法，比如预先分配一个缓冲区并在整个goroutine生命周期内重复使用它。当维护者看到这种情况时，应该视其为一个线索，说明相关的代码是性能敏感型的，未来修改这段代码时应该给予足够的谨慎。反过来，如果使用不当，这种复杂性会给那些需要在未来阅读或修改代码的人带来负担。</p>
<p>如果代码被证明是非常复杂的，而其目的应该是简单的，这往往是一个信号，可以重新审视实现，看看是否有更简单的方法来完成同样的事情。</p>
<h4 id="最少的机制">最少的机制</h4>
<p>如果有几种方法来表达同一个想法，最好选择使用最标准的一种。复杂的机制经常存在，但不应该无缘无故地使用。根据需要增加代码的复杂性是很容易的，而在发现没有必要之后再去掉现有的复杂性则要难得多。</p>
<ul>
<li>当足以满足你的使用情况时，要争取使用一个核心语言结构（例如channel、slice、map、循环或struct）；</li>
<li>如果没有，就在标准库中寻找一个工具（如HTTP客户端或模板引擎）；</li>
<li>最后，在引入新的依赖关系或自己造轮子之前，考虑谷歌代码库中是否有一个核心库是可以满足你要求的。</li>
</ul>
<p>举个例子，考虑生产环境代码中包含一个与变量绑定的标志(flag)，其默认值必须在测试中被重写。除非打算测试程序的命令行界面本身（例如，用os/exec），否则更可取的是直接重写绑定的值，这比使用flag.Set更简单。</p>
<p>同样地，如果一段代码需要检查集合的成员资格，一个值元素类型为布尔类型的map（例如map[string]bool）往往就足够了。只有在需要更复杂的操作，而使用map不可能完成或完成起来过于复杂时，才应使用提供类似集合类型和功能特性的库。</p>
<h3 id="简明concision">简明(Concision)</h3>
<p>简明的Go代码具有很高的信噪比。它很容易分辨出相关的细节，而命名和结构则可以引导读者了解这些细节。</p>
<p>在任何时候，都有很多东西会阻碍主要的细节浮现出来：</p>
<ul>
<li>重复的代码</li>
<li>不相干的语法</li>
<li><a href="https://google.github.io/styleguide/go/guide#naming">难懂的名字</a></li>
<li>不必要的抽象</li>
<li>空白</li>
</ul>
<p>重复的代码尤其掩盖了每个几乎相同的部分之间的差异，这需要读者可视化地比较相似的代码行后才能发现其中的差异。<a href="https://github.com/golang/go/wiki/TableDrivenTests">表格驱动的测试</a>是一个很好的例子，这种机制可以简明地从每个重复的重要细节中找出共同的代码，但是选择在表格中包括哪些部分会对表格的易懂程度有影响。</p>
<p>当考虑用多种方式来组织代码结构时，值得考虑哪种方式更能突显重要的细节。</p>
<p>理解和使用常见的代码结构和地道用法对于保持高信噪比也很重要。例如，下面这个代码块在<a href="https://go.dev/blog/errors-are-values">错误处理</a>中非常常见，读者可以很快理解这个代码块的意图：</p>
<pre><code>// Good:
if err := doSomething(); err != nil {
    // ...
}
</code></pre>
<p>如果代码看起来与此非常相似，但却有细微的不同，读者可能不会注意到这种变化。在这样的情况下，值得故意“提高”错误检查的信号，我们可以通过添加一个注释来引起注意。</p>
<pre><code>// Good:
if err := doSomething(); err == nil { // if NO error
    // ...
}
</code></pre>
<h3 id="可维护maintainability">可维护(Maintainability)</h3>
<p>代码被编辑的次数比它被写的次数多得多。可读的代码不仅对试图了解其工作原理的读者有意义，而且对需要改变它的程序员也有意义。清晰度是关键。</p>
<p>可维护的代码具有如下性质：</p>
<ul>
<li>易于被未来的程序员正确修改</li>
<li>具有结构化的API，使其能够优雅地扩展</li>
<li>清楚它所做的假设，并选择与问题结构相对应的抽象，而不是与代码的结构相对应。</li>
<li>避免不必要的耦合，不包含未用到的功能特性。</li>
<li>拥有一个全面的测试套件，以确保承诺的行为得到维护以及重要的逻辑是正确的，并且在测试失败的情况下为开发人员提供清晰、可操作的诊断。</li>
</ul>
<p>当使用像接口和类型这样的抽象时，顾名思义就是把信息从使用它们的环境中移除，因此必须确保它们提供足够的好处。当使用具体类型时，编辑器和IDE可以直接连接到方法定义并显示相应的文档，但在其他情况下只能参考接口定义。接口是一个强大的工具，但也是有代价的，因为维护者可能需要了解底层实现的具体细节才能正确使用接口，这必须在接口文档中或在调用现场进行解释。</p>
<p>可维护的代码还可以避免将重要的细节隐藏在容易被忽视的地方。例如，在下面示例的代码行中，一个字符的存在与否都会对代码的理解产生至关重要的影响：</p>
<pre><code>// Bad:
// 使用=而不是:=可以完全改变这一行。
if user, err = db.UserByID(userID); err != nil {
    // ...
}


// Bad:
// 这行代码中间的！很容易错过。
leap := (year%4 == 0) &amp;&amp; (!(year%100 == 0) || (year%400 == 0))
</code></pre>
<p>以上两段代码都不是不正确的，但都可以写得更明确，或者可以有一个附带的注释，提醒要注意的重要行为：</p>
<pre><code>// Good:
u, err := db.UserByID(userID)
if err != nil {
    return fmt.Errorf(&quot;invalid origin user: %s&quot;, err)
}
user = u


// Good:
// 格里高利闰年不能仅通过year%4==0来判定。
// 具体请参见https://en.wikipedia.org/wiki/Leap_year#Algorithm.
var (
    leap4   = year%4 == 0
    leap100 = year%100 == 0
    leap400 = year%400 == 0
)
leap := leap4 &amp;&amp; (!leap100 || leap400)
</code></pre>
<p>同样地，一个隐藏了关键逻辑或重要边缘情况的辅助函数(helper function)，可能会使未来的变化很容易无法正确地解释它。</p>
<p>可预测的名字是可维护代码的另一个特点。一个包的用户或一段代码的维护者应该能够预测一个变量、方法或函数在特定情况下的名称。相同概念的函数(形式)参数和接收器名称(receiver name)通常应该共享相同的名称，这既是为了保持文档的可理解性，也是为了方便以最小的开销重构代码。</p>
<p>可维护的代码尽量减少其依赖性（包括隐性和显性的依赖）。更少的包的依赖意味着更少的可以影响行为的代码行。避免对内部或未记录的行为的依赖，使得代码在将来这些行为发生变化时，不太可能造成维护负担。</p>
<p>当考虑如何构造或编写代码时，值得花时间去思考代码可能随着时间的推移而演变的方式。如果一个给定的方法更有利于未来更容易和更安全的变化，这往往是一个很好的权衡，即使它意味着一个稍微复杂的设计。</p>
<h3 id="一致consistency">一致(Consistency)</h3>
<p>一致性的代码是指在更广泛的代码库中，在一个团队的代码中或一个包的范围内，甚至在一个文件中，看起来、感觉和行为都类似的代码。</p>
<p>一致性的问题并不凌驾于上述的任何原则之上，但如果必须打破平衡，那么打破平局的往往是有利于一致性的实现。</p>
<p>一个包内的一致性通常是最直接重要的一致性水平的体现。如果同一个问题在一个包里有多种处理方式，或者同一个概念在一个文件里有很多名字，那就会非常不协调。然而，即使这样，也不应该凌驾于文件的风格原则或全局一致性之上。</p>
<h2 id="-核心指导准则core-guidelines">## 核心指导准则(Core guidelines)</h2>
<p>这些准则收集了所有Go代码都应遵循的Go风格的最重要方面。我们希望开发者在编写可读性代码时学习和遵循这些准则。这些准则预计不会经常改变，新增加的内容必须通过一个高标准的审核门槛。</p>
<p>下面的准则是对<a href="https://go.dev/doc/effective_go">Effective Go</a>中的建议的扩展，它为整个社区的Go代码提供了一个共同的基线。</p>
<h3 id="格式化">格式化</h3>
<p>所有Go源文件必须符合gofmt工具输出的格式。这种格式由Google代码库中的预提交(presubmit)检查强制执行。<a href="https://docs.bazel.build/versions/main/be/general.html#genrule">生成的代码</a>通常也应该被格式化（例如，通过使用<a href="https://pkg.go.dev/go/format#Source">format.Source</a>），因为它也可以在代码搜索中被浏览。</p>
<h3 id="驼峰命名mixedcaps">驼峰命名(MixedCaps)</h3>
<p>Go源代码在编写<strong>多字名称</strong>时使用MixedCaps或mixedCaps（驼峰命名）而不是下划线（蛇形命名）。</p>
<p>这甚至适用于打破其他语言的惯例的情况。例如，一个常量如果被导出，就用MaxLength（而不是MAX_LENGTH）；如果未导出，就用maxLength（而不是max_length）。</p>
<p>为了选择头母大写的导出变量方案，本地变量被认为是<a href="https://go.dev/ref/spec#Exported_identifiers">未导出的</a>。</p>
<h3 id="行的长度">行的长度</h3>
<p>Go源代码没有固定的行长。如果某一行感觉太长了，应该重构而不是断掉。如果它已经很短了，那么应该允许它继续保持长行。</p>
<p>在下面情况下，不要分割行：</p>
<ul>
<li>在<a href="https://google.github.io/styleguide/go/decisions#indentation-confusion">缩进改变</a>之前（例如，函数声明，条件）。</li>
<li>为了使一个长的字符串（例如，一个URL）适合于多个短行</li>
</ul>
<h3 id="命名">命名</h3>
<p>命名是艺术而不是科学。在Go中，名字往往比许多其他语言要短一些，但同样的<a href="https://testing.googleblog.com/2017/10/code-health-identifiernamingpostforworl.html">一般准则</a>也适用。名称应该具有如下属性：</p>
<ul>
<li>在使用时不感到<a href="https://google.github.io/styleguide/go/decisions#repetition">重复</a></li>
<li>将上下文因素考虑进去</li>
<li>不要重复已经很清楚的概念</li>
</ul>
<p>你可以在<a href="https://tonybai.com/google-go-style-decisions">决定</a>中找到更多关于命名的具体指导。</p>
<h3 id="局部一致性">局部一致性</h3>
<p>当风格指南对某一特定的风格点没有说明时，作者可以自由地选择他们喜欢的风格，除非附近的代码（通常在同一个文件或包内，但有时在一个团队或项目目录内）对这个问题采取了一致的风格。</p>
<p>有效的局部风格考量的例子：</p>
<ul>
<li>使用%s或%v的格式化打印错误</li>
<li>使用带缓冲channel来代替mutex</li>
</ul>
<p>无效的局部风格考量的例子：</p>
<ul>
<li>代码的行长限制</li>
<li>使用基于断言的测试库</li>
</ul>
<p>如果局部风格与风格指南不一致，但对可读性的影响仅限于一个文件，它通常会在代码审查中浮出水面，而一致的修正会超出有关CL的范围。在这一点上，提交一个bug来跟踪此类修复更为合适。</p>
<p>如果一个改变会使现有的风格偏差恶化，在更多的API表面暴露出来，扩大存在偏差的文件数量，或者引入一个实际的错误，那么局部一致性就不再是违反新代码风格指南的有效理由。在这些情况下，作者应该在同一CL中清理现有的代码库，在当前CL之前进行重构，或者找到一个至少不会使局部问题恶化的替代方案。</p>
]]></content:encoded></item><item><title>Google Go语言编码风格规范：最佳实践篇</title><link>https://tonybai.com/google-go-style/google-go-style-best-practices/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://tonybai.com/google-go-style/google-go-style-best-practices/</guid><description>&lt;p&gt;&lt;img alt="Image 1" loading="lazy" src="https://tonybai.com/images/wp-content/uploads/google-go-style/google-go-style-1.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tonybai.com/google-go-style/google-go-style-best-practices"&gt;本文永久链接&lt;/a&gt; – &lt;a href="https://tonybai.com/google-go-style/google-go-style-best-practices"&gt;https://tonybai.com/google-go-style/google-go-style-best-practices&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本页面是2022年11月中旬Google发布的&lt;a href="https://google.github.io/styleguide/go/index"&gt;Go语言编码风格规范&lt;/a&gt;系列文档的&lt;a href="https://google.github.io/styleguide/go/best-practices"&gt;最佳指南篇&lt;/a&gt;的中译版。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tonybai.com/google-go-style"&gt;概述&lt;/a&gt; | &lt;a href="https://tonybai.com/google-go-style/google-go-style-guide"&gt;指南&lt;/a&gt; | &lt;a href="https://tonybai.com/google-go-style/google-go-style-decisions"&gt;决定&lt;/a&gt; | &lt;a href="https://tonybai.com/google-go-style/google-go-style-best-practices"&gt;最佳实践&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;注意：这是介绍&lt;a href="https://tonybai.com/google-go-style"&gt;Google Go编码风格的系列文档&lt;/a&gt;的一部分。本文档既不是规范性的，也不是权威性的。本文档是&lt;a href="https://tonybai.com/google-go-style/google-go-style-guide"&gt;指南篇&lt;/a&gt;的辅助文档，更多信息请参见&lt;a href="https://tonybai.com/google-go-style"&gt;概述篇&lt;/a&gt;。&lt;/p&gt;</description><content:encoded><![CDATA[<p><img alt="Image 1" loading="lazy" src="/images/wp-content/uploads/google-go-style/google-go-style-1.png"></p>
<p><a href="https://tonybai.com/google-go-style/google-go-style-best-practices">本文永久链接</a> – <a href="https://tonybai.com/google-go-style/google-go-style-best-practices">https://tonybai.com/google-go-style/google-go-style-best-practices</a></p>
<p>本页面是2022年11月中旬Google发布的<a href="https://google.github.io/styleguide/go/index">Go语言编码风格规范</a>系列文档的<a href="https://google.github.io/styleguide/go/best-practices">最佳指南篇</a>的中译版。</p>
<p><a href="https://tonybai.com/google-go-style">概述</a> | <a href="https://tonybai.com/google-go-style/google-go-style-guide">指南</a> | <a href="https://tonybai.com/google-go-style/google-go-style-decisions">决定</a> | <a href="https://tonybai.com/google-go-style/google-go-style-best-practices">最佳实践</a></p>
<p>注意：这是介绍<a href="https://tonybai.com/google-go-style">Google Go编码风格的系列文档</a>的一部分。本文档既不是规范性的，也不是权威性的。本文档是<a href="https://tonybai.com/google-go-style/google-go-style-guide">指南篇</a>的辅助文档，更多信息请参见<a href="https://tonybai.com/google-go-style">概述篇</a>。</p>
<hr>
<h2 id="关于">关于</h2>
<p>本文档将对如何最好地应用Go风格指南给出指导建议。这些指导建议旨在解决经常出现的常见问题情况，但不一定适用于所有情况。在可能的情况下，我们讨论了多种替代方法，以及决定何时和何时不应用这些方法的考虑因素。</p>
<p>更多内容，请参阅本系列文档的<a href="https://tonybai.com/google-go-style">概述篇</a>。</p>
<h2 id="命名">命名</h2>
<h3 id="函数和方法名">函数和方法名</h3>
<h4 id="避免重复">避免重复</h4>
<p>当选择一个函数或方法的名字时，需要考虑该名字将被使用的上下文环境。请考虑以下建议，以避免在调用时出现过多的<a href="https://google.github.io/styleguide/go/decisions#repetition">重复</a>：</p>
<ul>
<li>
<p>以下内容一般可以从函数和方法的名字中省略。</p>
<ul>
<li>输入和输出的类型（当不存在冲突的时候）</li>
<li>方法的接收器的类型</li>
<li>输入或输出是否是一个指针</li>
</ul>
</li>
<li>
<p>对于函数，<a href="https://google.github.io/styleguide/go/decisions#repetitive-with-package">不要重复包的名称</a>。</p>
<p>// Bad:
package yamlconfig</p>
<p>func ParseYAMLConfig(input string) (*Config, error)</p>
<p>// Good:
package yamlconfig</p>
<p>func Parse(input string) (*Config, error)</p>
</li>
<li>
<p>对于方法，不要重复方法接收器的名称。</p>
<p>// Bad:
func (c *Config) WriteConfigTo(w io.Writer) (int64, error)</p>
<p>// Good:
func (c *Config) WriteTo(w io.Writer) (int64, error)</p>
</li>
<li>
<p>不要重复作为参数传递的变量的名称。</p>
<p>// Bad:
func OverrideFirstWithSecond(dest, source *Config) error</p>
<p>// Good:
func Override(dest, source *Config) error</p>
</li>
<li>
<p>不要重复返回值的名称和类型。</p>
<p>// Bad:
func TransformYAMLToJSON(input *Config) *jsonconfig.Config</p>
<p>// Good:
func Transform(input *Config) *jsonconfig.Config</p>
</li>
</ul>
<p>当有必要区分相似名称的函数时，名字中可以包含额外的信息：</p>
<pre><code>// Good:
func (c *Config) WriteTextTo(w io.Writer) (int64, error)
func (c *Config) WriteBinaryTo(w io.Writer) (int64, error)
</code></pre>
<h4 id="命名惯例">命名惯例</h4>
<p>在为函数和方法选择名称时，还有一些常见的惯例。</p>
<ul>
<li>
<p>返回某事物的函数通常被赋予类似名词的名字。</p>
<p>// Good:
func (c *Config) JobName(key string) (value string, ok bool)</p>
</li>
</ul>
<p>这方面的一个推论是，函数和方法名称应该避免使用Get前缀。</p>
<pre><code>// Bad:
func (c *Config) GetJobName(key string) (value string, ok bool)
</code></pre>
<ul>
<li>
<p>做某事的函数被赋予类似动词的名称。</p>
<p>// Good:
func (c *Config) WriteDetail(w io.Writer) (int64, error)</p>
</li>
<li>
<p>只因所涉及的类型而不同的相同的函数在名称的末尾包括类型的名称。</p>
<p>// Good:
func ParseInt(input string) (int, error)
func ParseInt64(input string) (int64, error)
func AppendInt(buf []byte, value int) []byte
func AppendInt64(buf []byte, value int64) []byte</p>
</li>
</ul>
<p>如果有一个明确的 “主要 “版本，该版本的名称中可以省略类型。</p>
<pre><code>// Good:
func (c *Config) Marshal() ([]byte, error)
func (c *Config) MarshalText() (string, error)
</code></pre>
<h3 id="测试替身包和类型">测试替身包和类型</h3>
<p>在命名提供测试助手，特别是<a href="https://en.wikipedia.org/wiki/Test_double">测试替身</a>的包和类型时，有几条条款可以运用。测试替身可以是一个stub、fake、mock或spy。</p>
<p>这些例子大多使用stub这种测试替身。如果你的代码使用fake的或其他类型的测试替身，请相应地更新你的名字。</p>
<p>假设你有一个重点突出的包，提供类似这样的生产代码：</p>
<pre><code>package creditcard

import (
    &quot;errors&quot;

    &quot;path/to/money&quot;
)

// ErrDeclined indicates that the issuer declines the charge.
var ErrDeclined = errors.New(&quot;creditcard: declined&quot;)

// Card contains information about a credit card, such as its issuer,
// expiration, and limit.
type Card struct {
    // omitted
}

// Service allows you to perform operations with credit cards against external
// payment processor vendors like charge, authorize, reimburse, and subscribe.
type Service struct {
    // omitted
}

func (s *Service) Charge(c *Card, amount money.Money) error { /* omitted */ }
</code></pre>
<h4 id="创建测试助手包">创建测试助手包</h4>
<p>假设你想创建一个包，其中包含另一个包的测试替身。在这个例子中，我们将使用package creditcard（来自上面）。</p>
<p>一种方法是在生产包的基础上引入一个新的Go包进行测试。一个安全的选择是在原始包的名字后面加上test这个词（”creditcard “+”test”）。</p>
<pre><code>// Good:
package creditcardtest
</code></pre>
<p>除非另有明确说明，以下各节中的所有例子都是在creditcardtest包中。</p>
<h4 id="简单情况">简单情况</h4>
<p>你想为Service添加一组测试替身。因为Card是一个有效的哑数据类型，类似于Protocol Buffer消息，它在测试中不需要特殊处理，所以不需要测试替身。如果你预计只对一种类型（如Service）使用测试替身，你可以采取一种简洁的方法来命名替身。</p>
<pre><code>// Good:
import (
    &quot;path/to/creditcard&quot;
    &quot;path/to/money&quot;
)

// Stub stubs creditcard.Service and provides no behavior of its own.
type Stub struct{}

func (Stub) Charge(*creditcard.Card, money.Money) error { return nil }
</code></pre>
<p>严格来说，这比像StubService或非常差的StubCreditCardService这样的名字要好，因为基础包的名字和它的领域类型使得creditcardtest.Stub是什么变得显而易见。</p>
<p>最后，如果该包是用Bazel构建的，确保该包的新go_library规则被标记为testonly。</p>
<pre><code># Good:
go_library(
    name = &quot;creditcardtest&quot;,
    srcs = [&quot;creditcardtest.go&quot;],
    deps = [
        &quot;:creditcard&quot;,
        &quot;:money&quot;,
    ],
    testonly = True,
)
</code></pre>
<p>上述方法是符合惯例的，很容易被其他工程师所理解。</p>
<p>请参阅：<br>
- <a href="https://google.github.io/styleguide/go/index.html#gotip">Go tips#42：为测试编写stub</a></p>
<h4 id="多种测试替身行为">多种测试替身行为</h4>
<p>当一种stub不够用时（例如，你还需要一种总是失败的stub），我们建议根据它们模拟的行为来命名stub。这里我们将Stub重命名为AlwaysCharges，并引入一个新的stub，称为AlwaysDeclines。</p>
<pre><code>// Good:
// AlwaysCharges stubs creditcard.Service and simulates success.
type AlwaysCharges struct{}

func (AlwaysCharges) Charge(*creditcard.Card, money.Money) error { return nil }

// AlwaysDeclines stubs creditcard.Service and simulates declined charges.
type AlwaysDeclines struct{}

func (AlwaysDeclines) Charge(*creditcard.Card, money.Money) error {
    return creditcard.ErrDeclined
}
</code></pre>
<h4 id="针对多个类型的多个测试替身">针对多个类型的多个测试替身</h4>
<p>但是现在，假设包creditcard包含多种值得创建测试替身的类型，正如下面看到的Service和StoredValue。</p>
<pre><code>package creditcard

type Service struct {
    // omitted
}

type Card struct {
    // omitted
}

// StoredValue manages customer credit balances.  This applies when returned
// merchandise is credited to a customer's local account instead of processed
// by the credit issuer.  For this reason, it is implemented as a separate
// service.
type StoredValue struct {
    // omitted
}

func (s *StoredValue) Credit(c *Card, amount money.Money) error { /* omitted */ }
</code></pre>
<p>在这种情况下，我们应该使用更明确的测试替身命名。</p>
<pre><code>// Good:
type StubService struct{}

func (StubService) Charge(*creditcard.Card, money.Money) error { return nil }

type StubStoredValue struct{}

func (StubStoredValue) Credit(*creditcard.Card, money.Money) error { return nil }
</code></pre>
<h4 id="测试中的局部变量">测试中的局部变量</h4>
<p>当你的测试中的变量引用测试替身时，要根据上下文选择一个能最清楚地区分替身和其他生产类型的名称。考虑一下你要测试的一些生产代码。</p>
<pre><code>package payment

import (
    &quot;path/to/creditcard&quot;
    &quot;path/to/money&quot;
)

type CreditCard interface {
    Charge(*creditcard.Card, money.Money) error
}

type Processor struct {
    CC CreditCard
}

var ErrBadInstrument = errors.New(&quot;payment: instrument is invalid or expired&quot;)

func (p *Processor) Process(c *creditcard.Card, amount money.Money) error {
    if c.Expired() {
        return ErrBadInstrument
    }
    return p.CC.Charge(c, amount)
}
</code></pre>
<p>在测试中，一个被称为CreditCard的”spy”的测试替身与生产类型并列，所以给这个名字加上前缀可以提高清晰度。</p>
<pre><code>// Good:
package payment

import &quot;path/to/creditcardtest&quot;

func TestProcessor(t *testing.T) {
    var spyCC creditcardtest.Spy

    proc := &amp;Processor{CC: spyCC}

    // declarations omitted: card and amount
    if err := proc.Process(card, amount); err != nil {
        t.Errorf(&quot;proc.Process(card, amount) = %v, want %v&quot;, got, want)
    }

    charges := []creditcardtest.Charge{
        {Card: card, Amount: amount},
    }

    if got, want := spyCC.Charges, charges; !cmp.Equal(got, want) {
        t.Errorf(&quot;spyCC.Charges = %v, want %v&quot;, got, want)
    }
}
</code></pre>
<p>这比没有前缀的名称更清楚。</p>
<pre><code>// Bad:
package payment

import &quot;path/to/creditcardtest&quot;

func TestProcessor(t *testing.T) {
    var cc creditcardtest.Spy

    proc := &amp;Processor{CC: cc}

    // declarations omitted: card and amount
    if err := proc.Process(card, amount); err != nil {
        t.Errorf(&quot;proc.Process(card, amount) = %v, want %v&quot;, got, want)
    }

    charges := []creditcardtest.Charge{
        {Card: card, Amount: amount},
    }

    if got, want := cc.Charges, charges; !cmp.Equal(got, want) {
        t.Errorf(&quot;cc.Charges = %v, want %v&quot;, got, want)
    }
}
</code></pre>
<h3 id="遮蔽shadowing">遮蔽(shadowing)</h3>
<p>注意：本解释使用了两个非正式的术语，重踏(stomping)和遮蔽。它们并不是Go语言规范中的正式概念。</p>
<p>像许多编程语言一样，Go有可变的变量：向一个变量赋值会改变其值。</p>
<pre><code>// Good:
func abs(i int) int {
    if i &lt; 0 {
        i *= -1
    }
    return i
}
</code></pre>
<p>当使用带有:=操作符的短变量声明时，在某些情况下不会创建一个新的变量。我们可以把这称为<strong>重踏</strong>。当不再需要原来的值时，这样做是可以的。</p>
<pre><code>// Good:
// innerHandler is a helper for some request handler, which itself issues
// requests to other backends.
func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
    // Unconditionally cap the deadline for this part of request handling.
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()
    ctxlog.Info(&quot;Capped deadline in inner request&quot;)

    // Code here no longer has access to the original context.
    // This is good style if when first writing this, you anticipate
    // that even as the code grows, no operation legitimately should
    // use the (possibly unbounded) original context that the caller provided.

    // ...
}
</code></pre>
<p>不过要小心在新的作用域中使用短的变量声明：这将引入一个新的变量。我们可以把这称为<strong>对原始变量的遮蔽</strong>。代码块结束后，代码中的变量将指向原来的变量。下面是一个有条件地缩短deadline的错误尝试。</p>
<pre><code>// Bad:
func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
    // Attempt to conditionally cap the deadline.
    if *shortenDeadlines {
        ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
        defer cancel()
        ctxlog.Info(ctx, &quot;Capped deadline in inner request&quot;)
    }

    // BUG: &quot;ctx&quot; here again means the context that the caller provided.
    // The above buggy code compiled because both ctx and cancel
    // were used inside the if statement.

    // ...
}
</code></pre>
<p>一个正确版本的代码可能是这样的：</p>
<pre><code>// Good:
func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
    if *shortenDeadlines {
        var cancel func()
        // Note the use of simple assignment, = and not :=.
        ctx, cancel = context.WithTimeout(ctx, 3*time.Second)
        defer cancel()
        ctxlog.Info(ctx, &quot;Capped deadline in inner request&quot;)
    }
    // ...
}
</code></pre>
<p>在我们称之为<strong>重踏</strong>的情况下，因为没有新的变量，所以被分配的类型必须与原始变量的类型相匹配。而遮蔽则是引入一个全新的实体，所以它可以有不同的类型。有意的遮蔽可以是一种有用的做法，但如果从提高代码清晰度角度考虑，你总是可以使用一个新的名字。</p>
<p>除了非常小的范围之外，使用与标准包同名的变量并不是一个好主意，因为这使得该包的函数和值无法被访问。反过来说，在为你的包挑选名字时，要避免使用那些可能需要重命名导入包的名字，或者在客户端造成对其他好的变量名字的遮蔽。</p>
<pre><code>// Bad:
func LongFunction() {
    url := &quot;https://example.com/&quot;
    // Oops, now we can't use net/url in code below.
}
</code></pre>
<h3 id="util包">Util包</h3>
<p>Go包在包声明中指定了一个名称，与导入路径分开。包的名称比路径更重要，因为它的可读性。</p>
<p>Go包的名字应该<a href="https://google.github.io/styleguide/go/decisions#package-names">与包所提供的内容相关</a>。仅仅将一个包命名为util、helper、common或类似的名字通常是一个糟糕的选择（不过可以作为名字的一部分）。没有信息的名字会使代码更难阅读，而且如果使用的范围太广，很可能会造成不必要的<a href="https://google.github.io/styleguide/go/decisions#import-renaming">导入冲突</a>。</p>
<p>相反，要考虑到调用时会是什么样子。</p>
<pre><code>// Good:
db := spannertest.NewDatabaseFromFile(...)

_, err := f.Seek(0, io.SeekStart)

b := elliptic.Marshal(curve, x, y)
</code></pre>
<p>即使不知道导入列表（cloud.google.com/go/spanner/spannertest、io和crypto/elliptic），你也能大致知道这些包的作用。如果没那么关注命名，这些名字可能是：</p>
<pre><code>// Bad:
db := test.NewDatabaseFromFile(...)

_, err := f.Seek(0, common.SeekStart)

b := helper.Marshal(curve, x, y)
</code></pre>
<h2 id="包大小">包大小</h2>
<p>如果你在问自己，你的Go包应该有多大，是把相关的类型放在同一个包里，还是把它们分成不同的包，那么<a href="https://go.dev/blog/package-names">关于包名的Go博文</a>就是一个好的开始。尽管帖子的标题是这样的，但它并不只是关于命名的。它包含一些有用的提示，并引用了一些有用的文章和讲座。</p>
<p>下面是一些其他的考量和说明。</p>
<p>用户在一个页面中看到包的godoc，由包提供的类型导出的任何方法都按其类型分组。Godoc还将构造函数与它们返回的类型一起分组。如果客户端代码有可能需要两个不同类型的值来相互作用，那么把它们放在同一个包里可能会给用户带来方便。</p>
<p>包内的代码可以访问包内未导出的标识符。如果你有几个相关的类型，它们的实现是紧密耦合的，把它们放在同一个包里可以让你实现这种耦合，而不用用这些细节污染公共API。</p>
<p>综上所述，把你的整个项目放在一个包里，很可能会使这个包变得太大。当一个东西在概念上是不同的，将它自己放入一个单独的小包中可以使它更容易使用。客户端知道的包的短名称与导出的类型名称一起工作，构成一个有意义的标识符：例如bytes.Buffer，ring.New。<a href="https://go.dev/blog/package-names">这篇博文</a>中有更多的例子。</p>
<p>在文件大小方面，Go表现得很灵活，因为维护者可以将包内的代码从一个文件移到另一个文件，而不影响调用者。但作为一般准则：通常情况下，一个文件有几千行，或者有许多小文件，都不是一个好主意。Go没有像其他一些语言那样的“一个类型，一个文件”的惯例。作为一个经验法则，文件应该足够内聚，以便维护者可以知道哪个文件包含了什么东西，而且文件应该足够小，以便一旦有了它，就很容易找到。标准库经常将大型包分割成几个源文件，将相关的代码按文件分组。<a href="https://go.dev/src/bytes/">包byte</a>的源文件就是一个很好的例子。具有较长包文档的包可以选择一个名为doc.go的专门文件来放置<a href="https://google.github.io/styleguide/go/decisions#package-comments">包文档</a>，该文件中仅有包的文档和包的声明，而没有其他内容，但这并不是必须的。</p>
<p>在Google代码库和使用Bazel的项目中，Go代码的目录布局与开源Go项目不同：你可以在一个目录中拥有多个go_library目标。如果你期望在未来将你的项目开源，那么给每个包提供自己的目录是一个很好的理由。</p>
<p>另见：<br>
- 测试替身包</p>
<h2 id="导入">导入</h2>
<h3 id="protos和stub">Protos和Stub</h3>
<p>由于其跨语言的特性，Proto库导入的处理方式与标准Go导入不同。重命名proto导入的惯例是基于生成包的规则。</p>
<ul>
<li>pb后缀一般用于go_proto_library规则。</li>
<li>后缀grpc一般用于go_grpc_library规则。</li>
</ul>
<p>一般来说，会使用一个或两个字母的短前缀。</p>
<pre><code>// Good:
import (
    fspb &quot;path/to/package/foo_service_go_proto&quot;
    fsgrpc &quot;path/to/package/foo_service_go_grpc&quot;
)
</code></pre>
<p>如果一个包只使用一个proto，或者该包与该proto紧密相连，那么前缀可以省略。</p>
<pre><code>import (
    pb &quot;path/to/package/foo_service_go_proto&quot;
    grpc &quot;path/to/package/foo_service_go_grpc&quot;
)
</code></pre>
<p>如果proto中的符号是通用的，或者不是很好的自描述，或者用首字母缩写来缩短包的名称是不明确的，那么一个简短的词就可以作为前缀。</p>
<pre><code>// Good:
import (
    mapspb &quot;path/to/package/maps_go_proto&quot;
)
</code></pre>
<p>在这种情况下，如果有关的代码没有明确与maps相关，那么mapspb.Address可能比mpb.Address更清晰。</p>
<h3 id="导入顺序">导入顺序</h3>
<p>包导入通常分为以下两个（或更多）块，按顺序是。</p>
<ul>
<li>标准库导入（例如，”fmt”）。</li>
<li>普通项目导入（例如，”/path/to/somelib”）。</li>
<li>(可选）Protobuf导入（例如，fpb “path/to/foo_go_proto”）。</li>
<li>(可选) 副作用导入（例如，_ “path/to/package”）。</li>
</ul>
<p>如果一个文件没有上述可选的导入类别，相关的导入就会被包含在项目导入组中。</p>
<p>任何清晰易懂的导入分组一般都是可以的。例如，一个团队可以选择将gRPC导入与protobuf导入分开分组。</p>
<pre><code>注意: 对于只维护两个强制组的代码 (一个用于标准库， 一个用于所有其他导入的组)， goimports工具产生的输出与这个指南一致。

然而， goimports并不了解强制性组以外的组； 可选的组很容易被这个工具所忽略。当使用可选的组别时，代码作者和审查人都需要注意，以确保组别的一致性。

两种方法都可以，但不要让import部分处于不一致的、部分分组的状态。
</code></pre>
<h2 id="错误处理">错误处理</h2>
<p>在Go中，<a href="https://go.dev/blog/errors-are-values">错误是值</a>；它们由代码产生，也由代码消费。错误可以：</p>
<ul>
<li>转化为诊断信息，显示给人类</li>
<li>由维护者使用</li>
<li>由终端用户解释</li>
</ul>
<p>错误信息也显示在各种不同的表面上，包括日志信息、错误转储和渲染的UI。</p>
<p>处理（产生或消耗）错误的代码应该小心翼翼。忽略或盲目地传播错误的返回值可能是很诱人的。然而，我们总是应该考虑的是，函数调用栈中的当前函数是否是最适合处理该错误的那一个。这是一个很大的话题，很难给出明确的建议。使用你的判断，但要记住以下的考量。</p>
<ul>
<li>当创建一个错误值时，决定是否给它任何<a href="https://google.github.io/styleguide/go/best-practices#error-structure">结构</a>。</li>
<li>当处理一个错误时，考虑添加你所拥有的、但调用者和/或被调用者可能没有的信息。</li>
<li>也请看关于<a href="https://google.github.io/styleguide/go/best-practices#error-logging">错误记录</a>的指导。</li>
</ul>
<p>虽然忽略一个错误通常是不合适的，但一个合理的例外是当编排相关操作时，通常只有第一个错误是有用的。包errgroup为一组操作提供了一个方便的抽象，这些操作都可以作为一个组失败或被取消。</p>
<p>另见：</p>
<ul>
<li><a href="https://go.dev/doc/effective_go#errors">Effective Go中关于error的部分</a></li>
<li><a href="https://go.dev/blog/go1.13-errors">Go博客关于错误的文章</a></li>
<li><a href="https://pkg.go.dev/errors">package errors</a></li>
<li><a href="https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html">upspin.io/errors</a>包</li>
<li><a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #89: 何时使用规范的状态代码作为错误代码</a></li>
<li><a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #48：哨兵错误值</a></li>
<li><a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #13: 设计用于检查的错误</a></li>
</ul>
<h3 id="错误结构">错误结构</h3>
<p>如果调用者需要查询错误（例如，区分不同的错误条件），那么给出错误值结构，这样就可以通过编程完成查询，而不是让调用者进行字符串匹配。这个建议适用于生产代码，也适用于关心不同错误条件的测试代码。</p>
<p>最简单的结构化的错误是无参数的全局值。</p>
<pre><code>type Animal string

var (
    // ErrDuplicate occurs if this animal has already been seen.
    ErrDuplicate = errors.New(&quot;duplicate&quot;)

    // ErrMarsupial occurs because we're allergic to marsupials outside Australia.
    // Sorry.
    ErrMarsupial = errors.New(&quot;marsupials are not supported&quot;)
)

func pet(animal Animal) error {
    switch {
    case seen[animal]:
        return ErrDuplicate
    case marsupial(animal):
        return ErrMarsupial
    }
    seen[animal] = true
    // ...
    return nil
}
</code></pre>
<p>调用者可以简单地将函数返回的错误值与已知的错误值之一进行比较。</p>
<pre><code>// Good:
func handlePet(...) {
    switch err := process(an); err {
    case ErrDuplicate:
        return fmt.Errorf(&quot;feed %q: %v&quot;, an, err)
    case ErrMarsupial:
        // Try to recover with a friend instead.
        alternate = an.BackupAnimal()
        return handlePet(..., alternate, ...)
    }
}
</code></pre>
<p>上面使用了哨兵值，错误必须等于（在==的意义上）预期值。这在许多情况下是完全足够的。如果process函数返回包装后的错误值（在下面讨论），你可以使用errors.Is。</p>
<pre><code>// Good:
func handlePet(...) {
    switch err := process(an); {
    case errors.Is(err, ErrDuplicate):
        return fmt.Errorf(&quot;feed %q: %v&quot;, an, err)
    case errors.Is(err, ErrMarsupial):
        // ...
    }
}
</code></pre>
<p>不要试图根据字符串的形式来区分错误。(参见<a href="https://google.github.io/styleguide/go/index.html#gotip">Go Tip #13：为检查而设计错误</a>）。</p>
<pre><code>// Bad:
func handlePet(...) {
    err := process(an)
    if regexp.MatchString(`duplicate`, err.Error()) {...}
    if regexp.MatchString(`marsupial`, err.Error()) {...}
}
</code></pre>
<p>如果在错误中存在调用者需要的额外信息，最好是以结构化方式呈现。例如，os.PathError类型将失败操作的路径名放在调用者可以轻松访问的结构体字段中。</p>
<p>其他的错误结构可以酌情使用，例如一个包含错误代码和细节字符串的项目结构体。<a href="https://pkg.go.dev/google.golang.org/grpc/status">status包</a>是一种常见的封装方式；如果你选择这种方式（你没有义务这样做），请使用<a href="https://pkg.go.dev/google.golang.org/grpc/codes">codes</a>。参见<a href="https://google.github.io/styleguide/go/index.html#gotip">Go Tip #89。何时使用规范的状态代码作为错误</a>，以了解使用状态代码是否是正确的选择。</p>
<h3 id="给错误添加信息">给错误添加信息</h3>
<p>任何返回错误的函数都应该努力使错误值变得有用。通常情况下，该函数处于一个调用链的中间，并且只是在传播它所调用的其他函数的错误（甚至可能来自另一个包）。这里有机会用额外的信息来注解错误，但程序员应该确保错误中有足够的信息，而不添加重复的或不相关的细节。如果你不确定，可以尝试在开发过程中触发错误条件：这是一个很好的方法来评估错误的观察者（无论是人类还是代码）最终会得到什么。</p>
<p>符合惯例的良好的文档是有帮助的。例如，标准包os宣传其错误包含路径信息，当它可用时。这是一种有用的风格，因为得到错误的调用者不需要用他们已经提供了失败的函数的信息来注释它。</p>
<pre><code>// Good:
if err := os.Open(&quot;settings.txt&quot;); err != nil {
    return err
}

// Output:
//
// open settings.txt: no such file or directory
</code></pre>
<p>如果对错误的含义有什么有趣的说法，当然可以加入。只要考虑调用链的哪一层最适合理解这个含义。</p>
<pre><code>// Good:
if err := os.Open(&quot;settings.txt&quot;); err != nil {
    // We convey the significance of this error to us. Note that the current
    // function might perform more than one file operation that can fail, so
    // these annotations can also serve to disambiguate to the caller what went
    // wrong.
    return fmt.Errorf(&quot;launch codes unavailable: %v&quot;, err)
}

// Output:
//
// launch codes unavailable: open settings.txt: no such file or directory
</code></pre>
<p>与这里的冗余信息形成鲜明对比:</p>
<pre><code>// Bad:
if err := os.Open(&quot;settings.txt&quot;); err != nil {
    return fmt.Errorf(&quot;could not open settings.txt: %w&quot;, err)
}

// Output:
//
// could not open settings.txt: open settings.txt: no such file or directory
</code></pre>
<p>当向一个传播的错误添加信息时，你可以包装该错误或提出一个新的错误。用fmt.Errorf中的%w动词包装错误，允许调用者访问原始错误中的数据。这在某些时候是非常有用的，但在其他情况下，这些细节对调用者来说是误导或不感兴趣的。更多信息请参见<a href="https://blog.golang.org/go1.13-errors">关于错误包装的博文</a>。包装错误也会以一种不明显的方式扩展你的包的API界面，如果你改变了你的包的实现细节，这可能会导致破坏。</p>
<p>最好避免使用%w，除非你也记录（并有测试来验证）你所暴露的底层错误。如果你不希望你的调用者调用 errors.Unwrap, errors.Is 等等，就不要使用%w。</p>
<p>同样的概念适用于像*status.Status这样的<a href="https://google.github.io/styleguide/go/best-practices#error-structure">结构化错误</a>（见codes）。例如，如果你的服务器向后端发送不合法的请求，并收到一个InvalidArgument错误码，这个错误码不应该被传播到客户端，假设客户端没有做错。相反，向客户端返回一个内部的规范的code。</p>
<p>然而，注解错误有助于自动日志系统保留错误的状态有效载荷。例如，在一个内部函数中注释错误是合适的。</p>
<pre><code>// Good:
func (s *Server) internalFunction(ctx context.Context) error {
    // ...
    if err != nil {
        return fmt.Errorf(&quot;couldn't find remote file: %w&quot;, err)
    }
}
</code></pre>
<p>直接位于系统边界的代码（通常是RPC、IPC、存储和类似的）应该使用规范的错误空间报告错误。这里的代码有责任处理特定领域的错误，并以规范的方式表示它们。比如说。</p>
<pre><code>// Bad:
func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*pb.SuggestionResponse, error) {
    // ...
    if err != nil {
        return nil, fmt.Errorf(&quot;couldn't find remote file: %w&quot;, err)
    }
}



// Good:
import (
    &quot;google.golang.org/grpc/codes&quot;
    &quot;google.golang.org/grpc/status&quot;
)
func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*pb.SuggestionResponse, error) {
    // ...
    if err != nil {
        // Or use fmt.Errorf with the %w verb if deliberately wrapping an
        // error which the caller is meant to unwrap.
        return nil, status.Errorf(codes.Internal, &quot;couldn't find fortune database&quot;, status.ErrInternal)
    }
}
</code></pre>
<h3 id="在错误中放置w">在错误中放置%w</h3>
<p>倾向于将%w放在错误字符串的末尾。</p>
<p>错误可以用%w动词来包装，或者把它们放在一个实现了Unwrap() error的结构化错误中（例如：fs.PathError）。</p>
<p>被包装的错误形成错误链：每一层新的包装都会在错误链的前面增加一个新的条目。错误链可以用Unwrap() error进行遍历。比如说。</p>
<pre><code>err1 := fmt.Errorf(&quot;err1&quot;)
err2 := fmt.Errorf(&quot;err2: %w&quot;, err1)
err3 := fmt.Errorf(&quot;err3: %w&quot;, err2)
</code></pre>
<p>这就形成了一个错误链的形式。</p>
<pre><code>flowchart LR
  err3 == err3 wraps err2 ==&gt; err2;
  err2 == err2 wraps err1 ==&gt; err1;
</code></pre>
<p>无论%w动词放在哪里，返回的错误总是代表错误链的前面，而%w是下一个子节点。类似地，Unwrap() error 总是从最新的错误到最旧的错误来遍历错误链。</p>
<p>然而，%w动词的位置会影响错误链是从最新到最旧，从最旧到最新，还是两者都不影响。</p>
<pre><code>// Good:
err1 := fmt.Errorf(&quot;err1&quot;)
err2 := fmt.Errorf(&quot;err2: %w&quot;, err1)
err3 := fmt.Errorf(&quot;err3: %w&quot;, err2)
fmt.Println(err3) // err3: err2: err1
// err3 is a newest-to-oldest error chain, that prints newest-to-oldest.


// Bad:
err1 := fmt.Errorf(&quot;err1&quot;)
err2 := fmt.Errorf(&quot;%w: err2&quot;, err1)
err3 := fmt.Errorf(&quot;%w: err3&quot;, err2)
fmt.Println(err3) // err1: err2: err3
// err3 is a newest-to-oldest error chain, that prints oldest-to-newest.


// Bad:
err1 := fmt.Errorf(&quot;err1&quot;)
err2 := fmt.Errorf(&quot;err2-1 %w err2-2&quot;, err1)
err3 := fmt.Errorf(&quot;err3-1 %w err3-2&quot;, err2)
fmt.Println(err3) // err3-1 err2-1 err1 err2-2 err3-2
// err3 is a newest-to-oldest error chain, that neither prints newest-to-oldest
// nor oldest-to-newest.
</code></pre>
<p>因此，为了使错误文本反映错误链结构，最好将%w动词放在最后，形式为[&hellip;]：%w。</p>
<h3 id="日志中输出错误">日志中输出错误</h3>
<p>函数有时需要告诉外部系统一个错误，而不是把它传播给其调用者。在这里，日志是一个明显的选择；但要注意记录错误的内容和方式。</p>
<ul>
<li>就像好的测试失败信息一样，日志信息应该清楚地表达出错的原因，并通过包括相关信息来帮助维护者诊断问题。</li>
<li>避免重复。如果你返回一个错误，通常最好不要自己记录，而是让调用者处理。调用者可以选择记录错误，也可以用<a href="https://pkg.go.dev/golang.org/x/time/rate#Sometimes">rate.Sometimes</a>限制记录的速度。其他选择包括尝试恢复，甚至停止程序。在任何情况下，让调用者控制有助于避免记录“垃圾”日志。</li>
</ul>
<p>然而，这种方法的缺点是，任何日志都是用调用者的行号记录的。</p>
<ul>
<li>
<p>对<a href="https://en.wikipedia.org/wiki/Personal_data">PII</a>要小心。许多日志输出地并不是敏感的终端用户信息的合适目的地。</p>
</li>
<li>
<p>尽量少使用log.Error。ERROR级别的日志会导致刷新，并且比较低的日志级别更昂贵。这可能会对你的代码产生严重的性能影响。当决定错误和警告级别时，考虑最佳实践，即错误级别的信息应该是可操作的，而不是比警告”更严重”。</p>
</li>
<li>
<p>在谷歌内部，我们有监控系统，可以设置更有效的警报，而不是写到日志文件，希望有人注意到它。这与标准库包<a href="https://pkg.go.dev/expvar">expvar</a>类似，但不完全相同。</p>
</li>
</ul>
<h4 id="自定义日志级别">自定义日志级别</h4>
<p>使用冗长的日志（log.V）对你有利。冗长的日志对于开发和追踪是很有用的。建立一个围绕着log级别的约定是有帮助的。比如说。</p>
<ul>
<li>在V(1)写少量的额外信息</li>
<li>在V(2)中追踪更多信息</li>
<li>在V(3)中输出大量的内部状态</li>
</ul>
<p>为了最大限度地减少冗长日志的成本，你应该确保即使在log.V关闭的情况下也不要意外地调用昂贵的函数。log.V提供了两个API。更方便的那个带有这种意外开销的风险。当有疑问时，请使用稍显冗长的风格。</p>
<pre><code>// Good:
for _, sql := range queries {
  log.V(1).Infof(&quot;Handling %v&quot;, sql)
  if log.V(2) {
    log.Infof(&quot;Handling %v&quot;, sql.Explain())
  }
  sql.Run(...)
}


// Bad:
// sql.Explain called even when this log is not printed.
log.V(2).Infof(&quot;Handling %v&quot;, sql.Explain())
</code></pre>
<h3 id="程序初始化">程序初始化</h3>
<p>程序初始化错误（如错误的标志和配置）应该向上传播到main，main应该调用log.Exit，并解释如何修复错误。在这些情况下，一般不应使用log.Fatal，因为指向检查的堆栈跟踪不可能像人为生成的、可操作的消息那样有用。</p>
<h3 id="程序检查和panic">程序检查和panic</h3>
<p>正如在<a href="https://google.github.io/styleguide/go/decisions#dont-panic">反对panic的决定</a>中所说，标准错误处理应该围绕错误返回值进行结构化。库应该倾向于向调用者返回错误，而不是中止程序，特别是对于暂时错误。</p>
<p>偶尔有必要对一个不变量进行一致性检查，如果违反了这个不变量，就终止程序。一般来说，只有当不变量检查失败意味着内部状态已经无法恢复时，才会这样做。在谷歌代码库中，最可靠的方法是调用log.Fatal。在这些情况下使用panic是不可靠的，因为defer函数有可能会出现死锁或进一步破坏内部或外部状态。</p>
<p>同样地，要抵制恢复panic以避免崩溃的诱惑，因为这样做可能会导致传播损坏的状态。你离panic越远，你对程序的状态就越不了解，它可能持有锁或其他资源。然后，程序可以发展出其他意想不到的故障模式，使问题更加难以诊断。与其试图在代码中处理意外的panic，不如使用监控工具来浮现出意外的故障，并以高优先级修复相关的错误。</p>
<p>注意：标准的net/http服务器违反了这个建议，从请求处理程序中恢复panic。有经验的Go工程师们的共识是，这是一个历史性的错误。如果你对其他语言的应用服务器的日志进行取样，通常会发现有大量的堆栈轨迹没有被处理。在你的服务器中避免这种陷阱。</p>
<h3 id="何时用panic">何时用panic</h3>
<p>标准库对API的误用会报panic。例如，在许多情况下，如果一个值的访问方式表明它被误解了，reflect就会报panic。这类似于对核心语言错误的panic，如访问一个超出边界的切片元素。代码审查和测试应该发现这样的错误，这些错误预计不会出现在生产代码中。这些panic作为不依赖库的不变性检查，因为标准库不能访问谷歌代码库使用的<a href="https://google.github.io/styleguide/go/decisions#logging">分级日志包</a>。</p>
<p>另一种情况是，虽然不常见，但panics可以作为一个包的内部实现细节，在调用链中始终有一个匹配的recover。解析器和类似的深度嵌套、紧密耦合的内部函数组可以从这种设计中受益，若使用管道错误返回会增加复杂性而且没有价值。这种设计的关键属性是，这些panic永远不允许跨越包的边界，不构成包的API的一部分。这通常是通过一个顶层的deferred recover来实现的，它将传播的panic转化为公共API表面的返回错误。</p>
<p>当编译器无法识别不可到达的代码时，例如使用像log.Fatal这样不会返回的函数时，也会使用Panic。</p>
<pre><code>// Good:
func answer(i int) string {
    switch i {
    case 42:
        return &quot;yup&quot;
    case 54:
        return &quot;base 13, huh&quot;
    default:
        log.Fatalf(&quot;Sorry, %d is not the answer.&quot;, i)
        panic(&quot;unreachable&quot;)
    }
}
</code></pre>
<p><a href="https://pkg.go.dev/github.com/golang/glog#pkg-overview">在命令行标志被解析之前，不要调用日志函数</a>。如果你必须在init func中退出程序，可以用panic来代替日志调用。</p>
<h2 id="文档">文档</h2>
<h3 id="惯例">惯例</h3>
<p>这一部分是对<a href="https://tonybai.com/google-go-style/google-go-style-decisions">决定文档</a>的注释部分的补充。</p>
<p>以熟悉的风格记录的Go代码比那些错误记录或根本没有记录的代码更容易阅读，更不容易被误用。可运行的例子显示在Godoc和代码搜索中，这是解释如何使用你的代码的绝佳方式。</p>
<h4 id="参数和配置">参数和配置</h4>
<p>不是每个参数都必须在文档中列举出来。这适用于</p>
<ul>
<li>函数和方法参数</li>
<li>结构体字段</li>
<li>选项(option)的API</li>
</ul>
<p>将易出错或不明显的字段和参数记录下来，说说它们为什么有趣。</p>
<p>在下面的片段中，突出显示的注释对读者来说没有增加什么有用的信息。</p>
<pre><code>// Bad:
// Sprintf formats according to a format specifier and returns the resulting
// string.
//
// format is the format, and data is the interpolation data.
func Sprintf(format string, data ...interface{}) string
</code></pre>
<p>然而，这个片段展示了一个与之前类似的代码场景，其中的注释反而说明了一些非显而易见或对读者有实质性帮助的东西。</p>
<pre><code>// Good:
// Sprintf formats according to a format specifier and returns the resulting
// string.
//
// The provided data is used to interpolate the format string. If the data does
// not match the expected format verbs or the amount of data does not satisfy
// the format specification, the function will inline warnings about formatting
// errors into the output string as described by the Format errors section
// above.
func Sprintf(format string, data ...interface{}) string
</code></pre>
<p>在选择文档的内容和深度时，要考虑到你可能的受众。维护者、新加入团队的人、外部用户，甚至是六个月后的你，可能会感激这些与你第一次来写文档时的想法略有不同的信息。</p>
<p>也请参见：<br>
– <a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #41: 识别函数调用参数</a><br>
– <a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #51: 配置的模式</a></p>
<h4 id="context">Context</h4>
<p>这意味着一个上下文参数的取消操作会中断提供给它的函数。如果该函数可以返回一个错误，惯例上是ctx.Err()。</p>
<p>这个事实不需要重述。</p>
<pre><code>// Bad:
// Run executes the worker's run loop.
//
// The method will process work until the context is cancelled and accordingly
// returns an error.
func (Worker) Run(ctx context.Context) error
</code></pre>
<p>因为这句话是隐含的，所以下面的说法更好。</p>
<pre><code>// Good:
// Run executes the worker's run loop.
func (Worker) Run(ctx context.Context) error
</code></pre>
<p>如果上下文行为是不同的或不明显的，应该明确地记录下来。</p>
<ul>
<li>
<p>如果函数在取消上下文时返回ctx.Err()以外的错误。</p>
<p>// Good:
// Run executes the worker&rsquo;s run loop.
//
// If the context is cancelled, Run returns a nil error.
func (Worker) Run(ctx context.Context) error</p>
</li>
<li>
<p>如果该函数有其他机制，可能会中断它或影响寿命。</p>
<p>// Good:
// Run executes the worker&rsquo;s run loop.
//
// Run processes work until the context is cancelled or Stop is called.
// Context cancellation is handled asynchronously internally: run may return
// before all work has stopped. The Stop method is synchronous and waits
// until all operations from the run loop finish. Use Stop for graceful
// shutdown.
func (Worker) Run(ctx context.Context) error</p>
<p>func (Worker) Stop()</p>
</li>
<li>
<p>如果该函数对上下文的寿命、脉络或附加值有特殊期望。</p>
<p>// Good:
// NewReceiver starts receiving messages sent to the specified queue.
// The context should not have a deadline.
func NewReceiver(ctx context.Context) *Receiver</p>
<p>// Principal returns a human-readable name of the party who made the call.
// The context must have a value attached to it from security.NewContext.
func Principal(ctx context.Context) (name string, ok bool)</p>
</li>
</ul>
<p>警告：避免设计对调用者提出这种要求（比如上下文没有截止日期）的API。以上只是一个例子，说明在无法避免的情况下该如何记录，而不是对该模式的认可。</p>
<h4 id="并发">并发</h4>
<p>Go用户认为概念上的只读操作对于并发使用是安全的，不需要额外的同步。</p>
<p>在这个Godoc中，关于并发性的额外说明可以安全地删除。</p>
<pre><code>// Len returns the number of bytes of the unread portion of the buffer;
// b.Len() == len(b.Bytes()).
//
// It is safe to be called concurrently by multiple goroutines.
func (*Buffer) Len() int
</code></pre>
<p>然而，修改操作并不被认为对并发使用是安全的，需要用户考虑同步化。</p>
<p>同样地，这里可以安全地删除关于并发的额外注释。</p>
<pre><code>// Grow grows the buffer's capacity.
//
// It is not safe to be called concurrently by multiple goroutines.
func (*Buffer) Grow(n int)
</code></pre>
<p>强烈鼓励在以下情况下提供文档：</p>
<ul>
<li>
<p>目前还不清楚该操作是只读的还是包含修改的。</p>
<p>// Good:
package lrucache</p>
<p>// Lookup returns the data associated with the key from the cache.
//
// This operation is not safe for concurrent use.
func (*Cache) Lookup(key string) (data []byte, ok bool)</p>
</li>
</ul>
<p>为什么？在查找key的时候，缓存命中会在内部改变一个LRU缓存。这一点是如何实现的，对所有读者来说可能并不明显。</p>
<ul>
<li>
<p>同步是由API提供的</p>
<p>// Good:
package fortune_go_proto</p>
<p>// NewFortuneTellerClient returns an *rpc.Client for the FortuneTeller service.
// It is safe for simultaneous use by multiple goroutines.
func NewFortuneTellerClient(cc *rpc.ClientConn) *FortuneTellerClient</p>
</li>
</ul>
<p>为什么？Stubby提供了同步特性。</p>
<p>注意：如果API是一个类型，并且API完整地提供了同步性，惯例上只有类型定义记录了语义。</p>
<ul>
<li>
<p>该API消费用户实现的接口类型，并且该接口的消费者有特殊的并发性要求。</p>
<p>// Good:
package health</p>
<p>// A Watcher reports the health of some entity (usually a backen service).
//
// Watcher methods are safe for simultaneous use by multiple goroutines.
type Watcher interface {
// Watch sends true on the passed-in channel when the Watcher&rsquo;s
// status has changed.
Watch(changed chan&lt;- bool) (unwatch func())</p>
<pre><code>// Health returns nil if the entity being watched is healthy, or a
// non-nil error explaining why the entity is not healthy.
Health() error
</code></pre>
<p>}</p>
</li>
</ul>
<p>为什么？一个API是否能被多个goroutines安全使用是其契约的一部分。</p>
<h4 id="清理">清理</h4>
<p>记录API的任何明确的清理要求。否则，调用者不会正确使用API，会导致资源泄漏和其他可能的错误。</p>
<p>调出由调用者决定的清理工作。</p>
<pre><code>// Good:
// NewTicker returns a new Ticker containing a channel that will send the
// current time on the channel after each tick.
//
// Call Stop to release the Ticker's associated resources when done.
func NewTicker(d Duration) *Ticker

func (*Ticker) Stop()
</code></pre>
<p>如果有可能不清楚如何清理资源，请解释如何清理。</p>
<pre><code>// Good:
// Get issues a GET to the specified URL.
//
// When err is nil, resp always contains a non-nil resp.Body.
// Caller should close resp.Body when done reading from it.
//
//    resp, err := http.Get(&quot;http://example.com/&quot;)
//    if err != nil {
//        // handle error
//    }
//    defer resp.Body.Close()
//    body, err := io.ReadAll(resp.Body)
func (c *Client) Get(url string) (resp *Response, err error)
</code></pre>
<h3 id="预览">预览</h3>
<p>Go的特点是有一个文档服务器。建议在代码审查前和审查过程中都要预览你的代码产生的文档。这有助于验证godoc的格式是否会正确呈现。</p>
<h3 id="godoc格式化">Godoc格式化</h3>
<p>Godoc提供了一些特定的语法来格式化文档。</p>
<ul>
<li>
<p>段落之间需要有一个空行。</p>
<p>// Good:
// LoadConfig reads a configuration out of the named file.
//
// See some/shortlink for config file format details.</p>
</li>
<li>
<p>测试文件可以包含<a href="https://google.github.io/styleguide/go/decisions#examples">可运行的例子</a>，这些例子会出现在godoc中相应的文档后面。</p>
<p>// Good:
func ExampleConfig_WriteTo() {
cfg := &amp;Config{
Name: &ldquo;example&rdquo;,
}
if err := cfg.WriteTo(os.Stdout); err != nil {
log.Exitf(&ldquo;Failed to write config: %s&rdquo;, err)
}
// Output:
// {
//   &ldquo;name&rdquo;: &ldquo;example&rdquo;
// }
}</p>
</li>
<li>
<p>缩进行加上两个空格，就可以将它们逐字排开。</p>
<p>// Good:
// Update runs the function in an atomic transaction.
//
// This is typically used with an anonymous TransactionFunc:
//
//   if err := db.Update(func(state *State) { state.Foo = bar }); err != nil {
//     //&hellip;
//   }</p>
</li>
</ul>
<p>然而，请注意，把代码放在可运行的例子中，而不是把它放在注释中，往往会更合适。</p>
<p>这种逐字格式化可以用于非godoc原生的格式化，如列表和表格。</p>
<pre><code>// Good:
// LoadConfig reads a configuration out of the named file.
//
// LoadConfig treats the following keys in special ways:
//   &quot;import&quot; will make this configuration inherit from the named file.
//   &quot;env&quot; if present will be populated with the system environment.
</code></pre>
<ul>
<li>
<p>一行以大写字母开始，除括号和逗号外不含标点符号，后面是另一个段落，这样的行将被格式化为标题。</p>
<p>// Good:
// The following line is formatted as a heading.
//
// Using headings
//
// Headings come with autogenerated anchor tags for easy linking.</p>
</li>
</ul>
<h3 id="信号增强">信号增强</h3>
<p>有时，一行代码看起来很普通很常见，但实际上不是。这方面最好的例子之一是err == nil检查（因为err != nil更常见）。下面的两个条件检查很难区分。</p>
<pre><code>// Good:
if err := doSomething(); err != nil {
    // ...
}


// Bad:
if err := doSomething(); err == nil {
    // ...
}
</code></pre>
<p>你可以通过添加注释来“增强信号”：</p>
<pre><code>// Good:
if err := doSomething(); err == nil { // if NO error
    // ...
}
</code></pre>
<p>该只是提示请注意条件的不同。</p>
<h2 id="变量声明">变量声明</h2>
<h3 id="初始化">初始化</h3>
<p>为了保持一致性，在用非零值初始化一个新的变量时，首选:=而不是var。</p>
<pre><code>// Good:
i := 42


// Bad:
var i = 42
</code></pre>
<h3 id="非指针零值">非指针零值</h3>
<p>下面的声明使用了<a href="https://golang.org/ref/spec#The_zero_value">零值</a>：</p>
<pre><code>// Good:
var (
    coords Point
    magic  [4]byte
    primes []int
)
</code></pre>
<p>当你想表达一个空值，准备以后使用时，你应该使用零值来声明。使用显式初始化的复合字面值可能会很笨重。</p>
<pre><code>// Bad:
var (
    coords = Point{X: 0, Y: 0}
    magic  = [4]byte{0, 0, 0, 0}
    primes = []int(nil)
)
</code></pre>
<p>零值声明的一个常见应用是当使用一个变量作为反序列化的输出时：</p>
<pre><code>// Good:
var coords Point
if err := json.Unmarshal(data, &amp;coords); err != nil {
</code></pre>
<p>如果你需要一个锁或其他<a href="https://google.github.io/styleguide/go/decisions#copying">不能复制</a>的结构体字段时，你可以把它变成一个值类类型，以利用零值初始化的优势。这确实意味着，现在必须通过指针而不是值来传递包含的类型。该类型的方法必须采取指针类型接收器。</p>
<pre><code>// Good:
type Counter struct {
    // This field does not have to be &quot;*sync.Mutex&quot;. However,
    // users must now pass *Counter objects between themselves, not Counter.
    mu   sync.Mutex
    data map[string]int64
}

// Note this must be a pointer receiver to prevent copying.
func (c *Counter) IncrementBy(name string, n int64)
</code></pre>
<p>对复合类型（如结构体和数组）的局部变量使用值类型是可以接受的，即使它们包含这种不可复制的字段。然而，如果复合类型是由函数返回的，或者如果对它的所有访问最终都需要取一个地址，那么最好一开始就把变量声明为指针类型。同样地，protobufs也应该被声明为指针类型。</p>
<pre><code>// Good:
func NewCounter(name string) *Counter {
    c := new(Counter) // &quot;&amp;Counter{}&quot; is also fine.
    registerCounter(name, c)
    return c
}

var myMsg = new(pb.Bar) // or &quot;&amp;pb.Bar{}&quot;.
</code></pre>
<p>这是因为*pb.Something实现了proto.Message，而pb.Something没有：</p>
<pre><code>// Bad:
func NewCounter(name string) *Counter {
    var c Counter
    registerCounter(name, &amp;c)
    return &amp;c
}

var myMsg = pb.Bar{}
</code></pre>
<p>重要：map类型在修改之前必须被显式初始化。但是针对零值map变量进行读操作是可以的。</p>
<p>对于map和slice类型，如果代码对性能特别敏感，并且你事先知道尺寸，请看<a href="https://google.github.io/styleguide/go/best-practices#vardeclsize">尺寸提示部分</a>。</p>
<h3 id="复合字面值">复合字面值</h3>
<p>下面是一些复合字面值的声明：</p>
<pre><code>// Good:
var (
    coords   = Point{X: x, Y: y}
    magic    = [4]byte{'I', 'W', 'A', 'D'}
    primes   = []int{2, 3, 5, 7, 11}
    captains = map[string]string{&quot;Kirk&quot;: &quot;James Tiberius&quot;, &quot;Picard&quot;: &quot;Jean-Luc&quot;}
)
</code></pre>
<p>当你知道初始元素或成员时，你应该使用复合字面值来声明一个值。</p>
<p>相反，与零值初始化相比，使用复合字面值来声明空值或无成员的值在视觉上会有太多噪声。</p>
<p>当你需要一个指向零值的指针时，你有两个选择：空复合字面值和new。两者都很好，但是new关键字可以提醒读者，如果需要一个非零值，复合字面值就不能用。</p>
<pre><code>// Good:
var (
  buf = new(bytes.Buffer) // non-empty Buffers are initialized with constructors.
  msg = new(pb.Message) // non-empty proto messages are initialized with builders or by setting fields one by one.
)
</code></pre>
<h3 id="尺寸提示">尺寸提示</h3>
<p>以下是利用尺寸提示的声明，以便预先分配容量。</p>
<pre><code>// Good:
var (
    // Preferred buffer size for target filesystem: st_blksize.
    buf = make([]byte, 131072)
    // Typically process up to 8-10 elements per run (16 is a safe assumption).
    q = make([]Node, 0, 16)
    // Each shard processes shardSize (typically 32000+) elements.
    seen = make(map[string]bool, shardSize)
)
</code></pre>
<p>尺寸提示和预分配是重要的步骤，当与代码及其集成的经验分析相结合时，可以创建对性能敏感和资源高效的代码。</p>
<p>大多数代码不需要大小提示或预分配，可以允许运行时根据需要自动扩展切片或map。当最终大小已知时，预分配是可以接受的（例如，在map和切片之间转换时），但这不是可读性的要求，而且在小规模情况下可能不值得这样做。</p>
<p>警告：预先分配比你需要的更多的内存会浪费内存，甚至损害性能。如有疑问，请参阅<a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #3: Benchmarking Go Code</a>，默认<a href="https://google.github.io/styleguide/go/best-practices#vardeclzero">零值初始化</a>或<a href="https://google.github.io/styleguide/go/best-practices#vardeclcomposite">复合字面值</a>声明。</p>
<h3 id="channel方向">channel方向</h3>
<p>尽可能指明<a href="https://go.dev/ref/spec#Channel_types">channel方向</a>。</p>
<pre><code>// Good:
// sum computes the sum of all of the values. It reads from the channel until
// the channel is closed.
func sum(values &lt;-chan int) int {
    // ...
}
</code></pre>
<p>这可以防止在没有规范的情况下可能出现的随意编程错误。</p>
<pre><code>// Bad:
func sum(values chan int) (out int) {
    for v := range values {
        out += v
    }
    // values must already be closed for this code to be reachable, which means
    // a second close triggers a panic.
    close(values)
}
</code></pre>
<p>当方向被指定时，编译器会捕捉到像这样的简单错误。它还有助于向类型传达一种所有权的措施。</p>
<p>也请看Bryan Mills的演讲 “重新思考经典的并发模式”：<a href="https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view?usp=sharing">幻灯片</a>和<a href="https://www.youtube.com/watch?v=5zXAHh5tJqQ">视频</a>。</p>
<h2 id="函数参数列表">函数参数列表</h2>
<p>不要让一个函数的签名变得太长。当一个函数中的参数越多，单个参数的作用就越不明确，同一类型的相邻参数就越容易混淆。有大量参数的函数不容易被记住，在调用的时候也更难读懂。</p>
<p>在设计API时，可以考虑将一个签名越来越复杂的高可配函数分割成几个更简单的函数。如果有必要，这些函数可以共享一个（未导出的）实现。</p>
<p>当一个函数需要许多输入时，可以考虑为一些参数引入一个<a href="https://google.github.io/styleguide/go/best-practices#option-structure">功能选项结构</a>，或者采用更高级的<a href="https://google.github.io/styleguide/go/best-practices#variadic-options">variadic选项技术</a>。选择哪种策略的主要考虑因素应该是函数调用在所有预期的使用情况下看起来如何。</p>
<p>下面的建议主要适用于导出的API，它的标准比未导出的API要高。这些技术对于你的用例可能是不必要的。使用你的判断，并平衡清晰性和最小机制的原则。</p>
<p>也请参见：<a href="https://google.github.io/styleguide/go/index.html#gotip">Go技巧#24：使用特定案例的结构</a></p>
<h3 id="功能选项结构">功能选项结构</h3>
<p>功能选项结构是一种结构体类型，它汇集了一个函数或方法的部分或全部参数，然后作为最后一个参数传递给该函数或方法。(只有在导出的函数中使用该结构时，才应该导出该结构)。</p>
<p>使用选项结构有很多好处：</p>
<ul>
<li>结构体字面值包括每个参数的字段和值，这使得它们可以自我记录，并且更难被交换。</li>
<li>不相关的或”默认”的字段可以被省略。</li>
<li>调用者可以共享选项结构，并编写帮助程序对其进行操作。</li>
<li>与函数参数相比，结构体提供了更清晰的每个字段的文档。</li>
<li>选项结构可以随着时间的推移而增长，而不会影响到存量的函数调用。</li>
</ul>
<p>下面是一个可以改进的函数的例子。</p>
<pre><code>// Bad:
func EnableReplication(ctx context.Context, config *replicator.Config, primaryRegions, readonlyRegions []string, replicateExisting, overwritePolicies bool, replicationInterval time.Duration, copyWorkers int, healthWatcher health.Watcher) {
    // ...
}
</code></pre>
<p>上面的函数可以用一个选项结构重写如下：</p>
<pre><code>// Good:
type ReplicationOptions struct {
    Config              *replicator.Config
    PrimaryRegions      []string
    ReadonlyRegions     []string
    ReplicateExisting   bool
    OverwritePolicies   bool
    ReplicationInterval time.Duration
    CopyWorkers         int
    HealthWatcher       health.Watcher
}

func EnableReplication(ctx context.Context, opts ReplicationOptions) {
    // ...
}
</code></pre>
<p>然后，该函数可以在不同的包中被调用：</p>
<pre><code>// Good:
func foo(ctx context.Context) {
    // Complex call:
    storage.EnableReplication(ctx, storage.ReplicationOptions{
        Config:              config,
        PrimaryRegions:      []string{&quot;us-east1&quot;, &quot;us-central2&quot;, &quot;us-west3&quot;},
        ReadonlyRegions:     []string{&quot;us-east5&quot;, &quot;us-central6&quot;},
        OverwritePolicies:   true,
        ReplicationInterval: 1 * time.Hour,
        CopyWorkers:         100,
        HealthWatcher:       watcher,
    })

    // Simple call:
    storage.EnableReplication(ctx, storage.ReplicationOptions{
        Config:         config,
        PrimaryRegions: []string{&quot;us-east1&quot;, &quot;us-central2&quot;, &quot;us-west3&quot;},
    })
}
</code></pre>
<p>注意：<a href="https://google.github.io/styleguide/go/decisions#contexts">选项结构中从不包含上下文</a>。</p>
<p>当以下一些情况适用时，这个选项通常是首选。</p>
<ul>
<li>所有调用者都需要指定一个或多个选项。</li>
<li>大量的调用者需要提供许多选项。</li>
<li>用户将调用的多个函数之间共享这些选项。</li>
</ul>
<h3 id="不定长选项">不定长选项</h3>
<p>使用不定长选项，可以创建导出的函数，其返回的闭包可以传递给函数的不定长选项参数。该函数将选项的值作为其参数（如果有的话），而返回的闭包接受一个可变的引用（通常是一个指向结构体类型的指针），该引用将根据输入进行更新。</p>
<p>使用不定长选项可以提供很多好处：</p>
<ul>
<li>当不需要配置时，在调用函数时选项将不占用空间</li>
<li>选项仍然是值，所以调用者可以共享它们，编写帮助程序，并积累它们。</li>
<li>选项可以接受多个参数（例如 cartesian.Translate(dx, dy int) TransformOption）。</li>
<li>选项函数可以返回一个命名的类型，以便在godoc中把选项组合起来。</li>
<li>包可以允许（或阻止）第三方包，定义（或不定义）他们自己的选项。</li>
</ul>
<p>注意：使用不定长选项需要大量的额外代码（见下面的例子），所以只有在优势大于开销的情况下才可以使用。</p>
<p>下面是一个可以改进的函数的例子：</p>
<pre><code>// Bad:
func EnableReplication(ctx context.Context, config *placer.Config, primaryCells, readonlyCells []string, replicateExisting, overwritePolicies bool, replicationInterval time.Duration, copyWorkers int, healthWatcher health.Watcher) {
  ...
}
</code></pre>
<p>上面的例子可以用不定长选项改写如下：</p>
<pre><code>// Good:
type replicationOptions struct {
    readonlyCells       []string
    replicateExisting   bool
    overwritePolicies   bool
    replicationInterval time.Duration
    copyWorkers         int
    healthWatcher       health.Watcher
}

// A ReplicationOption configures EnableReplication.
type ReplicationOption func(*replicationOptions)

// ReadonlyCells adds additional cells that should additionally
// contain read-only replicas of the data.
//
// Passing this option multiple times will add additional
// read-only cells.
//
// Default: none
func ReadonlyCells(cells ...string) ReplicationOption {
    return func(opts *replicationOptions) {
        opts.readonlyCells = append(opts.readonlyCells, cells...)
    }
}

// ReplicateExisting controls whether files that already exist in the
// primary cells will be replicated.  Otherwise, only newly-added
// files will be candidates for replication.
//
// Passing this option again will overwrite earlier values.
//
// Default: false
func ReplicateExisting(enabled bool) ReplicationOption {
    return func(opts *replicationOptions) {
        opts.replicateExisting = enabled
    }
}

// ... other options ...

// DefaultReplicationOptions control the default values before
// applying options passed to EnableReplication.
var DefaultReplicationOptions = []ReplicationOption{
    OverwritePolicies(true),
    ReplicationInterval(12 * time.Hour),
    CopyWorkers(10),
}

func EnableReplication(ctx context.Context, config *placer.Config, primaryCells []string, opts ...ReplicationOption) {
    var options replicationOptions
    for _, opt := range DefaultReplicationOptions {
        opt(&amp;options)
    }
    for _, opt := range opts {
        opt(&amp;options)
    }
}
</code></pre>
<p>函数可以在不同的包中调用：</p>
<pre><code>// Good:
func foo(ctx context.Context) {
    // Complex call:
    storage.EnableReplication(ctx, config, []string{&quot;po&quot;, &quot;is&quot;, &quot;ea&quot;},
        storage.ReadonlyCells(&quot;ix&quot;, &quot;gg&quot;),
        storage.OverwritePolicies(true),
        storage.ReplicationInterval(1*time.Hour),
        storage.CopyWorkers(100),
        storage.HealthWatcher(watcher),
    )

    // Simple call:
    storage.EnableReplication(ctx, config, []string{&quot;po&quot;, &quot;is&quot;, &quot;ea&quot;})
}
</code></pre>
<p>当以下许多情况适用时，最好选择不定长选项：</p>
<ul>
<li>大多数调用者不需要指定任何选项。</li>
<li>大多数选项不经常使用。</li>
<li>有大量的选项。</li>
<li>选项需要参数。</li>
<li>选项可能会失败或被错误地设置（在这种情况下，选项函数会返回一个error）。</li>
<li>选项需要大量的文档，在一个结构中很难容纳。</li>
<li>用户或其他软件包可以提供自定义选项。</li>
</ul>
<p>这种风格的选项应该接受参数，而不是用存在来表示它们的价值；后者会使参数的动态组成变得更加困难。例如，二进制设置应该接受一个布尔值（例如，rpc.FailFast(enable bool)比rpc.EnableFailFast()更合适。） 枚举的选项应该接受一个枚举的常量（例如，log.Format(log.Capacitor)比log.CapacitorFormat()更合适）。另一种方法使那些必须以编程方式选择传递哪些选项的用户更加困难；这种用户被迫改变参数的实际组成，而不是简单地改变选项的参数。不要假设所有的用户都会静态地知道全部的选项。</p>
<p>一般来说，选项应该被按顺序处理。如果有冲突或者一个非累积的选项被多次传递，以最后一个参数为准。</p>
<p>在这种模式下，选项函数的参数通常是非导出的，以限制选项只在包本身内定义。这是一个很好的默认值，尽管有时允许其他包定义选项也是合适的。</p>
<p>参见Rob Pike的<a href="http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html">原始博文</a>和<a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis">Dave Cheney的演讲</a>，以更深入地了解这些选项的使用方法。</p>
<h2 id="复杂的命令行交互界面">复杂的命令行交互界面</h2>
<p>有些程序希望为用户提供丰富的命令行交互界面，包括子命令。例如，kubectl create、kubectl run以及其他许多子命令都是由程序kubectl提供的。至少有以下常用的库可以实现这一点。</p>
<p>如果你没有偏好或者其他考虑因素相同，推荐使用子命令，因为它最简单，而且容易正确使用。然而，如果你需要它无法提供的不同功能，请挑选其他候选之一。</p>
<ul>
<li>
<p><a href="https://pkg.go.dev/github.com/spf13/cobra">cobra</a></p>
<ul>
<li>命令行标志惯例：getopt</li>
<li>在谷歌代码库之外很常见。</li>
<li>许多额外的功能。</li>
<li>usage中的陷阱（见下文）。</li>
</ul>
</li>
<li>
<p><a href="https://pkg.go.dev/github.com/google/subcommands">subcommands</a></p>
<ul>
<li>命令行标志约定：Go</li>
<li>简单且易于正确使用。</li>
<li>如果你不需要额外的功能，推荐使用。</li>
</ul>
</li>
</ul>
<p>警告：cobra命令函数应该使用cmd.Context()来获取上下文，而不是用context.Background()创建自己的根上下文。使用subcommands包的代码已经将正确的上下文作为一个函数参数来接收。</p>
<p>你不需要把每个子命令放在一个单独的包中，而且通常也没有必要这样做。应用与任何Go代码库相同的关于包边界的考虑。如果你的代码既可以作为库也可以作为二进制文件使用，通常将CLI代码和库分开是有益的，使CLI只是诸多客户端中的一个。(这不是专门针对有子命令的CLI的，但在此提及，因为它是一个常见的地方。）</p>
<h2 id="测试">测试</h2>
<h3 id="把测试留给test函数">把测试留给Test函数</h3>
<p>Go区分了”测试助手程序”和”断言助手程序”：</p>
<ul>
<li>
<p>测试助手是负责设置或清理任务的函数。所有发生在测试助手中的故障都被认为是环境的故障（而不是被测代码的故障）–例如，当测试数据库无法启动时，因为这台机器上已经没有空闲的端口了。对于这样的函数，调用t.Helper通常是合适的，可以将其<a href="https://google.github.io/styleguide/go/decisions#mark-test-helpers">标记为测试助手</a>。更多细节见<a href="https://google.github.io/styleguide/go/best-practices#test-helper-error-handling">测试助手的错误处理</a>。</p>
</li>
<li>
<p>断言助手是检查系统正确性的函数，如果没有达到预期，则测试失败。<a href="https://google.github.io/styleguide/go/decisions#assert">使用断言助手在Go中并不是一种惯例</a>。</p>
</li>
</ul>
<p>测试的目的是报告被测代码的通过/失败情况。测试失败的理想场所是在Test函数自身中，因为这可以确保<a href="https://google.github.io/styleguide/go/decisions#useful-test-failures">失败信息</a>和测试逻辑是清晰的。</p>
<p>随着你的测试代码的增长，可能有必要将一些功能分解为独立的功能。标准的软件工程考虑仍然适用，因为测试代码仍然是代码。如果功能不与测试框架交互，那么所有的常规规则都适用。然而，当普通代码与框架交互时，必须注意避免常见的陷阱，这些陷阱会导致信息量不足的失败信息和不可维护的测试。</p>
<p>如果许多独立的测试用例需要相同的验证逻辑，请以下列方式之一安排测试，而不是使用断言助手或复杂的验证函数。</p>
<ul>
<li>在Test函数中内联逻辑（包括验证和失败），即使它是重复的。这在简单的情况下效果最好。</li>
<li>如果输入是类似的，考虑将它们统一到一个<a href="https://google.github.io/styleguide/go/decisions#table-driven-tests">表驱动的测试</a>中，同时在循环中保持逻辑的内联。这有助于避免重复，同时在测试中保持验证和失败。</li>
<li>如果有多个调用者需要相同的验证功能，但表格测试不适合（通常是因为输入不够简单或验证需要作为操作序列的一部分），安排验证函数，使其返回一个值（通常是一个error），而不是采取testing.T参数并使用它来使测试失败。在测试中使用逻辑来决定是否失败，并提供有用的测试失败。你也可以创建测试助手，以抽出常见的模板设置代码。</li>
</ul>
<p>在最后一点中概述的设计保持正交性。例如，package cmp的设计不是为了使测试失败，而是为了比较（和差异）值。因此，它不需要知道进行比较的上下文，因为调用者可以提供这个。如果你的普通测试代码为你的数据类型提供了一个cmp.Transformer，这通常可以是最简单的设计。对于其他验证，可以考虑返回一个错误值。</p>
<pre><code>// Good:
// polygonCmp returns a cmp.Option that equates s2 geometry objects up to
// some small floating-point error.
func polygonCmp() cmp.Option {
    return cmp.Options{
        cmp.Transformer(&quot;polygon&quot;, func(p *s2.Polygon) []*s2.Loop { return p.Loops() }),
        cmp.Transformer(&quot;loop&quot;, func(l *s2.Loop) []s2.Point { return l.Vertices() }),
        cmpopts.EquateApprox(0.00000001, 0),
        cmpopts.EquateEmpty(),
    }
}

func TestFenceposts(t *testing.T) {
    // This is a test for a fictional function, Fenceposts, which draws a fence
    // around some Place object. The details are not important, except that
    // the result is some object that has s2 geometry (github.com/golang/geo/s2)
    got := Fencepost(tomsDiner, 1*meter)
    if diff := cmp.Diff(want, got, polygonCmp()); diff != &quot;&quot; {
        t.Errorf(&quot;Fencepost(tomsDiner, 1m) returned unexpected diff (-want+got):\n%v&quot;, diff)
    }
}

func FuzzFencepost(f *testing.F) {
    // Fuzz test (https://go.dev/doc/fuzz) for the same.

    f.Add(tomsDiner, 1*meter)
    f.Add(school, 3*meter)

    f.Fuzz(func(t *testing.T, geo Place, padding Length) {
        got := Fencepost(geo, padding)
        // Simple reference implementation: not used in prod, but easy to
        // reasonable and therefore useful to check against in random tests.
        reference := slowFencepost(geo, padding)

        // In the fuzz test, inputs and outputs can be large so don't
        // bother with printing a diff. cmp.Equal is enough.
        if !cmp.Equal(got, reference, polygonCmp()) {
            t.Errorf(&quot;Fencepost returned wrong placement&quot;)
        }
    })
}
</code></pre>
<p>polygonCmp函数对它的调用方式是不可知的；它不接受具体的输入类型，也不规定在两个对象不匹配的情况下该做什么。因此，更多的调用者可以使用它。</p>
<p>注意：在测试助手和普通库代码之间有一个类比。除非在极少数情况下，库中的代码通常不应该报panic；从测试中调用的代码不应该停止测试，除非继续下去没有意义。</p>
<h3 id="设计可扩展的验证apis">设计可扩展的验证APIs</h3>
<p>风格指南中关于测试的大部分建议都是关于测试你自己的代码。本节是关于如何为其他人提供设施来测试他们编写的代码，以确保它符合你的库的要求。</p>
<h4 id="验收测试">验收测试</h4>
<p>这种测试被称为<a href="https://en.wikipedia.org/wiki/Acceptance_testing">验收测试</a>。这种测试的前提是，使用测试的人不知道测试中的每一个细节；他们只是把输入交给测试设施来完成工作。这可以被认为是一种<a href="https://en.wikipedia.org/wiki/Inversion_of_control">控制倒置</a>的形式。</p>
<p>在一个典型的Go测试中，test函数控制着程序流程，没有断言和test函数指导鼓励你保持这种方式。本节解释了如何以符合Go风格的方式来编写对这些测试的支持。</p>
<p>在深入探讨如何做之前，请考虑下面摘录自io/fs中的一个例子。</p>
<pre><code>type FS interface {
    Open(name string) (File, error)
}
</code></pre>
<p>虽然已存在许多fs.FS的实现，但Go开发者可能会期望自己编写一个。为了帮助验证用户的fs.FS实现是否正确，testing/fstest中提供了一个名为fstest.TestFS的通用库。这个API将实现作为一个黑箱来处理，以确保它维护io/fs契约的最基本部分。</p>
<h4 id="编写验收测试">编写验收测试</h4>
<p>现在我们知道了什么是验收测试，以及为什么要使用验收测试，让我们来探讨为包chess建立验收测试，这是一个用来模拟国际象棋游戏的包。国际象棋的用户应该实现chess.Player接口。这些实现是我们要验证的主要内容。我们的验收测试关注的是棋手的实现是否合法走出棋子，而不是这些棋子是否聪明。</p>
<ol>
<li>
<p>为验证行为创建一个新的包，通常在包名后面加上test一词来<a href="https://google.github.io/styleguide/go/best-practices#naming-doubles-helper-package">命名</a>（例如，chesstest）。</p>
</li>
<li>
<p>创建执行验证的函数，接受被测试的实现作为参数并对其进行练习。</p>
<p>// ExercisePlayer tests a Player implementation in a single turn on a board.
// The board itself is spot checked for sensibility and correctness.
//
// It returns a nil error if the player makes a correct move in the context
// of the provided board. Otherwise ExercisePlayer returns one of this
// package&rsquo;s errors to indicate how and why the player failed the
// validation.
func ExercisePlayer(b *chess.Board, p chess.Player) error</p>
</li>
</ol>
<p>测试应该注意哪些不变式被破坏，以及如何破坏。你的设计可以在两种失败报告的规则中选择。</p>
<ul>
<li>快速失败：一旦实现违反了一个不变式，就返回一个错误。</li>
</ul>
<p>这是最简单的方法，如果预计验收测试会快速执行，那么它的效果很好。简单的<a href="https://google.github.io/styleguide/go/index.html#gotip">错误哨兵</a>和<a href="https://google.github.io/styleguide/go/index.html#gotip">自定义类型</a>可以很容易地用在这里，这反倒使测试验收测试变得容易。</p>
<pre><code>for color, army := range b.Armies {
    // The king should never leave the board, because the game ends at
    // checkmate.
    if army.King == nil {
        return &amp;MissingPieceError{Color: color, Piece: chess.King}
    }
}
</code></pre>
<ul>
<li>汇总所有的失败：收集所有的失败，并全部报告。</li>
</ul>
<p>这种方法感觉上类似于<a href="https://google.github.io/styleguide/go/decisions#keep-going">“继续进行下去”的规则</a>，如果验收测试预计执行缓慢，这种方法可能更可取。</p>
<p>你如何聚集失败，应该由你是否想让用户或你自己有能力询问单个失败（例如，测试你的验收测试）来决定。下面演示了使用一个自定义的错误类型来聚合错误。</p>
<pre><code>var badMoves []error

move := p.Move()
if putsOwnKingIntoCheck(b, move) {
    badMoves = append(badMoves, PutsSelfIntoCheckError{Move: move})
}

if len(badMoves) &gt; 0 {
    return SimulationError{BadMoves: badMoves}
}
return nil
</code></pre>
<p>验收测试应该遵守<a href="https://google.github.io/styleguide/go/decisions#keep-going">“继续进行下去”</a>的规则，不调用t.Fatal，除非测试检测到被测试的系统中的不变量被损坏。</p>
<p>例如，t.Fatal应该保留给特殊情况，如<a href="https://google.github.io/styleguide/go/best-practices#test-helper-error-handling">设置失败</a>：</p>
<pre><code>func ExerciseGame(t *testing.T, cfg *Config, p chess.Player) error {
    t.Helper()

    if cfg.Simulation == Modem {
        conn, err := modempool.Allocate()
        if err != nil {
            t.Fatalf(&quot;no modem for the opponent could be provisioned: %v&quot;, err)
        }
        t.Cleanup(func() { modempool.Return(conn) })
    }
    // Run acceptance test (a whole game).
}
</code></pre>
<p>这种技术可以帮助你创建简明、规范的验证。但不要试图用它来绕过断言的规则。</p>
<p>最终产品应该以类似这样的形式提供给终端用户。</p>
<pre><code>// Good:
package deepblue_test

import (
    &quot;chesstest&quot;
    &quot;deepblue&quot;
)

func TestAcceptance(t *testing.T) {
    player := deepblue.New()
    err := chesstest.ExerciseGame(t, chesstest.SimpleGame, player)
    if err != nil {
        t.Errorf(&quot;deepblue player failed acceptance test: %v&quot;, err)
    }
}
</code></pre>
<h3 id="使用真实的传输">使用真实的传输</h3>
<p>当测试组件集成时，特别是HTTP或RPC被用作组件之间的底层传输时，最好使用真正的底层传输来连接到后端的测试版本。</p>
<p>例如，假设你要测试的代码（有时被称为 “被测系统 “或SUT）与实现<a href="https://pkg.go.dev/google.golang.org/genproto/googleapis/longrunning">长期运行操作的API</a>的后端交互。为了测试你的SUT，使用一个真正的<a href="https://pkg.go.dev/google.golang.org/genproto/googleapis/longrunning#OperationsClient">OperationsClient</a>，它连接到<a href="https://pkg.go.dev/google.golang.org/genproto/googleapis/longrunning#OperationsServer">OperationsServer</a>的测试替身（例如，一个mock、stub或fake）。</p>
<p>由于正确模仿客户端行为的复杂性，我们建议不要手工实现客户端。通过使用生产客户端和测试专用服务器，你可以确保你的测试尽可能多地使用真实代码。</p>
<p>提示：在可能的情况下，使用由被测服务的作者提供的测试库。</p>
<h3 id="terror-vs-tfatal">t.Error vs. t.Fatal</h3>
<p>正如<a href="https://tonybai.com/google-go-style/google-go-style-decisions">决定篇</a>中所讨论的，测试一般不应该在第一次遇到问题时就中止。</p>
<p>然而，有些情况需要测试不要继续进行。当某些测试设置失败时，调用t.Fatal是合适的，特别是在测试设置助手中，没有它你就不能运行测试的其余部分。在表驱动的测试中，t.Fatal适合于在测试循环之前设置整个测试功能的失败。影响测试表中单个条目的故障，使该条目无法继续进行，应按以下方式报告。</p>
<ul>
<li>如果你没有使用t.Run子测试，使用t.Error，后面跟一个continue语句，继续执行下一个表项。</li>
<li>如果你使用了子测试（并且你在调用t.Run的过程中），使用t.Fatal，结束当前的子测试并允许你的测试用例进入下一个子测试。</li>
</ul>
<p>警告：调用t.Fatal和类似函数并不总是安全的。更多的细节<a href="https://google.github.io/styleguide/go/best-practices#t-fatal-goroutine">在这里</a>。</p>
<h3 id="测试助手中的错误处理">测试助手中的错误处理</h3>
<p>注意：本节讨论的是Go使用的测试助手：执行测试设置和清理的函数，而不是普通的断言设施。更多讨论见<a href="https://google.github.io/styleguide/go/best-practices#test-functions">测试函数部分</a>。</p>
<p>由测试助手执行的操作有时会失败。例如，创建一个带有文件的目录涉及到I/O，这可能会失败。当测试助手失败时，它们的失败往往意味着测试不能继续，因为一个设置前提条件失败了。当这种情况发生时，最好在帮助器中调用一个Fatal函数。</p>
<pre><code>// Good:
func mustAddGameAssets(t *testing.T, dir string) {
    t.Helper()
    if err := os.WriteFile(path.Join(dir, &quot;pak0.pak&quot;), pak0, 0644); err != nil {
        t.Fatalf(&quot;Setup failed: could not write pak0 asset: %v&quot;, err)
    }
    if err := os.WriteFile(path.Join(dir, &quot;pak1.pak&quot;), pak1, 0644); err != nil {
        t.Fatalf(&quot;Setup failed: could not write pak1 asset: %v&quot;, err)
    }
}
</code></pre>
<p>这就使调用方比帮助器返回错误给测试本身更干净：</p>
<pre><code>// Bad:
func addGameAssets(t *testing.T, dir string) error {
    t.Helper()
    if err := os.WriteFile(path.Join(d, &quot;pak0.pak&quot;), pak0, 0644); err != nil {
        return err
    }
    if err := os.WriteFile(path.Join(d, &quot;pak1.pak&quot;), pak1, 0644); err != nil {
        return err
    }
    return nil
}
</code></pre>
<p>警告：调用t.Fatal和类似函数并不总是安全的。更多的细节<a href="https://google.github.io/styleguide/go/best-practices#t-fatal-goroutine">在这里</a>。</p>
<p>失败信息应该包括对所发生的事情的描述。这一点很重要，因为你可能会向许多用户提供测试用的API，特别是随着帮助器中产生错误的步骤的增加。当测试失败时，用户应该知道在哪里，以及为什么。</p>
<p>提示：Go 1.14引入了一个t.Cleanup函数，可以用来注册清理函数，在你的测试完成后运行。该函数也适用于测试帮助器。请参阅<a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #4: 清理你的测试以获得简化测试助手</a>的指导。</p>
<p>下面是一个名为paint_test.go的虚构文件中的片段，演示了(*testing.T).Helper如何影响Go测试中的失败报告。</p>
<pre><code>package paint_test

import (
    &quot;fmt&quot;
    &quot;testing&quot;
)

func paint(color string) error {
    return fmt.Errorf(&quot;no %q paint today&quot;, color)
}

func badSetup(t *testing.T) {
    // This should call t.Helper, but doesn't.
    if err := paint(&quot;taupe&quot;); err != nil {
        t.Fatalf(&quot;could not paint the house under test: %v&quot;, err) // line 15
    }
}

func mustGoodSetup(t *testing.T) {
    t.Helper()
    if err := paint(&quot;lilac&quot;); err != nil {
        t.Fatalf(&quot;could not paint the house under test: %v&quot;, err)
    }
}

func TestBad(t *testing.T) {
    badSetup(t)
    // ...
}

func TestGood(t *testing.T) {
    mustGoodSetup(t) // line 32
    // ...
}
</code></pre>
<p>下面是上面示例的运行输出结果，请注意高亮部分以及它们的内容差别：</p>
<pre><code>=== RUN   TestBad
    paint_test.go:15: could not paint the house under test: no &quot;taupe&quot; paint today
--- FAIL: TestBad (0.00s)
=== RUN   TestGood
    paint_test.go:32: could not paint the house under test: no &quot;lilac&quot; paint today
--- FAIL: TestGood (0.00s)
FAIL
</code></pre>
<p>paint_test.go:15的错误是指在badSetup中失败的setup函数的那一行。</p>
<pre><code>t.Fatalf(&quot;could not paint the house under test: %v&quot;, err)
</code></pre>
<p>而paint_test.go:32指的是TestGood中失败的测试行。</p>
<pre><code>goodSetup(t)
</code></pre>
<p>正确地使用(*testing.T).Helper可以将失败的位置归结得更好：</p>
<ul>
<li>助手函数扩展</li>
<li>帮助器函数调用其他帮助器</li>
<li>测试函数中的帮助器使用量增加</li>
</ul>
<p>提示：如果一个帮助器调用 (_testing.T).Error 或 (_testing.T).Fatal，在格式字符串中提供一些上下文，以帮助确定什么地方出错以及为什么。</p>
<p>提示：如果一个帮助器没有做任何事情会导致测试失败，它就不需要调用t.Helper。通过从函数参数列表中删除t来简化其签名。</p>
<h3 id="不要从独立的goroutine中调用tfatal">不要从独立的goroutine中调用t.Fatal</h3>
<p>正如包testing中所记载的，从任何goroutine中调用t.FailNow、t.Fatal等都是不正确的，除了运行Test函数（或子测试）的goroutine。如果你的测试启动了新的goroutine，它们一定不能从这些goroutine内部调用这些函数。</p>
<p>测试助手通常不会从新的goroutine发出失败信号，因此他们使用t.Fatal是完全正确的。如果有疑问，可以调用t.Error并返回。</p>
<pre><code>// Good:
func TestRevEngine(t *testing.T) {
    engine, err := Start()
    if err != nil {
        t.Fatalf(&quot;Engine failed to start: %v&quot;, err)
    }

    num := 11
    var wg sync.WaitGroup
    wg.Add(num)
    for i := 0; i &lt; num; i++ {
        go func() {
            defer wg.Done()
            if err := engine.Vroom(); err != nil {
                // This cannot be t.Fatalf.
                t.Errorf(&quot;No vroom left on engine: %v&quot;, err)
                return
            }
            if rpm := engine.Tachometer(); rpm &gt; 1e6 {
                t.Errorf(&quot;Inconceivable engine rate: %d&quot;, rpm)
            }
        }()
    }
    wg.Wait()

    if seen := engine.NumVrooms(); seen != num {
        t.Errorf(&quot;engine.NumVrooms() = %d, want %d&quot;, seen, num)
    }
}
</code></pre>
<p>在测试或子测试中添加t.Parallel并不意味着调用t.Fatal就不安全。</p>
<p>当所有对testing包API的调用都在测试函数中时，通常很容易发现不正确的用法，因为go关键字是显而易见的。传递testing.T参数会使跟踪这种用法更加困难。通常情况下，传递这些参数的原因是为了引入一个测试助手，而这些测试助手不应该依赖于被测系统。因此，如果一个测试助手注册了一个致命的测试失败，它可以而且应该从测试的goroutine中这样做。</p>
<h3 id="使用结构体字面值的字段标签">使用结构体字面值的字段标签</h3>
<p>在表格驱动的测试中，最好为每个测试用例指定key。当测试用例覆盖了大量的垂直空间（例如，超过20-30行），当有相同类型的相邻字段时，以及当你希望省略具有零值的字段时，这是有帮助的。比如说。</p>
<pre><code>// Good:
tests := []struct {
    foo     *pb.Foo
    bar     *pb.Bar
    want    string
}{
    {
        foo: pb.Foo_builder{
            Name: &quot;foo&quot;,
            // ...
        }.Build(),
        bar: pb.Bar_builder{
            Name: &quot;bar&quot;,
            // ...
        }.Build(),
        want: &quot;result&quot;,
    },
}
</code></pre>
<h3 id="保持setup代码在特定的测试范围内">保持setup代码在特定的测试范围内</h3>
<p>在可能的情况下，资源和依赖关系的设置应该尽可能地与具体的测试案例紧密联系。例如，给定一个设置函数。</p>
<pre><code>// mustLoadDataSet loads a data set for the tests.
//
// This example is very simple and easy to read. Often realistic setup is more
// complex, error-prone, and potentially slow.
func mustLoadDataset(t *testing.T) []byte {
    t.Helper()
    data, err := os.ReadFile(&quot;path/to/your/project/testdata/dataset&quot;)

    if err != nil {
        t.Fatalf(&quot;could not load dataset: %v&quot;, err)
    }
    return data
}
</code></pre>
<p>在需要它的测试函数中明确调用mustLoadDataset：</p>
<pre><code>// Good:
func TestParseData(t *testing.T) {
    data := mustLoadDataset(t)
    parsed, err := ParseData(data)
    if err != nil {
        t.Fatalf(&quot;unexpected error parsing data: %v&quot;, err)
    }
    want := &amp;DataTable{ /* ... */ }
    if got := parsed; !cmp.Equal(got, want) {
        t.Errorf(&quot;ParseData(data) = %v, want %v&quot;, got, want)
    }
}

func TestListContents(t *testing.T) {
    data := mustLoadDataset(t)
    contents, err := ListContents(data)
    if err != nil {
        t.Fatalf(&quot;unexpected error listing contents: %v&quot;, err)
    }
    want := []string{ /* ... */ }
    if got := contents; !cmp.Equal(got, want) {
        t.Errorf(&quot;ListContents(data) = %v, want %v&quot;, got, want)
    }
}

func TestRegression682831(t *testing.T) {
    if got, want := guessOS(&quot;zpc79.example.com&quot;), &quot;grhat&quot;; got != want {
        t.Errorf(`guessOS(&quot;zpc79.example.com&quot;) = %q, want %q`, got, want)
    }
}
</code></pre>
<p>测试函数TestRegression682831没有使用数据集，因此没有调用慢且容易失败的mustLoadDataset：</p>
<pre><code>// Bad:
var dataset []byte

func TestParseData(t *testing.T) {
    // As documented above without calling mustLoadDataset directly.
}

func TestListContents(t *testing.T) {
    // As documented above without calling mustLoadDataset directly.
}

func TestRegression682831(t *testing.T) {
    if got, want := guessOS(&quot;zpc79.example.com&quot;), &quot;grhat&quot;; got != want {
        t.Errorf(`guessOS(&quot;zpc79.example.com&quot;) = %q, want %q`, got, want)
    }
}

func init() {
    dataset = mustLoadDataset()
}
</code></pre>
<p>用户可能希望在与其他函数隔离的情况下运行一个函数，不应受到这些因素的惩罚：</p>
<pre><code># No reason for this to perform the expensive initialization.
$ go test -run TestRegression682831
</code></pre>
<h3 id="何时使用自定义testmain入口">何时使用自定义TestMain入口</h3>
<p>如果软件包中的所有测试都需要共同的setup，并且setup需要teardown，你可以使用一个<a href="https://go.dev/pkg/testing/#hdr-Main">自定义的testmain入口</a>。如果测试用例需要的资源的设置特别昂贵，而且成本应该被摊销，就会发生这种情况。通常情况下，你在这一点上已经从测试套件中提取了任何无关的测试。它通常只用于<a href="https://en.wikipedia.org/wiki/Functional_testing">功能测试</a>。</p>
<p>使用一个自定义的TestMain不应该是你的第一选择，因为正确使用它应该有一定的谨慎。首先考虑在<a href="https://google.github.io/styleguide/go/best-practices#t-setup-amortization">摊销普通测试设置部分</a>的解决方案或普通测试助手是否足以满足你的需求。</p>
<pre><code>// Good:
var db *sql.DB

func TestInsert(t *testing.T) { /* omitted */ }

func TestSelect(t *testing.T) { /* omitted */ }

func TestUpdate(t *testing.T) { /* omitted */ }

func TestDelete(t *testing.T) { /* omitted */ }

// runMain sets up the test dependencies and eventually executes the tests.
// It is defined as a separate function to enable the setup stages to clearly
// defer their teardown steps.
func runMain(ctx context.Context, m *testing.M) (code int, err error) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    d, err := setupDatabase(ctx)
    if err != nil {
        return 0, err
    }
    defer d.Close() // Expressly clean up database.
    db = d          // db is defined as a package-level variable.

    // m.Run() executes the regular, user-defined test functions.
    // Any defer statements that have been made will be run after m.Run()
    // completes.
    return m.Run(), nil
}

func TestMain(m *testing.M) {
    code, err := runMain(context.Background(), m)
    if err != nil {
        // Failure messages should be written to STDERR, which log.Fatal uses.
        log.Fatal(err)
    }
    // NOTE: defer statements do not run past here due to os.Exit
    //       terminating the process.
    os.Exit(code)
}
</code></pre>
<p>理想情况下，一个测试用例在自身的调用和其他测试用例之间是隔离的。</p>
<p>至少要确保单个测试用例重置他们所修改的任何全局状态，如果他们已经这样做了（例如，如果测试是与外部数据库一起工作）。</p>
<h3 id="摊销共同测试设置">摊销共同测试设置</h3>
<p>如果共同setup有以下情况，使用sync.Once可能是合适的，尽管不是必须的：</p>
<ul>
<li>
<p>它很昂贵。</p>
</li>
<li>
<p>它只适用于某些测试。</p>
</li>
<li>
<p>它不需要teardown。</p>
<p>// Good:
var dataset struct {
once sync.Once
data []byte
err  error
}</p>
<p>func mustLoadDataset(t *testing.T) []byte {
t.Helper()
dataset.once.Do(func() {
data, err := os.ReadFile(&ldquo;path/to/your/project/testdata/dataset&rdquo;)
// dataset is defined as a package-level variable.
dataset.data = data
dataset.err = err
})
if err := dataset.err; err != nil {
t.Fatalf(&ldquo;could not load dataset: %v&rdquo;, err)
}
return dataset.data
}</p>
</li>
</ul>
<p>当mustLoadDataset被用于多个测试函数时，其成本被摊销。</p>
<pre><code>// Good:
func TestParseData(t *testing.T) {
    data := mustLoadDataset(t)

    // As documented above.
}

func TestListContents(t *testing.T) {
    data := mustLoadDataset(t)

    // As documented above.
}

func TestRegression682831(t *testing.T) {
    if got, want := guessOS(&quot;zpc79.example.com&quot;), &quot;grhat&quot;; got != want {
        t.Errorf(`guessOS(&quot;zpc79.example.com&quot;) = %q, want %q`, got, want)
    }
}
</code></pre>
<p>共同的teardown之所以棘手，是因为没有统一的地方来注册清理例程。如果setup函数（在这种情况下是loadDataset）依赖于一个上下文，sync.Once可能会有问题。这是因为对setup函数的两次竞争调用中的第二次需要等待第一次调用完成后再返回。这段等待时间可能与上下文的取消操作有竞争。</p>
<h2 id="字符串连接">字符串连接</h2>
<p>Go支持很多种字符串连接的方法，比如：</p>
<ul>
<li>“+” 操作符</li>
<li>fmt.Sprintf</li>
<li>strings.Builder</li>
<li>text/template</li>
<li>safehtml/template</li>
</ul>
<p>虽然没有一个放之四海而皆准的选择规则，但以下指导意见概述了每种方法在什么情况下是首选。</p>
<h3 id="简单情况下首选操作符">简单情况下首选”+”操作符</h3>
<p>当连接几个字符串时，更倾向于使用”+”。这种方法在语法上是最简单的，不需要导入。</p>
<pre><code>// Good:
key := &quot;projectid: &quot; + p
</code></pre>
<h3 id="在需要格式化的情况下选择fmtsprintf">在需要格式化的情况下，选择fmt.Sprintf</h3>
<p>当建立一个带有格式化的复杂字符串时，更倾向于使用fmt.Sprintf。使用许多”+”运算符可能会掩盖最终的结果。</p>
<pre><code>// Good:
str := fmt.Sprintf(&quot;%s [%s:%d]-&gt; %s&quot;, src, qos, mtu, dst)


// Bad:
bad := src.String() + &quot; [&quot; + qos.String() + &quot;:&quot; + strconv.Itoa(mtu) + &quot;]-&gt; &quot; + dst.String()
</code></pre>
<p>最佳实践：当字符串构建操作的输出是io.Writer时，不要为了发送到Writer而用fmt.Sprintf构建一个临时字符串，相反，使用fmt.Fprintf来直接向Writer发送。</p>
<p>当格式化更加复杂时，请酌情选择text/template或safehtml/template。</p>
<h3 id="倾向于使用stringsbuilder基于零散字符串构建字符串">倾向于使用strings.Builder基于零散字符串构建字符串</h3>
<p>尽量使用strings.Builder基于零散字符串构建字符串。strings.Builder需要摊销的线性时间，而”+”和fmt.Sprintf在连续调用以形成一个较大的字符串时需要二次方时间。</p>
<pre><code>// Good:
b := new(strings.Builder)
for i, d := range digitsOfPi {
    fmt.Fprintf(b, &quot;the %d digit of pi is: %d\n&quot;, i, d)
}
str := b.String()
</code></pre>
<p>注意：关于更多的讨论，请参阅<a href="https://google.github.io/styleguide/go/index.html#gotip">GoTip #29: 高效地构建字符串</a>。</p>
<h3 id="常量字符串">常量字符串</h3>
<p>尽量用“构建多行常量字符串。</p>
<pre><code>// Good:
usage := `Usage:

custom_tool [args]`


// Bad:
usage := &quot;&quot; +
  &quot;Usage:\n&quot; +
  &quot;\n&quot; +
  &quot;custom_tool [args]&quot;
</code></pre>
]]></content:encoded></item></channel></rss>