Vibe 编码 幻觉

发布: (2025年12月15日 GMT+8 21:47)
6 min read
原文: Dev.to

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

本地状态 (nameemail) 手动与 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 a UserProfile component based on the UserProfileProps interface 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,而是改变 使用方式

  1. 在提示之前先写好清晰的规格、类型和约束。
  2. 将 AI 生成的代码片段视为草稿,必须经过审查、测试并加固后才能投入生产。
  3. 将 “怎么做” 明确化——错误处理、竞争条件的缓解、安全性以及状态管理——从而让最终代码可靠、可维护且安全。
Back to Blog

相关文章

阅读更多 »