很早看到一句话: “Go 语言只有25个关键字, 简单得很”, 从此「误入歧途」. 后来发现, Golang远没有想想的简单, 如果想写出健壮高效的Golang代码, 那就更难了. 在市面上我一直没有找到一本全面的Golang书籍, 不过互联网上却能找到挺多不错的文章, 这些文章扩展着我对Golang的理解, 也解答了我很多疑惑. 感谢这些文章的作者, 总结于此, 不定期更新.
设计理念
-
我的Go语言推荐 go 吹, 顺便dis java ~
代码规范
-
记录几个我觉得重要或者之前容易忽略的:
- every blocking or IO function call should be cancelable or at least timeoutable
TODO
-
Go 比较提倡 「局部变量应当尽可能短小」, 比如buf指代buffer,使用i指代index, 不过我个人并不喜欢这种风格, 我还是喜欢name能做到见名知意, 长点没关系, 见仁见智吧.
-
几点之前容易忽略的:
- 在函数签名中会包括形参和返回值, 会作为接口文档的一部分, 所以可导出函数的必须使用命名返回值
- 习惯上接收器的命名命名一般是 1 到 2 个字母的接收器类型的缩写, 同个类型的不同方法中接收器命名要保持一致
- 包名使用纯小写、能精确描述包功能且精炼的名词(有点难度),不带下划线,不引起迷惑的前提下可以用缩写,比如标准库的 strconv 。如果包名比较复杂出现了多个单词,就应该考虑是不是要分层了
基础语法
-
GO 的环境变量讲得不错
-
很多坑
-
对于返回值未命名情况: return会创建一个新的返回变量, 然后对该变量赋值, 然后执行defer, 因此defer是无法访问到这个return创建的返回变量
而对于返回值命名的情况: 返回变量提前声明了. return没有创建新的变量, defer可以访问到return处的返回变量.
-
struct{}
不占空间, 在Channel中可以用于发送信号 -
一些小技巧
-
不错的一些gotcha
nil
-
侧重nil的比较
反射相关
-
「这个(value, type)pair在接口变量的连续赋值过程中是不变的」
-
GO语言反射 后面还没看完
data域(pair中的value)的指针指向的是一个全新的拷贝
反射只是用来检测(type,value)pair的。也就是之前提到的(见原文中图)数据域和itab域里的type
设计模式
HTTP 相关
-
Golang的HTTP操作大全 简单但清晰地描述HTTP 操作
-
阅读 Golang Http 源码时, 我和作者有同样的困惑, HandlerFunc 的实现太绕了, 直到我阅读了下一篇文章, 关于接口型函数, 一切都豁然开朗.
-
本文也分析了一下Http源码, 可以作为补充
-
Things to know about HTTP in Go
TODO: 并没有读完
-
和http 关系不大, 通过反射黑科技获取变量内部变量, 可以看看
Channel
-
Go 原理解析:channel是如何工作的 和上一篇类似, 有不错的图示
-
Go语言并发模型:像Unix Pipe那样使用channel 利用channel实现管道, 不错的应用
-
如何优雅地关闭Go channel 很不错的译文
-
golang channel的设计瑕疵 channel每次读写都要用锁, 竞争太大, 楼主提出用环形队列代替
-
如果觉得繁体字看得生硬, 可以看文中的英文原文 (其实我没看完)
-
go channel 关键特性解读和示例 一点细节, 不过Println阻塞的例子还没怎么看明白
关闭的channel, 可读, 不可写入, 不可再次关闭
被关闭的 channel 可以进行 range 迭代, 未被关闭的 channel, range 在输出完 channel 中的消息之后将会阻塞一直等待
goroutine
-
理解 G-P-M模型, 五星推荐
-
和上篇类似, 图更清晰, 参考阅读
并发
-
-
普通的线程,需要消耗1M的堆栈; 而协程很低(大概是4~5KB)
-
非阻塞I/O模型协程(Coroutines)使得开发者可以采用阻塞式的开发风格,却能够实现非阻塞I/O的效果隐式事件调度
-
进程, 线程由操作系统调度; 协程由程序员在协程的代码里显示调度
-
从性能角度来说,callback的典型node.js和golang的性能测试结果,两者差不多; 不过从代码可读性角度来说,callback确实有点不太好
然后es6降低了这种不好
-
-
我们使用了一个空结构体的管道:struct{}。这明确地指明该管道仅用于发信号,而不是传递数据
数据竞争有一个很不错的例子
锁
Context
-
一个使用示例
-
主要是源码阅读
-
Context 的设计理念以及使用示例
-
视频笔记:如何正确使用 Context - Jack Lindamood
一图胜千言, 大桥下的蜗牛 的笔记从不让人失望.
内存与指针
-
既然map是引用型类型(有指针), 为什么json.unmarshal需要传入一个map的指针. 我和提问者有同样的问题, 回答者图文并茂, 赞!
RPC
- Go RPC 开发指南 电子书
- gRPC 官方文档中文版 电子书
性能测试相关
-
想做性能优化, 必须要知道如何度量性能 (TODO: 缺乏实践)
性能优化
-
给我启发是: 语言本身提供的构建, 比如goroutine, channel等, 是基本工具, 但是工具本身也是有开销的, 需要精益求精的场景下, 我们甚至需要去优化如何使用这些构建, 但前提是我们要能深入理解这些构建的原理, 比如goroutine的开销, 比如channel是一个带锁的环形队列, 等等.
-
「在Channel的底层实现中,使用的还是锁。在没有锁竞争的单线程应用中,它能工作的很好,但是在多线程场景下,性能会急剧下降。我们可以很容易的使用无锁队列ring buffer来替代channel的功能」
黑魔法
-
谁说Golang 不能实现猴子补丁, 不过在决定用之前, 请慎重研究实现.
-
可视化学习Go并发编程 不错不错
-
gore Golang REPL
一些标准库
golang failed exec command that works in terminal
应用
- hystrix-go Golang 熔断器
- 用golang来编写cli程序吧
- Awesome Go
面试相关
- Golang面试题解析
- 如果你是一个Golang面试官,你会问哪些问题? 高票回答大赞, 不止Golang
- Gopher面试中的Coding map 本身无序
- Golang精编100题 不错的测试题
博客
- GoCN每日新闻 每天会分享几篇Golang精华文章, 五星推荐
- Tony Bai 很多高质量的golang分享
- 大桥下的蜗牛 Golang 相关的分享视频笔记, 质量相当高
- 鸟窝 不是Golang专栏, 但是有很多Golang相关文章
- RyuGou的博客 作者最近专注Golang
专题笔记
视频笔记:给 Go 库作者的建议 - Jack Lindamood
包名是将来使用过程中的一部分,所以避免包名和包中的结构体、函数式重名
// bad
var c client Client
// accepted, 因为content包不太多
var c context.Context
// good
var h http.Client
// not ok
context.NewContext
// ok
context.Background()
对象构造的两种方式:
- 默认零值: 推荐, go对nil处理比较好, 比如nil slice 还是可以执行len(), nil, slice chan map 都可以执行read, 不过各方法要处理好零值.
- 单独的构造函数:
NewSomething()
: 太灵活, 不利于阅读, 可能隐藏很多东西;
Singleton:
标准库有很多, 比如expvar/http/rand/etc
作者不推荐, 不过没说原因
函数配置/可选参数:
- 高阶函数: 作者不推荐, 笨重, (不过我觉得, 对于大量配置都使用默认值的情况, 这种方式比较好, 参考Go 函数式选项模式)
- Config struct: 推荐, 可以用json加载, 存储, 易于理解, 易于和配置系统结合
Logging:
- 库不一定要打日志, 最好别打, 返回错误等等
- 如一定要用, 可以用callback方式, 把日志交给用户
- 不要直接使用stdout stderr
- 不要假定传入的是标准库log, 使用接口方式.
原文的例子不错:
type Log interface {
Println(v ...interface{})
}
type Server struct {
Logger Log
}
func (s *Server) log(v ...interface{}) {
if s.Logger != nil {
s.Logger.Println(v...)
}
}
func (s *Server) handleRequest(r *Request) {
if r.user == 0 {
s.log("No user set")
return
}
}
interface:
golang 支持的是implicit interface, struct 可以赋予合适的接口.
最佳实践:
-
函数接受interface, 返回structs
-
lib 减少直接import 外部的interface: 避免import cycles, 自定义接口可以实现这个需求, 不需要显示import其他外部interface
-
避免large interface, 把large interface转换为small interface, 把逻辑封装到相应的struct中.
代码对比:
type Rand interface {
ExpFloat64() float64
Float32() float32
Float64() float64
Int() int
Int31() int32
Int31n(n int32) int32
Int63() int64
Int63n(n int64) int64
Intn(n int) int
NormFloat64() float64
Perm(n int) []int
Read(p []byte) (n int, err error)
Seed(seed int64)
Uint32() uint32
}
// A Source represents a source of uniformly-distributed
// pseudo-random int64 values in the range [0, 1<<63).
type Source interface {
Int63() int64
Seed(seed int64)
}
// A Rand is a source of random numbers.
type Rand struct {
src Source
}
未完待续
TODO
还有些没读完的链接, 后续读完后再归类:
- 视频笔记:如何正确使用 Context - Jack Lindamood
- Effective Go中文版
- 《effective Go》读后记录
- Go語言聖經(中文版)
- Go net/http 超时机制完全手册 译文
- awesome-go