AI 寫程式越快,為什麼 Code Review 越不能省?

現在整個前端圈都在享受自動化提效的紅利。在掘金或者微信技術群裡,大家都在分享 `Claude Code` 或 `Codex` 有多高效。幾行簡短的提示詞,成百上千行的複雜業務邏輯瞬間生成。

速度確實很快。但在自動化紅利背後的陰暗角落裡,無數個一線夥伴正在默默承受風險,頻繁地替這些 AI 生成的隱患程式碼收尾🤷‍♂️。

之前團隊裡一位經驗尚淺的開發者,透過大語言模型全自動生成了一個帶有複雜聯動的商品篩選元件。

image.png

程式碼表面看起來簡直無懈可擊:縮排完美,變數命名規範,甚至還極其貼心地附上標準註解。這種極其專業的程式碼賣相,讓很多人在做程式碼合併審查時,下意識地放鬆了警惕。

結果推到測試環境,直接引發了災難。因為 Hooks 的依賴陣列遺漏,再加上複雜的閉包狀態未清理,導致後端介面在特定操作下被瘋狂無限迴圈請求,測試伺服器直接當機。

這就是擺在我們面前最真實的 P0 級風險:AI 寫程式越快,你離線上業務癱瘓可能就越近🤔。


為什麼傳統的純人工審查一定會失效?

很多技術主管認為,既然自動化工具有缺陷,我們在 Merge Request(MR) 環節加強人工程式碼審核不就行了?

這條路根本走不通。致命弱點在於 AI 生成的程式碼欺騙性太強了😖

人類大腦在看程式碼時是存在啟發式偏誤的。當我們看到一段排版錯亂的程式碼時,會本能地提高警覺並逐行排查邏輯。但面對格式完美、看似邏輯完全自洽的程式碼時,大腦的防禦機制會自動降級,主觀認定這段程式碼是安全的。

更要命的是產出規模的暴增😢

以前一個前端開發者一天最多產出幾百行核心邏輯,現在借助 AI 工具,單日可以提交數千行。面對呈幾何倍數成長的 MR,技術負責人的精力根本無力應對,最終一定是被迫疲勞妥協,看兩眼沒有語法錯誤就直接通過😢。

單純依靠人力去審核 AI 的海量產出,註定會被這種低效的協作模式拖垮。


用 AI 驗證 AI?

真正高級的工程思維,是懂得轉移防禦陣地。既然程式碼是 AI 批量生成的,那驗證程式碼底層邏輯的重任,也必須前置交給 AI🫵。

不要再去人工排查低級的型別推斷和語法遺漏,必須把約束前置到 ESLintTypeScript 的最嚴格模式裡。在程式碼推送到遠端程式碼託管平台之前,直接用本地自動化流水線攔截 AI 產生的幻覺。

下面這段設定,是我們專門針對 AI 編碼習慣加入的底層安全防線:

json 代碼解讀複製程式碼// tsconfig.json
{
  "compilerOptions": {
    // 嚴禁為了省事隨手產生隱式 any,必須給出確切型別
    "noImplicitAny": true,
    // 強制處理 null 和 undefined 的邊界情況
    "strictNullChecks": true,
    // 防止捏造不存在的可選屬性
    "exactOptionalPropertyTypes": true,
    // 強制清理未使用的本地變數
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}
javascript 代碼解讀複製程式碼// eslint.config.js - 針對自動化程式碼常見漏檢的強力規則
module.exports = {
  rules: {
    // 極容易為了規避警告而亂刪依賴陣列,必須強制報錯阻斷
    'react-hooks/exhaustive-deps': 'error', 
    // 防止忘寫 await 導致非同步狀態徹底失控,這是極其常見的低級錯誤
    '@typescript-eslint/no-floating-promises': 'error',
    // 攔截隨手硬編碼的魔法數字
    'no-magic-numbers': ['warn', { ignore: [0, 1, -1] }]
  }
};

建立這套穩如磐石的工程基建來兜底。哪怕工具寫得再快,只要踩了團隊的安全紅線,在提交程式碼的瞬間就會被攔截報錯。人類無需為這種可以被靜態分析精準識別的低級錯誤買單✌️。


AI 最喜歡挖的三個無聲陷阱

哪怕過了各種嚴格的靜態檢查規則,自動化生成的程式碼依然隱藏著致命的業務邏輯死角。

在排查了數萬行 AI 產出的程式碼後,我總結了 AI 最愛挖的三個深水區大坑。這也是我們在人工審核階段真正應該死守的防線。

它會無視弱網與競態條件🤷‍♂️

大模型生成的業務邏輯往往是絕對理想主義的。它總是假設網路永遠暢通,介面永遠毫秒級返回,而且使用者的操作總是按部就班的。

比如寫一個搜尋框的非同步模糊比對請求,它百分之九十會給你這樣毫無防禦措施的程式碼:

typescript 代碼解讀複製程式碼import { useState, useEffect } from 'react';
import { fetchSearchResults } from '@/api/search';

export const useSearch = (keyword: string) => {
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (!keyword) return;

    // 致命缺陷:沒有請求防抖,也沒有底層請求取消機制
    fetchSearchResults(keyword).then(res => {
      setResults(res.data);
    });
  }, [keyword]);

  return results;
};

image.png

這段程式碼在開發者本地的高速網路下跑完全沒問題。但在實際的行動端弱網環境下,如果使用者快速輸入 A 後再輸入 B,由於網路抖動,A 的回應完全可能比 B 晚到。最後輸入框裡明明是 AB,但頁面列表顯示的卻是 A 的舊資料。(我覺得這種場景大家都遇到過🤷‍)

資深架構師在審核時,必須強行補全 AbortController 以及完善的例外捕獲兜底:

typescript 代碼解讀複製程式碼import { useState, useEffect, useRef } from 'react';
import { fetchSearchResults } from '@/api/search';
import { message } from 'antd'; // antd 彈窗元件

// 嚴格定義回傳的資料結構
interface SearchResult {
  id: string;
  name: string;
}

export const useSearch = (keyword: string) => {
  const [results, setResults] = useState<SearchResult[]>([]);
  const [loading, setLoading] = useState<boolean>(false);
  const debounceTimer = useRef<NodeJS.Timeout | null>(null);

  useEffect(() => {
    if (!keyword) {
      setResults([]);
      return;
    }

    // 必須引入原生的中斷控制器,防範競態
    const controller = new AbortController();

    // 加入防抖處理,避免對伺服器發起高頻無效請求
    if (debounceTimer.current) clearTimeout(debounceTimer.current);

    debounceTimer.current = setTimeout(() => {
      setLoading(true);
      fetchSearchResults(keyword, { signal: controller.signal })
        .then(res => {
          if (res?.code === 200) {
            setResults(res.data);
          } else {
            message.error(res?.message || '取得資料失敗');
          }
        })
        .catch(err => {
          // 忽略被業務主動取消的錯誤,防止向上拋出未捕獲的 Promise 異常
          if (err.name === 'AbortError') return; 
          console.error('搜尋介面異常:', err);
          message.error('網路發生抖動,請稍後重試');
        })
        .finally(() => {
          setLoading(false);
        });
    }, 300);

    // 元件卸載或 keyword 變化時,立刻切斷上一個仍在進行的請求
    return () => {
      controller.abort();
      if (debounceTimer.current) clearTimeout(debounceTimer.current);
    };
  }, [keyword]);

  return { results, loading };
};

只管掛載不管清理的記憶體洩漏

AI 生成的程式碼就像是一個極度缺乏責任心的新人。它特別喜歡使用全域的事件監聽或者 WebSocket 長連線操作,但有極高的機率,它會完全忘記在元件生命週期結束時把這些副作用安全地清理掉。

image.png

如果不在人工審核階段把這個隱患揪出來,使用者在這個複雜的後台系統裡連續操作十幾分鐘,瀏覽器的記憶體堆疊就會直接被塞滿,導致整個應用徹底卡死崩潰。這種動態的記憶體陷阱,自動化工具很難提前預判,必須靠人工去嚴防死守:所有的副作用都在元件卸載時徹底清理乾淨了嗎?

為了邏輯自洽而捏造套件名,AI 幻覺?

這是最細思極恐的安全隱患。

當 AI 遇到一個複雜的業務演算法時,為了讓程式碼看起來簡潔且邏輯自洽,它會憑空捏造一個根本不存在的內部方法,甚至是一個完全虛假的 NPM 相依套件。

比如 import { deepMerge } from 'lodash/deepMerge'。這段程式碼結構看起來極其合理,但 Lodash 函式庫根本沒有這種路徑匯出方式。如果在審核時沒有仔細核對第三方相依項,不僅會導致正式環境建置打包直接阻斷,更可怕的是引入了名稱極其相似但實際被植入木馬的惡意相依套件,直接導致整個專案的權限大範圍暴露,給公司帶來毀滅性的打擊。


真正的全端,是做好工程把關人

在這行敲了快十年的程式碼,見證了從原生開發到 React 再到如今的 AI 程式設計時代。

作為國內技術團隊的負責人,我非常支持大家使用先進的 AI 工具來極速提效。但我們必須重新定義人與 AI 的協作邊界。

永遠要把這些工具當作一個不知疲倦、極度高產,但極其缺乏真實業務場景和底層安全意識的應屆生。

試問,應屆實習生剛寫完的程式碼,你能閉著眼睛直接合併發布到線上正式環境嗎?

絕對不行的😖🤔。

好了,今天分享到這裡🙌

Suggestion.gif


原文出處:https://juejin.cn/post/7651794244281516041


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬1   ❤️1
463
🥈
我愛JS
📝1   ❤️1
22
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登