go

go的不完全避坑指南

Posted by Zeusro on April 29, 2019
👈🏻 Select language

在翻阅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篇文章

  1. Slice length and capacity
  2. Go Slices: usage and internals

简单地说,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. 问题1: 运行的结果
  2. 问题2: 去掉runtime.GC()之后的结果
  3. 问题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

goproxy.io for Go modules

开发建议

  1. CodeReviewComments
  2. Effective Go

其他语言的可看 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:

  1. Slice length and capacity
  2. Go Slices: usage and internals

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")
}
  1. Question 1: The running result
  2. Question 2: The result after removing runtime.GC()
  3. 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

goproxy.io for Go modules

Development Recommendations

  1. CodeReviewComments
  2. Effective Go

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 статьи:

  1. Длина и емкость среза
  2. Срезы Go: использование и внутреннее устройство

Проще говоря, 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. Вопрос 1: Результат выполнения
  2. Вопрос 2: Результат после удаления runtime.GC()
  3. Вопрос 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

goproxy.io для модулей Go

Рекомендации по разработке

  1. CodeReviewComments
  2. Effective Go

Для других языков см.: 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.

在 GitHub 参与讨论 / Discuss on GitHub