Junedayday Blog

六月天天的个人博客

0%

【每周小结】2023-Week3

本周,我的工作模式正式从远程办公回到了现场办公。恰逢过年,整体工作节奏放缓,切换的过程很顺畅。

虽然我十分期待远程办公成为常态,但不得不承认,这种模式在中国落地,还有一段路要走。

Go技巧 - 提高ORM使用体验的三个要点

ORM是一个非常高频使用的开发工具。以下图为例,Go程序内与MySQL中,数据存储是异构的 ,这就导致传统开发方式会分成两步:

  1. 将Go程序中的数据转换成MySQL的 SQL 语句
  2. 解析MySQL 返回的数据到具体结构体中

这部分的开发有大量重复性的代码,如拼接SQL、数据解析,所以就有了ORM这个概念 - 将内存中的数据结构(对象)与数据库中的表对应起来。一旦映射关系建立,那就可以调用ORM里的CRUD完成日常开发。在Go语言程序中,最常见的就是gorm

示例

我们以Book作为对象为例,它在Go程序中的定义是:

1
2
3
4
5
6
type Book struct {
Id int64 `gorm:"column:id"`
BookName string `gorm:"column:book_name"`
UpdateTime time.Time `gorm:"column:update_time"`
Status int `gorm:"column:status"`
}

对应MySQL中的建表语句为:

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE `books`
(
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`book_name` varchar(255) NOT NULL DEFAULT '' COMMENT '书名',
`update_time` datetime NOT NULL DEFAULT NOW() COMMENT '更新时间',
`status` tinyint(3) NOT NULL DEFAULT 0 COMMENT '状态',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
CHARSET = utf8mb4
COLLATE = utf8mb4_bin COMMENT '书';

完成定义后,我们可以使用gorm库实现CRUD了。但基于ORM库,开发中还是会高频出现一些奇怪的问题:明明程序没有bug,ORM的操作结果却没有达到预期。例如插入时status字段是0,没有报错,但查询时缺变成了100。

这类问题,往往是开发者在设计时没有注重 用户认知 导致的,也就是说 现象反直觉、所见非所得。我们今天的话题,将基于此展开:

要点一:程序侧 - 节制地使用ORM能力

ORM往往扩展了很多能力,但大幅度地增加了用户的学习成本与排查问题时的成本。以GORM字段权限控制为例:

1
2
3
4
5
6
7
8
9
10
11
12
type User struct {
Name string `gorm:"<-:create"` // 允许读和创建
Name string `gorm:"<-:update"` // 允许读和更新
Name string `gorm:"<-"` // 允许读和写(创建和更新)
Name string `gorm:"<-:false"` // 允许读,禁止写
Name string `gorm:"->"` // 只读(除非有自定义配置,否则禁止写)
Name string `gorm:"->;<-:create"` // 允许读和写
Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
Name string `gorm:"-"` // 通过 struct 读写会忽略该字段
Name string `gorm:"-:all"` // 通过 struct 读写、迁移会忽略该字段
Name string `gorm:"-:migration"` // 通过 struct 迁移会忽略该字段
}

看起来特性很酷,但如果你作为读代码的人,你愿意去读一个结构体中每个Field的tag详情吗?而且,这种限制藏得很隐蔽,发生问题后排查起来很累。

因此,程序侧的ORM定义,最重要的是能保证程序数据结构与数据库存储结构一一映射,其余特性需要慎用

慎用不代表不用。

如果能在团队内部形成规范,一方面这个规范能落地到代码里,另一方面也能宣传到各个成员、让大家形成共识,那就能用这些特性提升开发效率。

要点二:数据库侧 - 最简化设计

程序侧的代码对开发者可见,排查问题相对清晰。而如果问题最终是在数据库侧导致的,那么就变得复杂了:

  • 技术领域不同 - 数据库存在一定的专业性,经验尚浅的开发者需要一定的经验积累
  • 访问权限 - 角色、环境等问题,可能导致排查困难
  • 滞后性 - 出现问题的优先排查对象往往是代码,数据库往往会被我们“默认”认为没问题

所以,我们在前期设计数据库侧的内容时,要尽可能地保证简单。我个人的评判标准是:让Go结构体的数据,和MySQL表中的一行数据完全对应,不做额外的工作。

我举两个反例:

  1. 字段默认值有特殊的含义,如建表时status的默认值设置为100
    1. 改进方案:如果100这个值有业务含义,应在Go程序中设置
  2. 表中增加Trigger,如status字段修改为某个值后,自动触发另一个字段的修改
    1. 改进方案:在Go程序中实现这块逻辑

要点三:ORM能力与数据库特性的综合考量

第三个要点最为复杂,它需要结合ORM库的具体能力以及数据库的自身特性来综合考量:ORM的有些特性并不完善,具体在哪实现?

依旧以gorm为例,在用Book结构体进行多列更新时,无法更新其中的默认值,如

1
2
3
4
// 官方示例
// 代码原理:Active字段是默认值false,所以不会更新
// 用户认知:因为惯性思维,往往认为这个值会被设置为false
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})

我们先不考虑具体解决方案,而是希望大家能认识到ORM的局限性 - 想用一个结构体完全覆盖所有的增删改查场景,是不现实的。选择方案,其实是trade-off,选择一个团队更能快速理解的策略。

想了解方案的同学,可以参考我之前的博客

第三点是进阶性质的能力,需要大量ORM与数据库侧的开发经验,今天不作展开。

小结

ORM的使用体验会大幅提升CRUD的开发与维护效率。我比较提倡 在设计时,最简化ORM与数据库侧的特性,只采用其核心的映射能力。

而当简化到一定程度后,我们可以打通两侧的数据结构,如示例中的Book结构体与books建表语句。由于MySQL中的数据类型更为复杂,可以维护一个从 解析建表语句,自动生成Go中ORM结构体 的代码生成工具。

实现可以参考博客

编程思考 - 开发者的coding经验

如今的应届生在校或实习时就具备了颇为深厚的编程经验,参加工作后能快速地胜任日常需求,这就引起了老一批工程师的焦虑,不禁怀疑:我们的coding经验究竟有什么价值?

下面,我分享一下个人的思考,会从低到高三个维度进行讲述:

  1. 代码维度 - 写得好,读得懂:看过、写过的代码多,一方面让自己写代码时可读性提高,另一方面也能适应五花八门的项目风格。
  2. 功能维度 - 懂需求,善取舍:代码所实现的功能,往往和最终预期有出入,如沟通损耗、认知差异等;而功能实现的过程中往往需要取舍,要理清主次先后。
  3. 系统维度 - 识风险,促迭代:开发的代码从来就不是孤立的,需要识别出它对系统其余功能是否会产生风险;同时,本次开发也是一个迭代的机会,例如建设更通用的模块、修复一些历史包袱等。

以上三点维度不同,但很难从价值维度区分高低。从这三点来看,一个资深coder对团队的价值非常重要。

工作生活 - 焦虑感的缓解

这几年,我的焦虑感与日俱增,尤其是近两年的行业低谷。面对焦虑,专家们有很多思路,这里分享三个对我帮助最大的方法:

  • 多锻炼,既能保证身体能量充沛,又可以释放很多负能量
  • 多读书(尤其是心理学),提升心智成熟,坦然地面对不确定性
  • 多沟通,与同事、领导、朋友等多种角色,进行真诚的交流

Github: https://github.com/Junedayday/code_reading

Blog: http://junes.tech/

Bilibili: https://space.bilibili.com/293775192

公众号: golangcoding

二维码