go

Go 语言链接收藏

Posted on November 20, 2017

很早看到一句话: “Go 语言只有25个关键字, 简单得很”, 从此「误入歧途」. 后来发现, Golang远没有想想的简单, 如果想写出健壮高效的Golang代码, 那就更难了. 在市面上我一直没有找到一本全面的Golang书籍, 不过互联网上却能找到挺多不错的文章, 这些文章扩展着我对Golang的理解, 也解答了我很多疑惑. 感谢这些文章的作者, 总结于此, 不定期更新.


设计理念

代码规范

  • Go-advices

    记录几个我觉得重要或者之前容易忽略的:

    • every blocking or IO function call should be cancelable or at least timeoutable

    TODO

  • Go语言小技巧–二 命名规范

    Go 比较提倡 「局部变量应当尽可能短小」, 比如buf指代buffer,使用i指代index, 不过我个人并不喜欢这种风格, 我还是喜欢name能做到见名知意, 长点没关系, 见仁见智吧.

  • Golang代码规范

  • golang 项目实战简明指南

    几点之前容易忽略的:

    • 在函数签名中会包括形参和返回值, 会作为接口文档的一部分, 所以可导出函数的必须使用命名返回值
    • 习惯上接收器的命名命名一般是 1 到 2 个字母的接收器类型的缩写, 同个类型的不同方法中接收器命名要保持一致
    • 包名使用纯小写、能精确描述包功能且精炼的名词(有点难度),不带下划线,不引起迷惑的前提下可以用缩写,比如标准库的 strconv 。如果包名比较复杂出现了多个单词,就应该考虑是不是要分层了

基础语法

nil

反射相关

  • 理解go的反射机制reflection

    「这个(value, type)pair在接口变量的连续赋值过程中是不变的」

  • GO语言反射 后面还没看完

    data域(pair中的value)的指针指向的是一个全新的拷贝

    反射只是用来检测(type,value)pair的。也就是之前提到的(见原文中图)数据域和itab域里的type

设计模式

HTTP 相关

Channel

goroutine

并发

  • Golang适合高并发场景的原因分析

    • 普通的线程,需要消耗1M的堆栈; 而协程很低(大概是4~5KB)

    • 非阻塞I/O模型协程(Coroutines)使得开发者可以采用阻塞式的开发风格,却能够实现非阻塞I/O的效果隐式事件调度

    • 进程, 线程由操作系统调度; 协程由程序员在协程的代码里显示调度

    • 从性能角度来说,callback的典型node.js和golang的性能测试结果,两者差不多; 不过从代码可读性角度来说,callback确实有点不太好

      然后es6降低了这种不好

  • Go并发编程基础(译)

    我们使用了一个空结构体的管道:struct{}。这明确地指明该管道仅用于发信号,而不是传递数据

    数据竞争有一个很不错的例子

Context

内存与指针

RPC

性能测试相关

  • Profiling Go

    想做性能优化, 必须要知道如何度量性能 (TODO: 缺乏实践)

性能优化

  • 性能优化实战:百万级WebSockets和Go语言

    给我启发是: 语言本身提供的构建, 比如goroutine, channel等, 是基本工具, 但是工具本身也是有开销的, 需要精益求精的场景下, 我们甚至需要去优化如何使用这些构建, 但前提是我们要能深入理解这些构建的原理, 比如goroutine的开销, 比如channel是一个带锁的环形队列, 等等.

  • Go语言·听说你想让程序运行的更快? 译文

    「在Channel的底层实现中,使用的还是锁。在没有锁竞争的单线程应用中,它能工作的很好,但是在多线程场景下,性能会急剧下降。我们可以很容易的使用无锁队列ring buffer来替代channel的功能」

黑魔法

一些标准库

golang failed exec command that works in terminal

应用

面试相关

博客


专题笔记

视频笔记:给 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

还有些没读完的链接, 后续读完后再归类: