Go特性,编程惯例(命名,格式,程序构造)
1.代码格式
gofmt(包级而不是源码级),代码格式化。
所有标准包中的Go代码都用gofmt格式化了。
使用Tab间隔(gofmt自动设置),而不是空格。
vscode 集成了gofmt,go文件保存时会自动格式化。
2.注释
块注释:/**/(包注释) 行注释//
godoc产生代码文档,处理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
如果包很简单,行注释。
注释不需要额外的格式例如*。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) {}
如果每个文档注释以它描述的名称开头,可以使用go工具的doc子命令并通过grep运行输出。例如,记不住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 )
3.名称
命名有语义影响:包外是否可见取决于首字母是否大写。
包名
当包被导入后,报名成为包内容的访问器。例:bytes.Buffer
import "bytes"
包命名应该短,简洁,有意义。按照惯例,包名是小写字母,一个词,不需要下划线或混合。不必担心包命名冲突,导入时可以选择局部的不同名来使用。
另一个惯例:包名基于它的源目录。在src/encoding/base64的包以"encoding/base64"被导入,但是以base64命名,而不是encoding_base64
或encodingBase64。
长名称不能自动使代码可读性更强。有用的文档注释相比起额外的长命名会更有价值。
属性
Go不自动支持获取和设置。提供获取或设置没任何问题,通常需要这么做,但是将Get放入获取函数名称内并无必要。如果有一个owner字段(小写,非导出),owner应该通过Owner获取,而不是GetOwner.
如果需要设置方法,命名SetOwner。
接口名称
按照惯例,一个方法的接口以方法名加上er后缀(或类似修改构建一个代理名词)来命名,如Reader,Writer,Formatter。
这样的命名很多,Read,Write,Close等有规定的签名和含义。为了避免混淆,不要用上述名称来给你的方法命名除非它有相同签名和含义。相反地,如果你的类型实现与现有类型相同含义的方法,给它相同的命名和签名方式。
将字符串转换方法命名为String,而不是ToString。
混合
使用MixedCaps或mixedCaps来写多单词名称而不是下划线。
4.分号
词义解释器自动插入分号,可以概括为:如果新行的上一行是可能结束一个语句的标记,在上一行添加分号
这导致if,for,switch或select的{不能换行写;不然if i<f()后面自动添加分号,不会执行条件判断。
5.控制结构
go的控制结构和C的显示,但没有do或while循环,只有for。if,switch,for接受可选初始化语句。
语法也有些不同:提交件判断没有括号,主体必须用{}包裹。
If
if err := file.Chmod(0664); err != nil { log.Print(err) return err }
重声明和重赋值
f, err := os.Open(name) if err != nil { return err } d, err := f.Stat() if err != nil { f.Close() return err } codeUsing(f, d)
os.Open->err 被f.Start()->err覆盖。看起来,是err被声明了两次(通过:=),合法,第二次只是被重赋值。
通过:=声明已被声明的变量v在以下情况会出现:
1.该声明与已有声明在同一个作用域
2.相关的值在初始化时赋值给v,至少有另一个变量在这个声明中创建
这个特性非常实用,在长的if-else链中只使用一个err值。
For
Go语言中的for循环统一了for,while不包含(do-while)。共有三种形式:
// Like a C for for init; condition; post { } // Like a C while for condition { } // Like a C for(;;)死循环 for { }
下划线标志
遍历数组,切片,字符串,map, 使用range
for key, value := range oldMap { newMap[key] = value }
for _, value := range oldMap {
newMap[key] = value
}
对于字符串,range做的更多,通过转换UTF-8划分单个Unicode编码点。错误的编码使用一个字节产生替换的rune U+FFFD(内建类型rune是go 术语-单个Unicode编码点。)
Switch
Go的switch比C的更常用。表达式不需要为常量或者整数,cases从上到下匹配直到找到匹配,不需要显示break,匹配上就会break,不会匹配其他cases。可以用来写if-else-if-else链,相当于do{break;}while(false)。break+标签跳出循环(区别于跳出switch)
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) } }
类型switch
switch可以用于识别接口变量的动态类型。type switch很实用类型诊断语法(关键字type)。如果switch在表达式中声明了明亮,变量在每个字句中都有相应的类型。
var t interface{} t = functionOfSomeType() switch t := t.(type) { default: fmt.Printf("unexpected type %T ", t) // %T prints whatever type t has case bool: fmt.Printf("boolean %t ", t) // t has type bool case int: fmt.Printf("integer %d ", t) // t has type int case *bool: fmt.Printf("pointer to boolean %t ", *t) // t has type *bool case *int: fmt.Printf("pointer to integer %d ", *t) // t has type *int }
6.函数
多个返回值
Go的特性之一就是函数或方法可以返回多个值。
在C语言中,写错误通过负值标记。在go语言中,Write可以返回数值和error(字符串描述信息)。
命名返回参数
Defer
7.数据