在翻阅kubernetes的文档时,里面刚好谈到go一些注意事项。
结合以前遇过的坑爹API,汇成此文.
语言特性
数据切片
原则是取下标,不取上标
1
2
3
4
5
a:=[]int{0,1,2,3,4}
a=a[:]
a=a[2:4] //从第[2]位起取,直至[4-1]位,所以结果只有2个元素
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
//len(a):2 ; cap(a):3; values:[2 3]
数组切片是一种指针,所以才会引申出len/cap/append的问题
len/cap/append的问题
关于len/cap的问题可以看下面2篇文章
简单地说,len就是当前数组/切片实际元素的计数,cap是底层数组的长度,砍头不砍尾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a :=make([]int,2,3)
// a[2]=666 //panic: runtime error: index out of range
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):2 ; cap(a):3; values:[0 0]
a=a[1:]
// 第0位没了,但是后面的还在,所以cap=3-1
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):1 ; cap(a):2; values:[0]
a=append(a,2)
a=append(a,3)
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
//len(a):3 ; cap(a):4; values:[0 2 3]
b:=append(a,4)
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):3 ; cap(a):4; values:[0 2 3]
fmt.Printf("len(b):%d ; cap(b):%d; values:%v \n",len(b),cap(b),b)
// len(b):4 ; cap(b):4; values:[0 2 3 4]
append是在数组末尾追加元素,返回一个新的数组,原数组不会发生改变.
len=cap时,继续append,cap会翻倍
不会用defer就别瞎装逼
defer是倒序执行,而且后定义的defer先执行(这个很好理解,先定义A变量,然后定义B变量,A的作用域比B长,先清理B是正确选择).
1
2
3
4
5
6
7
8
9
for i := 0; i < 10; i++ {
defer fmt.Println(i) // OK; prints 9 ... 0
defer func() { fmt.Println(i) }() // WRONG; prints "10" 10 times
defer fmt.Println(i) //跟下面一行结果一样
defer func(i int) { fmt.Println(i) }(i) // OK prints 0 ... 9 in
defer print(&i) // WRONG; prints "10" 10 times unpredictable order
go func() { fmt.Println(i) }() // WRONG; totally unpredictable.
}
这个例子是The Three Go Landmines.markdown提出来的。要理解其实不难,把for拆解出来就行了。注意for只是控制了循环的边界,循环结束后,i=10.
需要分清的传入参数的情况
defer func() { fmt.Println(i) }()
相当于
1
2
3
4
5
6
7
8
i:=0
defer func() { fmt.Println(i) }()
i=1
defer func() { fmt.Println(i) }()
......
i=9
defer func() { fmt.Println(i) }()
i=10
所以defer func() { fmt.Println(i) }()的结果为打印10次10
而defer fmt.Println(i)等价于defer func(i int) { fmt.Println(i) }(i)
把局部变量传入,defer内部获得的是值的复制,所以实际上是
1
2
3
4
5
6
7
8
i:=0
defer fmt.Println(0)
i=1
defer fmt.Println(1)
......
i=9
defer fmt.Println(9)
i=10
还有无法定义变量获取defer函数的返回值,defer函数带返回值根本毫无意义
1
2
3
4
5
6
7
defer func() string {
if str := "e"; str == "e" {
return str
}
return "e"
}()
if变量作用域
if 里面定义的变量,即便是跟已定义变量同名,在if中对其赋值,对已经被定义的变量不会有影响。
1
2
3
4
5
6
7
8
9
10
11
var ErrDidNotWork = errors.New("did not work")
func DoTheThing(reallyDoIt bool) (err error) {
if reallyDoIt {
result, err := tryTheThing()
if err != nil || result != "it worked" {
err = ErrDidNotWork
}
}
return err
}
半新不旧变量
常见于err和多重返回值函数,针对这种情况,可以通过if作用域强制覆盖,或者命名一个新变量解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var e error
func main() {
s, e := a() //无法编译
s, err2 := a() //OK
if s, e := a(); e != nil { //OK
}
······
fmt.Print(s)
}
func a() (str string, err error) {
return "", nil
}
goroutine 机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import (
"fmt"
"runtime"
)
func main() {
var i byte
go func() {
for i = 0; i < 255; i++ {
fmt.Println(i)
}
}()
fmt.Println("start")
runtime.Gosched()
runtime.GC()
fmt.Println("end")
}
- 问题1: 运行的结果
- 问题2: 去掉
runtime.GC()之后的结果 - 问题3: 如果你是goruntime,去掉
fmt.Println(i)之后要怎么优化编译
答案:
Gosched() 让出了CPU时间片,让 goroutine 有机会运行
Goroutine采用的是半抢占式的协作调度,只有在当前Goroutine发生阻塞时才会导致调度
GC()需要stop the world,所以会等待协程运行完
如果没有 GC()这个方法,则运行结果完全不可控
for 里面一堆废话,最合理的优化,应该是连协程都不创建,哈哈哈哈哈
坑爹API
获取字符串长度:
1
len([]rune("文件夹,子文件夹,"))
Split的坑
1
2
3
4
5
s := strings.Split("shit,", ",")
fmt.Printf("len(s):%d\n", len(s))//2
for _, v := range s {
fmt.Printf("%s", v)
}
strings.Split的字符串,如果用来分割的字符串恰好出现完整字符串的最后面,获得的数组长度会+1,这个数组的最后一个元素会是一个空白
时间转换函数
golang的字符串转日期函数非常不灵活,并且格式化的字符串是一个魔术变量,代表golang的面世时间…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ToTime 字符串转golang内置当地时间
func ToTime(str string) (time.Time, error) {
var err error
format1 := "2006-01-02 15:04:05"
loc, err := time.LoadLocation("Local");
if err != nil {
return time.Now(), err
}
date, err := time.ParseInLocation(format1, str, loc);
if err == nil {
return date, nil
}
format2 := "2006-01-02"
date, err = time.ParseInLocation(format2, str, loc);
if err == nil {
return date, nil
}
sqlserverFormat:= "2006-01-02T15:04:05"
date, err = time.ParseInLocation(sqlserverFormat, str, loc);
if err == nil {
return date, nil
}
return time.Now(), err
}
值类型不会溢出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var s int32 = 5120
fmt.Print(s * 1024 * 1024)
fmt.Print("\n")
fmt.Print(int64(s) * 1024 * 1024)
fmt.Print("\n")
fmt.Print(math.MaxInt32)
fmt.Print("\n")
fmt.Print(math.MaxInt64)
/*
1073741824
5368709120
2147483647
9223372036854775807
*/
以前用C#的时候,如果定义一个值类型变量,赋予它一个超出范围的值的话是会出异常的,然而到了golang,直接变成这个类型无符号最大值
构建篇
启用了go module之后,对依赖的拉取变得更加频繁。但是基于中国有中国特色的互联网,我们有时候很难get到我们需要的依赖源代码,进而导致项目编译失败,CI失败。于是,我们需要一个proxy。
1
export GOPROXY=https://goproxy.io
开发建议
其他语言的可看 Google Style Guides
论喷子,还是没王垠强
课后作业
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
26
27
28
29
30
31
32
33
func a() (str string) {
defer func() {
str = "a"
}()
defer func(str string) {
str = "b"
}(str)
defer func() {
if str2 := "c"; str2 == "c" {
str = str2
}
}()
defer func() {
if str := "d"; str == "d" {
str = "d"
}
}()
defer func() string {
if str := "e"; str == "e" {
return str
}
return "e"
}()
str = "f"
return str
}
注释掉里面的defer,观察一下不同组合下的函数的结果,看懂了,就算理解defer了
When reading kubernetes documentation, it happened to mention some Go considerations.
Combined with previously encountered problematic APIs, compiled into this article.
Language Features
Data Slicing
The principle is to take the lower index, not the upper index.
1
2
3
4
5
a:=[]int{0,1,2,3,4}
a=a[:]
a=a[2:4] //Start from position [2], up to position [4-1], so the result only has 2 elements
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
//len(a):2 ; cap(a):3; values:[2 3]
Array slicing is a pointer, which is why len/cap/append issues arise.
len/cap/append Issues
For len/cap issues, see the following 2 articles:
Simply put, len is the count of actual elements in the current array/slice, cap is the length of the underlying array, cut head not tail.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a :=make([]int,2,3)
// a[2]=666 //panic: runtime error: index out of range
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):2 ; cap(a):3; values:[0 0]
a=a[1:]
// Position 0 is gone, but the rest remains, so cap=3-1
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):1 ; cap(a):2; values:[0]
a=append(a,2)
a=append(a,3)
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
//len(a):3 ; cap(a):4; values:[0 2 3]
b:=append(a,4)
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):3 ; cap(a):4; values:[0 2 3]
fmt.Printf("len(b):%d ; cap(b):%d; values:%v \n",len(b),cap(b),b)
// len(b):4 ; cap(b):4; values:[0 2 3 4]
append appends elements at the end of the array, returns a new array, the original array does not change.
When len=cap, continuing append, cap will double.
Don’t Show Off If You Don’t Know How to Use defer
defer executes in reverse order, and later defined defer executes first (this is easy to understand, define variable A first, then define variable B, A’s scope is longer than B, cleaning up B first is the correct choice).
1
2
3
4
5
6
7
8
9
for i := 0; i < 10; i++ {
defer fmt.Println(i) // OK; prints 9 ... 0
defer func() { fmt.Println(i) }() // WRONG; prints "10" 10 times
defer fmt.Println(i) //Same result as the line below
defer func(i int) { fmt.Println(i) }(i) // OK prints 0 ... 9 in
defer print(&i) // WRONG; prints "10" 10 times unpredictable order
go func() { fmt.Println(i) }() // WRONG; totally unpredictable.
}
This example was proposed by The Three Go Landmines.markdown. It’s not hard to understand, just break down the for loop. Note that for only controls the loop boundary, after the loop ends, i=10.
Need to distinguish cases of passing parameters:
defer func() { fmt.Println(i) }()
is equivalent to
1
2
3
4
5
6
7
8
i:=0
defer func() { fmt.Println(i) }()
i=1
defer func() { fmt.Println(i) }()
......
i=9
defer func() { fmt.Println(i) }()
i=10
So the result of defer func() { fmt.Println(i) }() is printing 10 ten times.
And defer fmt.Println(i) is equivalent to defer func(i int) { fmt.Println(i) }(i)
Passing local variables, defer internally gets a copy of the value, so actually:
1
2
3
4
5
6
7
8
i:=0
defer fmt.Println(0)
i=1
defer fmt.Println(1)
......
i=9
defer fmt.Println(9)
i=10
Also cannot define variables to get return values from defer functions. Defer functions with return values are completely meaningless.
1
2
3
4
5
6
7
defer func() string {
if str := "e"; str == "e" {
return str
}
return "e"
}()
if Variable Scope
Variables defined inside if, even if they have the same name as already defined variables, assigning to them inside if will not affect the already defined variables.
1
2
3
4
5
6
7
8
9
10
11
var ErrDidNotWork = errors.New("did not work")
func DoTheThing(reallyDoIt bool) (err error) {
if reallyDoIt {
result, err := tryTheThing()
if err != nil || result != "it worked" {
err = ErrDidNotWork
}
}
return err
}
Half-New Half-Old Variables
Common with err and multi-return functions. For this situation, you can force override through if scope, or name a new variable to solve it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var e error
func main() {
s, e := a() //Cannot compile
s, err2 := a() //OK
if s, e := a(); e != nil { //OK
}
······
fmt.Print(s)
}
func a() (str string, err error) {
return "", nil
}
goroutine Mechanism
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import (
"fmt"
"runtime"
)
func main() {
var i byte
go func() {
for i = 0; i < 255; i++ {
fmt.Println(i)
}
}()
fmt.Println("start")
runtime.Gosched()
runtime.GC()
fmt.Println("end")
}
- Question 1: The running result
- Question 2: The result after removing
runtime.GC() - Question 3: If you were goruntime, how would you optimize compilation after removing
fmt.Println(i)
Answers:
Gosched() yields the CPU time slice, giving goroutine a chance to run.
Goroutine uses semi-preemptive cooperative scheduling, only when the current Goroutine blocks will it cause scheduling.
GC() needs stop the world, so it will wait for the coroutine to finish running.
If there’s no GC() method, the running result is completely unpredictable.
There’s a bunch of nonsense in the for loop. The most reasonable optimization should be not even creating the coroutine, hahahaha.
Problematic APIs
Getting String Length:
1
len([]rune("文件夹,子文件夹,"))
Split Pitfall
1
2
3
4
5
s := strings.Split("shit,", ",")
fmt.Printf("len(s):%d\n", len(s))//2
for _, v := range s {
fmt.Printf("%s", v)
}
For strings.Split, if the string used for splitting happens to appear at the very end of the complete string, the obtained array length will be +1, and the last element of this array will be a blank.
Time Conversion Function
golang’s string to date function is very inflexible, and the formatted string is a magic variable, representing golang’s birth time…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ToTime String to golang built-in local time
func ToTime(str string) (time.Time, error) {
var err error
format1 := "2006-01-02 15:04:05"
loc, err := time.LoadLocation("Local");
if err != nil {
return time.Now(), err
}
date, err := time.ParseInLocation(format1, str, loc);
if err == nil {
return date, nil
}
format2 := "2006-01-02"
date, err = time.ParseInLocation(format2, str, loc);
if err == nil {
return date, nil
}
sqlserverFormat:= "2006-01-02T15:04:05"
date, err = time.ParseInLocation(sqlserverFormat, str, loc);
if err == nil {
return date, nil
}
return time.Now(), err
}
Value Types Don’t Overflow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var s int32 = 5120
fmt.Print(s * 1024 * 1024)
fmt.Print("\n")
fmt.Print(int64(s) * 1024 * 1024)
fmt.Print("\n")
fmt.Print(math.MaxInt32)
fmt.Print("\n")
fmt.Print(math.MaxInt64)
/*
1073741824
5368709120
2147483647
9223372036854775807
*/
When I used C# before, if you defined a value type variable and assigned it a value beyond its range, it would throw an exception. However, in golang, it directly becomes the unsigned maximum value of that type.
Build Section
After enabling go module, dependency pulling becomes more frequent. But based on China’s unique internet, we sometimes have difficulty getting the dependency source code we need, which leads to project compilation failure and CI failure. So, we need a proxy.
1
export GOPROXY=https://goproxy.io
Development Recommendations
For other languages, see: Google Style Guides
In terms of trolling, still not as strong as Wang Yin
Homework
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
26
27
28
29
30
31
32
33
func a() (str string) {
defer func() {
str = "a"
}()
defer func(str string) {
str = "b"
}(str)
defer func() {
if str2 := "c"; str2 == "c" {
str = str2
}
}()
defer func() {
if str := "d"; str == "d" {
str = "d"
}
}()
defer func() string {
if str := "e"; str == "e" {
return str
}
return "e"
}()
str = "f"
return str
}
Comment out the defer inside, observe the function’s result under different combinations. Once you understand, you’ve understood defer.
kubernetesのドキュメントを読んでいると、Goのいくつかの注意事項について言及されていました。
以前に遭遇した問題のあるAPIと組み合わせて、この記事にまとめました。
言語特性
データスライス
原則は、下のインデックスを取り、上のインデックスは取りません。
1
2
3
4
5
a:=[]int{0,1,2,3,4}
a=a[:]
a=a[2:4] //位置[2]から開始し、位置[4-1]まで、したがって結果には2つの要素しかありません
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
//len(a):2 ; cap(a):3; values:[2 3]
配列スライスはポインターであるため、len/cap/appendの問題が発生します。
len/cap/appendの問題
len/capの問題については、次の2つの記事を参照してください:
簡単に言えば、lenは現在の配列/スライスの実際の要素のカウントで、capは基になる配列の長さで、頭を切って尾を切らない。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a :=make([]int,2,3)
// a[2]=666 //panic: runtime error: index out of range
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):2 ; cap(a):3; values:[0 0]
a=a[1:]
// 位置0がなくなりましたが、残りは残っているため、cap=3-1
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):1 ; cap(a):2; values:[0]
a=append(a,2)
a=append(a,3)
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
//len(a):3 ; cap(a):4; values:[0 2 3]
b:=append(a,4)
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):3 ; cap(a):4; values:[0 2 3]
fmt.Printf("len(b):%d ; cap(b):%d; values:%v \n",len(b),cap(b),b)
// len(b):4 ; cap(b):4; values:[0 2 3 4]
appendは配列の末尾に要素を追加し、新しい配列を返します。元の配列は変更されません。
len=capの場合、appendを続けると、capは2倍になります。
deferの使い方がわからない場合は見せびらかさない
deferは逆順で実行され、後で定義されたdeferが先に実行されます(これは理解しやすいです。まず変数Aを定義し、次に変数Bを定義すると、AのスコープがBより長いため、Bを先にクリーンアップするのが正しい選択です)。
1
2
3
4
5
6
7
8
9
for i := 0; i < 10; i++ {
defer fmt.Println(i) // OK; prints 9 ... 0
defer func() { fmt.Println(i) }() // WRONG; prints "10" 10 times
defer fmt.Println(i) //次の行と同じ結果
defer func(i int) { fmt.Println(i) }(i) // OK prints 0 ... 9 in
defer print(&i) // WRONG; prints "10" 10 times unpredictable order
go func() { fmt.Println(i) }() // WRONG; totally unpredictable.
}
この例はThe Three Go Landmines.markdownによって提案されました。理解するのは難しくありません。forループを分解するだけです。forはループの境界を制御するだけであることに注意してください。ループが終了すると、i=10になります。
パラメータを渡す場合を区別する必要があります:
defer func() { fmt.Println(i) }()
は以下と同等です
1
2
3
4
5
6
7
8
i:=0
defer func() { fmt.Println(i) }()
i=1
defer func() { fmt.Println(i) }()
......
i=9
defer func() { fmt.Println(i) }()
i=10
したがって、defer func() { fmt.Println(i) }()の結果は、10を10回印刷することです。
そしてdefer fmt.Println(i)はdefer func(i int) { fmt.Println(i) }(i)と同等です
ローカル変数を渡すと、defer内部は値のコピーを取得するため、実際には:
1
2
3
4
5
6
7
8
i:=0
defer fmt.Println(0)
i=1
defer fmt.Println(1)
......
i=9
defer fmt.Println(9)
i=10
また、defer関数の戻り値を取得する変数を定義することはできません。戻り値を持つdefer関数は完全に意味がありません。
1
2
3
4
5
6
7
defer func() string {
if str := "e"; str == "e" {
return str
}
return "e"
}()
if変数スコープ
if内で定義された変数は、既に定義された変数と同じ名前であっても、if内でそれに代入しても、既に定義された変数には影響しません。
1
2
3
4
5
6
7
8
9
10
11
var ErrDidNotWork = errors.New("did not work")
func DoTheThing(reallyDoIt bool) (err error) {
if reallyDoIt {
result, err := tryTheThing()
if err != nil || result != "it worked" {
err = ErrDidNotWork
}
}
return err
}
半新半旧変数
errと複数の戻り値を持つ関数で一般的です。この状況では、ifスコープで強制的にオーバーライドするか、新しい変数に名前を付けて解決できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var e error
func main() {
s, e := a() //コンパイルできません
s, err2 := a() //OK
if s, e := a(); e != nil { //OK
}
······
fmt.Print(s)
}
func a() (str string, err error) {
return "", nil
}
goroutineメカニズム
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import (
"fmt"
"runtime"
)
func main() {
var i byte
go func() {
for i = 0; i < 255; i++ {
fmt.Println(i)
}
}()
fmt.Println("start")
runtime.Gosched()
runtime.GC()
fmt.Println("end")
}
- 問題1:実行結果
- 問題2:
runtime.GC()を削除した後の結果 - 問題3:goruntimeの場合、
fmt.Println(i)を削除した後、コンパイルをどのように最適化しますか
答え:
Gosched()はCPUタイムスライスを譲り、goroutineが実行される機会を与えます。
Goroutineは半プリエンプティブな協調スケジューリングを採用しており、現在のGoroutineがブロックした場合にのみスケジューリングが発生します。
GC()はstop the worldが必要なため、コルーチンの実行が完了するまで待機します。
GC()メソッドがない場合、実行結果は完全に予測不可能です。
forループ内に多くの無駄があります。最も合理的な最適化は、コルーチンさえ作成しないことです、はははは。
問題のあるAPI
文字列の長さを取得:
1
len([]rune("文件夹,子文件夹,"))
Splitの落とし穴
1
2
3
4
5
s := strings.Split("shit,", ",")
fmt.Printf("len(s):%d\n", len(s))//2
for _, v := range s {
fmt.Printf("%s", v)
}
strings.Splitの文字列の場合、分割に使用される文字列が完全な文字列の最後に正確に出現する場合、取得された配列の長さは+1になり、この配列の最後の要素は空白になります。
時間変換関数
golangの文字列から日付への関数は非常に柔軟性がなく、フォーマットされた文字列はマジック変数で、golangの誕生時間を表しています…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ToTime 文字列をgolang組み込み現地時間に変換
func ToTime(str string) (time.Time, error) {
var err error
format1 := "2006-01-02 15:04:05"
loc, err := time.LoadLocation("Local");
if err != nil {
return time.Now(), err
}
date, err := time.ParseInLocation(format1, str, loc);
if err == nil {
return date, nil
}
format2 := "2006-01-02"
date, err = time.ParseInLocation(format2, str, loc);
if err == nil {
return date, nil
}
sqlserverFormat:= "2006-01-02T15:04:05"
date, err = time.ParseInLocation(sqlserverFormat, str, loc);
if err == nil {
return date, nil
}
return time.Now(), err
}
値型はオーバーフローしない
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var s int32 = 5120
fmt.Print(s * 1024 * 1024)
fmt.Print("\n")
fmt.Print(int64(s) * 1024 * 1024)
fmt.Print("\n")
fmt.Print(math.MaxInt32)
fmt.Print("\n")
fmt.Print(math.MaxInt64)
/*
1073741824
5368709120
2147483647
9223372036854775807
*/
以前C#を使用していたとき、値型変数を定義し、範囲を超える値を割り当てると、例外が発生していました。しかし、golangでは、その型の符号なし最大値に直接なります。
ビルドセクション
go moduleを有効にした後、依存関係の取得がより頻繁になります。しかし、中国の独特のインターネットに基づいて、必要な依存関係のソースコードを取得することが困難な場合があり、プロジェクトのコンパイル失敗とCI失敗につながります。したがって、プロキシが必要です。
1
export GOPROXY=https://goproxy.io
開発の推奨事項
他の言語については、以下を参照: Google Style Guides
トローリングに関しては、王垠ほど強くはありません
宿題
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
26
27
28
29
30
31
32
33
func a() (str string) {
defer func() {
str = "a"
}()
defer func(str string) {
str = "b"
}(str)
defer func() {
if str2 := "c"; str2 == "c" {
str = str2
}
}()
defer func() {
if str := "d"; str == "d" {
str = "d"
}
}()
defer func() string {
if str := "e"; str == "e" {
return str
}
return "e"
}()
str = "f"
return str
}
内部のdeferをコメントアウトし、異なる組み合わせでの関数の結果を観察してください。理解できれば、deferを理解したことになります。
При чтении документации kubernetes там как раз упоминались некоторые соображения по Go.
В сочетании с ранее встреченными проблемными API, собрано в эту статью.
Особенности языка
Срезы данных
Принцип — брать нижний индекс, не верхний.
1
2
3
4
5
a:=[]int{0,1,2,3,4}
a=a[:]
a=a[2:4] //Начать с позиции [2], до позиции [4-1], поэтому результат имеет только 2 элемента
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
//len(a):2 ; cap(a):3; values:[2 3]
Срезы массивов — это указатели, поэтому возникают проблемы len/cap/append.
Проблемы len/cap/append
По проблемам len/cap см. следующие 2 статьи:
Проще говоря, len — это подсчет фактических элементов в текущем массиве/срезе, cap — это длина базового массива, обрезать голову, не хвост.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a :=make([]int,2,3)
// a[2]=666 //panic: runtime error: index out of range
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):2 ; cap(a):3; values:[0 0]
a=a[1:]
// Позиция 0 исчезла, но остальное остается, поэтому cap=3-1
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):1 ; cap(a):2; values:[0]
a=append(a,2)
a=append(a,3)
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
//len(a):3 ; cap(a):4; values:[0 2 3]
b:=append(a,4)
fmt.Printf("len(a):%d ; cap(a):%d; values:%v \n",len(a),cap(a),a)
// len(a):3 ; cap(a):4; values:[0 2 3]
fmt.Printf("len(b):%d ; cap(b):%d; values:%v \n",len(b),cap(b),b)
// len(b):4 ; cap(b):4; values:[0 2 3 4]
append добавляет элементы в конец массива, возвращает новый массив, исходный массив не изменяется.
Когда len=cap, продолжая append, cap удвоится.
Не хвастайтесь, если не умеете использовать defer
defer выполняется в обратном порядке, и позже определенный defer выполняется первым (это легко понять, сначала определить переменную A, затем определить переменную B, область видимости A длиннее, чем B, сначала очистить B — правильный выбор).
1
2
3
4
5
6
7
8
9
for i := 0; i < 10; i++ {
defer fmt.Println(i) // OK; prints 9 ... 0
defer func() { fmt.Println(i) }() // WRONG; prints "10" 10 times
defer fmt.Println(i) //Тот же результат, что и строка ниже
defer func(i int) { fmt.Println(i) }(i) // OK prints 0 ... 9 in
defer print(&i) // WRONG; prints "10" 10 times unpredictable order
go func() { fmt.Println(i) }() // WRONG; totally unpredictable.
}
Этот пример был предложен The Three Go Landmines.markdown. Понять не сложно, просто разложите цикл for. Обратите внимание, что for только контролирует границу цикла, после окончания цикла i=10.
Нужно различать случаи передачи параметров:
defer func() { fmt.Println(i) }()
эквивалентно
1
2
3
4
5
6
7
8
i:=0
defer func() { fmt.Println(i) }()
i=1
defer func() { fmt.Println(i) }()
......
i=9
defer func() { fmt.Println(i) }()
i=10
Поэтому результат defer func() { fmt.Println(i) }() — это печать 10 десять раз.
А defer fmt.Println(i) эквивалентно defer func(i int) { fmt.Println(i) }(i)
Передавая локальные переменные, defer внутри получает копию значения, поэтому фактически:
1
2
3
4
5
6
7
8
i:=0
defer fmt.Println(0)
i=1
defer fmt.Println(1)
......
i=9
defer fmt.Println(9)
i=10
Также нельзя определить переменные для получения возвращаемых значений из defer-функций. Defer-функции с возвращаемыми значениями совершенно бессмысленны.
1
2
3
4
5
6
7
defer func() string {
if str := "e"; str == "e" {
return str
}
return "e"
}()
Область видимости переменных if
Переменные, определенные внутри if, даже если они имеют то же имя, что и уже определенные переменные, присвоение им внутри if не повлияет на уже определенные переменные.
1
2
3
4
5
6
7
8
9
10
11
var ErrDidNotWork = errors.New("did not work")
func DoTheThing(reallyDoIt bool) (err error) {
if reallyDoIt {
result, err := tryTheThing()
if err != nil || result != "it worked" {
err = ErrDidNotWork
}
}
return err
}
Полуновые-полустарые переменные
Часто встречается с err и функциями с несколькими возвращаемыми значениями. Для этой ситуации можно принудительно переопределить через область видимости if или назвать новую переменную для решения.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var e error
func main() {
s, e := a() //Не компилируется
s, err2 := a() //OK
if s, e := a(); e != nil { //OK
}
······
fmt.Print(s)
}
func a() (str string, err error) {
return "", nil
}
Механизм goroutine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import (
"fmt"
"runtime"
)
func main() {
var i byte
go func() {
for i = 0; i < 255; i++ {
fmt.Println(i)
}
}()
fmt.Println("start")
runtime.Gosched()
runtime.GC()
fmt.Println("end")
}
- Вопрос 1: Результат выполнения
- Вопрос 2: Результат после удаления
runtime.GC() - Вопрос 3: Если бы вы были goruntime, как бы вы оптимизировали компиляцию после удаления
fmt.Println(i)
Ответы:
Gosched() уступает срез времени CPU, давая goroutine шанс запуститься.
Goroutine использует полупреemptive кооперативное планирование, только когда текущая Goroutine блокируется, это вызовет планирование.
GC() требует stop the world, поэтому будет ждать завершения выполнения корутины.
Если нет метода GC(), результат выполнения полностью непредсказуем.
В цикле for куча ерунды. Самая разумная оптимизация должна быть даже не создавать корутину, хахахаха.
Проблемные API
Получение длины строки:
1
len([]rune("文件夹,子文件夹,"))
Ловушка Split
1
2
3
4
5
s := strings.Split("shit,", ",")
fmt.Printf("len(s):%d\n", len(s))//2
for _, v := range s {
fmt.Printf("%s", v)
}
Для strings.Split, если строка, используемая для разделения, точно появляется в самом конце полной строки, полученная длина массива будет +1, и последний элемент этого массива будет пустым.
Функция преобразования времени
Функция преобразования строки в дату golang очень негибкая, и форматированная строка — это магическая переменная, представляющая время рождения golang…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ToTime Строка в локальное время, встроенное в golang
func ToTime(str string) (time.Time, error) {
var err error
format1 := "2006-01-02 15:04:05"
loc, err := time.LoadLocation("Local");
if err != nil {
return time.Now(), err
}
date, err := time.ParseInLocation(format1, str, loc);
if err == nil {
return date, nil
}
format2 := "2006-01-02"
date, err = time.ParseInLocation(format2, str, loc);
if err == nil {
return date, nil
}
sqlserverFormat:= "2006-01-02T15:04:05"
date, err = time.ParseInLocation(sqlserverFormat, str, loc);
if err == nil {
return date, nil
}
return time.Now(), err
}
Типы значений не переполняются
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var s int32 = 5120
fmt.Print(s * 1024 * 1024)
fmt.Print("\n")
fmt.Print(int64(s) * 1024 * 1024)
fmt.Print("\n")
fmt.Print(math.MaxInt32)
fmt.Print("\n")
fmt.Print(math.MaxInt64)
/*
1073741824
5368709120
2147483647
9223372036854775807
*/
Когда я использовал C# раньше, если вы определяли переменную типа значения и присваивали ей значение за пределами диапазона, это вызывало исключение. Однако в golang это напрямую становится максимальным значением без знака этого типа.
Раздел сборки
После включения go module получение зависимостей становится более частым. Но на основе уникального интернета Китая нам иногда трудно получить исходный код зависимостей, который нам нужен, что приводит к сбою компиляции проекта и сбою CI. Итак, нам нужен прокси.
1
export GOPROXY=https://goproxy.io
Рекомендации по разработке
Для других языков см.: Google Style Guides
Что касается троллинга, все еще не так силен, как Ван Инь
Домашнее задание
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
26
27
28
29
30
31
32
33
func a() (str string) {
defer func() {
str = "a"
}()
defer func(str string) {
str = "b"
}(str)
defer func() {
if str2 := "c"; str2 == "c" {
str = str2
}
}()
defer func() {
if str := "d"; str == "d" {
str = "d"
}
}()
defer func() string {
if str := "e"; str == "e" {
return str
}
return "e"
}()
str = "f"
return str
}
Закомментируйте defer внутри, наблюдайте результат функции при разных комбинациях. Как только поймете, вы поняли defer.
💬 讨论 / Discussion
对这篇文章有想法?欢迎在 GitHub 上发起讨论。
Have thoughts on this post? Start a discussion on GitHub.