為什麼這個問題現在會跑出來

最近很多工程師用 AI 寫 prototype(原型,用白話文講,就是先驗證想法能不能跑的粗版),最常遇到的不是語法錯,而是程式看起來太像真的。我同事前陣子就碰過一次:AI 把整段 import(匯入宣告,用白話文講,就是把外部函式庫接進這支檔案)寫好,變數命名也自然,linter 沒亮紅燈,結果一跑才發現 `lib.specialMethod()` 根本不存在。很像真的。 這篇的核心判斷很短:AI 用了不存在的 API 函式,linter 沒抓到,通常不是檢查工具失職,而是你讓靜態檢查去承擔執行驗證的工作。API(應用程式介面,用白話文講,就是函式庫開給你呼叫的功能入口)有沒有某個方法,常常跟版本、匯出方式、型別檔、實際執行環境綁在一起;linter(程式碼檢查器,用白話文講,就是在程式還沒跑之前掃出格式與常見錯誤的工具)看的是它能推論到的形式。它看不到全部現場。 這個問題現在特別常見,原因是 AI 生成程式的流暢度提高了。以前錯得很明顯,少一個括號、變數名打錯,2 秒就看出來;現在它會連錯都包裝成一段很合理的設計。它知道某個函式庫「大概會有」查詢、轉換、建立、更新這類方法,於是補出一個命名很像官方會設計的函式。這就是陷阱。 標題裡的 API 函式,就是你呼叫外部函式庫時使用的方法;linter 則是你在編輯器或提交前看到的檢查器。它們會一起出現在這個案例裡,是因為 AI 產出的程式碼剛好跨過了「看起來合理」這一關,卻沒跨過「真的能跑」這一關。對工程師的啟示很直接:看 code 信它之前,先讓它跑一次。先跑再說。

真正的原因不在表面

真正的原因不在 AI 會不會寫那一行,而在你把哪一層檢查當成最後保證。type system(型別系統,用白話文講,就是用資料形狀和函式簽名先幫你擋錯的規則)很有用,TypeScript(微軟推出的 JavaScript 型別語言,用白話文講,就是替 JavaScript 加上事前檢查)也很有用,但它們多半處理的是「我手上知道的資訊是否對得起來」。如果它手上的資訊本來就缺了一塊,判斷就會偏。資訊不夠。 最容易誤判的地方,是把「import 沒錯」看成「函式存在」。AI 可能寫了 `import lib from "some-lib"`,這一行在專案裡可以被解析,套件也真的安裝了,所以 linter 沒有理由立刻報錯。可是 `lib.specialMethod()` 是另一件事:那個套件目前版本是否真的有這個方法、預設匯出是不是這個物件、方法名稱是不是改過,這些都不是每個 linter 都會深入確認。它只看到門牌,不代表進屋看過。 還有一個更麻煩的情況:型別資訊太寬。很多 JavaScript 函式庫的型別宣告可能用 `any`,any(任意型別,用白話文講,就是先放行、之後再說)會讓檢查器少問很多問題。AI 只要把程式寫進這個寬鬆區域,TypeScript 就可能不吵,編輯器也顯得很安靜。安靜不等於安全。 這也是為什麼 AI coding(AI 輔助寫程式,用白話文講,就是讓模型幫你補程式、改程式、解釋程式)在這個階段常常讓人有錯覺:它不是只產出答案,它也產出「答案看起來可信」的外觀。命名像官方、註解像官方、錯誤處理也像官方,於是你很容易把閱讀順暢感當成可靠度。這一步很容易失準。 所以這個 case 的代表性在於,它不是單純的幻覺笑話,而是工作流程的縫。AI 補了一個不存在的方法,linter 沒抓到,TypeScript 也可能沒吵,最後問題在執行時才爆。這提醒你:工具鏈每一層都有責任範圍,靜態檢查能省時間,但不適合被當成外部函式庫真實行為的最後裁判。裁判不同。

現在先做什麼

現在先做的事,不是把整段 AI 生成的程式重讀 3 次,而是把最可疑的外部呼叫抽出來跑一次。尤其是你看到那種「名字很漂亮、你沒親手用過、文件印象也模糊」的方法,先不要讓它躲在完整流程裡。把 import、建立 client、呼叫那個函式、印出結果這幾行拆出來,做一個最小執行案例。小到能看清楚。 我自己會用一個很土的方法:凡是 AI 寫出我不熟的第三方 API,我先開一個暫時檔,控制在 20 行以內,只驗證一件事——這個方法到底能不能被呼叫。能跑,再回到主程式;不能跑,就去查版本文件或改用已知方法。這比在 300 行 prototype 裡追錯快很多,因為你把問題從「整個功能壞了」縮小成「這個方法存不存在」。範圍變小。 第二個習慣,是看套件版本,不只看套件名稱。AI 很常混用不同時期的寫法:它可能知道舊版 API,也可能猜到新版命名,但你的專案安裝的是另一個版本。package.json(套件設定檔,用白話文講,就是記錄專案裝了哪些套件與版本的檔案)裡那串版本號,常常比 AI 的語氣更可靠。先對版本。 如果你已經有測試環境,不用一開始就寫很完整的測試。先讓一個最短路徑通過就好:真實匯入、真實呼叫、真實回傳。這三件事會抓出很多 linter 抓不到的錯,包括方法不存在、參數順序不對、回傳格式跟想像不同。你要驗的是現場。 這裡的重點不是否定 linter,也不是叫你每行都手動查文件。比較穩的做法是分工:linter 幫你掃形式錯,TypeScript 幫你擋型別錯,最小執行案例幫你確認外部行為。三者合起來,才比較接近你以為的安全感。別混成一件事。

  1. 1 / 4 · HOOK

    沒紅線,不代表函式真的存在。

    • AI 把假方法寫得像官方
    • 程式檢查器只先看形式
    • 一跑才發現門牌是假的
  2. 2 / 4 · WHY

    你把靜態檢查當現場驗貨。

    • 匯入成功,只代表找得到套件
    • 型別太寬,檢查器就少問
    • 版本不同,方法名可能換過
  3. 3 / 4 · HOW

    先把可疑呼叫拆小跑。

    • 開暫存檔,控制 20 行內
    • 只測匯入、呼叫、回傳
    • 對套件版本,再查文件
  4. 4 / 4 · TAKEAWAY

    看起來能跑,先當還沒驗貨。

    • 信語法前,先跑最小案例
    • 外部功能入口,信任降一級
    • 檢查器管形式,執行管真相

常見的陷阱跟誤解

第一個陷阱,是看到編輯器沒有紅線就往下接功能。紅線只是其中一種警訊,沒有紅線不代表外部函式真的存在。別把沉默當通過。 第二個陷阱,是把 AI 的命名合理性當成文件證據。`createClient`、`specialMethod`、`parseAdvancedOptions` 這類名字都很像真的,但函式庫作者未必這樣命名。像,不等於有。 第三個陷阱,是只在大流程裡測。當登入、資料讀取、轉換、畫面渲染全綁在一起時,你會以為錯在狀態管理或網路請求,結果源頭只是某個不存在的方法。先切小塊。 還有一個常被忽略的點:有些錯在本機不一定馬上浮現,因為那段程式還沒被走到,或測試資料沒有碰到那條分支。AI 生成的 code 特別容易藏這種「看得到、跑不到」的路徑。要主動觸發。

下次再遇到時可以怎麼判斷

下次再遇到「AI 用了不存在的 API 函式,為什麼 linter 沒抓到」這種狀況,可以先問一個問題:我現在相信的是語法、型別,還是真實執行結果?如果答案只是前兩者,先別急著把功能繼續疊上去。先驗那一刀。 比較實用的小框架是這樣:內建語法和你熟悉的框架寫法,可以讓 linter 和 TypeScript 先扛多一點;第三方套件、陌生方法、版本剛更新的 API,就把信任降一級,先用最小案例跑過。這不是哪個工具比較強,而是每個工具適合看的東西不同。看情境。 如果你在趕 prototype,取捨會更明顯。你不需要把每段 AI 程式碼都審成正式產品,但需要抓出最容易讓整個 demo 翻車的地方:外部 API 呼叫、資料格式、非同步回傳。先驗這 3 類,通常就能少踩很多坑。沒有單一正解。 派派會這樣收:AI 可以幫你把路鋪快一點,但遇到它編出來的函式名稱,linter 多半只能提醒一部分。真正省時間的習慣,是不要看 code 信它;讓它跑一次,再決定要不要繼續蓋。下次先跑。