Vibe 编码 幻觉
Source: Dev.to
Introduction
我最近参加了一次让我感到害怕的代码审查。一位初级工程师——聪明、热情、善意——刚刚用 vibe coding 完成了一个新功能。他向大语言模型描述了该功能的“氛围”,把模型输出粘贴到我们的代码库中,并打开了一个 PR。代码可以运行:像素位置正确,按钮可以点击,数据也能加载。
但文件里硬编码了超时值,在三个组件中重复了状态逻辑,并且 useEffect 钩子使用了一个极其宽松的依赖数组。这是一个只考虑“happy path”的杰作,却会在生产环境中炸掉。
当前 AI 热潮的承诺是自然语言将成为新的编程语言。你告诉计算机你想要什么,它负责 怎么做。这很危险,因为 怎么做 的地方正是 bug 和安全漏洞的滋生地。
Vibe‑coded example
// UserProfile.jsx (The Vibe Version)
import React, { useState, useEffect } from 'react';
const UserProfile = ({ userId }) => {
const [user, setUser] = useState(null);
const [isEditing, setIsEditing] = useState(false);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
useEffect(() => {
fetch(`https://api.example.com/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setName(data.name);
setEmail(data.email);
});
}, [userId]);
const handleSave = () => {
fetch(`https://api.example.com/users/${userId}`, {
method: 'PUT',
body: JSON.stringify({ name, email }),
headers: { 'Content-Type': 'application/json' }
}).then(() => {
setIsEditing(false);
// refetch to update
fetch(`https://api.example.com/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
});
};
if (!user) return Loading...;
return (
{isEditing ? (
setName(e.target.value)} />
setEmail(e.target.value)} />
Save
) : (
## {user.name}
{user.email}
setIsEditing(true)}>Edit
)}
);
};
export default UserProfile;
对初级开发者来说,这看起来简洁且能工作。对资深工程师而言,却敲响了警钟。
Why the Vibe code is problematic
Race condition
useEffect 在每次 userId 变化时都会发起一次请求。快速切换会导致响应顺序错乱,显示错误的用户数据。AI 并不会自动使用 AbortController 来取消过时的请求。
State desynchronization
本地状态 (name、email) 手动与 user 同步。如果父组件更新了 user,本地字段可能会变得陈旧,导致 bug。
Missing error handling
对 fetch 的 Promise 没有 .catch()。如果 API 挂掉,组件可能崩溃或什么也不显示,因为 AI 只假设了 happy path。
Hard‑coded fragility
URL 被硬编码,且保存操作没有加载状态。快速多次点击会触发重复的 PUT 请求。
Security oversight
没有发送授权头部。真实应用中组件会因 401 而静默失败。
Specification Engineering approach
Define types and contracts
// types.ts
export type User = {
id: string;
name: string;
email: string;
updatedAt: string;
};
export type UserProfileProps = {
userId: string;
onUpdate?: (user: User) => void;
// Dependency injection for fetching prevents hardcoded URLs
fetchUser: (id: string, signal: AbortSignal) => Promise;
updateUser: (id: string, data: Partial) => Promise;
};
Prompt with explicit constraints
Prompt:
Generate aUserProfilecomponent based on theUserProfilePropsinterface above.
Constraints:
- Use
useQuery(or a similar pattern) to handle loading and error states explicitly.- Handle race conditions with
AbortSignal.- Show a loading state on the Save button to prevent double‑submission.
- Do not duplicate state; treat server data as the source of truth.
- Display user‑friendly error UI instead of console logs.
Engineered version
// UserProfile.tsx (The Engineered Version)
import React, { useState, useEffect } from 'react';
import { User, UserProfileProps } from './types';
// Utility for safe state management
type FetchState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
export const UserProfile = ({
userId,
fetchUser,
updateUser,
onUpdate,
}: UserProfileProps) => {
const [state, setState] = useState>({ status: 'idle' });
const [isSaving, setIsSaving] = useState(false);
const [editForm, setEditForm] = useState | null>(null);
useEffect(() => {
const controller = new AbortController();
setState({ status: 'loading' });
fetchUser(userId, controller.signal)
.then(data => setState({ status: 'success', data }))
.catch(error => {
if (error.name !== 'AbortError') {
setState({ status: 'error', error });
}
});
return () => controller.abort();
}, [userId, fetchUser]);
const handleSave = async () => {
if (!editForm || state.status !== 'success') return;
try {
setIsSaving(true);
const updated = await updateUser(userId, editForm);
setState({ status: 'success', data: updated });
setEditForm(null); // Exit edit mode
onUpdate?.(updated);
} catch (e) {
alert('Failed to save'); // Ideally use a toast system here
} finally {
setIsSaving(false);
}
};
if (state.status === 'loading') return Loading...;
if (state.status === 'error')
return Error: {state.error.message};
const user = state.status === 'success' ? state.data : null;
return (
{editForm ? (
setEditForm({ ...editForm, name: e.target.value })}
/>
setEditForm({ ...editForm, email: e.target.value })}
/>
{isSaving ? 'Saving…' : 'Save'}
) : (
user && (
## {user.name}
{user.email}
setEditForm({ name: user.name, email: user.email })}>
Edit
)
)}
);
};
Takeaway
解决方案并不是停止使用 AI,而是改变 使用方式。
- 在提示之前先写好清晰的规格、类型和约束。
- 将 AI 生成的代码片段视为草稿,必须经过审查、测试并加固后才能投入生产。
- 将 “怎么做” 明确化——错误处理、竞争条件的缓解、安全性以及状态管理——从而让最终代码可靠、可维护且安全。