Go语言控制语句惯用法以及注意事项
快乐路径
func doSomething() error {
if errorCondition1 {
// 错误逻辑
...
return err1
}
// 成功逻辑
...
if errorCondition2 {
// 错误逻辑
...
return err2
}
// 成功逻辑
...
return nil
}
// 伪代码段2
func doSomething() error {
if successCondition1 {
// 成功逻辑
...
if successCondition2 {
// 成功逻辑
...
return nil
} else {
// 错误逻辑
...
return err2
}
} else {
// 错误逻辑
...
return err1
}
}
- 没有使用else,遇到错误立即返回
- 成功逻辑始终居左并延续到函数结尾,没有嵌入if语句中
- 整个代码段布局扁平,没有深度缩进
- 逻辑一目了然,可读性好
for range的避坑指南
注意参与迭代的是range表达式的副本
参与循环的是range表达式的副本。
func arrayRangeExpression() {
var a = [5]int{1, 2, 3, 4, 5}
var r [5]int
fmt.Println("arrayRangeExpression result:")
fmt.Println("a = ", a)
for i, v := range a {
if i == 0 {
a[1] = 12
a[2] = 13
}
r[i] = v
}
fmt.Println("r = ", r)
fmt.Println("a = ", a)
}
Go中的数组在内部表示为连续的字节序列,长度是编译器在编译期计算出来的,对range表达式的复制即对一个数组的复制,使用数组指针可以避免这个坑,在Go的1.23版本中for range已经避免了这个问题,自动变为指针。
在Go中,大多数应用数组的场景都可以用切片替代。 切片在Go内部表示为一个结构体,由(* T, len, cap)三元组组成。其中T指向切片对应的底层数组的指针,len是切片当前长度,cap为切片的容量。在进行range表达式复制时,它实际上复制的是一个切片,也就是表示切片的那个结构体。表示切片副本的结构体中的T依旧指向原切片对应的底层数组,因此对切片副本的修改也都会反映到底层数组a上。而v从切片副本结构体中*T指向的底层数组中获取数组元素,也就得到了被修改后的元素值。
range表达式的复制行为会带来一些性能上的损耗,尤其是range表达式的类型为数组,range需要复制整个数组;而当range表达式类型为数组指针或者切片时,这个损耗就小得多,因为仅仅需要复制一个指针或一个切片的内部表示(一个结构体)即可。
break跳到哪里去了
func main() {
exit := make(chan interface{})
go func() {
for {
select {
case <-time.After(time.Second):
fmt.Println("tick")
case <-exit:
fmt.Println("exiting...")
break
}
}
fmt.Println("exit!")
}()
time.Sleep(3 * time.Second)
exit <- struct{}{}
// wait child goroutine exit
time.Sleep(3 * time.Second)
}
子协程在收到channel信号后执行的break并未退出外层的for循环(没有输出exit),而是再次进入循环打印“tick”
Go语言规范中明确规定break语句(不接label情况下)结束执行并跳出的是同一函数内break所在的最内层的for,select,switch的执行
修正后代码
func main() {
exit := make(chan interface{})
go func() {
loop:
for {
select {
case <-time.After(time.Second):
fmt.Println("tick")
case <-exit:
fmt.Println("exiting...")
break loop
}
}
fmt.Println("exit!")
}()
time.Sleep(3 * time.Second)
exit <- struct{}{}
// 等待子goroutine退出
time.Sleep(3 * time.Second)
}
outerLoop:
for i := 0; i < n; i++ {
// ...
for j := 0; j < m; j++ {
// 当不满足某些条件时,直接终止最外层循环的执行
break outerLoop
// 当满足某些条件时,直接跳出内层循环,回到外层循环继续执行
continue outerLoop
}
}
小结:
- 使用if语句时遵循“快乐路径”原则
- 小心 for range的循环变量重用,明确真实参与循环的是range表达式的副本
- 明确break和continue执行后的真实目的地
- 使用fallthrough关键字前,考虑能否用更简洁、清晰的case表达式列表替代