V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
yaodao
V2EX  ›  Go 编程语言

V 站的朋友们,想问一个关于 Go 的“基本类型零值问题”的问题?

  •  
  •   yaodao · 2023-09-09 21:09:56 +08:00 · 2191 次点击
    这是一个创建于 445 天前的主题,其中的信息可能已经有所发展或是发生改变。

    V2EX 的朋友们大家好。

    目前我正在学习 GO 语言,是个入门小白,试着从应用的角度去学习 GO 语言,想先从 web 开发入手去做一些有意思的后台应用。

    于是我挑选了 Fiber 作为 web 框架,XORM 作为数据库(目前只涉及 mysql )的交互框架。

    当我试着基于数据库去做一些查询功能时,我懵逼了。

    我需要根据一个结构体参数( Java 中叫参数对象)中是否有某个字段来决定我的 sql 中的 where 条件中是否要有这个筛选条件。

    例如下面这段代码片段:

    type Param struct {
    	UserId    int64
    	Type      int
    	StartTime time.Time
    	EndTime   time.Time
    }
    
    session := engine.Where("is_delete = 0")
    // userId 不为空时
    if param.UserId != 0 {
    	session.And("user_id = ?", param.UserId)
    }
    

    我一开始尝试着写 param.UserId != nil 幸好有编译器告诉我这是错误的写法,我只能摈弃继承于 Java 的编程思想。可是现在问题来了,我如何区分,到底这个 0 是来自默认赋值,还是用户真的输入了一个 0 呢?

    或者说有什么优雅的方法能解决我的这种场景么?还是说 golang 真的存在某些局限性不适合做 web 开发?

    先提前感谢大佬们和前辈们的指教!

    参考链接: go 的零值问题: https://youwu.today/skill/backend/golang-zero-value-and-reference-and-null-value-testing/#%E5%AD%97%E7%AC%A6%E7%B1%BB%E5%9E%8B%E5%8F%98%E9%87%8F%E7%9A%84%E7%A9%BA%E5%80%BC%E5%88%A4%E6%96%AD

    XORM 的使用: https://xorm.io/docs/chapter-05/1.conditions/

    30 条回复    2023-09-13 13:00:07 +08:00
    lianyue
        1
    lianyue  
       2023-09-09 21:13:23 +08:00
    *int64

    只有 结构体, 接口,map , 切片,数组,指针 才有 nil
    lianyue
        2
    lianyue  
       2023-09-09 21:13:43 +08:00
    哦 结构体也没 指针的结构体才有
    Nazz
        3
    Nazz  
       2023-09-09 21:13:57 +08:00
    类型设置为 *int64
    peterlitszo
        4
    peterlitszo  
       2023-09-09 21:20:38 +08:00
    没想到吧,这就是大道至简!

    当然吐槽归吐槽,事实上我这边的业务开发就是大家默认就避开零值。如果实在没有办法就用 *int 之类的。糊代码,这就是 Go 语言。
    yaodao
        5
    yaodao  
    OP
       2023-09-09 21:47:48 +08:00
    谢谢大家的回答,这是一个可行的思路,虽然目前需要多拐一个弯才能理解。
    @peterlitszo
    @lianyue
    @Nazz
    由于最初的数据来源一般是前端的 JSON 数据,所以还有一个疑问,不过已经解决了,在此也贴上问题和答案:
    问:go 中 json 转结构体,如果某个字段在 go 的结构体中是 *int 类型,这样能成功转化么?
    chatGpt 答:是的,可以成功将 JSON 转换为 Go 结构体,即使某个字段在 Go 结构体中是 *int 类型。在进行 JSON 转换时,Go 语言的 encoding/json 包会自动处理指针类型的字段。
    以下是一个示例代码,演示了如何将 JSON 转换为包含 *int 类型字段的 Go 结构体:

    package main

    import (
    "encoding/json"
    "fmt"
    )

    type MyStruct struct {
    ID int
    Name string
    Age *int
    }

    func main() {
    jsonData := `{"ID": 1, "Name": "John", "Age": 25}`

    var obj MyStruct
    err := json.Unmarshal([]byte(jsonData), &obj)
    if err != nil {
    fmt.Println("JSON 解析失败:", err)
    return
    }

    fmt.Println("ID:", obj.ID)
    fmt.Println("Name:", obj.Name)
    fmt.Println("Age:", *obj.Age)
    }

    在上述示例中,Age 字段在 MyStruct 结构体中是 *int 类型,表示它可以是一个指向整数的指针,或者是 nil 。当 JSON 数据中的 "Age" 字段存在且值为整数时,json.Unmarshal 函数会将其解析为整数,并将其地址赋值给 Age 字段。如果 JSON 数据中的 "Age" 字段不存在或者值为 null ,则 Age 字段将被设置为 nil 。
    请注意,如果 JSON 数据中的 "Age" 字段的值不是整数类型,而是其他类型(例如字符串),则在解析时会出现错误。在实际应用中,建议在解析 JSON 数据之前,先进行类型检查或错误处理,以确保数据的正确性。
    gitrebase
        6
    gitrebase  
       2023-09-09 21:50:16 +08:00
    如果某个字段是 nullable 的,一般就设置为指针类型
    leonshaw
        7
    leonshaw  
       2023-09-09 22:00:23 +08:00 via Android
    @peterlitszo 和 Go 没太大关系,C 和 Java 的 int 一样区分不出来
    dobelee
        8
    dobelee  
       2023-09-09 22:06:12 +08:00   ❤️ 2
    必填字段避免使用零值,任何语言都一样。
    vitoliu
        9
    vitoliu  
       2023-09-09 22:06:50 +08:00
    我从 Java 转 Go 一直以为这个是作者设置默认值是刻意的。
    因为 Box 往往在生产环境中更加容易引起 NPE 。。
    至于解决你这个问题,太简单不过了。CreateTable 后加初始自增 ID 为随机正数就行。
    btw ,生产环境只要是偏 C 端业务都会要求尽量避免用数据库 ID 。
    一个是众所周知容易暴露业务量问题。
    第二个就是链路一旦长了,上下游唯一标识的主键类型是很有可能发生变化的。从 Long-->String ,进而很有可能引起某一些不可描述的问题。
    shingle
        10
    shingle  
       2023-09-10 00:51:37 +08:00
    不想用指针的话还有其他思路可以参考

    - 参数都用 string 接收,转 int 的时候根据 error 判断前端到底传了这个值没

    - Fiber 不是很了解,像 gin 框架有 c.GetQuery 这种方法去判断前端有没有传这个参数

    当然最好的办法当然是这种情况避免前端默认使用零值
    troywinter
        11
    troywinter  
       2023-09-10 02:12:08 +08:00
    "可是现在问题来了,我如何区分,到底这个 0 是来自默认赋值,还是用户真的输入了一个 0 呢?"
    你用任何语言都需要处理这个问题,和语言无关,cs101 的第一节课这些问题都应该解释过,也许你在 java 中数据库使用 null ,那 go 也有对应的数字类型对象可以使用,总之,这个问题和语言一点关系都没有
    Ericcccccccc
        12
    Ericcccccccc  
       2023-09-10 02:21:37 +08:00
    为了区分引入一个 npe 是不是值得的也是得思考的问题.

    不如用 1/2 两个值.
    yaodao
        13
    yaodao  
    OP
       2023-09-10 08:07:54 +08:00 via iPhone
    @leonshaw 可是 java 有 integer😂
    yaodao
        14
    yaodao  
    OP
       2023-09-10 08:09:34 +08:00 via iPhone
    @Ericcccccccc 是的。作者应该思考过了 坚持极简
    cosiner
        15
    cosiner  
       2023-09-10 08:13:00 +08:00
    @yaodao java 的 integer 是对象, 能判断 null, go 里面换成*int 不是一样的效果, 基础类型和指针/对象类型, 任何语言都会有这个问题
    leonshaw
        16
    leonshaw  
       2023-09-10 08:30:14 +08:00 via Android
    @yaodao Go 也有 *int
    另外即使引用/指针类型也有类似问题,无法区分没设置和 null. 例如查询条件里一个 nullable 的字段是 null, 那应该是 where foo is null 还是没有这个条件呢?
    yaodao
        17
    yaodao  
    OP
       2023-09-10 08:46:35 +08:00 via iPhone
    感想大家的问题,受教了!
    @leonshaw
    @cosiner
    @Ericcccccccc
    @troywinter
    @shingle
    @vitoliu
    @dobelee
    @gitrebase
    yaodao
        18
    yaodao  
    OP
       2023-09-10 08:47:13 +08:00 via iPhone
    感谢大家的回答(上一条留言 打错字了)
    rekulas
        19
    rekulas  
       2023-09-10 08:51:58 +08:00
    其实即使不使用指针也可以判断, 你这个无法判断是因为缺了主键字段啊,任何情况下都建议保留主键到结构体
    lanlanye
        20
    lanlanye  
       2023-09-10 11:33:55 +08:00
    可为空的时候才需要使用指针,但基本类型使用指针非常别扭,这就是语言本身的问题……

    经验上来说除非业务上真的要用零值,都建议避开它,比如枚举第一个使用 iota + 1
    lysS
        21
    lysS  
       2023-09-10 11:58:15 +08:00
    这和语言没关系啊
    ninjashixuan
        22
    ninjashixuan  
       2023-09-10 15:13:40 +08:00
    @lanlanye 是的,不想用指针就在业务层面规避,日常使用枚举就是 iota+1 。
    peterlitszo
        23
    peterlitszo  
       2023-09-10 15:39:15 +08:00
    @leonshaw 的确,不过作为一个相对较新的语言,的确还是令人大吃一惊......
    peterlitszo
        24
    peterlitszo  
       2023-09-10 15:46:02 +08:00
    @cosiner 只能说这是作者的选择,其他一些语言是明确区分空和非空的,比如 TypeScript ,我们使用 int | undefined (我们有语法糖,也就是符号 ? 来帮助我们定义这种类型),比如 Rust ,我们有 Option<T> 来帮助我们定义。我认为 Rust 或者 Haskell 这种方案的确是最好的,它要求使用者先解包再使用,不用担心 NPE ,而且也不用担心心智负担。我个人认为最糟糕的就是 context.Context ,很多业务场景就把一些乱七八糟的东西往里面塞,你根本不知道里面到底有没有你想要的东西,这种方式太糟糕了。
    debuggerx
        25
    debuggerx  
       2023-09-10 17:18:52 +08:00 via Android   ❤️ 1
    这是硬伤,已经碰到好多次相关的问题了。
    我的选择是宁愿选 sql.nullXxx 系列,虽然难看繁琐但好歹路子是对的;用指针只能说是逆行倒施,而用默认值只能说是错的离谱。
    cosiner
        26
    cosiner  
       2023-09-10 17:50:49 +08:00
    @peterlitszo 任何语言 int 的定义都只能表示一个 32/64bit 值, Integer, int|undefined, Optional<T>, ?这种都是有附加属性的, 和 int 完全不对等, 类型都不一样, 不然 optional 的 true 和 false 是在哪里存储的.
    go 现在也有范型可以定义 Optional[T], 只能说语言层面没有语法糖, 写起来不方便

    至于 context 我基本只用它的 Done() channel, value 之类的只能约定项目规范了, 我不是很了解
    Sendya
        27
    Sendya  
       2023-09-11 11:15:33 +08:00
    我们这的类型定义从 1 开始,不用 0 值。
    dyllen
        28
    dyllen  
       2023-09-13 11:41:05 +08:00
    @peterlitszo 业务数据往里面塞那不就是用错了 context ,context 本意是用来做协程控制的,虽然可以带数据,那也是带一些通用固定的数据。
    peterlitszo
        29
    peterlitszo  
       2023-09-13 12:55:49 +08:00
    @dyllen 的确说得很好听,但是就是架不住大家滥用。traceid 放进去了,权限校验放进去了,logger 放进去了,我都不知道还有啥没有放进去过。这个 context 老老实实做个协程控制的不好吗?偏偏搞一个伪 map ,一点都不强类型。
    peterlitszo
        30
    peterlitszo  
       2023-09-13 13:00:07 +08:00
    @cosiner 就要要让它类型不一样啊..... Option<T> 这个就是附加一个小 tag ,后面跟个 union 。

    至于 Option 设计和 Result 设计,很多地方都要看相关的设计哲学的。如果你标准库都 “凑合凑合”,那很多第三方库也 “凑合凑合”,然后你写的代码也开始 “凑合凑合” 了。

    “什么?区分无值、零值、其他值?区分这么多干什么?会不会写工程?工程讲究的就是实现快!”
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   6027 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 02:38 · PVG 10:38 · LAX 18:38 · JFK 21:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.