最近学习了一下go语言,这个语言的设计非常有趣,很多简洁精巧的构思,学习起来也很舒服,摆脱了CPP语言那些麻烦的特性,同时。并没有产生过多的混淆。并带来了效书写效率的提升,虽然Vibe
Coding的时代并不需要手写代码了。这篇文章希望仅从语法层面列举一些我认为比较不同的特点
- 零值机制,尽管无法避免空指针问题,但是省去了很多初始化的麻烦
- 类型名后置,并且将声明和类型定义区分开了,声明方法有(var s string 和 s := “”),并且常量提供了无类型初始化,防止了溢出。
- 文件组织很有秩序,现代化;一个Go语言编写的程序对应一个或多个以.go为文件后缀名的源文件。每个源文件中以包的声明语句开始,说明该源文件是属于哪个包。包声明语句之后是import语句导入依赖的其它包,然后是包一级的类型、变量、常量、函数的声明语句,包一级的各种类型的声明语句的顺序无关紧要(译注:函数内部的名字则必须先声明之后才能使用)。
- go中没有类,但是通过方法,将变量包等都以一等公民对待,都可以实现自己的方法。不需要设置访问修饰符例如public private。通过首字母大小写就可以实现封装
| 要点 | 含义 |
|---|---|
| 包一级 | type 通常定义在顶层,让整个包共享该类型 |
| 大写导出 | Go 用首字母大小写控制可见性,不需要写 public/private 关键字 |
| 小写私有 | 小写开头的类型/字段只能在当前包内使用,是封装的基础 |
- 标准库对web开发封装很好
- for循环的写法也很方便(for initialization; condition; post方法和for _, arg := range os.Args[1:])
- go通过自动将脱离作用域的局部变量分配到堆上,而不是栈上,实现了对局部变量的返回。尽管局部变量的作用域还是函数内,但是生命周期被gc延长至不再使用时才回收。构成了闭包机制
- 对于包级变量(package-level variables)来说,Go 的编译器不严格按照代码的书写顺序来初始化包级变量,而是根据变量之间的依赖关系(dependency)自动分析并决定初始化顺序。
- 匿名嵌套结构体的方法会自动提升
- 取消了函数声明的大部分复杂操作。不支持默认参数值,不支持函数重载,不支持按参数名传参。同时支持多返回值。甚至可以被看做第一类值,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。设置了defer关键字确保在函数返回前执行,减少了许多防御编程。当panic异常发生时,程序会中断运行,并立即执行在该goroutine(可以先理解成线程,在第8章会详细介绍)中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息
- go的错误机制包括:
| 模式 | 示例 | 说明 |
|---|---|---|
| 立即返回 | if err != nil { return err } |
最标准写法 |
| 添加上下文 | return fmt.Errorf("process %d: %w", id, err) |
保留原始错误 |
| 忽略错误 | _ = f.Close() |
必须显式用 _ 忽略 |
| 哨兵错误 | var ErrNotFound = errors.New("not found") |
包级公开错误供 errors.Is 判断 |
| 异常 | panic(err) |
panic 用于 bug / 不可恢复状态(如代码逻辑错误、初始化失败),不用于 业务错误(如文件不存在、网络超时)。 |
| 通常不对panic处理,但是又是可以尝试恢复 |
- 使用鸭子类型,来定义接口,无需implement,实现了参数多态,并且极易实现依赖倒置。但是在接口值处理上有一些需要注意的地方
- 原生协程机制,使用go方便调用,另外提供无缓存同步channel,和有缓存异步channel很容易在协程之间传递数据。
- 另外,go提供了context结构体,可以通过设置KV对来传值,但是由于只能在子节点查询父节点值,而不能在父节点查询子节点值。另外,不同协程储存储的值可能会导致重复,所以应该慎用.context 使用起来非常方便。源码里对外提供了一个创建根节点 context 的函数:
1 | func Background() Context |
background 是一个空的 context, 它不能被取消,没有值,也没有超时时间。
有了根节点 context,又提供了四个函数创建子节点 context:
1 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) |
context 会在函数传递间传递。只需要在适当的时间调用 cancel 函数向 goroutines 发出取消信号或者调用 Value 函数取出 context 中的值。
- 不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx。
- 不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。
- 不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。
- 同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的