程序员圈子最著名的观点,莫过于“过早优化是万恶之源”,在踩了无数坑之后,我认为:过早抽象也是万恶之源!
一、C++ 领域的过度设计 / 过早抽象
所谓“过早抽象”,可以认为是过度设计的另一个称呼,但是最早我对过度设计有误解。作为一个C/C++开发者,我曾以为过度设计,就是 Java 那种过度依赖设计模式,无论干什么都先来一套工厂模式、观察者模式,导致业务逻辑冗余。但是,我发现过度设计在 C++ 领域,一个更形象的说法是过度抽象。
C++ 和 Rust 这种高级语言的精髓是零成本抽象,尤其在嵌入式 C++ 这种高度 C 和 C++ 混写的领域,开发者需要用 C++ 提供的功能封装原始的 C 接口,不仅要做的对 C 接口的兼容,还用利用 C++ 的高级特性来引入 RAII、生命周期、所有权等内存安全的理念,来使得基于 C SDK 开发的项目更具有可读性和可维护性。
但是,这种 C 和 C++ 混写的领域恰恰是改革深水区:一个芯片的某个领域的 C SDK(例如 RK 的 Rockit),是原厂工程师不断调试和封装的结果,内部封装了复杂的内存管理、硬件管理,抑或是隐式逻辑,它会有自己的一套逻辑,例如某个函数封装了对寄存器的配置,或者利用回调函数来实现数据的转移。而作为这个 C SDK 的二次开发者,我们很难说能完全理解这个 SDK,更不用说比原厂工程师还懂,这种情况下,使用 C API 反而是更明智的选择。
二、试图用语言特性去掩盖业务知识的匮乏
我这个半吊子 C++ 开发者,总是妄图用所谓“现代 C++ 的高级特性”来封装原始 C API,例如:
用 C++17 的 std::variant 来封装 Rockit 的媒体帧,以下是当时我和Gemimi的对话
![FireShot Capture 025 - Google Gemini - [gemini.google.com]](https://markdownforyuanhao.oss-cn-hangzhou.aliyuncs.com/img1/20260213121016898.png)
事实上:当时我根本没有多少对这个媒体帧的理解!
最终的结果是:这个模型在接触到 VI、VPSS、VENC 之间的硬件绑定或者软件处理模型时失效了,复杂度上升,而我也无力维护,最终不了了之。
简言之,就是我试图用语言的高度,去掩盖对业务深度理解的匮乏。
三、AI 时代的认知负债
“过早抽象”本质上是一种认知负债——在还没赚到足够的“领域知识”时,就提前支取了“架构设计”。
我曾经产生过一种幻觉,在现在的AI时代,我只要问问 AI,就能掌握 C++ 的种种高级特性,并且运用于工程中,但是问题就出在这儿:我用的 Agent,默认我和它都是 C++/Rust 的高级开发者,它的语料库里充斥着
-
设计模式
-
零成本抽象
-
ownership
-
trait system
-
variant / enum
AI 可以对各种特性可以有鞭辟入里的讲解,但唯独缺少了贴合实际的实战,最终的结果就是诱导我进行过度抽象,可以说是双向奔赴。
在 AI 时代,无论是代码还是写作,高级特性、华丽的语言开始变得廉价,但是对某个领域的深入理解仍然昂贵,我想,这也是人工智能与作家、艺术家、工程师的本质区别。
C++、Rust那种零成本抽象,某种程度上是高级开发者的工具,它有巨大的认知开销,需要开发者对业务逻辑非常熟悉,才能用语言特性来写出高性能的代码。
而对于我这种半吊子初学者,更应该遵守的是
20/80 原则:
- Make it work(先跑通功能)。
- Make it right(对代码进行抽象)。
- Make it fast(对代码进行性能优化)。
抽象应该是业务逻辑驱动的,而不是直觉驱动的,性能优化应该是数据驱动的,而不是直觉驱动的。
四、什么时候才应该进行抽象
抽象的本质是压缩重复信息,在 Make it work 的过程中,注意那些需要多次重复的操作,它们才是需要进行抽象的地方,如果没有重复,或者用的很少,就不应该去进行抽象。
还是回到那个观点:想要把语言的高级特性、设计模式运用熟练,需要开发者对业务逻辑有深刻的理解,我作为一个成长在 AI 时代的开发者,最不缺的就是高级特性和设计模式,缺的是对业务逻辑的理解。