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 开发?
先提前感谢大佬们和前辈们的指教!
1
lianyue 2023-09-09 21:13:23 +08:00
*int64
只有 结构体, 接口,map , 切片,数组,指针 才有 nil |
2
lianyue 2023-09-09 21:13:43 +08:00
哦 结构体也没 指针的结构体才有
|
3
Nazz 2023-09-09 21:13:57 +08:00
类型设置为 *int64
|
4
peterlitszo 2023-09-09 21:20:38 +08:00
没想到吧,这就是大道至简!
当然吐槽归吐槽,事实上我这边的业务开发就是大家默认就避开零值。如果实在没有办法就用 *int 之类的。糊代码,这就是 Go 语言。 |
5
yaodao OP 谢谢大家的回答,这是一个可行的思路,虽然目前需要多拐一个弯才能理解。
@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 数据之前,先进行类型检查或错误处理,以确保数据的正确性。 |
6
gitrebase 2023-09-09 21:50:16 +08:00
如果某个字段是 nullable 的,一般就设置为指针类型
|
7
leonshaw 2023-09-09 22:00:23 +08:00 via Android
@peterlitszo 和 Go 没太大关系,C 和 Java 的 int 一样区分不出来
|
8
dobelee 2023-09-09 22:06:12 +08:00 2
必填字段避免使用零值,任何语言都一样。
|
9
vitoliu 2023-09-09 22:06:50 +08:00
我从 Java 转 Go 一直以为这个是作者设置默认值是刻意的。
因为 Box 往往在生产环境中更加容易引起 NPE 。。 至于解决你这个问题,太简单不过了。CreateTable 后加初始自增 ID 为随机正数就行。 btw ,生产环境只要是偏 C 端业务都会要求尽量避免用数据库 ID 。 一个是众所周知容易暴露业务量问题。 第二个就是链路一旦长了,上下游唯一标识的主键类型是很有可能发生变化的。从 Long-->String ,进而很有可能引起某一些不可描述的问题。 |
10
shingle 2023-09-10 00:51:37 +08:00
不想用指针的话还有其他思路可以参考
- 参数都用 string 接收,转 int 的时候根据 error 判断前端到底传了这个值没 - Fiber 不是很了解,像 gin 框架有 c.GetQuery 这种方法去判断前端有没有传这个参数 当然最好的办法当然是这种情况避免前端默认使用零值 |
11
troywinter 2023-09-10 02:12:08 +08:00
"可是现在问题来了,我如何区分,到底这个 0 是来自默认赋值,还是用户真的输入了一个 0 呢?"
你用任何语言都需要处理这个问题,和语言无关,cs101 的第一节课这些问题都应该解释过,也许你在 java 中数据库使用 null ,那 go 也有对应的数字类型对象可以使用,总之,这个问题和语言一点关系都没有 |
12
Ericcccccccc 2023-09-10 02:21:37 +08:00
为了区分引入一个 npe 是不是值得的也是得思考的问题.
不如用 1/2 两个值. |
14
yaodao OP @Ericcccccccc 是的。作者应该思考过了 坚持极简
|
15
cosiner 2023-09-10 08:13:00 +08:00
@yaodao java 的 integer 是对象, 能判断 null, go 里面换成*int 不是一样的效果, 基础类型和指针/对象类型, 任何语言都会有这个问题
|
16
leonshaw 2023-09-10 08:30:14 +08:00 via Android
@yaodao Go 也有 *int
另外即使引用/指针类型也有类似问题,无法区分没设置和 null. 例如查询条件里一个 nullable 的字段是 null, 那应该是 where foo is null 还是没有这个条件呢? |
17
yaodao OP |
18
yaodao OP 感谢大家的回答(上一条留言 打错字了)
|
19
rekulas 2023-09-10 08:51:58 +08:00
其实即使不使用指针也可以判断, 你这个无法判断是因为缺了主键字段啊,任何情况下都建议保留主键到结构体
|
20
lanlanye 2023-09-10 11:33:55 +08:00
可为空的时候才需要使用指针,但基本类型使用指针非常别扭,这就是语言本身的问题……
经验上来说除非业务上真的要用零值,都建议避开它,比如枚举第一个使用 iota + 1 |
21
lysS 2023-09-10 11:58:15 +08:00
这和语言没关系啊
|
22
ninjashixuan 2023-09-10 15:13:40 +08:00
@lanlanye 是的,不想用指针就在业务层面规避,日常使用枚举就是 iota+1 。
|
23
peterlitszo 2023-09-10 15:39:15 +08:00
@leonshaw 的确,不过作为一个相对较新的语言,的确还是令人大吃一惊......
|
24
peterlitszo 2023-09-10 15:46:02 +08:00
@cosiner 只能说这是作者的选择,其他一些语言是明确区分空和非空的,比如 TypeScript ,我们使用 int | undefined (我们有语法糖,也就是符号 ? 来帮助我们定义这种类型),比如 Rust ,我们有 Option<T> 来帮助我们定义。我认为 Rust 或者 Haskell 这种方案的确是最好的,它要求使用者先解包再使用,不用担心 NPE ,而且也不用担心心智负担。我个人认为最糟糕的就是 context.Context ,很多业务场景就把一些乱七八糟的东西往里面塞,你根本不知道里面到底有没有你想要的东西,这种方式太糟糕了。
|
25
debuggerx 2023-09-10 17:18:52 +08:00 via Android 1
这是硬伤,已经碰到好多次相关的问题了。
我的选择是宁愿选 sql.nullXxx 系列,虽然难看繁琐但好歹路子是对的;用指针只能说是逆行倒施,而用默认值只能说是错的离谱。 |
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 之类的只能约定项目规范了, 我不是很了解 |
27
Sendya 2023-09-11 11:15:33 +08:00
我们这的类型定义从 1 开始,不用 0 值。
|
28
dyllen 2023-09-13 11:41:05 +08:00
@peterlitszo 业务数据往里面塞那不就是用错了 context ,context 本意是用来做协程控制的,虽然可以带数据,那也是带一些通用固定的数据。
|
29
peterlitszo 2023-09-13 12:55:49 +08:00
@dyllen 的确说得很好听,但是就是架不住大家滥用。traceid 放进去了,权限校验放进去了,logger 放进去了,我都不知道还有啥没有放进去过。这个 context 老老实实做个协程控制的不好吗?偏偏搞一个伪 map ,一点都不强类型。
|
30
peterlitszo 2023-09-13 13:00:07 +08:00
@cosiner 就要要让它类型不一样啊..... Option<T> 这个就是附加一个小 tag ,后面跟个 union 。
至于 Option 设计和 Result 设计,很多地方都要看相关的设计哲学的。如果你标准库都 “凑合凑合”,那很多第三方库也 “凑合凑合”,然后你写的代码也开始 “凑合凑合” 了。 “什么?区分无值、零值、其他值?区分这么多干什么?会不会写工程?工程讲究的就是实现快!” |