本文带你十分钟速读了解 Effective Go 关键信息
1、代码示例
Golang 源码中包含各种函数的使用示例,所以查找函数如何使用 go.dev/src/ 是最好的地方,另外文档中都包含可在线运行的环境,直接测试函数使用
2、格式化:
- 官方提供统一的格式化工具 gofmt,vscode、goland 等 ide 都是天然支持的,务必要配置为 "Format on Save"
- Golang 中推荐使用 Tab缩进,而不是 4 个空格来替代
3、注释
- 官方提供了统一的格式注释的工具
godoc,更多可以阅读 go.dev/doc/comment - 每个包都应包含一段包注释,所有包中导出函数(即大写字母开头的函数)均应添加注释
4、命名
- 包的命名应该尽可能是全小写的单个单词,函数命名不应在重复包的名称,比如下面示例中,使用时 ring.New 很清晰的可以表达要创建的对象,没必要声明为 NewRing
package ring
func New() { }
// 使用
ring.New()
- 除了单元测试外,不应使用
import .的方式导入任何包(原文是:Don't use theimport .notation) - Golang 中没有默认的
Getters/Setters支持,可以自己提供命名时推荐规则如下:
func Owner() { } // Getters 不推荐的命名 GetOwner
func SetOwner() { }
- Golang 中使用
大写驼峰(MixedCaps)或者小写驼峰(mixedCaps)命名,而非下划线
5、分号
Golang 中基本上不再需要显示的在代码中使用 ;, 编译器在做语法分析时自动会帮你添加;
6、控制结构
Golang 中只有
if / for / switch三个控制结构,没有 do while 等
if
如果 if 的执行范围(即大括号)内必然会执行到 return / break / continue / goto 这种控制结构,后续的代码一定不会执行,则没必要添加 else (个人感觉各种语言都是这样,这里没必要特别强调)
f, err := os.Open(name)
if err != nil {
return err // 没必要将 if 下面的代码包裹在 else{} 内
}
d, err := f.Stat() // 这里的 err 并不是再次被声明的新变量,而是复用的上面的 err,只是改了值
if err != nil {
f.Close()
return err
}
for
首先是类似于 c 语言的使用模式:
// 与 C for类似
for init; condition; post { }
// 与 C while类似
for condition { }
// 与 C for(;;) or while(true) 类似
for { }
类似于 php 的 foreach的用法,遍历数组、切片、字符串或者映射,或从信道中读取消息:
// 类似 php 的:foreach ($map as $key => $value)
for key, value := range oldMap {
newMap[key] = value
}
// 只读取 value: foreach ($map as $value)
for _, value := range array {
}
// 只读取 key,php 中不支持
for key := range m {
value := m[key]
}
另,for 循环可以非常友好的处理 Unicode 字符串:
for pos, char := range "我爱\x80中国" { // \x80 是个非法的UTF-8编码
fmt.Printf("字符 %#U 始于字节位置 %d\n", char, pos) // %c 可以输出单个字符
}
// Output: // 可以看到每个字符的位置并不是连续的
字符 U+6211 '我' 始于字节位置 0
字符 U+7231 '爱' 始于字节位置 3
字符 U+FFFD '�' 始于字节位置 6
字符 U+4E2D '中' 始于字节位置 7
字符 U+56FD '国' 始于字节位置 10
最后,在 c/c++ 的 for 循环中如果存在两个变量,例如: for(i=0,j=10; i<j;i++,j--) 我们通常会在条件中使用 , 来分割表达式,但是在 Golang 中,并不支持 , 操作符,所以写法上就需要改变下:
for i, j := 0, 10; i < j; i, j = i+1, j-1 { }
switch
- 在 switch 关键词后若没有表达式,则在 case 中匹配
true,否则就匹配对应的表达式一致的值; - 推荐使用无表达式的 switch 替换多个
if-else-if-else结构
func unhex(c byte) byte {
switch { // switch 后无表达式,则匹配第一个 case 为 true 的代码
case '0' <= c && c <= '9': // 利用 switch 替换 `if else`
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
- switch 中 break 与其他语言略有差异,break 通常在需要提前退出 switch 才应用,否则没必要添加 switch,另外如果想在 switch 中跳出外层循环,则需要配合
标签(label)(goto 指令同样会使用 label)
Loop:
for n := 0; n < 10; n++ {
switch {
case n < 3:
if n < 2 {
break // 提前跳出case 分支
}
fmt.Println("number: ", n)
case n < 8:
if n == 7 {
break Loop // 跳出外层 for 循环,注意 continue 也支持这种写法,不过加不加 label 对 continue 来说表现一致
}
fmt.Println("number: ", n)
default:
fmt.Println("number: ", n)
}
}
// Output:
number: 2
number: 3
number: 4
number: 5
number: 6
- switch 另外一个常见用处是通过
type断言的方式动态识别类型为 interface{} 变量的真实类型
var t interface{}
t = 1
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
}
国庆假期,略有颓废,未完待续 ~