本文是Effective Go的第一篇翻译,对应的章节为:Introduction, Formatting, Commentary。
第二篇翻译在这里,对应的章节为:Names.
第三篇翻译在这里,对应的章节为:Control structures.
第四篇翻译在这里,对应的章节为:Functions.
第五篇翻译在这里,对应的章节为:Data.
第六篇翻译在这里,对应的章节为:Initialization, Methods.
第七篇翻译在这里,对应的章节为:Interfaces and other types,The blank identifier.
第八篇翻译在这里,对应的章节为:Embedding.

介绍

Go是一门新的编程语言。尽管它继承了一些现有语言的思想,但它独特的特性可以编写与其他语言截然不同的高效程序。将C++或Java程序直接转换成Go程序并不会令人满意——Java程序是Java写的,并不是用Go。另一方面,从Go的角度去考虑问题,可能会产生(与Java相比)非常不一样的程序。换句话说,要想会写Go,就要了解语言的特性和惯用用法。了解例如命名,格式化,程序构造之类的约定也同样重要,这样编写的Go程序才易于其他Go程序员理解。
本文档提供了编写清晰,惯用的Go代码的技巧。它拓展了语言的规范,建议先阅读 A Tour of Go和How to Write Go Code

示例

Go软件包的源代码不仅是一个核心库,同时也是如何使用Go语言的示例源码。此外,很多软件包还包含了可以运行、自包含的可执行示例,可以直接在golang.org(需要科学上网,如图1)网站上运行。例如本示例(译者注:方便起见,直接复制代码到下文了).

package main

import (
	"fmt"
	"strings"
)

func main() {
	rot13 := func(r rune) rune {
		switch {
		case r >= 'A' && r <= 'Z':
			return 'A' + (r-'A'+13)%26
		case r >= 'a' && r <= 'z':
			return 'a' + (r-'a'+13)%26
		}
		return r
	}
	fmt.Println(strings.Map(rot13, "'Twas brillig and the slithy gopher..."))
}

如果读者对如何解决问题或某些东西如何显示有疑问,也可以从软件包中的文档,代码和示例中获得答案,想法和背景。

图1 golang.org

格式

格式问题是最有争议但其实后果最不严重的问题。人们虽然可以适应不同的格式化风格,但是如果他们不必这么做其实是更好的一种选择,如果每个人都遵循相同的风格,那么浪费在格式风格上的时间就很少。问题就在于如何在没有冗长格式风格说明指南的情况下达到这种“乌托邦式”的理想情况。
使用GO时,我们独辟蹊径,让机器处理绝大多数格式化问题gofmt程序(也可以作为 go fmt使用,它在包级别而不是源文件级别运行)读取Go程序,以缩进和垂直对齐的标准样式处理源代码,并保留注释,如果有必要,则重新格式化注释。如果读者想知道如何处理一些新的布局情况,请运行gofmt;如果结果看起来不正确,请调整自己的程序(或者提交一个gofmt的bug),不要纠结。
例如,无需花时间将结构体字段上的注释进行排列,Gofmt会做到这一点。比如:

type T struct {
    name string // name of the object
    value int // its value
}

gofmt会重新格式化成:

type T struct {
    name    string // name of the object
    value   int    // its value
}

标准软件包中的所有Go代码均已使用gofmt格式化。
有一些保留的格式细节,非常简要:

  • 缩进
    除非自己必须使用空格,我们默认使用Tab缩进。
  • 单行长度
    Go没有单行长度限制。如果单行感觉太长,可以用tab进行包裹缩进。
  • 括号
    Go需要的括号比C和Java少:控制结构(if, for, switch)在语法上没有括号。 而且,运算符优先级层次更短更清晰,比如:x<<8 + y<<16。与其他语言不同,空格可以很容易知道它的用意。

注释

Go提供了C语言的块注释样式:/ * * /和C++的行注释样式//。通常使用行注释,而块注释主要用于包,或者用于禁用大段代码。
godoc既是一个程序,也是一个Web服务器,它对Go源文件进行处理,提取包内容。在顶级声明之前出现的注释(没有中间的换行符)与声明一起被提取,以用作该项目的说明文档。这些注释的类型和样式决定了godoc生成的文档质量。
每个包都应在包声明之前有一个块注释作为整个包的注释。对于多文件包,包注释仅需要出现在一个任意的文件中。包注释应介绍包,并提供与包整体有关的信息。它会首先出现在godoc页面上,并为紧随其后的内容建立详细的文档说明。

/*
Package regexp implements a simple library for regular expressions.

The syntax of the regular expressions accepted is:

    regexp:
        concatenation { '|' concatenation }
    concatenation:
        { closure }
    closure:
        term [ '*' | '+' | '?' ]
    term:
        '^'
        '$'
        '.'
        character
        '[' [ '^' ] character-ranges ']'
        '(' regexp ')'
*/
package regexp

如果包比较简单,包的注释可以很简要:

// Package path implements utility routines for
// manipulating slash-separated filename paths.

注释不需要额外的格式,例如星号横幅。生成的输出甚至不会以固定宽度的字体显示,因此不必依赖对齐间距——godoc会处理这些问题。注释是不会被解析的纯文本,因此HTML和其他注释,例如_this_将被原样输出,所以不应使用。godoc所调整就是以固定宽度的字体显示已经缩进的文本,来适用程序片段。fmt软件包的包注释使用此方法获得了不错的效果。
根据上下文的不同,godoc可能甚至不会重新格式化注释,因此需要确保他们看起来非常清晰:使用正确的拼写,标点和句子结构,折叠长行等。
在包内部,顶级声明之前的任何注释都将用作该声明的doc注释。程序中每个导出的(首字母大写的)名字都应该带有文档注释。
文档注释最好作为完整的句子使用,这样才能适应各种自动化的展示。第一句应为单句摘要,并以声明的名称开头。

// Compile parses a regular expression and returns, if successful,
// a Regexp that can be used to match against text.
func Compile(str string) (*Regexp, error) {

如果每个doc注释以条目所描述的项目名称开头,那么就可以使用go doc命令,通过grep查找输出。假如你需要寻找正则表达式的解析(单词:parse)函数,但是忘记了"Compile"的函数名称,你就可以运行命令:
$ go doc -all regexp | grep -i parse
若包中的所有文档注释都以"This function..."开头,grep就不能帮你想起这个函数。但是因为包以每个文档注释的名称开头,你就可以看到以下的内容,它就会帮你回想起想找的单词:

$ go doc -all regexp | grep -i parse
    Compile parses a regular expression and returns, if successful, a Regexp
    MustCompile is like Compile but panics if the expression cannot be parsed.
    parsed. It simplifies safe initialization of global variables holding
$

Go的声明语法允许对声明进行分组。单个文档注释可以引入一组相关的常量或者变量。由于是整体声明,这种注释比较简单。

// Error codes returned by failures to parse an expression.
var (
    ErrInternal      = errors.New("regexp: internal error")
    ErrUnmatchedLpar = errors.New("regexp: unmatched '('")
    ErrUnmatchedRpar = errors.New("regexp: unmatched ')'")
    ...
)

分组还可以表明项目之间的关系,比如某一组由互斥锁保护的变量。

var (
    countLock   sync.Mutex
    inputCount  uint32
    outputCount uint32
    errorCount  uint32
)