Files

16 KiB
Raw Permalink Blame History

Vibe Coding 程式碼品質保障

如何確保 AI 生成的程式碼品質

你好,我是魚皮。

很多同學在用 AI 開發時,會有這樣的擔心:AI 生成的程式碼靠譜嗎?會不會有隱藏的 bug?

這個擔心是有道理的。AI 雖然能快速生成程式碼,但它不能保證程式碼的品質。作為開發者,你需要建立一套品質保障體系。

本文我就來分享一些實用的方法,幫你確保 AI 生成的程式碼品質。

一、什麼是好程式碼?

在講如何保障品質之前,我們先要明確什麼是好程式碼。

好程式碼的特徵

什麼樣的程式碼才算好程式碼?

這個問題看似簡單,但很多人說不清楚。其實,除了能運行之外,好程式碼最重要的是 可讀性好:程式碼清晰易懂、符合團隊開發規範,別人(包括未來的你)能快速理解。在此基礎上,還要 可維護性強,修改和擴展都很容易,不會牽一髮而動全身。

當然,這些都要建立在 功能正確 的前提下,程式碼能正確實現需求,沒有 Bug。同時,性能要合理,在可接受的時間內完成任務,不浪費資源。此外,程式碼還要 安全可靠,沒有安全漏洞、能處理異常情況。

AI 生成程式碼的常見問題

那麼,AI 生成的程式碼通常會有哪些問題呢?

根據我的經驗,最常見的是 過度複雜,AI 為了實現功能,經常寫很多不必要的程式碼。另一個常見問題是 缺少邊界處理,AI 為了讓程式碼能快速運行,可能只考慮正常情況,忽略了空值、錯誤等特殊情況。

AI 生成的程式碼還經常出現 重複程式碼 的問題。特別是在前端開發中,如果你讓 AI 分別生成多個相似的頁面,它不會主動複用程式碼,而是為每個頁面生成獨立的程式碼。

舉個例子,假設你要做一個管理後台,有用戶列表頁、文章列表頁、評論列表頁。這三個頁面的佈局和功能都很相似:都是表格展示數據、都有搜索框、都有分頁。但如果你分 3 次讓 AI 生成,它會給你 3 套幾乎一樣的程式碼,只是數據字段不同。更好的做法是先讓 AI 生成一個通用的列表組件,然後用不同的配置來複用。這樣不僅程式碼量少,維護起來也容易。

有時候還會存在 性能問題,使用了低效的算法或數據結構。了解這些問題,你才能有針對性地檢查和改進。

建立品質標準

知道了什麼是好程式碼,接下來就要為你的項目建立明確的品質標準。

在程式碼規範方面,建議使用 ESLint 或 Prettier 統一程式碼風格,定義清晰的命名規則(比如變數用 camelCase 駝峰式寫法,常量用 UPPER_SNAKE_CASE 寫法),並規定好文件和文件夾的組織方式。

在功能標準上,要求所有功能都要有測試、要處理邊界情況、錯誤要有友好的提示。

性能方面,可以設定具體的指標,比如頁面加載時間不超過 3 秒,API 響應時間不超過 1 秒,數據量大時使用虛擬滾動等。

可以把這些標準寫在項目文檔裡,讓 AI 也知道。

💡 不過實際開發中還是要靈活,如果你只是單純開發個 Demo 類的小項目,幹就完了,不用考慮那麼多。

二、程式碼審查

程式碼審查(Code Review)是保障品質的第一道防線。

為什麼要審查 AI 程式碼?

有些同學覺得:AI 生成的程式碼能跑就行了,為什麼還要審查?

這個想法其實挺危險的。

首先,AI 不是完美的,它會犯錯,會生成有 Bug 的程式碼。更重要的是,AI 只知道技術,不了解你的具體業務邏輯,它生成的程式碼可能在技術上沒問題,但在業務上不合理。

另外,AI 可能只關注當前功能,不考慮未來的擴展性。今天能跑的程式碼,明天可能就成了技術債。而且,審查程式碼也是一個學習的機會,能幫你理解程式碼的工作原理,提升自己的技術水平。所以,審查 AI 程式碼是必不可少的,尤其是對於有程式設計基礎的同學。

審查的重點

那麼,審查 AI 生成的程式碼時,應該重點關注哪些方面呢?

1、功能正確性

最基本的要求是:程式碼能不能正確實現需求?

這聽起來簡單,但很容易被忽視。你需要運行程式碼,測試所有功能,嘗試各種輸入,包括正常的和異常的。

特別要注意邊界情況,比如空值、極大值、極小值等,這些往往是 Bug 的高發區。

舉個例子:

// AI 生成的程式碼
function divide(a: number, b: number): number {
  return a / b;
}

想想看,這段程式碼有什麼問題呢?

答案是:沒有處理除數為 0 的情況。

// 改進後:
function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error('除數不能為 0');
  }
  return a / b;
}

2、程式碼可讀性

功能正確之後,接下來要看程式碼是否易讀。

記住,程式碼是給人看的,不只是給機器執行的。

檢查時要問自己幾個問題:

  • 變數名是否清晰?
  • 函數名是否表達了功能?
  • 邏輯是否容易理解?
  • 是否需要添加註釋?

如果你看程式碼時感到困惑,那別人也會困惑。

舉個例子:

// 不好的命名
function f(x: number): number {
  return x * 2 + 1;
}

// 好的命名
function calculateDiscountedPrice(originalPrice: number): number {
  const discount = 0.2; // 8 折優惠
  return originalPrice * (1 - discount);
}

3、錯誤處理

程式碼要能優雅地處理錯誤,不能一出錯就崩潰。

你要檢查 API 調用有沒有錯誤處理、用戶輸入有沒有驗證、異常情況有沒有友好的提示。很多 AI 生成的程式碼只考慮了正常流程,完全忽略了錯誤處理,這是很危險的。

舉個例子:

// 不好的錯誤處理(根本沒做)
async function fetchUser(id: string) {
  const response = await fetch(`/api/users/${id}`);
  const user = await response.json();
  return user;
}

// 好的錯誤處理
async function fetchUser(id: string) {
  try {
    const response = await fetch(`/api/users/${id}`);
    
    if (!response.ok) {
      throw new Error(`獲取用戶失敗: ${response.statusText}`);
    }
    
    const user = await response.json();
    return { data: user, error: null };
  } catch (error) {
    console.error('獲取用戶出錯:', error);
    return { data: null, error: error.message };
  }
}

4、性能問題

然後是性能問題。程式碼要高效,不能浪費資源。

可以看看有沒有不必要的循環、有沒有重複的計算、數據結構選擇是否合理。AI 有時候為了快速實現功能,會選擇最簡單但不是最高效的方案。

舉個例子:

// 性能不好
function findUser(users: User[], id: string): User | undefined {
  // 每次都遍歷整個數組,O(n)
  return users.find(user => user.id === id);
}

// 性能更好
class UserManager {
  private userMap: Map<string, User>;
  
  constructor(users: User[]) {
    // 用 Map 存儲,查找是 O(1)
    this.userMap = new Map(users.map(u => [u.id, u]));
  }
  
  findUser(id: string): User | undefined {
    return this.userMap.get(id);
  }
}

5、安全問題

對於商業項目來說,這點至關重要。程式碼要安全,不能有漏洞。

需要檢查有沒有 SQL 注入風險、有沒有 XSS 攻擊風險、敏感信息有沒有加密、API Key 有沒有暴露。AI 對安全的理解可能不夠深入,很容易留下安全隱患。

舉個 SQL 注入的例子。SQL 注入是指攻擊者通過在輸入中插入惡意的 SQL 程式碼,來執行非預期的數據庫操作。

比如下面這段程式碼是不安全的:

// ❌ 不安全:直接拼接用戶輸入
const query = `SELECT * FROM users WHERE name = '${userName}'`;

假設用戶在登錄時輸入用戶名 admin' OR '1'='1,直接把這個輸入拼接到 SQL 語句裡,就會變成:

SELECT * FROM users WHERE name = 'admin' OR '1'='1'

這個查詢會返回所有用戶,因為 '1'='1' 永遠為真。攻擊者就能繞過驗證登錄任何賬戶。

正確的做法是使用參數化查詢:

// ✅ 安全:使用參數化查詢
const query = 'SELECT * FROM users WHERE name = ?';
db.execute(query, [userName]);

參數化查詢會自動轉義特殊字符,防止 SQL 注入。

如果你對 Web 安全感興趣,可以利用魚皮 免費的網絡安全自學網 來學習這些知識:

審查流程

可以按照下面的步驟,建立一個系統的審查流程:

  1. 自己先看一遍:快速瀏覽程式碼,看有沒有明顯問題

  2. 運行測試:測試所有功能,包括邊界情況

  3. 逐行審查:仔細檢查每一行程式碼,思考是否有問題

  4. 記錄問題:把發現的問題記下來

  5. 要求 AI 改進:把問題反饋給 AI,讓它修復

  6. 再次審查:確認修復後的程式碼沒有新問題

這個流程可能有點繁瑣,但能大大提高程式碼品質。

對於沒有程式設計基礎的朋友,如果自己看不懂程式碼,可以利用其他的 AI 大模型來幫忙審查。這是一個很實用的技巧:用多個 AI 交叉驗證

比如用 Cursor(Claude)生成的程式碼,可以複製到 ChatGPT 或 Gemini,讓它們幫你審查一遍:

請審查這段程式碼,找出潛在的問題,包括 bug、性能問題、安全隱患。

不同的 AI 有不同的視角和訓練數據,能互相補充,一個 AI 可能忽略的問題,另一個 AI 能發現。

我自己在做重要項目時,經常會讓 2 ~ 3 個不同的 AI 審查同一段程式碼,然後綜合它們的建議。這樣雖然多花了點時間,但能大大降低出錯的風險。特別是對於關鍵的業務邏輯、安全相關的程式碼、性能敏感的部分,多一層保障總是好的。

三、測試

測試是保障程式碼品質的關鍵手段。

為什麼要寫測試?

很多同學覺得寫測試浪費時間,但其實恰恰相反。測試能在開發階段就發現問題,而不是等到上線後用戶發現。有了測試,你可以放心地重構程式碼,不怕改壞。而且,測試程式碼本身就是很好的文檔,展示了如何使用你的函數或組件。

此外,雖然寫測試要花時間,但能省下更多調試時間。想想看,如果你每次改程式碼都要手動測試所有功能,那得花多少時間?有了自動化測試,運行一下就知道有沒有問題。

所以,寫測試是值得的。

測試的類型

測試主要有 3 種類型。

  • 單元測試:測試單個函數或組件,速度快,容易定位問題,覆蓋率要高。
  • 集成測試:測試多個模塊的協作,確保模塊之間的接口正確,覆蓋主要流程。
  • 端到端測試:模擬用戶的完整操作,測試整個系統,覆蓋關鍵場景。

對於 Vibe Coding 項目,我建議 重點寫單元測試和集成測試。端到端測試雖然也重要,但成本較高,可以只覆蓋最關鍵的場景。

讓 AI 幫你寫測試

如今絕大多數的測試程式碼已經不需要人工編寫了,你可以直接讓 AI 幫你生成測試程式碼。

請為這個函數寫單元測試,覆蓋正常情況和邊界情況:
```typescript
function calculateTotal(items: CartItem[]): number {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
```

AI 會生成類似這樣的測試程式碼:

import { describe, it, expect } from 'vitest';

describe('calculateTotal', () => {
  it('應該正確計算總價', () => {
    const items = [
      { price: 10, quantity: 2 },
      { price: 5, quantity: 3 }
    ];
    expect(calculateTotal(items)).toBe(35);
  });
  
  it('應該處理空數組', () => {
    expect(calculateTotal([])).toBe(0);
  });
  
  it('應該處理數量為 0 的情況', () => {
    const items = [{ price: 10, quantity: 0 }];
    expect(calculateTotal(items)).toBe(0);
  });
  
  it('應該處理小數', () => {
    const items = [{ price: 10.5, quantity: 2 }];
    expect(calculateTotal(items)).toBe(21);
  });
});

這段測試程式碼的作用是:用 describe 定義一個測試組(測試 calculateTotal 函數),然後用多個 it 定義具體的測試用例。每個測試用例都會調用函數並檢查結果是否符合預期。比如第一個測試檢查正常情況、第二個測試檢查空數組、第三個測試檢查數量為 0 的情況、第四個測試檢查小數。運行這些測試時,如果所有 expect 都通過,說明函數工作正常;如果有任何一個失敗,就說明程式碼有問題。

有了這些測試,你就能確保函數在各種情況下都能正常工作。

擴展知識 - 測試驅動開發(TDD

你可以嘗試測試驅動開發(Test-Driven Development,簡稱 TDD)。這是一種 「先寫測試,再寫程式碼」 的開發方式。

聽起來很反直覺對吧?一般不都是先寫程式碼,再寫測試嗎?

但 TDD 的邏輯是:你先定義好函數應該如何工作(寫測試),然後讓 AI 根據測試來實現功能。這樣能確保程式碼從一開始就是符合需求的、可測試的。

具體流程是:

  1. 先寫一個會失敗的測試(因為功能還沒實現)
  2. 然後讓 AI 實現功能讓測試通過,運行測試確保所有測試都通過
  3. 最後優化程式碼但保持測試通過

這樣一來,能避免寫出 「看起來能用但實際有問題」 的程式碼。

四、調試技巧進階

即使有了審查和測試,還是難免會遇到 Bug。這時候,你需要掌握調試技巧。

1、使用斷點調試

很多同學調試程式碼時只會用 console.log,也就是在程式碼裡加一行 console.log(變數名) 來打印變數的值,然後在瀏覽器控制台查看。

這個方法雖然簡單,但效率不高,而且調試完還要把這些 log 刪掉。

其實斷點調試要高效得多。在 VS Code 或 Cursor 中,你只需要在程式碼行號左邊點擊設置斷點,然後按 F5 開始調試。

程式碼會在斷點處暫停,這時你可以查看所有變數的值:

還可以單步執行程式碼,看看每一步發生了什麼。這比到處加 console.log 然後再刪掉要方便多了。

2、瀏覽器調試工具

前端開發時,瀏覽器的調試工具是你的好幫手。在瀏覽器中按 F12 就能打開開發者工具。

裡面有幾個常用的面板:

  • Console(控制台)面板可以查看日誌和錯誤,執行 JavaScript 程式碼,查看變數值。
  • Sources(源程式碼)面板可以設置斷點,單步執行,查看調用棧。
  • Network(網絡)面板可以查看 API 請求,檢查請求和響應,分析加載時間。
  • Performance(性能)面板可以分析性能瓶頸,查看渲染時間,找出慢的操作。

掌握這些工具,你的調試效率會大大提高。

3、二分法定位問題

如果你不確定問題在哪裡,可以試試二分法。

很好理解,直接把程式碼分成兩半,註釋掉其中一半,看問題是否還在。如果還在,說明問題在另一半;如果不在,說明問題在這一半。然後繼續把有問題的那一半再分成兩半,重複這個過程,直到找到問題。

這個方法雖然簡單,但很有效,特別是在處理大段程式碼時。

4、橡皮鴨調試法

這是一個看似玄學呆板,實則有一定科學原理的方法。

當你被 bug 困住時,試著向別人(或者一隻橡皮鴨)解釋你的程式碼:阿巴阿巴,這個函數應該做什麼…… 它先做了這個…… 然後做了那個…… 咦,這裡好像不對……

很神奇的是,在解釋的過程中,你往往就會發現問題。因為解釋的過程會強迫你重新理清思路,從不同的角度看問題。

哪個