【后端开发】Golang基础语法中可能会踩的坑1(go get invalid: disallowed version string解决方案, 2006, panic, range, slice…)

【后端开发】Golang基础语法中可能会踩的坑1(go mod, 2006, panic, range, slice…)


从其他语言转到Go时容易碰到的一些踩坑。
参考资料: 1, 2, 34, 5, 6

1、go mod: disallowed version string

问题:通过go get拉取公司私有包的时候报错:invalid: disallowed version string,但master、develop分支则可以正常拉取。

解决:

  • 问题的关键在分支名称中的’/',在gomod中斜杠是有其他的用意的,没有设置GOPRIVATE时,默认拉取依赖都会走goproxy,所以会报错。
  • 设置GOPRIVATE后,gomod就不会走goproxy而直接在源地址拉取,就不会报错了。
go env -w GOPRIVATE=gitlab.xxxx.com
go get -u gitlab.xxxx.com/xxxx@feature/xxxx
go: downloading gitlab.xxxx.com/xxxx v1.0.4

引用

2024.8 补充一个版本:
问题:

go get github.com/xxx/api@feature/test1
go get: github.com/xxx/api@feature/test1: invalid version: version "github.com/xxx/api@feature/test1" invalid: disallowed version string

解决方案:

  • 考虑搜索“分支名带斜杠拉不下来”,git地址后面多出一个斜杠“/“问题的解决, 方案1, git的时候带上账户
  • 考虑搜索“go get如何拉取指定的分支”,参考1, 23 得到解决方案,拉tag即可,事实上这个方案在官方的issue回复里也有提到。
go get golang.org/x/text@latest
go get golang.org/x/text@master # 分支带了/拉不下来
go get golang.org/x/text@v0.3.2
go get golang.org/x/text@342b2e # 替代解决方案!

2、Format(“2006-01-02 15:04:05”)

问题:

  • Go 语言的时间格式化模板,不像其他很多语言,使用 “yyyy-mm-dd” 类似的格式。
  • 而是采用其特殊的模板参数,然后实际的时间,会按照这个格式进行格式化。

解决:

  • 没错,就是下面这个时间,换成任何其他时间都不行,指定格式的时候只能用下面这个时间
//时间
"Mon Jan 2 15:04:05 MST 2006"
"2006-01-02 03:04:05PM -0700"

// 格式化方式
newTime := time.Unix(x, 0).Format("2006-01-02 15:04:05")

Go 语言 time 库中,预定义的时间戳模板参数常量也可以使用

const (
    ANSIC       = "Mon Jan _2 15:04:05 2006"
    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
    RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
    RFC822      = "02 Jan 06 15:04 MST"
    RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 (小时数差标识时区)
    RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
    RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
    RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 (小时数差标识时区)
    RFC3339     = "2006-01-02T15:04:05Z07:00"
    RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
    Kitchen     = "3:04PM"
    // Handy time stamps.
    Stamp      = "Jan _2 15:04:05"
    StampMilli = "Jan _2 15:04:05.000"
    StampMicro = "Jan _2 15:04:05.000000"
    StampNano  = "Jan _2 15:04:05.000000000"
)

引用

3、使用 defer 和 recover 捕获 panic

panic是什么?

  • Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引起painc异常
  • 一般而言,当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数,逐层展开调用栈,运行在每一层调用中定义的 defer 语句。
  • 如果 panic 未被恢复(即未被 recover 捕获) 最终会导致程序崩溃,并输出错误信息和调用栈。
  • 程序崩溃并输出日志信息。日志信息包括panic value和函数调用的堆栈跟踪信息。

一些panic的例子:

  • 运行时错误:数组越界: var arr [3]int \\ print(arr[4])
  • 手动触发:panic("Something went wrong!")

使用 defer 和 recover 捕获 panic

package main

import "fmt"

func main() {
    // 捕获 panic,避免程序崩溃
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Recovered from panic:", err)
        }
    }()

    fmt.Println("Starting the program")
    // 触发 panic
    panic("Something went wrong!")
    fmt.Println("This line will not be executed")
}

引用:1

补充一些会导致panic的场景
直接使用值为 nil 的 slice、map
允许对值为 nil 的 slice 添加元素,但对值为 nil 的 map 添加元素则会造成运行时 panic

// map 错误示例
func main() {
    var m map[string]int
    m["one"] = 1        // error: panic: assignment to entry in nil map
    // m := make(map[string]int)// map 的正确声明,分配了实际的内存
}    


// slice 正确示例
func main() {
    var s []int
    s = append(s, 1)
}

将 JSON 中的数字解码为 interface 类型
在 encode/decode JSON 数据时,Go 默认会将数值当做 float64 处理,比如下边的代码会造成 panic:

func main() {
    var data = []byte(`{"status": 200}`)
    var result map[string]interface{}

    if err := json.Unmarshal(data, &result); err != nil {
        log.Fatalln(err)
    }

    fmt.Printf("%T\n", result["status"])    // float64
    var status = result["status"].(int)    // 类型断言错误
    fmt.Println("Status value: ", status)
}

// 将 decode 的值转为 int 使用
func main() {
    var data = []byte(`{"status": 200}`)
    var result map[string]interface{}

    if err := json.Unmarshal(data, &result); err != nil {
        log.Fatalln(err)
    }

    var status = uint64(result["status"].(float64))
    fmt.Println("Status value: ", status)
}

4、range引用, if作用域, switch break

在 range 迭代 slice、array、map 时通过更新引用来更新元素

  • 在 range 迭代中,得到的值其实是元素的一份值拷贝,更新拷贝并不会更改原来的元素,即是拷贝的地址并不是原有元素的地址:
func main() {
    data := []int{1, 2, 3}
    for _, v := range data {
        v *= 10        // data 中原有元素是不会被修改的
    }
    fmt.Println("data: ", data)    // data:  [1 2 3]
}


func main() {
    data := []int{1, 2, 3}
    for i, v := range data {
        data[i] = v * 10    
    }
    fmt.Println("data: ", data)    // data:  [10 20 30]
}

if变量作用域

  • 在 Go 的 if 语句中,变量可以在条件语句中定义,这些变量的作用域只在 if 语句内。
if x := someFunc(); x > 0 {
    fmt.Println("x is positive")
}
// fmt.Println(x)  // 错误:x 未定义

x := someFunc()
if x > 0 {
    fmt.Println("x is positive")
}
fmt.Println(x)

switch 语句 无需显式 break

  • 在 Go 的 switch 语句中,每个 case 块自动带有 break,无需显式添加。
  • 但是没有指定标签的 break 只会跳出 switch/select 语句,若不能使用 return 语句跳出的话,可为 break 跳出标签指定的代码块:
// break 配合 label 跳出指定代码块
func main() {
loop:
    for {
        switch {
        case true:
            fmt.Println("breaking out...")
            //break    // 死循环,一直打印 breaking out...
            break loop
        }
    }
    fmt.Println("out...")
}

5、array, map, slice,切片合并,三个点…

三个点

将slice打散展开,还有种情况就是通过append合并两个slice:

stooges := []string{"Moe", "Larry", "Curly"}
lang := []string{"php", "golang", "java"}
stooges = append(stooges, lang...) // 三个点
fmt.Println(stooges)

stooges := [...]string{"Moe", "Larry", "Curly"} //等价于stooges := [3]string{"Moe", "Larry", "Curly"}
arr := [...]int{1, 2, 3}

切片引用与数据共享

  • 切片本质上是对底层数组的引用,多个切片引用同一底层数组时,修改其中一个切片会影响到其他引用该数组的切片。
a := []int{1, 2, 3, 4}
b := a[1:3]
b[0] = 99

此时 `a` 变成了 `[1, 99, 3, 4]`。
如果需要独立的副本,请使用 `copy` 函数创建一个新的切片。
a := []int{1, 2, 3, 4}
b := make([]int, len(a[1:3]))
copy(b, a[1:3])
b[0] = 99
此时 `a` 仍然是 `[1, 2, 3, 4]``b``[99, 3]`

数组和切片的不同。数组是固定长度的,而切片是动态长度的。

  • 超过切片容量的扩展
  • 在向切片中追加元素时,如果超过了原有容量,Go 会创建一个更大的底层数组,旧切片与新切片之间没有数据共享。
var a [4]int  // 数组,长度固定为 4
s := []int{1, 2, 3, 4}  // 切片

a := make([]int, 2, 2)
a = append(a, 1)
fmt.Println(cap(a)) // 输出 4,因为容量被扩展了

未初始化 map 就直接使用,会导致运行时错误。

# Python 
d = {}
d['key'] = 'value'
print(d)  # 输出 {'key': 'value'}

var m map[string]string
m["key"] = "value"  // 导致 panic: assignment to entry in nil map

//解决方案: 使用 make 函数初始化 map。
m := make(map[string]string)
m["key"] = "value"
fmt.Println(m)  // 输出 map[key:value]

访问不存在的键不会抛出错误,但会返回零值。

# python
d = {'key': 'value'}
print(d['non_existent'])  # 抛出 KeyError

m := map[string]string{"key": "value"}
fmt.Println(m["non_existent"])  // 输出空字符串 ""

//解决方案: 使用第二个返回值来检查键是否存在。
value, exists := m["non_existent"]
if !exists {
    fmt.Println("key does not exist")
} else {
    fmt.Println(value)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小哈里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值