返回文章列表

AI 写代码越来越猛,但你的技术债谁来还?

323·2 分钟阅读
AI 编程技术债工程实践

后台私信炸了。各大群也在聊同一个话题:AI 写出来的代码,很难维护。

这句话我记了 10 多年

十多年前我还是个刚入行不久的开发,老板是个技术出身的老兵。有次我跟他汇报说想在项目里引入一个新框架,他听完没直接说行不行,就甩了一句话:

"不管你用的什么技术,核心是你要驾驭它。"

当时觉得这话有点虚 —— 不就是"学好再用"吗?但这些年过来,我越来越觉得这句话的分量。

放在今天 AI 辅助编码的时代,这话更是金玉良言。

AI 写代码,到底出了什么问题?

最近收到太多类似的反馈:

  • "用 Claude/Codex/Copilot 生成了一堆代码,功能是实现了,但三个月后自己都看不懂"
  • "AI 给的方案能跑,但架构一塌糊涂,改个需求牵一发动全身"
  • "项目越写越像意大利面,AI 不断往里面塞代码,没人敢重构"

说实话,这些问题的根源

不在 AI,在于我们把 AI 当成了"自动写代码机",而不是"辅助工具"

我见过两种极端:

第一种:完全不用 AI。 抱着键盘从头敲到尾,觉得用 AI 就是"作弊"。这种人会慢慢被效率淘汰。

第二种:什么都让 AI 写。 Prompt 一丢,代码一贴,功能一跑,交差。三个月后面对一堆"能跑但看不懂"的代码,开始骂 AI。

这两种都走偏了。

驾驭的核心:你得先懂,才能借力

我写 Rust 的时候有个很深的体会:编译器不会替你思考,它只会替你检查。

AI 也是一样。它不会替你做架构决策,不会替你理解业务上下文,不会替你判断"这段代码放在这个位置合不合适"。它能做的是:在你已经想清楚的前提下,帮你加速实现。

举个例子。我之前写一个 CLI 工具,需要处理配置文件解析。我跟 AI 说:

"用 clap 解析命令行参数,配置文件用 toml 格式,支持环境变量覆盖,配置优先级是 CLI > 环境变量 > 配置文件 > 默认值"

AI 给出的代码能跑,但结构是这样的:

// AI 给的——能跑,但耦合严重
fn main() {
    let cli_args = parse_cli();
    let config_file = read_config_file(&cli_args.config_path);
    let env_vars = read_env_vars();
    let config = merge_all(cli_args, config_file, env_vars);
    // ... 后面 300 行全堆在 main 里
}

功能没问题。但如果我要加一个新的配置来源,或者写测试,或者支持不同的输出格式——得大改。

我知道该怎么做,所以我跟 AI 说:

"把配置解析拆成独立的 Config 层,用 trait 抽象配置来源,支持组合和优先级覆盖"

这次出来的东西就不一样了:

// 配置来源抽象
trait ConfigSource {
    fn get(&self, key: &str) -> Option<String>;
}
 
// 优先级组合
struct ConfigChain {
    sources: Vec<Box<dyn ConfigSource>>,
}
 
impl ConfigChain {
    fn get(&self, key: &str) -> Option<String> {
        self.sources.iter()
            .find_map(|s| s.get(key))
    }
}

你看,第一次是"我告诉 AI 做什么",第二次是"我告诉 AI 怎么做"。 区别在哪?在于我自己懂 Rust 的 trait 系统,知道怎么用抽象来解耦。

如果我不懂,我只会说"帮我写个配置解析",AI 就给我一个能跑的意大利面。

对技术要有敬畏之心

"敬畏"这个词听起来有点重,但我想表达的不是恐惧,是认真对待

我见过太多人(包括早年的自己)犯同一个错误:

对工具的理解停留在"能用"的层面,从来没想过"用好"。

  • Python 能写脚本,也能写大型服务 —— 但如果你只把它当脚本语言用,项目一大就会失控
  • Go 的 goroutine 用起来很方便 —— 但如果你不理解调度器和 channel 的语义,写出的并发代码可能比单线程还慢
  • Rust 的所有权系统很严格 —— 但如果你只是"让编译器不报错"地写代码,而不是理解为什么这么设计,你永远写不出地道的 Rust
  • Java 的 Spring 生态很成熟 —— 但如果你只会注解驱动开发,不理解底层的 IoC 和 AOP,遇到复杂问题就抓瞎

每门语言背后都有一套设计哲学。 你用它,就得尊重它的哲学,而不是用 A 语言的思维写 B 语言的代码。

我自己写 Rust 的时候,最深的体会是:

你越理解编译器为什么这么设计,你写代码就越顺。

一开始跟编译器 battle 觉得它烦,后来发现它每一次报错都在帮你避免一个潜在的 bug。这种"敬畏"让我写出了更可靠的代码。

AI 时代的正确姿势

回到正题。AI 到底该怎么用?

我的做法是:把 AI 当成一个"手很快但不懂业务的初级工程师"。

  • 你可以让它帮你写样板代码(boilerplate)——但你得 review
  • 你可以让它帮你查 API 用法——但你得确认它给的是对的
  • 你可以让它帮你重构一段代码——但你得告诉它重构的方向
  • 你可以让它帮你写测试——但你得定义清楚要测什么

关键在于:你在主导,AI 在执行。 不是反过来。

我用 AI 写 Rust 代码的时候,基本流程是这样的:

  1. 我自己先想清楚架构——模块怎么拆、接口怎么定义、数据流怎么走
  2. 把具体的实现交给 AI——"帮我写一个实现了这个 trait 的 struct,字段是这些"
  3. 我 review 每一行代码——不是走马观花,是真的读一遍,确认它符合我的设计意图
  4. 发现不对就纠正——"这里应该用 enum 而不是 String,因为状态只有这几种"

这个流程下来,代码质量是可控的。因为

设计在我脑子里,AI 只是帮我把想法变成代码

不管你用什么语言,道理都一样

有人可能觉得这是 Rust 独有的问题——毕竟 Rust 对开发者的要求比较高。但其实不是。

任何语言,如果你不理解它的设计哲学,你写出来的代码都会有问题。

  • 用 Go 不理解"少即是多",你就会写出一堆过度抽象的代码
  • 用 Python 不理解鸭子类型和协议,你就会写出一堆 type hint 堆砌但逻辑混乱的代码
  • 用 Zig 不理解显式内存管理的哲学,你就会用着 unsafe 然后奇怪为什么 segfault
  • 用 C 不理解指针和内存布局,你就会写出一堆 buffer overflow 的安全隐患

AI 只是放大了这个问题。以前你不懂语言哲学,写出来的代码只是"不太地道";现在有了 AI 帮你量产代码,不懂语言哲学写出来的是"大量不地道的代码"。

量变引起质变,技术债滚雪球一样越滚越大。

说到底,敬畏是一种态度

敬畏不是说你要把每门语言都学到专家级别,而是:

  • 你知道自己不知道什么——遇到不熟的领域,会去查文档、读规范,而不是直接丢给 AI
  • 你会 review AI 的输出——不是无脑信任,而是带着批判性思维去看
  • 你愿意花时间理解"为什么"——不只是"怎么用",还有"为什么这么设计"
  • 你尊重语言的设计哲学——用它擅长的方式解决问题,而不是强行把它掰成你熟悉的样子

我老板那句话,放在 10 年前是对技术栈的要求,放在今天是对"人 + AI"协作模式的要求。

AI 是你的加速器,不是你的自动驾驶。

你可以让它帮你跑得更快,但方向盘得在你手里。

最后

如果你现在用 AI 写代码,我的建议很简单:

  1. 先把基础打扎实。 不管你用什么语言,理解它的核心概念和设计哲学。这是你驾驭 AI 的前提。
  2. AI 生成的代码,每一行都要过脑子。 不是说不能用,而是你要知道它在干什么、为什么这么干。
  3. 定期重构。 AI 倾向于"堆叠"解决方案,不会主动帮你整理代码。这个活儿得你自己干。
  4. 对技术保持敬畏。 不是因为它会惩罚你,而是因为认真对待写出的代码,会让你走得更远。

说句掏心窝的话:

技术工具一直在变,但"驾驭工具"这个能力是永恒的。

10 年前我老板教我的这句话,今天我送给你。

不管你是写 Rust、Zig、C、Go、Python 还是 Java —— 敬畏它,理解它,驾驭它。

这才是一个工程师该有的态度。