Golang第二语言指南: 学习路径、最佳实践以及工程化

      ☕ 10 分钟
🏷️
  • #Golang
  • 这篇文章是为已经有一些编程基础,打算将Golang作为第二编程语言进行学习的同学准备的,希望能够为你的学习提供一些方向。

    Go语言的吉祥物是Gopher(地鼠),Go语言爱好者也会自称Gopher 图/Renee French
    Go语言的吉祥物是Gopher(地鼠),Go语言爱好者也会自称Gopher 图/Renee French

    概述

    Go语言问世于2009年,由Google支持,Robert Griesemer, Rob Pike和Ken Thompson三位计算机大牛设计,是一个静态强类型、编译型,跨平台,语言原语支持并发,具有垃圾收集功能的类C风格编程语言。

    Robert Griesemer(V8 Javascript engine, Java HotSpot VM), Rob Pike(UNIX, Plan9, UTF-8)和Ken Thompson(B, Unix, UTF-8, grep) 视频截图/Youtube: Google I/O 2012 - Meet the Go Team
    左起,Robert Griesemer(V8 Javascript engine, Java HotSpot VM), Rob Pike(UNIX, Plan9, UTF-8)和Ken Thompson(B, Unix, UTF-8, grep) 视频截图/Youtube: Google I/O 2012 - Meet the Go Team

    由于go(v. 出发; n. 围棋)这个单词太过常见,容易引发歧义,社区常用Golang称呼Go语言,一般搜索Golang返回的结果会有更好的相关性。

    Golang目前处于v1大版本,一般每6个月发布一个小版本(1.x),每隔数日到数周发布修复和安全更新版本(1.x.y),大版本范围内保证向前兼容,你可以安心升级。

    Golang是一门相对稳定、约束、保守的语言,语言的特性很少,新特性的引入往往至少花费一年,因此不用担心学不动(滑稽)。

    Golang是云原生的主场语言,这里列举一些应用广泛的项目:

    • Golang:Go语言是自举的(先有鸡还是先有蛋?Go 1.5及以上,2015年起)。
    • Kubernetes:企业级容器调度不二之选。
    • Docker:容器化开山鼻祖,市场占有率第一。
    • Istio:容器化网络服务网格。
    • Prometheus:应用指标监控事实行业标准。
    • InfluxDB:时序数据库。
    • TiDB:国人牵头的分布式SQL数据库,一个流行而健康的开源项目。

    国内外优秀团队广泛地使用Golang,例如七牛GoogleCloudflare微软PaypalDropBoxTwitterUberNetflixHashiCorp阿里巴巴腾讯百度PingCAP拳头游戏

    学习路径

    比较有效的学习方式是在理解语言基础后实际地写几个项目,做中学,学中做。

    语言基础

    IDE

    常见的IDE选择有:

    • Visual Studio Code + Golang扩展:免费,插件由Golang官方提供支持。
    • GoLand:付费,JetBrains家的,IDEA、PhpStorm、WebStorm的同门。
    • vim + vim-go:免费。
    • Atom + Go-Plus:免费。

    如果你没有在上面找到喜欢的IDE,官方wiki里有一份更全的列表

    依赖库和文档

    Golang的标准库非常务实,大部分功能都可以仅依赖标准库实现。标准库的列表见这里

    典型常用的标准库:

    Go不需要中心化的库托管服务,所有满足一定规则的代码仓库都是合法的第三方库拉取源。你可以在Github或者搜索引擎中搜索第三方库,并且在官方提供的Go Dev站点查询其文档。Go的文档基于按特定规则书写的代码注释生成。例如,我自家养大的HTTP gzip压缩中间件github.com/nanmu42/gzip的文档位于: https://pkg.go.dev/github.com/nanmu42/gzip

    一些常用第三方库:

    大陆的开发者可以使用由七牛云提供的镜像服务以极大提升第三方库的拉取体验。

    深入学习

    最佳实践

    语言风格

    编程是一个创造可能性的过程,同时是一个消去可能性的过程。

    Golang倡导的语言风格是追求简洁明确和秩序的:

    • 简洁明确:关键词少,API精练;
    • 秩序(约束):CSP, gofmt,静态分析。

    消去可能性让答案变得明显,秩序可以提升团队整体产率。

    gofmt/goimports

    一些小问题容易引发大争论:使用空格还是tab?数组末尾的元素要添加逗号吗?函数的大括号需要先换行吗?……?

    Golang具有完善的静态代码辅助和分析工具,gofmt/goimports就是一个很重要的例子。

    在大多编程语言中,代码文本的格式都难以统一——每个人对格式的看法不一,缺乏统一格式的工具(或者工具提供了太多选项让每个人都有机会设定得不一样),软件工程一般都需要团队协作,格式的争论给团队风格统一和代码评审(想想那些因为空格回车或者逗号带来的git diff)带来了困难。

    gofmt会格式化Golang代码,这个过程非常迅速,你可以放心地将其添加到你的IDE的保存时钩子(on-save hook)中。gofmt没有提供任何格式上的选项,只要团队中每个人都使用gofmt,每个人的代码格式都是一样的。

    没人喜欢golang/gofmt的格式,但是人人都喜欢gofmt.

    goimports是gofmt的增强版,格式化代码的同时对依赖进行排序,以保证代码格式上的更高一致性,实际生产中常用于替代gofmt.

    静态分析

    Golang的编译为代码的正确性提供了基本保障,但这还不够。

    静态分析意味着在代码编译前找到错误、可疑问题或优化点。

    Golang的自举意味者其标准库中提供了大量用于解析代码文本和AST语法树的工具,这为Golang代码静态分析的繁荣生态提供了可能。

    golangci-linter是近几年崭露头角的静态分析新秀,它集成了大量主题的linter,例如Golang官方提供的检查代码中可疑错误的govet,检查变量(再)赋值后但没有使用的ineffassign,检查忘记处理错误的errcheck,提出代码简化意见的gosimple等等。

    和gofmt/goimports一样,我推荐你将golangci-linter添加到你的IDE的保存时钩子(on-save hook)中,在你编码过程中,它会在第一时间第一现场发现问题并提醒你。

    注释和文档

    官方指南Effective Go中阐述了编写代码注释以生成文档的最佳实践。

    文档并不是越多越好,下面这些位置可能适合写文档:

    • 依赖库(package)描述。
    • 公开的(导出的)函数、结构体及其方法、变量、常量。
    • 业务或算法复杂的过程。
    • 排期进行的工作(TODO)。

    下面这些位置尤其适合写文档:

    • 由于业务、算法或技术需要,高自由度,多可能性的API,这些地方常常有interface{}或者访问者模式(visitor mode),调用者或者实现者需要文档来提供更明确的信息和要求以避免误用API.
    • 临时的解决方案,需要说明目的和预期的永久方案。

    下面这些位置可能不适合写文档:

    • 作用范围有限的私有(未导出)变量、结构体、方法;
    • 名称、入参、出参和它们的类型已经能明确表征其用途和非用途的函数和方法;
    • 一目了然的过程。

    测试

    测试可以侦测实现错误、回归错误和意外更改,提升交付效率和信心。如果你已经反复多次手工测试过某个特性,我推荐你考虑为它写个测试。

    Golang原生支持测试,官方提供了清晰的教程文档.

    部分IDE(如goland和Visual Studio Code)可以快速地生成测试代码所需的格式文本,你可以善用这个特性以提升效率。

    如果你的测试需要数据库或者外部资源,除了自己部署这些资源并固定运行测试的环境以外,你还可以考虑使用github.com/ory/dockertest/v3在测试时启动一个即用即丟的容器化资源实例以支撑测试,这可以明显降低测试代码对环境的依赖。

    编译和部署

    二进制

    Golang支持在Linux、Windows和Mac上编译二进制,二进制可执行文件包含完整的Golang运行时和你的程序功能,拷贝到相同系统相同架构的设备中可直接执行。

    根据你的代码规模和依赖多寡,二进制的大小一般会在数MB到数十MB之间变动。

    如果你的代码中不含cgo,你还可以在上述任意一个平台交叉编译另外两个平台的二进制。像Gox这样的工具可以让你的交叉编译工作更容易,但是它不是必须的。如果你的代码中含有cgo而你确实需要交叉编译,你可以考察xgo能不能满足你的需求,它提供了二进制命令行工具和容器化的依赖环境来支撑交叉编译。

    如果你的二进制中需要嵌入静态文件(比如将前端工程打包到二进制中),你可以考虑使用Go 1.16引入的特性embed.

    容器化

    由于Golang本身就产出二进制可执行文件,镜像可以做得非常地薄,甚至from scratch这里提供一个参考的Dockerfile:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    FROM golang:1-alpine as golang
    RUN apk --no-cache add git zip tzdata ca-certificates
    # avoid go path, use go mod
    WORKDIR /app
    COPY . .
    RUN CGO_ENABLED=0 go build -ldflags "-s -w -extldflags "-static"" ./cmd/appnamehere
    WORKDIR /usr/share/zoneinfo
    # -0 means no compression.  Needed because go's
    # tz loader doesn't handle compressed data.
    RUN zip -r -0 /zoneinfo.zip .
    
    
    FROM scratch
    # the timezone data:
    ENV ZONEINFO /zoneinfo.zip
    COPY --from=golang /zoneinfo.zip /
    # the tls certificates:
    COPY --from=golang /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
    # Copy the binary
    COPY --from=golang /app/appnamehere/
    # example ENV: COS secret ID and secret key
    ENV COS_SECRET_ID="" COS_SECRET_KEY=""
    # example EXPOSE
    EXPOSE 3030
    ENTRYPOINT ["/appnamehere"]
    

    实际在生产中我推荐使用Alpine做底包,为调试和排错留个口子,同时也更容易管理时区信息和CA证书:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    FROM golang:alpine3.12 as golang
    RUN apk --no-cache add make git zip tzdata ca-certificates nodejs npm gcc musl-dev
    WORKDIR /app
    COPY . .
    RUN make
    
    FROM alpine:3.12
    # Dependencies
    RUN apk --no-cache add tzdata ca-certificates
    # where application lives
    WORKDIR /app
    # Copy the products
    COPY --from=golang /app/bin .
    # env
    ENV GIN_MODE="release"
    EXPOSE 3000
    ENTRYPOINT ["/app/telescope"]
    

    工程化

    HTTP服务

    我推荐使用github.com/gin-gonic/gin这个HTTP服务框架来处理路由、中间件、请求绑定和验证。这里有一个脚手架工程作为例子

    你也可以直接依赖标准库net/http,但是这些工作就需要自己做了。

    绝大部分Golang的HTTP服务框架(包括gin)都是基于net/http开发的,官方库采用的策略是“goroutine per connection”,即对每个连接分配一个goroutine. 这个方案的好处就是你可以以阻塞的思路进行controller开发,并且这个模型在一般负载下能够很好地发挥。

    如果你确定你的服务负载极高,在依赖服务、IO、内存、GPU、CPU上很可能遇到瓶颈,我推荐你考虑做服务限流/熔断/goroutine池,同时考虑换用gnet承载你的业务,这么做的缺点是会让服务更加复杂。

    这里有一个使用goroutine池以规避内存和IO压力的例子,它支撑的服务是这个娱乐性的动物书封面生成器

    命令行程序

    github.com/spf13/cobra提供了一个二进制工具和一套组织代码的方式让你构建命令行程序的过程更加容易,它提供了解析命令行子命令和参数的便利方法。

    其他

    • Golang支持在一定条件下编译到WebAssembly,从而在浏览器或者node.js上运行;
    • 虽然不是主流,但是Golang可以用来开发GUI程序,Fyne是一个可供考虑的框架;
    • 偶然地你可能会有在Android或者iOS移动设备上调用由Golang编写的依赖库的需求,Golang官方的Go Mobile项目也许能帮到你。

    工具推荐

    下面这些工具不是必须的,但是可以极大地提升你的编码幸福度:

    • JSON-to-go:即时自动生成和JSON对应的结构体;
    • Gopherize.me:为你的项目定制一个Gopher logo.

    资源推荐

    分享

    nanmu42
    作者
    nanmu42
    用心构建美好事物。

    目录