为什么 AI-Generated Code 感觉怪异
I’m happy to help translate the article, but I’ll need the full text you’d like translated. Could you please paste the content (excluding the source line you’ve already provided) here? Once I have it, I’ll translate it into Simplified Chinese while preserving the original formatting, markdown, and any code blocks or URLs.
“AI‑like” 代码感受
你可能已经体会过这种感觉。你在审查一个 pull request,或者接手一个新模块时,代码总是让人觉得……不对劲。它能正常运行,逻辑也正确,但却带有一种刻板、教材式的完美气质,而真实的开发者很少写出这种代码。这就是软件的“恐怖谷”——看起来像人写的代码,却并非人写。
在浏览了成千上万行人类和大语言模型(LLM)生成的代码后,一些模式变得异常明显。下面是从实战中提炼出的细节分析。
1. 格式化 — “完美对称”
它的表现
想象打开一个文件,所有行都恰好在 80 字符处换行,缩进始终是 4 个空格,运算符前后以及每个逗号后都有对称、几乎有节奏的空格。大括号总是出现在同一行(或总是另起一行),文件结构严格遵循模板化的布局。
为何显眼
这是一种基于风格指南训练的模型输出,而不是在截止日期前敲代码的人类作品。人类往往不够一致:在一个文件里可能用 2 个空格,在另一个文件里用 4 个空格;赶时间时会让行变长;对函数使用换行的大括号,却对简短的 if 语句保持行内。这种“过度一致”的格式缺少了上下文切换、疲劳或对一次性脚本不在乎美观所带来的自然漂移。
人类通常的差异
人写的代码库在不同文件之间常会出现细微的格式差异,尤其是多人协作时。你会看到不同的制表宽度、偶尔的行尾空格以及不统一的空行。代码显得更“有人居住的痕迹”。
JavaScript 示例
// AI‑like: 完全对称、规整。
function calculateTotal(items) {
let total = 0;
for (let item of items) {
total += item.price;
}
return total;
}
// Human‑like: 稍微松散、务实。
function calcTotal(items) {
let t = 0;
for (const item of items) t += item.price;
return t;
}
2. 注释 — “显而易见的文档”
它的表现
每个函数——即便是极其简单的——都有正式的文档字符串块。逐行注释解释 代码在做什么,而不是 为什么这么做。即使是一个简单的加法函数,也会出现长篇大论的注释:
"""Adds two numbers together and returns the result."""
注释像是为朗读代码的人准备的字幕。
为何显眼
AI 被训练去“文档化”,往往通过描述语法来生成注释,而缺乏判断哪些注释是多余的能力。整个代码库里出现完美、统一的注释是一大警示信号。人类倾向于有选择地添加注释,通常是解释不明显的 为什么、棘手的变通办法或 // TODO:。
人类通常的差异
人写的代码往往文档不足。我们只在必要时添加注释——逻辑复杂、业务规则晦涩,或是给未来的自己留个提醒。一次性脚本和内部工具常常根本没有注释。
Python 示例
# AI‑like: 注释显而易见的内容。
# Create a list of numbers from 0 to 9
numbers = list(range(10))
# Loop through the list
for num in numbers:
# Print the current number
print(num)
# Human‑like: 注释“为什么”。
# Use a fixed seed for reproducible test failures
random.seed(42)
# TODO: This breaks on February 29th, needs proper date lib
def is_weekday(date_str):
...
3. 命名 — “超描述或极其通用”
它的表现
变量和函数名要么超描述(calculate_total_amount_from_items_list),要么极其通用(temp、data、value1),且同一文件里会并存这两种极端。名字在语法上完美,却缺乏灵魂——没有人类代码中常见的俚语、缩写或个人风格(例如 getStuff() 或 final_final_parser_v3)。
为何显眼
LLM 会把各种命名模式拼凑在一起,却往往忽略了人类在实际开发中对命名的随意性和上下文依赖。
(未完,后续请参见第 2 部分)
Source: …
从它们的训练数据中,包含了从精心命名的开源项目到匆忙写成的代码片段。没有真正的意图,这些名称缺乏一致的“声音”。它们要么机械地精确,要么奇怪地模糊,且风格甚至可以在同一个文件中剧烈切换。
人类通常的区别
人类会形成个人或团队的命名文化。我们可能始终保持简洁(如 getX)或始终保持描述性,但很少在同一作用域内两者兼顾。疲惫时我们也会打破自己的规则,导致在快速原型中出现 foo、bar 或 thingy。
Python 示例
# AI‑like: Inconsistent, overly verbose, or oddly generic.
def process_user_input_data(input_data_string):
parsed_data = json.loads(input_data_string)
result = perform_calculation(parsed_data)
return result
def helper(a, b): # Suddenly super generic.
return a + b
# Human‑like: More consistent tone with accepted shorthand.
def parse_input(json_str):
data = json.loads(json_str)
return calc(data)
def add(a, b):
return a + b
4. 过度抽象 — “设计模式滥用”
表现形式
一个只有 20 行的简单脚本被重构成包含三个辅助方法和一个抽象基类的类,以防万一。设计模式(如单例或工厂)被过度使用,即使问题根本不需要它们。你会看到 enumerate() 出现在本可以用普通循环完成的地方,或者每个操作都被不必要的 try…except 包裹。
为何显眼
AI 模型在“最佳实践”示例上进行训练,这些示例往往强调抽象、可复用性和防御性模式。模型倾向于过度应用这些模式,导致代码看起来像学术论文,且对当前任务而言过度工程化。它在追求教科书式的正确性,而不是可交付性。
人类通常的区别
资深开发者知道何时抽象、何时保持简洁。我们通常先写直接、略有重复的代码,只有在重复真正成为问题时才进行重构。我们避免为仅仅是函数的东西创建类。
JavaScript 示例
// AI‑like: Over‑abstracted for a simple task.
class DataProcessor {
constructor(data) {
this.data = data;
}
validate() { /* ... */ }
normalize() { /* ... */ }
process() {
this.validate();
this.normalize();
// ... actual processing logic
}
}
// Human‑like: Simple functional approach.
function processData(data) {
// Validate and normalize inline when needed.
if (!Array.isArray(data)) throw new Error('Invalid data');
return data.map(item => /* ... */);
}
要点
“AI‑like”氛围并不是关于 代码做了什么——而是关于 代码是怎么写的。过度一致的格式、冗长却浅显的注释、在超精确和通用之间摇摆的命名,以及不必要的抽象,都是代码可能由模型生成而非在真实约束下工作的人工开发者编写的强烈信号。识别这些模式可以帮助你发现 AI 生成的代码,更重要的是,引导你编写出感觉“有人在使用”、可维护且真正有人情味的代码。
Source: …
处理
// Human‑like: a plain function that gets the job done.
function processData(data) {
if (!data) return null;
// quick validation and normalization inline
const cleaned = data.map(item => ({ ...item, value: Number(item.value) }));
// ... process
return cleaned;
}
AI‑like 防御式风格
它的表现:每个函数都有一个笼统的
try…catch,在捕获错误时只记录类似 “An error occurred.”(发生错误)的通用信息。即使是只会在受控环境中运行一次的脚本,也会对边缘情况进行过度处理。你可能会看到为一个简单 CLI 工具专门编写的自定义错误类。为何突出:模型在生成代码时倾向于“稳妥”,因此默认采用 “气泡包装” 的方式。它缺乏上下文判断,无法分辨防御性编码何时是必需的(例如支付服务),何时是多余的(例如一次性数据迁移脚本)。
人类通常的区别:我们更务实。在生产代码中只处理那些可能出现的、具体的错误;在脚本中则可能让程序直接崩溃并查看堆栈。我们常会写
// assuming the input is valid for now或// FIXME: add proper error handling作为占位。
Python – AI‑like 与 Human‑like 对比
# AI‑like: Defensive to a fault.
def read_config(path):
try:
with open(path, 'r') as f:
data = json.load(f)
return data
except FileNotFoundError:
print("File not found.")
return None
except json.JSONDecodeError:
print("Invalid JSON.")
return None
except Exception as e:
print(f"An error occurred: {e}")
return None
# Human‑like: often more direct, handling errors where it matters.
def read_config(path):
with open(path) as f:
return json.load(f) # Let it crash if the file is missing or malformed.
导入膨胀 vs 精简导入
它的表现:代码使用最安全、最保守的默认设置,避免使用“投机取巧”但常见的方案(例如在 JavaScript 中使用
setTimeout(..., 0)来延迟执行)。导入往往过于臃肿,几乎把所有可能用到的库都引进来,导致文件顶部出现大量未使用的 import 语句。为何突出:AI 模型被优化为生成正确、广泛可接受的代码,它们回避那些在技术上不够纯粹但在实际项目中非常有用的惯用技巧。导入膨胀的根源在于模型倾向于“以防万一”,确保所有潜在依赖都已准备好。
人类通常的区别:我们会使用语言特有的惯用写法,有时会加入带解释的巧妙 hack。我们会毫不留情地裁剪导入,只在确认需要时才添加库。
Python – AI‑heavy 与 Human‑lean 导入对比
# AI‑like: Conservative and import‑heavy.
import numpy as np
import pandas as np
import re, json, os, sys, time, datetime # Many unused.
def find_pattern(text):
return re.search(r'\d+', text)
# Human‑like: lean imports, idiomatic hack.
import re
def find_pattern(text):
# re.search returns None if not found, which is falsy.
return re.search(r'\d+', text) or 0
一致性 vs 人类的“疲劳”
它的表现:如果你浏览同一个 AI 生成的多个文件,会发现其中的措辞、结构,甚至变量名的选择都有一种诡异的重复感。代码呈现出一种对称性和光鲜度,而人类很少能保持如此一致。代码中没有“疲劳”——没有凌晨两点匆忙写出的草率片段。
为何突出:AI 不会感到疲倦、无聊或不耐烦。它的输出是统计上的规律性,来源于训练数据中最常见的模式。相反,人类有好日子和坏日子,这会在代码中留下痕迹。
人类通常的区别:我们的代码讲述一个故事。项目初期写下的细致、注释丰富的函数,往往会在后期因时间紧迫而出现简化或折衷的部分。
rasts with the messy, hard‑coded function added during a late‑night bug fix. This variation is a signature of human authorship.
The difference often boils down to intent. Junior developers might write messy code, but it’s mess with a personal fingerprint. Senior developers write clean code, but it’s clean with purpose—not perfection.
为什么生产代码更乱
真实世界的代码受到相互冲突的需求、紧迫的截止日期、遗留约束和 bug 修复的影响。它会积累伤痕(// HACK:)、临时变通以及层层历史。相比之下,AI 生成的代码常常像是从未经历过维护混乱的全新项目。
AI 代码为何显得“过于谨慎”
这就像一个背诵了教材的学生,却还没有学会在实际工作中打破规则以获取收益。代码是正确的,但缺少经验丰富的开发者那种自信、甚至有时残忍的实用主义——他们知道该忽略什么。
如何辨别 AI 生成的代码
辨别 AI 生成的代码并不是找 bug,而是寻找 缺乏人类指纹 的迹象:不一致性的缺失、解释的过剩以及对教材模式的过度应用。
“Any sufficiently advanced technology is indistinguishable from magic.”
— Arthur C. Clarke
额外说明: 看起来像 AI 写的代码往往是 多个 AI 协同生成的,这会放大“过度工程”的感觉。