背景

近两三个月,在项目(go)中踩了很多坑,在此汇总一下以备忘。

大坑小坑都是坑

指针

指针是个很强大的工具,日常开发中也有很多妙用,自认在delphi/lazarus中已经是得心应手了,但在go中却频频踩坑。

空指针

空指针是最近踩坑最多的,而且一不小心就会panic,动不动就被摁在地上摩擦,踩坑后也有了些心得:

  • 能使用值变量的情况下就不要使用指针变量,除非必须使用指针变量的场景

  • 避免var p *T这种单纯的声明指针变量,一定要在声明指针变量时立即初始化,除非期望的初始值是nil

  • 使用指针变量前先判空

赋值

目前在go(go1.25.x)中是不能直接给指针变量字面量值的,必须先把字面量赋给变量,再把变量的地址赋给指针变量,如:

1
2
n := 0
p := &n

时间转换

字符串时间解析

字符串时间解析为time.Time是经常会用到的,通常使用time.Parse即可,但必须要确保时间格式字符串时间的格式一致。可以自定义函数实现兼容多种格式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func parseTime(timeStr string) (time.Time, error) {
    // 尝试多种格式
    layouts := []string{
        "2006-01-02 15:04:05",
        "2006/01/02 15:04:05",
        "2006-01-02",
        time.RFC3339,
        "02/01/2006 15:04:05",
    }
    for _, layout := range layouts {
        if t, err := time.Parse(layout, timeStr); err == nil {
            return t, nil
        }
    }
    return time.Time{}, fmt.Errorf("无法解析时间: %s", timeStr)
}

时区问题

一般情况下,可以不考虑时区问题,但当字符串时间中含有时区时,就必须考虑并处理时区

  • time.Parse返回的是UTC时间

  • time.ParseInLocation返回的是指定时区的时间

  • time.LoadLocation用于指定时区

在容器中使用

基于alpinescratch等的镜像默认不包含时区数据,因此会无法正常使用time.LoadLocation等与时区相关的函数,解决方案如下:

  1. 打包镜像时安装时区数据库
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Dockerfile
FROM alpine:latest

# 安装时区数据库
RUN apk add --no-cache tzdata

# 设置默认时区(可选)
ENV TZ=Asia/Shanghai

# 验证
RUN ls -la /usr/share/zoneinfo/

# 复制应用
COPY app /app
CMD ["/app"]
  1. 源码导入"time/tzdata"(适用于go1.15+
1
2
3
4
5
6
// 仅在入口包导入
import (
    "time"
    _ "time/tzdata" //要在 time 包之后
)
// 需要注意避免重复导入的问题,尤其是在库包中慎用

panic

某些情况下程序会抛panic,如果未捕获则会导致程序异常退出,可在主函数或主路由中进行捕获,捕获后最好打印堆栈信息,以方便定位问题。

1
2
3
4
5
6
7
8
9
defer func() {
    if r := recover(); r != nil {
        //打印堆栈
        debug.PrintStack()
        //也可转换为 error 进行返回,例如:在某个具体的函数中使用
        //如有必要可重新抛出供后续处理,例如:日志中间件仅记录不处理
        panic(r)
    }
}()