Gemini
使用 Gemini AI 進行本地 Code Review
前言
作為一個前端工程師,在提交程式碼之前,我總是希望能有個「第二雙眼睛」幫我檢查程式碼。雖然團隊有 code review 的文化,但有時候自己在開發過程中就想先自我檢查一下。
於是我開始思考:能不能讓 AI 幫我在本地進行 code review?
經過一番研究和實作,我使用 Google Gemini AI 打造了一個本地的 code review 工具。這篇文章記錄了整個實作過程,包括遇到的問題和解決方案。
目標
- ✅ 使用 Gemini AI 分析程式碼變更
- ✅ 根據開發規範進行審查
- ✅ 產生詳細的審查報告
- ✅ 在提交前快速進行自我檢查
技術棧
- 程式語言:Node.js (ES6+)
- AI 模型:Google Gemini 2.0 Flash
- 專案類型:React + TypeScript + Vite
實作步驟
步驟一:取得 Gemini API Key
首先需要取得 Gemini API Key:
- 前往 Google AI Studio
- 點擊 "Get API key"
- 建立新的 API key
- 複製 API key 備用
重要:免費版的 Gemini API 每天有 200 次請求的配額,對於一般團隊來說完全足夠。
步驟二:建立 Code Review 腳本
在專案根目錄建立 gemini_code_review.mjs:
#!/usr/bin/env node
import { execSync } from "child_process";
import fs from "fs";
import { GoogleGenAI } from "@google/genai";
import path from "path";
import { fileURLToPath } from "url";
import { config } from "dotenv";
// ES6 模組中取得 __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 載入 .env 檔案(本地開發用)
config();
console.log("✓ 已載入 .env 檔案");
// 初始化變數
const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
const conventionsPath = process.env.CODING_CONVENTIONS_PATH;
// 初始化 Gemini AI
const ai = new GoogleGenAI({ apiKey: GEMINI_API_KEY });
const model = "gemini-2.0-flash";
/**
* 取得 Git diff
*/
function getGitDiff() {
try {
const targetBranch =
process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME || "main";
// 先檢查是否有未 commit 的變更(用於本地測試)
let filesOutput = "";
try {
filesOutput = execSync("git diff --name-only", { encoding: "utf-8" });
} catch (error) {
// 忽略錯誤
}
if (filesOutput.trim()) {
// 有未 commit 的變更
console.log(" 檢測到未 commit 的變更");
const changedFiles = filesOutput
.trim()
.split("\n")
.filter((f) => f);
const diffOutput = execSync("git diff", { encoding: "utf-8" });
return { diff: diffOutput, changedFiles };
} else {
// 沒有未 commit 的變更,比較分支
console.log(` 比較當前分支與 origin/${targetBranch}`);
const filesOutput = execSync(
`git diff --name-only origin/${targetBranch}...HEAD`,
{ encoding: "utf-8" }
);
const changedFiles = filesOutput
.trim()
.split("\n")
.filter((f) => f);
const diffOutput = execSync(`git diff origin/${targetBranch}...HEAD`, {
encoding: "utf-8",
});
return { diff: diffOutput, changedFiles };
}
} catch (error) {
console.error("取得 git diff 時發生錯誤:", error.message);
return null;
}
}
/**
* 分析變更檔案的類型
*/
function analyzeFileTypes(files) {
const fileTypes = {
components: [],
hooks: [],
utils: [],
styles: [],
configs: [],
tests: [],
types: [],
other: [],
};
for (const file of files) {
if (!file) continue;
if (
file.toLowerCase().includes("component") ||
file.includes("/components/")
) {
fileTypes.components.push(file);
} else if (
file.toLowerCase().includes("hook") ||
file.includes("/hooks/")
) {
fileTypes.hooks.push(file);
} else if (
file.toLowerCase().includes("util") ||
file.includes("/utils/")
) {
fileTypes.utils.push(file);
} else if (file.match(/\.(css|scss|less|styled\.(ts|js))$/)) {
fileTypes.styles.push(file);
} else if (
file.toLowerCase().includes("config") ||
file.match(/\.config\.(js|ts)$/)
) {
fileTypes.configs.push(file);
} else if (file.includes("test") || file.includes("spec")) {
fileTypes.tests.push(file);
} else if (file.endsWith(".d.ts") || file.includes("/types/")) {
fileTypes.types.push(file);
} else {
fileTypes.other.push(file);
}
}
return fileTypes;
}
/**
* 使用 Gemini AI 分析程式碼
*/
async function analyzeWithGemini(codeDiff, fileTypes) {
// 讀取公司開發規範(如果有設定)
let codingConventions = "";
if (conventionsPath && fs.existsSync(conventionsPath)) {
try {
codingConventions = fs.readFileSync(conventionsPath, "utf-8");
console.log(` ✓ 已載入公司開發規範: ${path.basename(conventionsPath)}`);
} catch (error) {
console.log(` ⚠️ 無法讀取開發規範: ${error.message}`);
}
} else {
console.log(" ℹ️ 未設定開發規範路徑");
}
const fileSummary = [];
for (const [category, files] of Object.entries(fileTypes)) {
if (files.length > 0) {
fileSummary.push(`- ${category}: ${files.length} 個檔案`);
}
}
const fileSummaryText = fileSummary.join("\n");
// 組合 prompt
const prompt = `你是一個專業的前端程式碼審查專家,專精於 React、TypeScript、Vite 專案。
${
codingConventions
? `
## 📋 公司開發規範
請**嚴格遵守**以下開發規範來審查程式碼:
${codingConventions.substring(0, 20000)}
---
`
: ""
}
本次 MR 變更摘要:
${fileSummaryText}
請仔細審查以下的 Git diff,針對前端專案提供專業的分析報告。
**重點檢查項目:**
${codingConventions ? "🔴 **首要任務:檢查是否符合公司開發規範**\n\n" : ""}
🎯 **React 最佳實踐**
- 元件設計是否合理(單一職責、可重用性)
- Hooks 使用是否正確(依賴陣列、記憶化)
- Props 設計是否清晰
- 狀態管理是否恰當
⚡ **效能考量**
- 不必要的重渲染
- 記憶化的使用(useMemo, useCallback, React.memo)
🔒 **型別安全**
- TypeScript 型別定義是否完整
- 是否過度使用 any
🎨 **程式碼品質**
- 程式碼可讀性和維護性
- 命名是否清晰
- 錯誤處理是否完善
🐛 **潛在問題**
- 記憶體洩漏風險
- 非同步處理問題
🧪 **測試覆蓋**
- 是否需要補充測試
Git Diff(前 30000 字元):
\`\`\`
${codeDiff.substring(0, 30000)}
\`\`\`
**請用繁體中文回覆,使用 Markdown 格式,包含:**
${codingConventions ? "## 🔴 開發規範檢查\n\n" : ""}
## 📊 總體評分
## ✅ 優點
## ⚠️ 需要改進
## 🐛 潛在問題
## 💡 具體建議
## 🎯 行動項目
`;
try {
const response = await ai.models.generateContent({
model: model,
contents: prompt,
});
return response.text;
} catch (error) {
console.error("❌ 呼叫 Gemini API 時發生錯誤:", error.message);
if (error.message.includes("429") || error.message.includes("quota")) {
console.error("⏱️ 達到 Rate Limit,請稍後再試");
}
throw error;
}
}
/**
* 儲存審查報告
*/
function saveReviewReport(reviewText, fileTypes) {
const fileSummary = [];
let totalFiles = 0;
for (const [category, files] of Object.entries(fileTypes)) {
if (files.length > 0) {
totalFiles += files.length;
fileSummary.push(`- **${category}**: ${files.length} 個檔案`);
for (let i = 0; i < Math.min(5, files.length); i++) {
fileSummary.push(` - \`${files[i]}\``);
}
if (files.length > 5) {
fileSummary.push(` - ... 還有 ${files.length - 5} 個檔案`);
}
}
}
const fileSummaryText = fileSummary.join("\n");
const report = `# 🤖 Gemini AI Code Review Report
**專案**: React + Vite + TypeScript
**變更摘要**: 共 ${totalFiles} 個檔案
${fileSummaryText}
---
${reviewText}`;
fs.writeFileSync("code_review_report.md", report, "utf-8");
console.log("✅ 審查報告已儲存至 code_review_report.md");
}
/**
* 主程式
*/
async function main() {
console.log("=".repeat(70));
console.log("🚀 Gemini AI Code Review for Frontend (React/Vite/TypeScript)");
console.log("=".repeat(70));
if (!process.env.GEMINI_API_KEY) {
console.error("❌ 錯誤: 未設定 GEMINI_API_KEY 環境變數");
process.exit(1);
}
console.log("✓ GEMINI_API_KEY 已設定");
console.log("\n📝 正在取得程式碼變更...");
const result = getGitDiff();
if (!result || !result.diff || result.diff.trim().length === 0) {
console.log("ℹ️ 沒有偵測到程式碼變更,跳過審查");
process.exit(0);
}
const fileTypes = analyzeFileTypes(result.changedFiles);
const totalFiles = result.changedFiles.length;
console.log(`✓ 偵測到 ${totalFiles} 個檔案變更`);
console.log(`✓ 程式碼變更大小: ${result.diff.length.toLocaleString()} 字元`);
console.log("\n🔍 正在使用 Gemini 分析程式碼...");
try {
const review = await analyzeWithGemini(result.diff, fileTypes);
console.log("✓ 審查完成\n");
console.log("=".repeat(70));
console.log("📋 審查結果");
console.log("=".repeat(70));
console.log(review);
console.log("=".repeat(70));
saveReviewReport(review, fileTypes);
console.log("\n✅ 程式碼審查完成!");
} catch (error) {
console.error("❌ 審查過程發生錯誤:", error.message);
process.exit(1);
}
}
main();
步驟四:安裝必要的套件
npm install @google/genai dotenv
如果你的專案要使用 ES6 模組,在 package.json 中加入:
{
"type": "module",
"dependencies": {
"@google/genai": "^latest",
"dotenv": "^17.2.3"
}
}
步驟五:本地測試
在本地測試腳本:
- 建立
.env檔案:
GEMINI_API_KEY=你的_API_KEY
CI_MERGE_REQUEST_TARGET_BRANCH_NAME=brnach名稱
CODING_CONVENTIONS_PATH=/path/to/your/conventions.md
- 確保
.env在.gitignore中:
echo ".env" >> .gitignore
- 執行測試:
# 安裝套件
npm install
# 執行 code review
node gemini_code_review.mjs
進階功能:整合開發規範
如果你有自己的開發規範文件,可以讓 Gemini 根據這些規範來審查程式碼:
設定規範檔案路徑
在 .env 中加入:
CODING_CONVENTIONS_PATH=/path/to/conventions/README.md
這樣 Gemini 就會根據你的開發規範來審查程式碼了!
遇到的問題與解決方案
問題 1:Python vs Node.js
問題:一開始想用 Python 寫,但我的專案是 Node.js,覺得混用兩種語言不太好。
解決:改用 Node.js 的 ES6 語法編寫,與專案技術棧保持一致。
問題 2:選擇哪個 Gemini 模型
問題:文檔中有很多模型,不知道該用哪個。
解決:
- ❌
gemini-1.5-flash- 已淘汰 - ❌
gemini-2.0-flash-exp- 實驗版,配額太低 - ✅
gemini-2.0-flash- 穩定版,推薦使用
問題 3:API 配額限制
問題:擔心免費版配額不夠用。
解決:查看 Google AI Studio Rate Limits,發現免費版配額非常充裕:
| 限制 | 免費版上限 | 實際需求 |
|---|---|---|
| RPM (每分鐘請求) | 15 | 通常 < 5 |
| TPM (每分鐘 tokens) | 1,000,000 | 通常 < 50,000 |
| RPD (每天請求) | 200 | 通常 < 50 |
對於一般團隊來說,免費版完全足夠!
問題 4:dotenv 無法載入 .env
問題:執行時顯示「未安裝 dotenv」。
解決:
npm install dotenv
並確保在程式開頭正確 import:
import { config } from "dotenv";
config();
問題 5:選擇官方 SDK
問題:發現有 @google/genai 和 @google/generative-ai 兩個套件。
解決:兩個都可以用,但 @google/genai 是較新的版本,API 更簡潔:
// 使用 @google/genai(推薦)
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({ apiKey: API_KEY });
const response = await ai.models.generateContent({ model, contents: prompt });
成本分析
因 Google 提供的方案 可能隨時有變化,使用前,需再次確認免費額度
免費版限制
- RPM: 15 次/分鐘
- TPM: 1,000,000 tokens/分鐘
- RPD: 200 次/天
付費版價格
如果超過免費限制,付費版價格也很便宜:
| 項目 | 價格 |
|---|---|
| 輸入 | $0.075 / 百萬 tokens |
| 輸出 | $0.30 / 百萬 tokens |
實際成本範例:
- 假設每次 code review 使用 30,000 輸入 + 2,000 輸出 tokens
- 成本 = (30,000/1,000,000 × $0.075) + (2,000/1,000,000 × $0.30)
- 每次約 $0.003 USD(不到 0.1 元台幣)
使用效果
審查報告範例
Gemini 會產生一份詳細的 Markdown 報告,包含:
# 🤖 Gemini AI Code Review Report
## 📊 總體評分
8/10 分
## ✅ 優點
- TypeScript 型別定義完整
- 元件職責清晰,符合單一職責原則
- 使用 useMemo 進行適當的效能優化
## ⚠️ 需要改進
1. **useState 依賴陣列缺失**
- ExpenseList.tsx 第 45 行的 useEffect 缺少依賴項
- 建議加入 `[expenses]` 作為依賴
2. **錯誤處理不足**
- API 呼叫缺少 try-catch
- 建議加入錯誤邊界處理
## 🐛 潛在問題
- 可能造成記憶體洩漏:useEffect 中的 subscription 沒有清理
## 💡 具體建議
[程式碼範例和改進建議...]
## 🎯 行動項目
1. 立即修正 useEffect 依賴陣列
2. 補充錯誤處理邏輯
3. 新增單元測試
實際數據
實測中:
- ✅ 每次審查耗時:約 10-15 秒
- ✅ 審查準確度:約 85-90%(能抓出大部分問題)
- ✅ 誤報率:約 5-10%(偶爾會有誤判)
- ✅ 配額使用:每天約 20-30 次(遠低於上限)
最佳實踐
1. 結合人工審查
AI 審查應該是輔助工具,不應完全取代人工 review:
AI Review (自助檢查) → Human Review (必要) → Commit
2. 定期檢查配額
定期查看 Rate Limits 頁面,確保沒有超過限制。
3. 適當的 Prompt 調整
根據需求調整 prompt:
- 強調特定的編碼風格
- 加入個人或團隊的開發規範
- 針對特定技術棧優化
4. 保存審查歷史
將審查報告保存在固定的資料夾中,方便日後查閱:
# 修改腳本,將報告保存到特定資料夾
mkdir -p code_reviews
mv code_review_report.md code_reviews/review_$(date +%Y%m%d_%H%M%S).md
未來改進方向
1. 客製化評分標準
根據個人或團隊需求,客製化評分權重和閾值。
2. 多語言支援
擴展到其他語言的專案(後端、行動端等)。
3. 審查報告統計
彙整歷史審查數據,產生程式碼品質趨勢報告。
4. 整合到 IDE
開發 VSCode 擴充功能,讓 AI 審查更加方便。
5. Git Hook 整合
在 pre-commit 階段自動執行審查,提早發現問題。
結論
使用 Gemini AI 進行本地 code review 是一個非常實用的實踐:
✅ 優點
- 提升效率:在提交前快速進行自我檢查
- 一致性:AI 審查標準一致,不受情緒影響
- 學習工具:從 AI 建議中學習最佳實踐
- 成本低廉:免費版配額充足,付費版也很便宜
- 容易使用:只需簡單的腳本設定
⚠️ 注意事項
- 不能完全取代人工:AI 會有誤判,仍需人工確認
- 需要調教:Prompt 需要根據需求調整
- 配額管理:要留意 API 使用量
- 敏感資訊:確保不要把機密程式碼送給 AI
💡 適合的使用情境
- ✅ 個人專案的程式碼檢查
- ✅ 提交前的自我審查
- ✅ 學習程式設計最佳實踐
- ✅ 快速發現潛在問題
參考資源
原始碼
完整的程式碼可以在我的 GitHub 找到:連結