本文所有代码可在 https://github.com/robinWongM/service-computing-dcs367/tree/main/4-golang-fundamental 获取。

前情提要

Golang 配置

在上次的 Golang 环境配置 中,博主将本地 lxc 容器作为开发环境,采用 apt 方式安装了 golang 1.13,并使用了传统的 $GOPATH 环境变量。近期博主获得了 GitHub Codespaces 的试用资格,于是我们考虑把这套环境搬上 GitHub,这样可以随时随地打开浏览器进行开发,还能规避奇怪的网络问题。同时我们换用了个更新版本的 Go(1.15.2),并配置 Go Module。

环境

  1. Instance Type: Basic (Linux): 2 cores, 4 GB RAM
  2. Linux codespaces_b2391a 5.4.0-1026-azure #26~18.04.1-Ubuntu SMP Thu Sep 10 16:19:25 UTC 2020 x86_64 GNU/Linux

安装 golang

从官方二进制包中安装并不比 apt 麻烦多少

  1. # Download from golang.org
  2. wget https://golang.org/dl/go1.15.2.linux-amd64.tar.gz
  3. # Extract to /usr/local
  4. sudo tar -C /usr/local -xzf go1.15.2.linux-amd64.tar.gz
  5. # 编辑 ~/.profile,在最后加入
  6. export PATH=$PATH:/usr/local/go/bin
  7. # 刷新环境变量
  8. source ~/.profile
  9. # 验证
  10. go version

image.png

初始化 Go Module

  1. # 新建 Go Module
  2. go mod init 4-golang-fundamental
  3. # 开启 Go Module
  4. go env -w GO111MODULE=on

VS Code 配置

网页版的 GitHub Codespaces 实际上就是一个 VS Code,并远程连接到后端的主机。所以 VS Code 的 Go 插件在 GitHub Codespaces 上也能用在插件面板搜索 Go 插件,并 Install 即可。

VS Code 对重构的支持

书中这里提到,我们需要熟悉编辑器里对重构功能的一些支持。

  1. Extract/Inline variable 和 extract method/function:选中需要被抽出的代码,按 Ctrl + . 或者点击旁边浮现的小灯泡,就有对应的操作项出现。
  2. Rename:选中需要更名的 Symbol,按 F2 就会弹出让你输入新名字的文本框。按 Enter 即可完成替换。All usages of the symbol will be renamed, across files.
  3. go fmt:VSCode 自带的 formatter 会调用 go fmt。可以设定保存时或换行时自动格式化。
  4. Run tests:VSCode 终端内运行 go test 即可。
  5. View function signature:鼠标移上去即可。
  6. View function definitionF12
  7. Find usages of a symbolShift + F12

    Bubble Sort

    遵循 TDD 思想,用 Go 实现一个朴实无华且枯燥的冒泡排序。

    先写测试

    新建文件 sort_test.go : ```go package sort

import “testing”

func TestSort(t *testing.T) { sorted := Sort([5]int{5, 4, 3, 2, 1}) expected := [5]int{1, 2, 3, 4, 5}

  1. if sorted != expected {
  2. t.Errorf("expected %v but got %v", expected, sorted)
  3. }

}

  1. <a name="EsU6M"></a>
  2. ## 尝试运行测试
  3. ```go
  4. # 4-golang-fundamental [4-golang-fundamental.test]
  5. ./sort_test.go:6:12: undefined: Sort
  6. FAIL 4-golang-fundamental [build failed]

先让它过编译再说

  1. package sort
  2. func Sort(numbers [5]int) [5]int {
  3. return numbers
  4. }
  1. --- FAIL: TestSort (0.00s)
  2. sort_test.go:10: expected [1 2 3 4 5] but got [5 4 3 2 1]
  3. FAIL
  4. exit status 1
  5. FAIL 4-golang-fundamental 0.003s

然后我们再尝试让它过测试

  1. package sort
  2. func Sort(numbers [5]int) [5]int {
  3. for i := 0; i < 5; i++ {
  4. for j := i + 1; j < 5; j++ {
  5. if numbers[i] > numbers[j] {
  6. numbers[i], numbers[j] = numbers[j], numbers[i]
  7. }
  8. }
  9. }
  10. return numbers
  11. }
  1. PASS
  2. ok 4-golang-fundamental 0.003s

重构

现在我们的 Sort 函数只能处理 [5]int ,要是它能够处理任意个元素的 []int 就好了。为了实现这个目标,我们需要把 Array 改成 Slice。

  1. package sort
  2. func Sort(numbers []int) []int {
  3. for i := 0; i < len(numbers); i++ {
  4. for j := i + 1; j < len(numbers); j++ {
  5. if numbers[i] > numbers[j] {
  6. numbers[i], numbers[j] = numbers[j], numbers[i]
  7. }
  8. }
  9. }
  10. return numbers
  11. }

此时运行测试:

  1. # 4-golang-fundamental [4-golang-fundamental.test]
  2. ./sort_test.go:6:23: cannot use [5]int literal (type [5]int) as type []int in argument to Sort
  3. ./sort_test.go:9:12: invalid operation: sorted != expected (mismatched types []int and [5]int)
  4. FAIL 4-golang-fundamental [build failed]

那我们试试把测试代码里的 array 直接改成 slice:

  1. package sort
  2. import "testing"
  3. func TestSort(t *testing.T) {
  4. sorted := Sort([]int{5, 4, 3, 2, 1})
  5. expected := []int{1, 2, 3, 4, 5}
  6. if sorted != expected {
  7. t.Errorf("expected %v but got %v", expected, sorted)
  8. }
  9. }
  1. # 4-golang-fundamental [4-golang-fundamental.test]
  2. ./sort_test.go:9:12: invalid operation: sorted != expected (slice can only be compared to nil)
  3. FAIL 4-golang-fundamental [build failed]

很遗憾,Go 的 slices 之间不能直接使用 != 来比较。“为了方便起见”,我们可以用 reflect.DeepEqual 来替代:(当然你也可以自己写一个循环来比较)

  1. package sort
  2. import (
  3. "reflect"
  4. "testing"
  5. )
  6. func TestSort(t *testing.T) {
  7. sorted := Sort([]int{5, 4, 3, 2, 1})
  8. expected := []int{1, 2, 3, 4, 5}
  9. if !reflect.DeepEqual(sorted, expected) {
  10. t.Errorf("expected %v but got %v", expected, sorted)
  11. }
  12. }

之后再运行 go test

  1. PASS
  2. ok 4-golang-fundamental 0.003s

跑分

为了测试这个算法的性能,我们加入一个 Benchmark,随机生成 1000 个数字并进行排序。

  1. package sort
  2. import (
  3. "math/rand"
  4. "reflect"
  5. "testing"
  6. )
  7. func TestSort(t *testing.T) {
  8. sorted := Sort([]int{5, 4, 3, 2, 1})
  9. expected := []int{1, 2, 3, 4, 5}
  10. if !reflect.DeepEqual(sorted, expected) {
  11. t.Errorf("expected %v but got %v", expected, sorted)
  12. }
  13. }
  14. func generateRandomArray(arrayLen int) []int {
  15. var a []int
  16. for i := 0; i < arrayLen; i++ {
  17. a = append(a, rand.Intn(1000000))
  18. }
  19. return a
  20. }
  21. func BenchmarkSort(b *testing.B) {
  22. numbers := generateRandomArray(1000)
  23. b.ResetTimer()
  24. for i := 0; i < b.N; i++ {
  25. Sort(numbers)
  26. }
  27. }
  1. goos: linux
  2. goarch: amd64
  3. pkg: 4-golang-fundamental
  4. BenchmarkSort-2 3787 308965 ns/op
  5. PASS
  6. ok 4-golang-fundamental 1.208s

参考

https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/iteration
https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/arrays-and-slices
https://golang.org/pkg/testing/
https://github.com/shubham7saxena/golang-sort-benchmarking