废话不多说直接上使用错误使用示例
有时我们为了避免切片底层数据扩展带来的开销,会提前指定make的cap大小
func Example1() {
persons := make([]*Person, 1000)
for i := 0; i < 10000; i++ {
persons = append(persons, &Person{
Name: "example1",
Age: i,
})
}
}
// 正确方法:声明lens为0,就可以使用append将item逐一加到切片中
type Person struct {
Name string
Age int
}
func Example1() {
persons := make([]*Person, 0, 1000)
for i := 0; i < 1000; i++ {
persons = append(persons, &Person{
Name: "example1",
Age: i,
})
}
}
// 正确方法2:使用切片逐个赋值的方式
type Person struct {
Name string
Age int
}
func Example1() {
persons := make([]*Person, 1000)
for i := 0; i < 1000; i++ {
persons[i] = &Person{
Name: "example1",
Age: i,
}
}
}
虽然我们指定了make的cap大小,但是我们并没有指定make的length字段的大小,这个时候length字段的大小等于cap的大小,参照下面切片用法
使用 Golang 内置的 make() 函数创建切片,此时需要传入一个参数来指定切片的长度:
// 创建一个整型切片
// 其长度和容量都是 5 个元素
slice := make([]int, 5)
此时只指定了切片的长度,那么切片的容量和长度相等。也可以分别指定长度和容量:
// 创建一个整型切片
// 其长度为 3 个元素,容量为 5 个元素
slice := make([]int, 3, 5)
分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但是初始化后并不能
访问所有的数组元素。
1. 向nil map中添加元素导致panic
func Example2() {
var m1 map[string]string
m1["a"] = "b"
…
}
// 解决方案:给map初始化
2. 函数返回值为map时,在使用前也应该初始化,否则会panic
func Example3() (m1 map[string]string) {
m1["a"] = "b"
return m1
}
// 解决方案:给map初始化
3. map有嵌套,嵌套的map也要初始化,否则会panic
func Example2() {
m1 := make(map[string]map[int]int)
m1["go"][0] = 0
}
// 正确方法
// 先初始化,再使用
func Example6() {
m1 := make(map[string]map[int]int)
m1["go"] = make(map[int]int)
m1["go"][0] = 0
}
对于带for循环的select,break并不能打破for循环,会永远循环下去
func Example3(ctx context.Context) {
for {
select {
case <-ctx.Done():
break
default:
...
}
}
}
正确方法1:采用break + 标签
func Example3(ctx context.Context) {
//LOOP 是标签
LOOP:
for {
select {
case <-ctx.Done():
break LOOP
default:
...
}
}
}
正确方法2:采用goto + 标签
func Example3(ctx context.Context) {
for {
select {
case <-ctx.Done():
goto LOOP
default:
...
}
}
//END 是标签
END:
}
当panic发生后,虽然有recover,但是并没有将error信息传递到上层,可能会导致后续逻辑异常
func Example4(ctx context.Context) {
err := DoExample4(ctx)
if err != nil {
return
}
...
}
func DoExample4() error {
defer func(ctx context.Context) {
if err := recover(); err != nil {
log.ErrorContextf(ctx, "panic: %v", err)
}
}()
panic("DoExample4")
}
// 正确做法:及时处理返回异常信息
func Example4(ctx context.Context) {
err := DoExample4(ctx)
if err != nil {
return
}
...
}
func DoExample4() (err error) {
defer func() {
if panicErr := recover(); panicErr != nil {
err = fmt.Errorf("panic %v", panicErr)
}
}()
panic("DoExample4")
}
Go协程启动的函数,没有recover,painc可能导致整个程序panic
func Example5() {
go func() {
doSomething()
}()
}
func doSomething() {
println("this is a good day!")
panic("but it panic")
}
// 正确方法:Go协程启动的函数,一定要有recover方法
func Example5() {
go func() {
defer func() {
// 捕获错误并做相应的处理
if err := recover(); err != nil {
println("the program is err = %s", err)
}
}()
doSomething()
}()
}
func doSomething() {
println("this is a good day!")
panic("but it panic")
}
Go协程中,采用闭包的形式使用请求传入的context,可能会导致context canceled错误
func Example6(ctx context.Context) {
go func() {
defer func() {
if err := recover(); err != nil {
...
}
}()
doSomething(ctx)
}()
}
func doSomething(ctx context.Context) {
...
}
// 解决方法:我们可以克隆一个context,例如使用trpc包中提供多个CloneContext方法
func Example6(ctx context.Context) {
go func(ctx context.Context) {
defer func() {
if err := recover(); err != nil {
...
}
}()
doSomething(ctx)
}(trpc.CloneContext(ctx))
}
func doSomething(ctx context.Context) {
...
}
使用gorm查询时,用Take, First, Last、Find函数查询数据库时,当传入函数的参结果集的变量只能为Struct类型或Slice类型,当传入变量为Struc类型时,如果检索出来的数据为0条,会抛出ErrRecordNotFound错误,当传入变量为Slice类型时,任何条件下均不会抛出ErrRecordNotFound错误
具体分析:
// 查不到数据返回ErrRecordNotFound
func Example7(db *gorm.DB) error {
person := model.Person{}
return db.Find(&person, -1).Error
}
// 查不到数据返回nil
func Example7(db *gorm.DB) error {
person := []model.Person{}
return db.Find(&person, -1).Error
}
当gorm更新使用struct时,会有零值更新问题,就是对于零值字段,会忽略更新
type Person struct {
Name string
Age int
}
// 更新数据库时忽略结构体中为零值的字段
db.Model(&person).Updates(Person{Name: "test", Age: 0})
// 解决方法:使用map可以不忽略零值字段
// 当然也可以在定义数据时避免使用零值字段
type Person struct {
Name string
Age int
}
db.Model(&person).Updates(map[string]interface{}{“Name": "test", "Age": 0})
闭包使用for…range声明的变量,导致非预期结果
func Example9() {
s := []string{"a", "b", "c"}
for _, v := range s {
go func() {
fmt.Println(v)
}()
}
select {}
}
// 正确方法1:通过复制变量,避免地址操作带来的影响
func Example9() {
s := []string{"a", "b", "c"}
for _, v := range s {
v1 := v
go func() {
fmt.Println(v1)
}()
}
select {}
}
// 正确方法2:通过函数传参深拷贝来解决,地址操作带来的影响
func Example9() {
s := []string{"a", "b", "c"}
for _, v := range s {
v1 := v
go func() {
fmt.Println(v1)
}()
}
select {}
}
map并发读写,会直接panic掉
func Example10() {
m := make(map[int]int)
go func() {
for i := 0; i < 10000; i++ {
m[i] = i
}
}()
go func() {
for i := 0; i < 10000; i++ {
fmt.Println(m[i])
}
}()
time.Sleep(time.Second * 5) // fatal error: concurrent map read and map write
}
解决方法:
加锁
sync.Map
改成协程内局部变量
slice并发写不会出现panic,能正常跑,但数据通常不是预期结果
func Example11() {
var arr []int
for i := 0; i < 1000; i++ {
go func(v int) {
arr = append(arr, v)
}(i)
}
time.Sleep(time.Second)
for i, v := range arr {
fmt.Println(i, ": ", v) // 数量,非预期的1000个
}
}
解决方法:加锁控制并发写入的并发覆盖问题
func Example11() {
lock := sync.Mutex{}
var arr []int
for i := 0; i < 1000; i++ {
go func(v int) {
lock.Lock()
arr = append(arr, v)
lock.Unlock()
}(i)
}
time.Sleep(time.Second)
for i, v := range arr {
fmt.Println(i, ": ", v) // 数量预1000个
}
}