惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

披萨盒的赛博日志

使用策略模式重构复杂业务分支 像 systemd 一样管理 MacOS 后台常驻任务 Git Merge VS Git Rebase: 如何优雅地合并分支? 修改Linux内核模块以支持WG OpenLDAP折腾日记 非特权模式容器 ssh 登录问题 在 Linux 开发环境中使用网络代理 白嫖 Aseprite 像素绘图软件 MongoDB 增删改查 Python数据分析工具包-Numpy 解决 CLion 中文乱码问题 搭建 RLCraft 服务器 SpringBoot读取配置文件 Centos 配置 LNMP 环境 部署项目时遇到的坑 浅谈 xhr 请求跨域问题 JavaScript 学习笔记 Eclipse配置Web开发环境 Vue2 基本知识 Ribbon 简单使用 Nacos 简单使用 Spring Cloud Alibaba 环境搭建 什么是RSS?什么是Feed?它们有什么关系? Docker基本使用 TensorFlow启用GPU加速 如何进行内网穿透 Hello World! Git基本使用 Butterfly常用标签外挂
以ORM看封装的边界
2025-05-30 · via 披萨盒的赛博日志

引言

今天在看《Go语言高级编程》这本书,注意到作者在谈论 ORM 框架时提到了两句话

  1. “喜欢强类型语言的人一般都不喜欢语言隐式地去做什么事情,例如各种语言在赋值操作时进行的隐式类型转换然后又在转换中丢失了精度的勾当,一定让你非常的头疼。所以一个程序库背地里做的事情还是越少越好,如果一定要做,那也一定要在显眼的地方做”
  2. “但这样的分析想证明的是,ORM 想从设计上隐去太多的细节。而方便的代价是其背后的运行完全失控。这样的项目在经过几任维护人员之后,将变得面目全非,难以维护”

我看到这两段话时第一时间想到的是面向对象程序设计语言中的封装以及高级编程语言对低级编程语言的抽象。封装实现的不就是隐藏功能的内部实现细节,只将使用方式暴露出来?而高级编程语言相较于底层编程语言(比如汇编)的进步不也就是对细节的隐藏?

但我仔细一想,也不是这么回事。经过一番查阅资料后,我觉得他们还是有本质区别的,关键在于“隐藏什么”以及“隐藏的代价”是否可控和透明。

辨析1: 封装 vs ORM

封装隐藏的是模块内部的实现细节(数据存储方式、具体算法步骤等)。它提供的是一个清晰、稳定、语义明确的接口。使用者通过接口调用功能,无需关心内部如何完成。接口本身是显式的、强类型的,调用行为是可控的、可预测的。封装的目的是降低耦合、提高内聚、增强安全性。它并没有试图改变或掩盖操作本身的本质语义。

ORM 的问题就在于它试图隐藏的往往是操作本身的本质和关键细节,甚至与数据库交互的语义都有可能被改变。《Go语言高级编程》这本书的作者还举了一个例子:

1
2
o := orm.NewOrm()
num, err := o.QueryTable("cardgroup").Filter("Cards__Card__Name", cardName).All(&cardgroups)

他指出“很多 ORM 都提供了这种 Filter 类型的查询方式,不过在某些 ORM 背后可能隐藏了非常难以察觉的细节,比如生成的 SQL 语句会自动 limit 1000”。这确实是一件很可怕的事情。

抛开上面的例子不谈,数据库操作本来就有其固有的复杂性。ORM 试图用面向对象的简单模型去映射关系型数据库的复杂世界,这种映射本身就可能引入歧义或意外的行为。

当遇到性能瓶颈、复杂查询或需要精确控制数据库行为时,ORM 又迫使开发者不得不去理解它试图隐藏的东西。这时,当初为了方便而引入的抽象,反而增加了认知负担和调试难度。

辨析2: 高级语言的抽象 vs ORM

高级语言隐藏的往往是机器底层的细节,比如寄存器、内存地址、指令集等。它们使得开发人员可以用心关注业务逻辑的实现而非程序在计算机上的运行方式。这种抽象是基础性的、安全的。编译器/解释器只负责高效并且准确地将高级语言程序代码翻译成底层机器代码,这种翻译通常是可靠并且行为一致的。虽说大部分编译器会进行编译优化,但这并不会影响程序本身的语义。

反观 ORM,它是在一个已经存在的、成熟的、语义明确的领域(关系数据库)之上,叠加了另一层领域模型(对象模型)。这层抽象可不是基础性的,它更像是一个胶水层。它的目标是弥合两个不同范式的差异,但这种弥合本身就可能引入新的复杂性和不确定性。它的“翻译”(对象操作 -> SQL)过程比高级语言编译要复杂得多,充满了各种配置选项和潜在的陷阱,其行为往往不如编译器那样确定和透明。

总结

经过一番思考后,我还是比较赞同该书作者的看法的。作者批评的并不是“隐藏细节”本身,而是过度地、不透明地隐藏那些对理解系统行为、性能和可维护性至关重要的细节。封装的隐藏是为了程序解耦和;高级语言的抽象是为了提供更为强大的基础能力。而 ORM 的过度抽象,其危险在于它让开发者在一个“方便”的幻象下工作,却可能在后台进行着复杂、低效甚至难以追踪的操作,最终导致系统变得像一个黑盒,难以理解、调试和优化,这正是作者担忧的“运行完全失控”和“面目全非,难以维护”的根源。好的抽象应该让复杂的事情变简单,而不是让简单的事情背后变得复杂且不可见。

但是🤓,让数据的操作和存储的具体实现相剥离这件事情我认为还是很有进步意义的,对于大部分 CURD项目,ORM 确实能极大地提升开发效率(比如大部分 ORM 都会有代码生成器,给它一个数据库地址,一行命令就能从 DAO 层生成到 Controller 层。真的牛逼)。我本身也是个 ORM 的重度使用者。

最后,用导师曾经说过的一句话作为文章结尾:

你犯下的很多小错误在当下可能并不会显现出来,但在未来的某一刻它们极有可能会突然爆发,而且爆发的时候你还不知道问题出在哪。