2天前Go团队发布了Go1.18版,这个版本包含了几个重要的特性,不仅性能得到极大的提升,对语言本身也做了颠覆性的改变,其中有些设计构想可以追溯到10多年前首次发布时。接下来我们就逐一了解学习一下。
泛型(Generics)
其实早在在2009年的博客中,Russ Cox就开始讨论如何设计Go语言的范型了。这个特性也是在社区中被经常要求的语言特征。于是在1.18版中,设计者终于引入使用参数化类型的范型,它长这个样子:
// 不使用泛型的代码
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
// 使用泛型改写后
// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func Sum[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
这个函数我们可以这样使用:
fmt.Printf("Generic Sums: %v and %v\n",
Sum[string, int64](ints),
Sum[string, float64](floats))
关于范型的全面使用,读者可以参考 GoCN翻译小组已经翻译了官方教程** 『每周译Go』Go 官方出品泛型教程:如何开始使用泛型
模糊测试(Fuzzing)
和泛型一样,模糊测试也是存在于语言设计列表很长时间了。模糊测试是使用随机输入来测试程序功能的测试技术,是对常规测试手段的重要补充,可发现某些在人工生成的输入数据下难以发现的Bug。从 Go 1.18开始,Go 已经成为第一个将模糊测试功能集成到标准工具链中的主流编程语言。
下面是一个具体的例子:
// app.go
package main
import "fmt"
func Reverse(s string) string {
b := []byte(s)
for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return string(b)
}
func main() {
input := "The quick brown fox jumped over the lazy dog"
rev := Reverse(input)
doubleRev := Reverse(rev)
fmt.Printf("original: %q\n", input)
fmt.Printf("reversed: %q\n", rev)
fmt.Printf("reversed again: %q\n", doubleRev)
}
测试程序代码
package main
import (
"testing"
)
// 常规测试函数
func TestReverse(t *testing.T) {
testcases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{" ", " "},
{"!12345", "54321!"},
}
for _, tc := range testcases {
rev := Reverse(tc.in)
if rev != tc.want {
t.Errorf("Reverse: %q, want %q", rev, tc.want)
}
}
}
// 模糊测试函数
func FuzzReverse(f *testing.F) {
testcases := []string{"Hello, world", " ", "!12345"}
for _, tc := range testcases {
f.Add(tc) // Use f.Add to provide a seed corpus
}
f.Fuzz(func(t *testing.T, orig string) {
rev := Reverse(orig)
doubleRev := Reverse(rev)
if orig != doubleRev {
t.Errorf("Before: %q, after: %q", orig, doubleRev)
}
if utf8.ValidString(orig) && !utf8.ValidString(rev) {
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
}
})
}
测试的运行结果长这个样子
$ go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers
fuzz: minimizing 38-byte failing input file...
--- FAIL: FuzzReverse (0.01s)
--- FAIL: FuzzReverse (0.00s)
reverse_test.go:20: Reverse produced invalid UTF-8 string "\x9c\xdd"
Failing input written to testdata/fuzz/FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
To re-run:
go test -run=FuzzReverse/af69258a12129d6cbba438df5d5f25ba0ec050461c116f777e77ea7c9a0d217a
FAIL
exit status 1
FAIL example/fuzz 0.030s
可以看到上面的测试结果是出现了FAIL,这时引起FAIL的输入数据会被保存到一个语料库文件里,下次再次运行go test的时候,即便没有给出-fuzz参数,这个语料库文件中的数据也会被再次使用。
常规测试的局限性在于测试输入必须由开发者指定加到测试用例中。而模糊测试的优点之一是可根据开发者指定输入数据作为基础,自动生成随机的测试数据,来发现指定输入数据没有覆盖到的边界情况。
进一步的学习读者可以参考模糊测试教程以及GoCN翻译小组翻译的官方教程 『每周译Go』Go 模糊测试
工作区(workspace)
虽然Go modules功能现在已经被广泛采用,但用户在使用模块时最常见的挑战是如何在多个Modules中进行跨Modules工作。 Go1.18通过新的工作区模式简化这种多Modules工作体验(https://go.dev/doc/tutorial/workspaces)。
结束
上面提到的Go1.18的新特性是我认为比较重要的,非常值得大家在平时的工作中尝试,希望这些特性能让大家更喜欢使用Go语言。