Skip to content
Anonymous

Senior Engineering Review:資深工程師的程式碼審計指南

深入解析資深工程師在程式碼審查中的檢查項目,從程式碼品質到架構決策,了解如何寫出可維護、高品質的程式碼。

#engineering #code-review #best-practices #architecture

程式碼品質不只是「能動就好」。資深工程師在審查程式碼時,會從可維護性、可測試性、效能、安全性等多個面向進行評估。本文將詳細解析資深工程師審查程式碼時關注的各個項目。


什麼是 Senior Engineering Review?

Senior Engineering Review 是一套系統化的程式碼審計流程,用於評估:

  • 程式碼品質:可讀性、可維護性、一致性
  • 架構設計:模組化、耦合度、擴展性
  • 最佳實踐:錯誤處理、型別安全、文件化
  • 效能考量:資源使用、查詢效率、快取策略

一、程式碼品質標準(Code Quality Standards)

1. 錯誤處理(Error Handling)

項目說明
檢查內容是否正確處理所有可能的錯誤情境
目的避免程式崩潰、提供有意義的錯誤訊息

為什麼重要?

未處理的錯誤可能導致:

  • 程式無預警崩潰
  • 使用者體驗受損
  • 安全漏洞(錯誤訊息洩露敏感資訊)
  • 難以除錯的問題

最佳實踐

// ❌ 不好:忽略錯誤
try {
  await saveData(data);
} catch (e) {
  // 吞掉錯誤,什麼都不做
}

// ❌ 不好:通用錯誤處理
try {
  await saveData(data);
} catch (e) {
  console.log('Error');  // 沒有有用資訊
}

// ✅ 好:具體處理錯誤
try {
  await saveData(data);
} catch (e) {
  if (e instanceof ValidationError) {
    return { error: 'Invalid data format', details: e.message };
  }
  if (e instanceof NetworkError) {
    return { error: 'Network unavailable, please retry' };
  }
  // 記錄未預期的錯誤供日後分析
  logger.error('Unexpected error in saveData', { error: e, data });
  return { error: 'An unexpected error occurred' };
}

優缺點

優點缺點
程式更穩定需要較多程式碼
易於除錯需要了解可能的錯誤類型
使用者體驗更好可能過度複雜化

2. 型別安全(Type Safety)

項目說明
檢查內容是否使用嚴格的 TypeScript 型別
目的在編譯時期捕捉錯誤,提升程式碼可靠性

常見問題

// ❌ 使用 any 型別
function processData(data: any): any {
  return data.items.map((item: any) => item.value);
}

// ✅ 使用明確型別
interface Item {
  id: string;
  value: number;
}

interface Data {
  items: Item[];
}

function processData(data: Data): number[] {
  return data.items.map(item => item.value);
}

何時使用 any?

情境建議
快速原型開發可接受,但要標註 TODO
第三方 API 回應使用 unknown + type guard
複雜泛型考慮 as 斷言,但加註解
無法改變的舊程式碼逐步遷移

優缺點

優點缺點
編譯時發現錯誤學習曲線
IDE 自動補全型別定義需要維護
程式碼自文件化第三方庫可能缺少型別

3. 命名規範(Naming Conventions)

項目說明
檢查內容變數、函式、類別的命名是否描述性且一致
目的提升程式碼可讀性

命名原則

// ❌ 不好的命名
const d = new Date();
const arr = users.filter(u => u.a);
function proc(x) { ... }

// ✅ 好的命名
const currentDate = new Date();
const activeUsers = users.filter(user => user.isActive);
function processUserRegistration(userData) { ... }

命名慣例

類型慣例範例
變數camelCaseuserName, isActive
函式camelCase, 動詞開頭getUserById, validateInput
常數UPPER_SNAKE_CASEMAX_RETRY_COUNT, API_BASE_URL
類別PascalCaseUserService, HttpClient
介面PascalCaseUserData, ApiResponse

優缺點

優點缺點
程式碼自解釋名稱可能變長
減少註解需求需要團隊一致性
易於搜尋有時難以想到好名稱

4. 函式設計(Function Design)

項目說明
檢查內容函式是否保持小型且單一職責
目的提升可測試性和可重用性

單一職責原則

// ❌ 做太多事的函式
async function handleUserRegistration(data) {
  // 驗證
  if (!data.email || !data.password) throw new Error('...');
  if (data.password.length < 8) throw new Error('...');
  
  // 儲存
  const user = await db.users.create(data);
  
  // 發送歡迎郵件
  await sendEmail(user.email, 'Welcome!', welcomeTemplate);
  
  // 記錄分析
  await analytics.track('user_registered', { userId: user.id });
  
  return user;
}

// ✅ 拆分成小函式
async function handleUserRegistration(data) {
  validateRegistrationData(data);
  const user = await createUser(data);
  await sendWelcomeEmail(user);
  await trackRegistration(user);
  return user;
}

function validateRegistrationData(data) {
  if (!data.email || !data.password) {
    throw new ValidationError('Email and password are required');
  }
  if (data.password.length < 8) {
    throw new ValidationError('Password must be at least 8 characters');
  }
}

函式長度指引

指標建議
行數< 30 行
參數數量≤ 3 個(多了用 object)
巢狀深度≤ 3 層
認知複雜度使用工具測量

5. 註解規範(Comments)

項目說明
檢查內容註解是否有價值,而非重複程式碼
目的解釋「為什麼」而非「做什麼」

好的註解 vs 壞的註解

// ❌ 重複程式碼的註解
// 設定使用者年齡為 18
user.age = 18;

// ❌ 過時的註解
// 回傳使用者名稱
function getUserEmail() { ... }

// ✅ 解釋業務邏輯
// 法規要求:未滿 18 歲不能註冊
if (user.age < 18) {
  throw new Error('Must be 18 or older');
}

// ✅ 解釋技術決策
// 使用 setTimeout 而非 requestAnimationFrame
// 因為需要精確的時間控制,而非畫面更新同步
setTimeout(callback, 100);

何時需要註解

情境需要註解
複雜演算法
業務規則
暫時解法(Workaround)✅ 加上 TODO
非直覺的設計決策
簡單的 CRUD 操作

二、程式碼審查清單(Code Review Checklist)

1. 自我審查(Self-Review)

項目說明
檢查內容提交前是否自己先審查過
目的減少明顯的錯誤,節省審查者時間

自我審查要點

  • 閱讀自己的 diff,像審查別人的程式碼一樣
  • 確認沒有遺漏的 console.log
  • 確認沒有註解掉的程式碼
  • 確認所有 TODO 都有對應的 issue

2. 測試涵蓋(Test Coverage)

項目說明
檢查內容新功能是否有對應的測試
目的確保程式碼正確性,防止回歸
// 功能程式碼
function calculateDiscount(price: number, percentage: number): number {
  if (percentage < 0 || percentage > 100) {
    throw new Error('Invalid percentage');
  }
  return price * (1 - percentage / 100);
}

// 對應的測試
describe('calculateDiscount', () => {
  it('should calculate 20% discount correctly', () => {
    expect(calculateDiscount(100, 20)).toBe(80);
  });
  
  it('should throw for negative percentage', () => {
    expect(() => calculateDiscount(100, -10)).toThrow();
  });
  
  it('should throw for percentage over 100', () => {
    expect(() => calculateDiscount(100, 150)).toThrow();
  });
});

3. 移除除錯程式碼

項目說明
檢查內容是否遺留 console.log、debugger 等
目的保持程式碼整潔,避免效能影響
# 可以使用 git hooks 自動檢查
# .git/hooks/pre-commit
git diff --cached | grep -E "console\.(log|debug|info)" && \
  echo "Error: console.log found" && exit 1

4. 安全考量

項目說明
檢查內容是否有硬編碼的密鑰、未驗證的輸入
目的防止安全漏洞
// ❌ 硬編碼密鑰
const API_KEY = 'sk_live_REPLACEMENT_TOKEN';

// ✅ 使用環境變數
const API_KEY = process.env.API_KEY;

// ❌ 未驗證的輸入
const query = `SELECT * FROM users WHERE id = ${req.params.id}`;

// ✅ 參數化查詢
const query = 'SELECT * FROM users WHERE id = $1';
const result = await db.query(query, [req.params.id]);

三、架構決策(Architecture Decisions)

架構決策紀錄(ADR)

項目說明
檢查內容重大技術決策是否有文件記錄
目的保留決策脈絡,方便未來參考

ADR 模板

## 決策:[決策標題]

### 背景
[為什麼需要做這個決策?]

### 考慮的選項
1. **選項 A** - [說明]
   - 優點:...
   - 缺點:...

2. **選項 B** - [說明]
   - 優點:...
   - 缺點:...

### 決策
[選擇了哪個選項,為什麼]

### 後果
[這個決策的影響和權衡]

四、效能考量(Performance Considerations)

1. 資料庫查詢

項目說明
檢查內容是否有 N+1 查詢問題
目的避免效能瓶頸
// ❌ N+1 問題
const posts = await db.posts.findAll();
for (const post of posts) {
  post.author = await db.users.findById(post.authorId);  // N 次查詢
}

// ✅ JOIN 或預先載入
const posts = await db.posts.findAll({
  include: [{ model: User, as: 'author' }]
});

2. 前端效能

項目說明
檢查內容是否考慮 bundle size、延遲載入
目的提升使用者體驗
// ❌ 全部載入
import { everything } from 'huge-library';

// ✅ 按需載入
import { specificFunction } from 'huge-library/specificModule';

// ✅ 動態載入
const HeavyComponent = lazy(() => import('./HeavyComponent'));

3. 記憶體管理

項目說明
檢查內容是否有記憶體洩漏風險
目的避免長時間運行的應用程式效能下降
// ❌ 忘記清理
useEffect(() => {
  const interval = setInterval(updateData, 1000);
  // 沒有 cleanup
}, []);

// ✅ 正確清理
useEffect(() => {
  const interval = setInterval(updateData, 1000);
  return () => clearInterval(interval);
}, []);

五、文件要求(Documentation Requirements)

何時需要更新文件

變更類型需更新的文件
新功能README、CHANGELOG
API 變更API 文件、遷移指南
架構變更架構文件、ADR
設定變更安裝指南、環境設定

JSDoc/TSDoc

/**
 * 計算兩個日期之間的工作天數
 * 
 * @param startDate - 開始日期
 * @param endDate - 結束日期
 * @param holidays - 國定假日列表
 * @returns 工作天數
 * 
 * @example
 * ```ts
 * const days = getWorkingDays(
 *   new Date('2026-01-01'),
 *   new Date('2026-01-31'),
 *   ['2026-01-01']
 * );
 * ```
 */
function getWorkingDays(
  startDate: Date,
  endDate: Date,
  holidays: string[] = []
): number {
  // ...
}

六、反模式(Anti-Patterns)

1. 過早優化

問題說明
什麼是在沒有效能資料的情況下進行優化
為什麼不好浪費時間、增加複雜度
如何避免先測量,再優化
// ❌ 過早優化:沒測量就假設這裡有效能問題
const cache = new Map();
function getData(id) {
  if (cache.has(id)) return cache.get(id);
  const data = fetchData(id);
  cache.set(id, data);
  return data;
}

// ✅ 先測量
// 發現 fetchData 確實在高流量時成為瓶頸後,再加入快取

2. 複製貼上程式設計

問題說明
什麼是複製相似程式碼而非抽取共用邏輯
為什麼不好維護困難、bug 會在多處出現
如何避免遵循 DRY 原則

3. 魔術數字/字串

問題說明
什麼是程式碼中的硬編碼數值
為什麼不好意義不明、難以修改
如何避免使用常數
// ❌ 魔術數字
if (user.age >= 18 && user.posts > 5) {
  grantAccess();
}

// ✅ 有意義的常數
const MINIMUM_AGE = 18;
const MINIMUM_POSTS_FOR_ACCESS = 5;

if (user.age >= MINIMUM_AGE && user.posts > MINIMUM_POSTS_FOR_ACCESS) {
  grantAccess();
}

4. 上帝類別(God Class)

問題說明
什麼是一個類別做太多事情
為什麼不好難以理解、測試、維護
如何避免單一職責原則,拆分成小模組

七、審查前檢查清單

程式碼品質

  • 所有錯誤都有適當處理
  • 沒有使用 any 型別(或有正當理由)
  • 變數和函式命名具描述性
  • 函式保持小型且單一職責
  • 註解解釋「為什麼」而非「做什麼」

提交前

  • 已自我審查程式碼
  • 新功能有對應測試
  • 移除所有 console.log 和除錯程式碼
  • 沒有硬編碼的密鑰或敏感資訊

文件

  • README 更新(如有 API/使用變更)
  • CHANGELOG 更新
  • 公開函式有 JSDoc 註解

總結

類別重點項目
程式碼品質錯誤處理、型別安全、命名、函式設計
審查流程自我審查、測試、移除除錯碼、安全考量
架構ADR、模組化、職責分離
效能N+1 查詢、bundle size、記憶體管理
文件README、CHANGELOG、JSDoc
反模式過早優化、複製貼上、魔術數字、上帝類別

成為資深工程師不只是寫出「能動」的程式碼,更是寫出「可維護」、「可測試」、「可理解」的程式碼。持續進行程式碼審查和自我反思,是提升程式碼品質的關鍵。


延伸閱讀