Golang 从入门到放弃 -0x05
函数、多返回值、具名返回值、defer,以及 Go 里最基础的错误处理方式。
上一章把Go的骨架大概认全了,这一章终于轮到函数。函数这个东西大家都认识,但Go偏偏喜欢在“返回值”和“错误处理”上搞出一点自己的脾气,所以单独拎出来说很有必要。
函数长什么样
Go里定义函数用 func 关键字,格式非常直白。
func add(a int, b int) int {
return a + b
}同类型参数可以合并写,这也是你以后最常见的姿势。
func add(a, b int) int {
return a + b
}调用就和别的语言差不多。
result := add(3, 5)
fmt.Println(result)多个返回值
这是Go非常有代表性的特征之一。一个函数可以同时返回多个值,这不是语法糖,这是日常操作。
func swap(a, b string) (string, string) {
return b, a
}x, y := swap("hello", "world")
fmt.Println(x, y)很多新手第一次看到会觉得有点怪,但你写几天后就会发现,这东西拿来返回结果加状态,实在太顺手了。
具名返回值
Go 允许你给返回值起名字。
func rectangle(width, height int) (area int, perimeter int) {
area = width * height
perimeter = 2 * (width + height)
return
}这个写法不是不能用,但我个人建议不要滥用。函数短一点还好,长一点就容易让读代码的人产生一种“你到底要 return 啥”的迷茫。除非返回值名字本身很有解释力,否则老老实实显式返回更清楚。
可变参数
如果你参数个数不固定,可以用 ...。
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}fmt.Println(sum(1, 2, 3))
fmt.Println(sum(10, 20, 30, 40))可变参数本质上就是一个切片,所以你也可以把现成的切片拆开传进去。
nums := []int{1, 2, 3}
fmt.Println(sum(nums...))defer:先记账,后结算
defer 的意思可以粗暴理解成“先把这句登记一下,等当前函数快结束时再执行”。这个东西在资源释放、解锁、收尾日志里特别好用。
func demo() {
fmt.Println("start")
defer fmt.Println("clean up")
fmt.Println("end")
}执行顺序会是:
start
end
clean up最常见的用法就是开完文件、拿完锁,立刻写一个 defer,避免你后面逻辑一复杂就忘了关。
file, err := os.Open("demo.txt")
if err != nil {
return err
}
defer file.Close()Go 的错误处理:没有 try/catch,只有老老实实 if err != nil
Go 不爱异常那一套。大部分业务错误都通过返回 error 来传出来,然后你自己判断。
package main
import (
"errors"
"fmt"
)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为0")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("出错了:", err)
return
}
fmt.Println("结果是:", result)
}这里的套路非常固定:
- 成功时返回正常结果和
nil - 失败时返回兜底值和一个具体的
error - 调用方拿到以后立刻判断
err
这种写法啰嗦吗?是有一点。但它的好处是,你一眼就知道哪段逻辑可能失败,错误是显式摆在台面上的,不是躲在角落里随时给你炸一下。
fmt.Errorf 也很常用
如果你想把上下文一起塞进错误信息里,fmt.Errorf 很方便。
func queryUser(id int) error {
if id <= 0 {
return fmt.Errorf("非法用户ID: %d", id)
}
return nil
}一点点习惯问题
Go 社区对错误处理有一种近乎执念的朴素审美:能早点返回就早点返回,别把代码套成俄罗斯套娃。
func doWork() error {
err := step1()
if err != nil {
return err
}
err = step2()
if err != nil {
return err
}
return nil
}这类代码看起来平铺直叙,甚至有点像流水账,但维护起来真的轻松。业务代码不是写诗,少一点悬念反而是好事。
小结
这一章你至少要记住三件事:
- Go 函数写法不复杂,但多返回值很常见。
defer是个特别顺手的收尾工具。- Go 处理错误的主流姿势,就是
if err != nil。
下一章我们聊指针。别怕,Go 里的指针没有 C 那么吓人,但它也绝不是摆设。