本文所有代码可在 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。
环境
Instance Type: Basic (Linux): 2 cores, 4 GB RAMLinux 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 麻烦多少:
# Download from golang.orgwget https://golang.org/dl/go1.15.2.linux-amd64.tar.gz# Extract to /usr/localsudo tar -C /usr/local -xzf go1.15.2.linux-amd64.tar.gz# 编辑 ~/.profile,在最后加入export PATH=$PATH:/usr/local/go/bin# 刷新环境变量source ~/.profile# 验证go version
初始化 Go Module
# 新建 Go Modulego mod init 4-golang-fundamental# 开启 Go Modulego env -w GO111MODULE=on
VS Code 配置
网页版的 GitHub Codespaces 实际上就是一个 VS Code,并远程连接到后端的主机。所以 VS Code 的 Go 插件在 GitHub Codespaces 上也能用在插件面板搜索 Go 插件,并 Install 即可。
VS Code 对重构的支持
书中这里提到,我们需要熟悉编辑器里对重构功能的一些支持。
- Extract/Inline variable 和 extract method/function:选中需要被抽出的代码,按
Ctrl + .或者点击旁边浮现的小灯泡,就有对应的操作项出现。 - Rename:选中需要更名的 Symbol,按 F2 就会弹出让你输入新名字的文本框。按 Enter 即可完成替换。All usages of the symbol will be renamed, across files.
- go fmt:VSCode 自带的 formatter 会调用
go fmt。可以设定保存时或换行时自动格式化。 - Run tests:VSCode 终端内运行
go test即可。 - View function signature:鼠标移上去即可。
- View function definition:
F12 - Find usages of a symbol:
Shift+F12Bubble 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}
if sorted != expected {t.Errorf("expected %v but got %v", expected, sorted)}
}
<a name="EsU6M"></a>## 尝试运行测试```go# 4-golang-fundamental [4-golang-fundamental.test]./sort_test.go:6:12: undefined: SortFAIL 4-golang-fundamental [build failed]
先让它过编译再说
package sortfunc Sort(numbers [5]int) [5]int {return numbers}
--- FAIL: TestSort (0.00s)sort_test.go:10: expected [1 2 3 4 5] but got [5 4 3 2 1]FAILexit status 1FAIL 4-golang-fundamental 0.003s
然后我们再尝试让它过测试
package sortfunc Sort(numbers [5]int) [5]int {for i := 0; i < 5; i++ {for j := i + 1; j < 5; j++ {if numbers[i] > numbers[j] {numbers[i], numbers[j] = numbers[j], numbers[i]}}}return numbers}
PASSok 4-golang-fundamental 0.003s
重构
现在我们的 Sort 函数只能处理 [5]int ,要是它能够处理任意个元素的 []int 就好了。为了实现这个目标,我们需要把 Array 改成 Slice。
package sortfunc Sort(numbers []int) []int {for i := 0; i < len(numbers); i++ {for j := i + 1; j < len(numbers); j++ {if numbers[i] > numbers[j] {numbers[i], numbers[j] = numbers[j], numbers[i]}}}return numbers}
此时运行测试:
# 4-golang-fundamental [4-golang-fundamental.test]./sort_test.go:6:23: cannot use [5]int literal (type [5]int) as type []int in argument to Sort./sort_test.go:9:12: invalid operation: sorted != expected (mismatched types []int and [5]int)FAIL 4-golang-fundamental [build failed]
那我们试试把测试代码里的 array 直接改成 slice:
package sortimport "testing"func TestSort(t *testing.T) {sorted := Sort([]int{5, 4, 3, 2, 1})expected := []int{1, 2, 3, 4, 5}if sorted != expected {t.Errorf("expected %v but got %v", expected, sorted)}}
# 4-golang-fundamental [4-golang-fundamental.test]./sort_test.go:9:12: invalid operation: sorted != expected (slice can only be compared to nil)FAIL 4-golang-fundamental [build failed]
很遗憾,Go 的 slices 之间不能直接使用 != 来比较。“为了方便起见”,我们可以用 reflect.DeepEqual 来替代:(当然你也可以自己写一个循环来比较)
package sortimport ("reflect""testing")func TestSort(t *testing.T) {sorted := Sort([]int{5, 4, 3, 2, 1})expected := []int{1, 2, 3, 4, 5}if !reflect.DeepEqual(sorted, expected) {t.Errorf("expected %v but got %v", expected, sorted)}}
之后再运行 go test :
PASSok 4-golang-fundamental 0.003s
跑分
为了测试这个算法的性能,我们加入一个 Benchmark,随机生成 1000 个数字并进行排序。
package sortimport ("math/rand""reflect""testing")func TestSort(t *testing.T) {sorted := Sort([]int{5, 4, 3, 2, 1})expected := []int{1, 2, 3, 4, 5}if !reflect.DeepEqual(sorted, expected) {t.Errorf("expected %v but got %v", expected, sorted)}}func generateRandomArray(arrayLen int) []int {var a []intfor i := 0; i < arrayLen; i++ {a = append(a, rand.Intn(1000000))}return a}func BenchmarkSort(b *testing.B) {numbers := generateRandomArray(1000)b.ResetTimer()for i := 0; i < b.N; i++ {Sort(numbers)}}
goos: linuxgoarch: amd64pkg: 4-golang-fundamentalBenchmarkSort-2 3787 308965 ns/opPASSok 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
