[Effective Go 中文翻译] 第一篇
Posted 凌星An
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Effective Go 中文翻译] 第一篇相关的知识,希望对你有一定的参考价值。
介绍
Go 是一门新语言。尽管它借鉴了现有语言的思想,但它具有不同寻常的特性,使得有效的 Go 程序在性质上不同于用其它语言编写的程序。将 C++ 或 Java 程序直接翻译成 Go程序会产生不太可能产生令人满意的结果—Java 程序是用 Java 编写的,而不是 Go。另一方面,从 Go 的角度思考问题,可能会编写一个能得到相同效果但完全不同的程序。换句话说,要写好 Go程序,理解它的属性和习语很重要。了解 Go 编程的既定约定也很重要,例如命名、格式、程序构造等,以便您编写的程序易于其他 Go 程序员理解。
本文档提供了编写清晰、惯用的 Go 代码的技巧。它扩充了language specification、Go 之旅和如何编写 Go 代码,所有这些都是您应该首先阅读的
Formatting 格式化
代码的格式是最有争议但最不重要的问题。不同的人可能习惯于不同的代码风格,但最好不必这样做,如果每个人都坚持相同的风格,那么因代码格式浪费的时间会大量减少。问题是如何在没有冗长的规范下,实现一致的代码风格。
在Go中,采取了一种不同寻常的方法,让机器处理大多数格式问题。
gofmt程序(即go fmt命令,它在包级别而不是源文件级别运行)读取 Go 程序并且将程序代码风格 改为规定的缩进、垂直风格,会保留原有的注释,注释也会变为规定的格式。
你可以运行gofmt函数,了解某些情况下的代码风格。如果得到的结果不正确,请重新排版您的程序(或提交关于gofmt的错误),不要解决它。
eg:无需花时间 纠结 结构字段上的注释。 Gofmt会进行处理。
type T struct
name string // name of the object
value int // its value
使用go fmt后
type T struct
name string // name of the object
value int // its value
注: Go标准库中所有代码都被 gofmt 统一了代码风格
缩进:
gofmt默认使用tab键进行处理。在一些必要情况下,你可以使用一些空格。
行代码长度:
Go每行长度没有限制。不需要太长导致溢出。如果太长,也可以适当位置换行,并使用tab进行缩进。
括号:
Go相比于C和Java需要更少的括号 :控制结构(if,for,switch)在语法不需要括号。运算符优先级层次结构更短也更加清晰
x<<8 + y<<16 (<<的优先级比 + 更高)
Commentary 注释
Go中提供了 C语言风格的 注释方法: /* */ 可以进行块注释 // 行注释
块注释常用于 包 的说明 ,在某些地方也是需要的,也可以注释一块区域的代码。
godoc程序会处理源文件并且提取关于包的注释。注释一般出现在最上边,会和声明一起提取出来,作为解释文档。注释的风格好坏 决定了godoc程序产生的文档质量。
每个包都应该有包注释,(写在package 语句上面 的块注释)。如果一个包当中有多个源文件,包注释只需要在任意一个文件中编写即可。注释应介绍包并提供与整个包相关的信息。它将首先出现在godoc页面上,然后应设置随后的详细文档。
eg:
/*
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
如果是一个很简单的包,则包注释也可以简单化。
eg:
// Package path implements utility routines for
// manipulating slash-separated filename paths.
注释不需要多余的格式 比如:星号 ;生成的注释可能不会以固定宽度的字体展示,也不需要尝试用空格 控制格式。像gofmt 一样,godoc会进行相关的处理。
依靠上下文,godoc可能不会格式化注释,所以确保注释的格式,使用正确的单词,符号,句子结构,拆分较长的行等等。
在包中,紧接在顶级声明之前的任何注释都用作该声明的文档注释。程序中的每个导出(大写)名称都应该有一个文档注释。
文档注释最好为完整且允许各种各样的自动演示的句子。第一句话应该是一个以声明的名称开头的一句摘要。
// 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)
Names 命名
命名在Go中,和其他语言一样,是非常重要的。命名甚至会有语法影响: 一个变量在包外是否可见取决于变量名称首字母的大小写。(首字母大写,包外可见;首字母小写,包外不可见。)
Package names 包命名:
当包被导入时,包名会成为包内容的访问器。
import “bytes” //导入bytes包
bytes.Buffer //导入包后,可以这么使用包内的变量
包名应该短小、简明、令人容易理解。按照惯例,包应该以一个全部小写的单词命名。不应该有下划线或者混合大写字母。
不需要担心包名冲突问题。包名只是包导入时的默认名称,不需要是唯一的。防止罕见的包名冲突问题,我们可以给冲突的包起别名,进行本地使用。
package main
//给fmt包起本地名称为f,可以使用f名称导入fmt包的内容
import f "fmt"
func main()
f.Println("Hello, 世界")
另一个习惯就是,包名一般为所在文件路径中最后一个文件夹的名称
eg: 包base64 所在的目录为src/encoding/base64
Getters(获取器):
Go 不提供对 getter 和 setter 的自动支持。自己提供 getter 和 setter 并没有什么问题,而且这样做通常是合适的,但是Get将 getter 的名称放入其中既不是惯用的也不是必需的。
如果您有一个名为 owner(小写,未导出)的字段,则应调用 getter 方法Owner(大写,已导出),而不是GetOwner. 使用大写名称进行导出 将字段与方法区分开来。如果需要,可能会调用 setter 函数SetOwner。这两个名字在实践中都很好读:
owner := obj.Owner()
if owner != user
obj.SetOwner(user)
Interface names 接口命名:
惯例: 只有一个方法的接口命名为 在方法名后面加上er后缀,
eg: Reader, Writer, Formatter, CloseNotifier
Read, Write, Close, Flush, String 还有很多其他这样的命名,它们都有标准的函数签名和确定的意义。为了避免歧义,不要使用其中的名称命名你写的方法,除非它有相同的函数签名和意义。
MixedCaps
Go中 倘若 命名中 使用多个单词,一般采用单词大写的形式,而不会使用下划线
例如: MixedCaps or mixedCaps
Semicolons 分号
和C一样,Go的语法也是使用分号来结束语句。但又和C不同,分号可以不出现在源码中。词法分析器在检测语法时凭借简单的规则自动插入分号,所以可以在源码中不写分号。
如果换行符前是一个标识符(如 int 、float64) ,字面常量(如 数字常量或者字符串常量 ) 或者 下面符号中的一个
break continue fallthrough return ++ – )
词法分析器会在结尾插入分号。
规则可以概括为:
“if the newline comes after a token that could end a statement, insert a semicolon”
分号会立即插入到左括号后面,所以下面的语句不需要写分号。
go func() for dst <- <-src ()
Go中,for循环必须写分号,用来间隔 初始化语句,条件判断语句,延续语句。
eg:
for i:=1;i<100;i++
如果一行当中有多条语句,必须使用分号进行间隔。
自动插入分号导致的影响,你不能将左括号放在控制结构的下面。
eg:
if i < f() // wrong!
// wrong!
g()
正确的演示:
if i < f()
g()
Control structures 控制结构
Go 的控制结构与 C 的控制结构相关,但在一些方面有所不同。
没有do-while、while循环,可以使用更具概括性的for循环来代替;
switch结构更灵活; if和switch可以像for一样,接收一个可选的初始化语句; break和continue语句采用可选标签来标识要中断或继续的内容;
有新的控制结构,包括 type switch和多路通信多路复用器,select。语法也略有不同:没有括号,必须始终用大括号分隔
if结构
下面是一种良好的示范,尤其是内部有控制语句如return 或者break时。
if x > 0
return y
if和switch 接收一个初始化语句,这是设置一个局部变量的普遍方式。
eg:
if err := file.Chmod(0664); err != nil
log.Print(err)
return err
在Go标准库中,如果if 语句不会延申出下一个if判断,尤其是内部以break,continue,goto或者return 结束, else是不必要的,会进行省略。
eg:
f, err := os.Open(name)
if err != nil
return err
codeUsing(f)
这是代码必须防范一系列错误条件的常见情况的示例。
如果成功的控制流向下运行,则代码可读性很好,从而消除了出现的错误情况。由于错误情况往往以return 语句结尾,因此代码不需要else语句。
f, err := os.Open(name)
if err != nil
return err
d, err := f.Stat()
if err != nil
f.Close()
return err
codeUsing(f, d)
Redeclaration and reassignment
短声明 := 在上面的代码中已经使用了
eg:
f, err := os.Open(name)
//声明了两个变量f和err
d, err := f.Stat()
//上面语句看起来声明了d和err变量,但是err在之前已经声明过了。
这种重复是合法的。 err在第一个语句中被声明,在第二个语句只是被重新赋值。这意味着调用f.Stat使用了之前声明的err变量,仅赋了新值。
如果之前使用过:= 声明过变量v ,如果两个处于相同的作用域则会使用之前的变量v,将相应的值赋值给v,但至少要声明一个新变量。如果处于不同的作用域,则会创建一个新的变量v。
:= 这种方式是一种纯粹的使用主义,它很容易使用同一个err变量,在一种长的if-else 链中,是很常用的。
值得注意的是: 在Go中函数的参数和返回值 与 函数体的范围是一样的,即使它们没有在函数体的括号内。
For
Go中的for循环和C中的是相似的,但不完全相同。它结合了for和while 。在Go中是没有do-while和while的。有三种形式,仅有一种形式必须写有分号。
// Like a C for
for init; condition; post
// Like a C while
for condition
// Like a C for(;;)
for
在循环中,可以使用 短声明 很简单的声明下标变量
sum := 0
for i := 0; i < 10; i++
sum += i
如果你在循环遍历数组(array)、切片(slice) 、字符串(string) 、map 、从通道(channel)读取数据,可以使用 range
eg:
for key, value := range oldMap
newMap[key] = value
如果你只需要使用key或者index(下标) ,可以不写第二个变量
eg:
for key := range m
if key.expired()
delete(m, key)
如果你只需要使用value ,不需要使用第一个变量,可以使用占位符( _ ) 丢弃第一个变量
eg:
sum := 0
for _, value := range array
sum += value
对于字符串,range可以解析UFT-8 编码,而不只是显示单个Unicode 编码。错误的编码占1个字节,会使用rune 类型U+FFFD的形式打印 。
注: rune is Go terminology for a single Unicode code point
eg:
for pos, char := range "日本\\x80語" // \\x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\\n", char, pos)
结果:
character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7
Go中没有逗号运算符。 ++和- - 是一个语句,而不是表达式。
(没有前缀++或者- - )
错误使用:
a:=0
//syntax error
++a
--a
b:=a++
正确使用:
a:=0
a++
a--
如果你想要在for循环中运行多个变量,你可以使用 平行赋值
eg:
// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1
a[i], a[j] = a[j], a[i]
Switch
Go中的switch比C当中的更加自然,强大。swith后的表达式,不限于常量或者整形数据 ,case 从上到下匹配,直到找到匹配项。如果switch后没有表达式,默认表达式为true 。
所以将if-else if -else 链改写为switch 是十分有用且常见的。
eg:
func unhex(c byte) byte
switch
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
return 0
There is no automatic fall through, but cases can be presented incomma-separated lists.
case 允许是逗号分隔的列表
eg:
func shouldEscape(c byte) bool
switch c
case ' ', '?', '&', '=', '#', '+', '%':
return true
return false
Go中和C语言不相同的用法是,break 语句用于终止最近的switch 。当然,有时也会终止最近的循环,而不单单是switch。Go中可以给循环设置label (标签) ,使用 break 终止标签的循环。
eg:
Loop:
for n := 0; n < len(src); n += size
switch
case src[n] < sizeOne:
if validateOnly
break
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src)
err = errShortInput
break Loop
if validateOnly
break
size = 2
update(src[n] + src[n+1]<<shift)
continue语句,也可以接收一个label(标签) ,他仅适用于循环。
下面是一段使用两个switch实现byte切片比较的代码
// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int
for i := 0; i < len(a) && i < len(b); i++
switch
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
switch
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
return 0
Type switch
switch也用于识别接口变量的动态类型 。所谓的type switch是使用了 关键字type够成的类型断言。如果 switch 在表达式中声明了一个变量,则该变量将在每个分支中具有相应的类型。在这种情况下重用名称也是惯用的,实际上是在每种情况下声明一个具有相同名称但类型不同的新变量。
eg:
var t interface
t = functionOfSomeType()
switch t := t.(type)
default:
fmt.Printf("unexpected type %T\\n", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\\n", t) // t has type bool
case int:
fmt.Printf("integer %d\\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\\n", *t) // t has type *int
以上是关于[Effective Go 中文翻译] 第一篇的主要内容,如果未能解决你的问题,请参考以下文章
[Effective Go 中文翻译] Initialization篇