十分钟速读 Effective Go (一)

347 阅读4分钟

本文带你十分钟速读了解 Effective Go 关键信息

1、代码示例

Golang 源码中包含各种函数的使用示例,所以查找函数如何使用 go.dev/src/ 是最好的地方,另外文档中都包含可在线运行的环境,直接测试函数使用

image.png

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 the import . 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
}

国庆假期,略有颓废,未完待续 ~