函数参数为T时必须首行判空,因外部输入(如JSON、gRPC、HTTP、map)不可信,json.Unmarshal遇缺失或null即留nil;错误写法if p != nil && p == "admin"会在p==nil时提前panic。

函数参数为 *T 时必须首行判空
外部调用方传入的指针不可信,尤其是来自 JSON 反序列化、gRPC、HTTP 请求体或 map 查找的结果。文档说“非空”也不可靠——json.Unmarshal 遇到缺失字段或 null 就留 nil。
正确姿势:函数一进来就写 if p == nil { return errors.New("xxx cannot be nil") } 或直接 return(视业务而定)。
错误写法:if p != nil && *p == "admin"——p == nil 时短路求值救不了你,*p 已先 panic。
多个指针参数要逐个检查,别图省事合并判断:if u == nil || p == nil || a == nil { return err } 看似简洁,但出错时无法定位是哪个为 nil,调试成本高。
嵌套指针字段访问前必须逐层判空
像 u.Profile.Address.City 这种链式访问,任意一环为 nil,下一行就崩。Go 不做隐式空跳过,也不会返回零值。
立即学习“go语言免费学习笔记(深入)”;
常见场景:
-
json.Unmarshal后的结构体,某字段声明为*string但前端没传 → 该字段为nil - gRPC 生成的 struct,optional 字段默认就是
nil - map 查找后断言为
*User,但 map 中存的就是nil
建议做法:
- 不靠注释或文档假设“这里不会是 nil”,每级访问前加
if u != nil && u.Profile != nil && u.Profile.Address != nil - 封装辅助函数,如
SafeGetCity(u *User) string,内部做完整链路判空并返回默认值 - 若某字段语义上“必须有值”,就别用
*string,改用string;允许为空才用指针,并在字段注释里写明
接口断言后仍需验证底层指针是否为 nil
接口变量本身可为 nil,更危险的是:它底层值是 *User,而该指针本身又是 nil。此时 u := data.(*User) 不 panic,但 u.Name 会 panic。
典型错误写法:
u := data.(*User); fmt.Println(u.Name)——data 是 nil 或底层指针是 nil 都会崩。
正确写法:
if u, ok := data.(*User); ok && u != nil { fmt.Println(u.Name) }
从 map[string]interface{} 取值也一样:if v, ok := m["user"]; ok { if u, ok := v.(*User); ok && u != nil { ... } }
别省掉 && u != nil 这一环——类型断言成功 ≠ 指针有效。
函数返回 *T, error 时必须先检查 error 再用指针
http.Get、os.Open、json.Unmarshal 等标准库函数返回 *T, error,但文档不保证指针非 nil;出错时指针恒为 nil,此时直接访问字段或调用方法必崩。
常见踩坑点:
-
f, err := os.Open("config.json"); defer f.Close()——err != nil时f是nil,f.Close()直接 panic -
json.Unmarshal(data, &user)中user.Name是*string,JSON 没传name→user.Name为nil→ 后续*user.Namepanic
务必按顺序操作:
- 先
if err != nil处理错误 - 再使用返回的指针
- 对指针字段仍需单独判空(因为 error 检查只保底“指针非 nil”,不保底“字段非 nil”)
复杂点在于:error 检查和字段判空是两层事,容易漏掉后者。最容易被忽略的是,即使函数成功返回了非 nil 指针,其内部指针字段仍可能是 nil —— 这不是 bug,是 Go 的零值语义。



















