
新手推薦路徑摘要在 AI 輔助開發(fā)工具快速普及的今天新手面對最多的困惑往往不是工具夠不夠強而是我該從哪一步開始用。本文提出一條經(jīng)過實踐驗證的三階段學習路徑先從 Ask問答式交互起步熟悉工具的脾氣再用 Plan計劃模式把控復雜任務的節(jié)奏最后信任 Agent智能體去執(zhí)行落地。文章會結合可運行的代碼示例逐層拆解每個階段的目標、典型用法、常見坑點與過渡信號幫助新手用最低的試錯成本建立起對 AI 編程工具的正確認知。引言很多新手第一次打開 AI 編程工具時會直接丟一句幫我寫一個完整的電商后端然后對著生成的一大坨代碼發(fā)愣——不知道對不對、不敢改、更不敢上線。問題不在工具而在使用順序。AI 編程工具的能力大致可以拆成三層Ask 層你問它答。它像一個隨時在線的資深同事負責解釋、答疑、給片段。Plan 層你給目標它拆步驟。它像一位項目經(jīng)理負責把模糊需求變成可執(zhí)行的清單。Agent 層你給任務它自己讀代碼、改文件、跑測試。它像一個能獨立干活的工程師。這三層不是越往后越好而是有先后依賴的。跳過 Ask 直接上 Agent你既看不懂它改了什么也無法判斷它對不對跳過 Plan 直接上 Agent復雜任務會跑偏且難以糾偏。本文按這條推薦路徑展開每一節(jié)都配有可直接運行的示例。一、為什么新手必須從 Ask 起步1.1 Ask 階段的真正目標Ask 階段的核心不是讓 AI 幫我寫代碼而是建立三個認知認知一模型的能力邊界。它擅長什么樣板代碼、正則、SQL、解釋報錯不擅長什么涉及時效性的庫版本、你公司內部約定。認知二提問的質量決定回答的質量。模糊問題得到模糊答案這是新手最容易踩的坑。認知三如何驗證答案。AI 給的代碼不一定對你需要有能力跑一遍、讀一遍、判斷一遍。1.2 一個對比示例模糊提問 vs 結構化提問假設你想實現(xiàn)防抖函數(shù)。新手常見的提問是幫我寫個防抖得到的答案可能是一個最簡版本沒有取消功能、沒有this綁定、沒有立即執(zhí)行選項。而結構化提問用 TypeScript 實現(xiàn)一個 debounce 函數(shù)要求1支持立即執(zhí)行選項immediate2返回函數(shù)上掛一個cancel方法3正確處理this指向4給出使用示例和單元測試。得到的代碼質量完全不同。下面是一個符合上述要求的完整實現(xiàn)// debounce.tsexportinterfaceDebouncedFunctionTextends(...args:any[])any{(this:ThisParameterTypeT,...args:ParametersT):void;cancel():void;}exportfunctiondebounceTextends(...args:any[])any(fn:T,wait:number,immediatefalse):DebouncedFunctionT{lettimer:ReturnTypetypeofsetTimeout|nullnull;constdebouncedfunction(this:ThisParameterTypeT,...args:ParametersT){constcontextthis;constlater(){timernull;if(!immediate)fn.apply(context,args);};constcallNowimmediatetimernull;// 每次調用都重置計時器if(timer!null)clearTimeout(timer);timersetTimeout(later,wait);// immediate 模式下首次調用立即執(zhí)行if(callNow)fn.apply(context,args);}asDebouncedFunctionT;debounced.cancel(){if(timer!null){clearTimeout(timer);timernull;}};returndebounced;}配套的單元測試使用 Vitest可直接運行// debounce.test.tsimport{describe,it,expect,vi,beforeEach,afterEach}fromvitest;import{debounce}from./debounce;describe(debounce,(){beforeEach(()vi.useFakeTimers());afterEach(()vi.useRealTimers());it(延遲執(zhí)行多次調用只執(zhí)行最后一次,(){constfnvi.fn();constddebounce(fn,100);d();d();d();expect(fn).not.toHaveBeenCalled();vi.advanceTimersByTime(100);expect(fn).toHaveBeenCalledTimes(1);});it(immediate 模式下首次立即執(zhí)行,(){constfnvi.fn();constddebounce(fn,100,true);d();expect(fn).toHaveBeenCalledTimes(1);d();expect(fn).toHaveBeenCalledTimes(1);// 窗口期內不重復執(zhí)行});it(cancel 能取消待執(zhí)行的調用,(){constfnvi.fn();constddebounce(fn,100);d();d.cancel();vi.advanceTimersByTime(200);expect(fn).not.toHaveBeenCalled();});});1.3 Ask 階段的練習方法建議每天做三件事拿真實報錯去問而不是自己猜。把完整堆棧貼進去問這個報錯的原因是什么、怎么修、為什么。要求解釋而非只給答案。追問為什么這樣寫、“有沒有別的寫法”這是積累判斷力的關鍵。手動驗證每個答案。跑一遍、改一個邊界條件看它會不會掛。這一步不能省。1.4 何時可以進入下一階段當你發(fā)現(xiàn)自己能預判 AI 大概會怎么答、并且能一眼看出答案里的問題時Ask 階段就基本畢業(yè)了。此時你會自然產(chǎn)生新需求“這個功能涉及五六個文件一句句問太累了”——這就是進入 Plan 階段的信號。二、Plan 階段用計劃把控復雜任務2.1 什么算復雜任務一個經(jīng)驗判據(jù)如果任務的完成需要改動超過 3 個文件或者需要你先想清楚步驟才能動手就屬于復雜任務。典型場景包括重構一個模塊、給現(xiàn)有系統(tǒng)加一個橫切功能如日志、鑒權、遷移一個庫的版本。這類任務的失敗模式高度一致——方向錯了越努力越糟。Plan 階段的價值就是在動手前把方向對齊。2.2 Plan 的產(chǎn)出物一份可執(zhí)行的任務清單好的計劃不是一段散文而是可勾選、可驗證、有依賴順序的清單。以給 Express 應用加上請求級日志與耗時統(tǒng)計為例一份合格的計劃長這樣## 任務為 Express 應用添加請求日志與耗時統(tǒng)計 ### 前置調研 - [ ] 確認現(xiàn)有中間件注冊順序app.ts 第 20-40 行 - [ ] 確認日志庫選型項目已依賴 winston復用而非新增 ### 實施步驟 - [ ] 1. 新建 src/middleware/requestLogger.ts - [ ] 2. 在 app.ts 中于路由注冊之前掛載該中間件 - [ ] 3. 為每個請求生成 requestId使用 crypto.randomUUID - [ ] 4. 記錄 method / path / status / duration - [ ] 5. 將 requestId 透傳到響應頭 X-Request-Id ### 驗證 - [ ] 單元測試中間件對 200 與 500 響應均能正確記錄 - [ ] 手動驗證curl 一次接口檢查日志字段完整2.3 計劃對應的實現(xiàn)代碼下面是與上述計劃對應的完整可運行實現(xiàn)// src/middleware/requestLogger.tsimport{Request,Response,NextFunction}fromexpress;import{randomUUID}fromcrypto;importwinstonfromwinston;constloggerwinston.createLogger({level:info,format:winston.format.json(),transports:[newwinston.transports.Console()],});exportfunctionrequestLogger(req:Request,res:Response,next:NextFunction){constrequestIdrandomUUID();conststartprocess.hrtime.bigint();// 透傳到響應頭便于前端排查res.setHeader(X-Request-Id,requestId);// 響應結束時統(tǒng)一記錄保證能拿到最終 statusres.on(finish,(){constdurationMsNumber(process.hrtime.bigint()-start)/1e6;logger.info(request,{requestId,method:req.method,path:req.originalUrl,status:res.statusCode,durationMs:Number(durationMs.toFixed(2)),});});next();}// src/app.tsimportexpressfromexpress;import{requestLogger}from./middleware/requestLogger;constappexpress();// 關鍵日志中間件必須在路由之前注冊否則路由內的響應不會被統(tǒng)計app.use(requestLogger);app.get(/health,(_req,res)res.json({ok:true}));app.get(/boom,(){thrownewError(intentional error);});app.listen(3000,()console.log(listening on :3000));2.4 Plan 階段的三條紀律先調研再列步驟。讓 AI 先讀相關文件、輸出現(xiàn)狀理解你確認無誤后再讓它出計劃。計劃要能被人讀懂。如果計劃里出現(xiàn)優(yōu)化一下相關邏輯這種模糊表述打回去重寫。一個步驟一個驗證點。沒有驗證方式的步驟等于沒有步驟。2.5 何時可以進入下一階段當你發(fā)現(xiàn)計劃本身已經(jīng)足夠清晰、剩下的只是按部就班敲代碼并且你已經(jīng)能信任 AI 在這個清晰邊界內自主執(zhí)行時就可以進入 Agent 階段了。注意信任的是邊界清晰的執(zhí)行不是無腦放手。三、Agent 階段在清晰邊界內信任執(zhí)行3.1 Agent 與 Ask/Plan 的本質區(qū)別Ask 是你說一句它答一句Plan 是它給你清單你確認Agent 是它自己讀文件、改文件、跑測試、根據(jù)結果調整。Agent 的能力來自工具調用循環(huán)讀 → 想 → 改 → 跑 → 看結果 → 再改。這也意味著 Agent 的風險點在于它會真的修改你的文件。所以進入 Agent 階段的前提是邊界已經(jīng)由 Plan 階段界定清楚。3.2 一個 Agent 任務的最小示例假設 Plan 階段已經(jīng)確認要在utils/下新增一個retry.ts實現(xiàn)帶指數(shù)退避的異步重試并配套測試不改動其他文件。給 Agent 的任務描述應該長這樣任務邊界只允許在 src/utils/ 目錄下新增文件不得修改其他目錄。 目標實現(xiàn) retry(fn, options) 異步重試工具。 要求 - 參數(shù)fn 為返回 Promise 的函數(shù)options 含 retries、baseDelay、maxDelay、shouldRetry - 指數(shù)退避第 n 次重試等待 min(baseDelay * 2^n, maxDelay) - shouldRetry(err, attempt) 返回 false 時立即拋出 - 配套單元測試覆蓋成功、重試后成功、耗盡重試、shouldRetry 攔截四種情況 完成標準npm test 全綠且 git diff 只顯示 src/utils/ 下的新增文件。Agent 產(chǎn)出的實現(xiàn)大致如下// src/utils/retry.tsexportinterfaceRetryOptions{retries?:number;baseDelay?:number;maxDelay?:number;shouldRetry?:(err:unknown,attempt:number)boolean;}constsleep(ms:number)newPromise((r)setTimeout(r,ms));exportasyncfunctionretryT(fn:()PromiseT,options:RetryOptions{}):PromiseT{const{retries3,baseDelay100,maxDelay5000,shouldRetry()true,}options;letlastErr:unknown;for(letattempt0;attemptretries;attempt){try{returnawaitfn();}catch(err){lastErrerr;// 最后一次嘗試失敗或調用方明確不重試直接拋出if(attemptretries||!shouldRetry(err,attempt))throwerr;constdelayMath.min(baseDelay*2**attempt,maxDelay);awaitsleep(delay);}}// 理論上不可達僅為類型收窄throwlastErr;}配套測試// src/utils/retry.test.tsimport{describe,it,expect,vi}fromvitest;import{retry}from./retry;describe(retry,(){it(首次成功直接返回,async(){constfnvi.fn().mockResolvedValue(ok);awaitexpect(retry(fn,{baseDelay:1})).resolves.toBe(ok);expect(fn).toHaveBeenCalledTimes(1);});it(失敗后重試成功,async(){constfnvi.fn().mockRejectedValueOnce(newError(fail)).mockResolvedValue(ok);awaitexpect(retry(fn,{baseDelay:1})).resolves.toBe(ok);expect(fn).toHaveBeenCalledTimes(2);});it(耗盡重試后拋出最后的錯誤,async(){constfnvi.fn().mockRejectedValue(newError(always));awaitexpect(retry(fn,{retries:2,baseDelay:1})).rejects.toThrow(always);expect(fn).toHaveBeenCalledTimes(3);// 1 次初始 2 次重試});it(shouldRetry 返回 false 時立即拋出,async(){constfnvi.fn().mockRejectedValue(newError(fatal));awaitexpect(retry(fn,{baseDelay:1,shouldRetry:()false})).rejects.toThrow(fatal);expect(fn).toHaveBeenCalledTimes(1);});});3.3 如何信任而不失控信任 Agent 的正確姿勢是用可驗證的完成標準兜底而不是用人盯人的方式。具體做法給任務寫死邊界如只允許改 src/utils/Agent 越界時你能從 diff 里一眼看出。給完成標準寫死驗證方式如npm test 全綠讓 Agent 自己跑、自己看結果、自己修。事后 review diff 而非 review 過程。過程交給 Agent結果由你驗收。小步提交。每個 Agent 任務對應一個 commit出問題能一鍵回滾。3.4 Agent 階段的典型坑邊界不清導致誤改沒寫只改某目錄Agent 順手重構了無關文件?!?任務描述里顯式寫邊界。完成標準模糊只寫實現(xiàn)功能Agent 可能不寫測試就宣布完成?!?完成標準必須可執(zhí)行、可驗證。一次給太大任務把 Plan 階段整份清單丟給 Agent?!?拆成單個步驟逐個交給 Agent。四、三階段如何平滑銜接三個階段不是割裂的而是一條能力遞進鏈。下面這張對照表幫你判斷自己處在哪、下一步該練什么階段你的角色AI 的角色核心產(chǎn)出畢業(yè)信號Ask提問者答疑同事可驗證的代碼片段/解釋能預判并挑出答案的問題Plan決策者項目經(jīng)理可勾選的任務清單計劃清晰到只剩敲代碼Agent驗收者執(zhí)行工程師通過驗證的改動能用邊界標準兜住自主執(zhí)行銜接的關鍵動作是每個階段都留下可復用的資產(chǎn)Ask 階段留下的是一批好問題模板Plan 階段留下的是一份份任務清單Agent 階段留下的是一串小步 commit。這些資產(chǎn)會讓你在下一階段起步時不是從零開始。五、常見誤區(qū)與糾正誤區(qū)一跳過 Ask 直接上 Agent。糾正你無法驗收你看不懂的東西。先花一周在 Ask 階段把工具脾氣摸熟。誤區(qū)二Plan 階段寫偽計劃。糾正計劃里每一步都要有驗證方式。優(yōu)化性能不是步驟把列表接口的 N1 查詢改為批量查詢并用 EXPLAIN 確認走了索引才是。誤區(qū)三Agent 階段全程盯著。糾正盯著過程說明你還不信任邊界。把精力放在寫清邊界和完成標準上過程放手。誤區(qū)四三個階段只用一種。糾正真實工作中三者是混用的。遇到陌生報錯回到 Ask接到大需求先 Plan邊界清晰后交 Agent??偨Y新手使用 AI 編程工具最穩(wěn)妥的路徑是Ask → Plan → Agent的遞進Ask 起步目的是建立對模型能力邊界、提問質量、答案驗證的三重認知產(chǎn)出可驗證的代碼片段Plan 居中目的是把復雜任務的模糊需求轉成可勾選、可驗證、有依賴順序的任務清單在動手前對齊方向Agent 收尾目的是在清晰邊界與可執(zhí)行完成標準的兜底下信任 AI 自主執(zhí)行并只驗收結果。