今年七月我開了一個學後端的練習專案 backend-lab,用 C# / ASP.NET Core 從零開始學。這個專案跟我其他專案最不一樣的地方,是我在給 AI 的設定檔裡寫的第一條規則。不准替我寫實作程式碼。 這篇在講我為什麼要這樣綁住它、這個模式抓到我哪些裝懂的地方,以及這個預計四週的課程,最後停在第二天。
為什麼要綁住一個很會寫程式的工具
我要的是自己真的懂,不是 AI 幫我處理好
現在要寫一支 API,叫 AI 產一個出來大概只要幾十秒,而且它會動。問題就在「它會動」這件事。
會動的程式碼放在我眼前,我看得懂,也改得動,久了就會以為自己會了。但如果有人叫我打開空白檔案、不看範例重寫一次,我寫不寫得出來?老實說我不知道,而且在開這個專案之前,我從來沒有真的試過。
這有點像開車靠導航。開導航可以到任何地方,每天走同一條路上下班也沒問題。但哪天導航沒訊號,你才發現自己根本不知道那條路怎麼走。
你沒有迷路過,不代表你認得路。
我想真的掌握 C# 跟 .NET 底層的邏輯,所以乾脆把導航關掉。
我給 AI 的規則,比給自己的還多
它的角色是家教跟審查者,不是寫手
CLAUDE.md 是 Claude Code 每次開工都會讀的設定檔。我在裡面寫的「絕對禁止」有五條是給它的:
- 不准替我寫實作程式碼
- 不准直接幫我修 bug,我卡住時只能引導
- 不准一步給出完整答案
- 不准給我「骨架讓我填空」,那只是包裝過的代寫
- 不准把我還沒理解的套件或 pattern 塞進來
然後有三條是給我自己的,而且要它主動監督我。IDE 的 AI 補全全部關掉、不准複製貼上我不懂的程式碼、不准跳過驗證就往下走。
我卡住的時候,提示分成四層,一次只給一層:
- 第 1 層:告訴我這是什麼概念,要我自己回想
- 第 2 層:給方向,或問我一個對的問題
- 第 3 層:給虛擬碼,不給 C#
- 第 4 層:我要明講「我放棄,給我看」,它才給最小範例,而且給完要我關掉、憑記憶重寫,重寫過才算數
最後整份設定只繞著一個問題轉:
「Hubert 現在能不能打開空白檔案、不看任何範例、不用補全,把這段重寫出來?」
不能的話,不管程式會不會動,一律標紅燈。
第一天就被抓到用補全
眼睛記住,不等於手記住
規則是我自己寫的,但第一天寫的第一段程式碼,就是 IDE 的 AI 補全幫我寫的。😅
那天的題目很簡單,寫一個 /health 端點,回傳服務狀態跟時間。補全關掉之後重寫一次,七個重點我對了六個,錯的那個是把 string 寫成 String。
這個錯本身很小,C# 裡兩個都能編譯。但它說明了一件事。我明明看過好幾次 string,看的時候也完全知道它是對的,手打出來的卻不是它。
看得懂跟寫得出,是兩種不同的記憶。
那天還抓到另一件更丟臉的事。我一直在問環境設定、專案結構、IDE 怎麼設,問了很久還沒開始寫 code。後來我在錯誤紀錄裡替它取了個名字叫「準備成癮」。感覺很像在進步,其實是在拖延真正難的那一步。
同一個錯,我犯了三次
講得出來的觀念不算數,印出來的數字才算
Day 2 學 async / await。JS 的 Promise 我寫了好幾年,心裡覺得這塊應該很快。
結果這是我整個專案翻車最多次的地方。
我的誤會是以為 await 之後,程式會「交給另一條執行緒」去跑。所以被問到「呼叫一個 async 方法之後,呼叫端的下一行是哪條執行緒在跑」,我前後講錯了三次,一下說是空閒的執行緒,一下說是 timer。
第三次講錯之後,AI 沒有再解釋一遍,而是叫我把執行緒 ID 印出來自己看。幾天後它又抽考我一次,我重寫了一個更精簡的版本:
async Task Main()
{
Console.WriteLine($"AAA thread = {Thread.CurrentThread.ManagedThreadId}");
await FooAsync();
Console.WriteLine($"DDD thread = {Thread.CurrentThread.ManagedThreadId}");
}
async Task FooAsync()
{
Console.WriteLine($"BBB thread = {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(1000);
Console.WriteLine($"CCC thread = {Thread.CurrentThread.ManagedThreadId}");
}
await Main();跑出來的是:
AAA thread = 1
BBB thread = 1
CCC thread = 5
DDD thread = 5await 之前都在主執行緒(1 號)上跑,而且 await 本身不會換執行緒,它只是把控制權交還給呼叫端。真正換人的是等待結束之後。後半段由 thread pool 裡剛好有空的執行緒接手(5 號)。至於那一秒的等待,是交給 timer 處理的,它根本不佔用任何執行緒。
我後來把它記成一句話,timer 負責等待,執行緒負責跑程式碼。
這也解釋了我覺得最神奇的一個實驗。同時開 99 個 Task.Delay(1000),全部等完只花了 1002 毫秒,不是 99 秒。後端用 async 不是為了讓單一請求變快,是為了在等待的時候不浪費執行緒,同樣的機器可以同時扛更多請求。
我在錯誤紀錄裡寫的教訓是,口頭最容易翻車,觀念要以實驗為準,不要相信自己的複述。
而且越是「我熟的東西」越危險,我在 JavaScript 那邊也犯了一個。await 了 setTimeout 的回傳值,以為它有在等。但 setTimeout 回傳的是一個數字,await 一個數字等於沒等,時間戳印出來是 0ms。
最毒的 bug,是數字剛好對
對的數字加上錯的來源,比錯的數字還難抓
Day 2 後段學 record 的相等性。我寫了一整排 HashSet 的實驗,驗證 record 跟一般 class 放進 HashSet 之後,重複的東西會不會被擋掉。
所有數字都跟預期一樣。但其中有一行,我 Add 進的是 A 這個 set,印出來的卻是 B 的 Count。兩個 set 的數量剛好都是 2,所以數字完全對,看起來一點問題都沒有。
如果數字是錯的,我會馬上去查。偏偏它是對的,所以我差點就讓它過去了。這件事讓我對「驗證」多了一層理解。不是看數字對不對就好,要較真這一行到底在量誰。
預計四週,停在第二天
最後一筆 commit 是 2026-07-28
整個課程排了四週。第一週 HTTP 服務跟分層架構,第二週資料庫,第三週認證跟安全,第四週測試跟部署。
我走到第一週的第二天。
原因很單純。後來案子一個接一個來,而且接到的案子是 Python + Flask 的 API,不是 C#。工作上用不到的東西,要在下班後擠出時間繼續學,說實話我沒擠出來。
這個專案停下來,但我覺得它證明了一件事。第二天的兩個主題,async 跟 record 的相等性,我都是打開空白檔案、不看範例重寫過,再用實驗自己證明的。
我沒有學很多,但這兩個,我是真的敢說我會。
如果你也想這樣學
先決定你要的是「做出來」還是「學會」
這兩個目標都沒有錯,但它們需要的 AI 用法剛好相反。趕案子的時候,讓 AI 多寫一點很合理。想學會某個東西的時候,它寫得越多,你學到的越少。
如果你想試試看,我自己覺得最有用的是這三件事:
- 把規則寫下來,並且要它監督你。 光靠自律,我第一天就破功了
- 不懂的時候要實驗,不要要解釋。 AI 講得再清楚,都沒有你自己印出來的那個 5 號執行緒有說服力
- 準備一張「我犯過的錯」的表。 我那張表現在是整個專案最有價值的檔案,因為它記的全是我以為自己懂、其實不懂的地方
接下來的案子是 Flask,我有在想用同一套規則再開一個 Python 的練習專案。至於 C# 那邊的第三天,就等案子告一段落再說了。
(來自一個規則自己寫、第一天就自己破功的人)
