# 模組 3：軟體架構入門 — 從蓋茅屋到建高樓

## 💎 AI 時代的核心洞察

> **AI 可以蓋房子，但只有你能設計藍圖**

### 📊 架構設計的價值

```
沒有架構設計                  有架構設計
────────────────────────      ────────────────────────
AI 生成一堆程式碼              你定義模組結構
→ 程式碼混亂                  → AI 按模組生成
→ 難以維護                    → 清晰易懂
→ 無法擴展                    → 易於擴展
→ 重複造輪子                  → 可重用組件
```

### 🎯 本模組教你的核心能力

**AI 可以寫程式碼，但無法設計系統架構**

- 🏗️ **系統拆解**：如何將大問題分解成小問題
- 📐 **介面設計**：模組之間如何溝通
- 🔄 **依賴管理**：避免模組間的混亂依賴
- 📦 **可重用設計**：一次設計，多次使用

**實際案例：**
```
場景：開發 AI 內容工作室

❌ 沒有架構：
「AI，幫我做一個內容工作室」
→ AI 生成 5000 行混亂的程式碼
→ 你看不懂，無法維護

✅ 有架構：
你設計：風格分析模組 + 內容生成模組 + 知識管理模組
→ AI 分別實作每個模組
→ 清晰、可維護、可擴展
```

---

## 🎯 核心目標
學習如何將一個大型複雜的專案，拆解成多個獨立、可管理的「規格模組」，並設計清晰的模組間介面。

## 📖 理論基礎

### 為什麼需要模組化？

**問題：** 隨著專案成長，程式碼變成「大泥球」
- 🔴 所有程式碼混在一起
- 🔴 修改一處影響全局
- 🔴 難以理解和維護
- 🔴 無法重用程式碼

**解決：** 模組化設計
- ✅ 關注點分離
- ✅ 獨立開發測試
- ✅ 易於維護擴展
- ✅ 程式碼可重用

### 模組化的好處

```
單一大檔案（3000 行）         vs        模組化設計（10 個檔案，各 300 行）
     ❌ 難以閱讀                              ✅ 容易理解
     ❌ 難以測試                              ✅ 獨立測試
     ❌ 改一處影響全部                        ✅ 隔離影響範圍
     ❌ 無法重用                              ✅ 可重用組件
```

---

## 🛠️ 實戰案例：個人數位筆記系統

### 步驟 1：識別功能模組

**系統需求：**
一個個人用的數位筆記系統，支援快速記錄想法、標籤分類、全文搜尋、每日回顧等功能。

**模組劃分：**

```
數位筆記系統
├── 📝 筆記管理 (Notes)
│   ├── 建立筆記
│   ├── 編輯筆記
│   ├── 刪除筆記
│   └── 查看筆記
│
├── 🏷️ 標籤管理 (Tags)
│   ├── 建立標籤
│   ├── 標籤分類
│   ├── 標籤統計
│   └── 標籤顏色
│
├── 🔍 搜尋功能 (Search)
│   ├── 全文搜尋
│   ├── 標籤搜尋
│   ├── 日期搜尋
│   └── 搜尋歷史
│
├── 📊 統計分析 (Analytics)
│   ├── 筆記數量統計
│   ├── 標籤使用統計
│   ├── 寫作習慣分析
│   └── 每日回顧
│
└── ⚙️ 設定管理 (Settings)
    ├── 個人偏好
    ├── 匯出/匯入
    └── 備份功能
```

---

### 步驟 2：設計專案結構

```
digital-notebook/
├── specs/                      # 📋 規格文件
│   ├── notes.spec.md
│   ├── tags.spec.md
│   ├── search.spec.md
│   ├── analytics.spec.md
│   └── settings.spec.md
│
├── tests/                      # 🧪 測試程式
│   ├── notes/
│   │   ├── create-note.test.js
│   │   ├── edit-note.test.js
│   │   └── delete-note.test.js
│   ├── tags/
│   │   └── tag-manager.test.js
│   ├── search/
│   │   └── search-engine.test.js
│   └── analytics/
│       └── stats.test.js
│
├── src/                        # 💻 原始碼
│   ├── notes/
│   │   ├── note.js            # 筆記資料模型
│   │   ├── note-manager.js    # 筆記管理邏輯
│   │   └── index.js           # 匯出介面
│   │
│   ├── tags/
│   │   ├── tag.js
│   │   ├── tag-manager.js
│   │   └── index.js
│   │
│   ├── search/
│   │   ├── search-engine.js
│   │   ├── indexer.js
│   │   └── index.js
│   │
│   ├── analytics/
│   │   ├── stats-calculator.js
│   │   └── index.js
│   │
│   └── shared/                 # 🔧 共用工具
│       ├── validators.js
│       ├── date-utils.js
│       ├── errors.js
│       └── storage.js
│
├── package.json
└── README.md
```

---

### 步驟 3：定義模組間的 API

#### 範例 1：筆記模組對外 API

```javascript
// src/notes/index.js

/**
 * 筆記管理模組 API
 * 其他模組透過這個介面與筆記模組互動
 */

const NoteManager = require('./note-manager');

// 建立筆記管理器實例
const noteManager = new NoteManager();

module.exports = {
  /**
   * 建立新筆記
   * @param {Object} noteData - 筆記資料
   * @returns {Object} 建立的筆記
   */
  createNote: (noteData) => noteManager.create(noteData),
  
  /**
   * 取得單一筆記
   * @param {string} noteId - 筆記 ID
   * @returns {Object} 筆記物件
   */
  getNote: (noteId) => noteManager.getById(noteId),
  
  /**
   * 更新筆記
   * @param {string} noteId - 筆記 ID
   * @param {Object} updates - 更新內容
   * @returns {Object} 更新後的筆記
   */
  updateNote: (noteId, updates) => noteManager.update(noteId, updates),
  
  /**
   * 刪除筆記
   * @param {string} noteId - 筆記 ID
   * @returns {boolean} 是否成功刪除
   */
  deleteNote: (noteId) => noteManager.delete(noteId),
  
  /**
   * 取得所有筆記
   * @returns {Array} 筆記陣列
   */
  getAllNotes: () => noteManager.getAll(),
  
  /**
   * 依標籤取得筆記
   * @param {string} tagId - 標籤 ID
   * @returns {Array} 筆記陣列
   */
  getNotesByTag: (tagId) => noteManager.getByTag(tagId)
};
```

#### 範例 2：搜尋模組使用筆記 API

```javascript
// src/search/search-engine.js

// 透過公開的 API 與筆記模組互動
const noteAPI = require('../notes');

class SearchEngine {
  /**
   * 全文搜尋筆記
   */
  searchNotes(keyword) {
    // 使用筆記 API 取得所有筆記
    const allNotes = noteAPI.getAllNotes();
    
    // 搜尋包含關鍵字的筆記
    return allNotes.filter(note => 
      note.title.includes(keyword) || 
      note.content.includes(keyword)
    );
  }
  
  /**
   * 依標籤搜尋
   */
  searchByTag(tagId) {
    // 使用筆記 API 的標籤搜尋功能
    return noteAPI.getNotesByTag(tagId);
  }
}

module.exports = SearchEngine;
```

---

## 🎯 關注點分離原則 (SoC)

### 什麼是關注點分離？

**定義：** 每個模組只負責一件事，且負責好這件事。

### 實戰範例：筆記建立流程

❌ **違反 SoC（全部混在一起）：**

```javascript
function createNote(title, content, tags) {
  // 驗證輸入
  if (!title || title.length < 1) {
    throw new Error('標題不能為空');
  }
  if (title.length > 100) {
    throw new Error('標題太長');
  }
  
  // 處理標籤
  const tagObjects = [];
  tags.forEach(tagName => {
    // 檢查標籤是否已存在
    let tag = findTagByName(tagName);
    if (!tag) {
      // 建立新標籤
      tag = { id: generateId(), name: tagName, color: randomColor() };
      saveTag(tag);
    }
    tagObjects.push(tag);
  });
  
  // 建立筆記
  const note = {
    id: generateId(),
    title: title,
    content: content,
    tags: tagObjects,
    createdAt: new Date(),
    wordCount: content.split(' ').length
  };
  
  // 儲存筆記
  saveToDatabase(note);
  
  // 更新搜尋索引
  updateSearchIndex(note);
  
  // 更新統計
  incrementNoteCount();
  updateTagStats(tagObjects);
  
  return note;
}
```

**問題：**
- 一個函數做太多事
- 難以測試（需要準備資料庫、搜尋引擎等）
- 修改任何功能都可能影響其他部分

---

✅ **遵循 SoC（各司其職）：**

```javascript
// src/notes/note-manager.js
const validators = require('../shared/validators');
const tagAPI = require('../tags');
const searchAPI = require('../search');
const analyticsAPI = require('../analytics');

class NoteManager {
  /**
   * 建立筆記（只負責筆記的核心邏輯）
   */
  async create(noteData) {
    // 1. 驗證（委派給驗證工具）
    const validation = validators.validateNote(noteData);
    if (!validation.isValid) {
      throw new Error(validation.errors[0]);
    }
    
    // 2. 處理標籤（委派給標籤模組）
    const tags = await tagAPI.processTagsForNote(noteData.tags);
    
    // 3. 建立筆記物件（自己的責任）
    const note = {
      id: this.generateId(),
      title: noteData.title,
      content: noteData.content,
      tags: tags,
      createdAt: new Date(),
      wordCount: this.calculateWordCount(noteData.content)
    };
    
    // 4. 儲存筆記（自己的責任）
    await this.storage.save(note);
    
    // 5. 更新搜尋索引（委派給搜尋模組）
    await searchAPI.indexNote(note);
    
    // 6. 更新統計（委派給統計模組）
    await analyticsAPI.recordNoteCreated(note);
    
    return note;
  }
  
  // 內部輔助方法
  generateId() {
    return `note_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
  
  calculateWordCount(content) {
    return content.split(/\s+/).filter(word => word.length > 0).length;
  }
}

module.exports = NoteManager;
```

**好處：**
- ✅ 每個模組專注於自己的領域
- ✅ 容易測試（可以 mock 其他服務）
- ✅ 容易維護（修改不影響其他模組）
- ✅ 容易重用（服務可在其他地方使用）

---

## 🔗 模組間通訊設計

### 方法 1：直接引用（適合小型專案）

```javascript
// src/analytics/stats-calculator.js
const noteAPI = require('../notes');
const tagAPI = require('../tags');

class StatsCalculator {
  getDailySummary(date) {
    const notes = noteAPI.getAllNotes();
    const todayNotes = notes.filter(n => isSameDay(n.createdAt, date));
    
    return {
      totalNotes: todayNotes.length,
      totalWords: todayNotes.reduce((sum, n) => sum + n.wordCount, 0),
      tags: this.getTagStats(todayNotes)
    };
  }
  
  getTagStats(notes) {
    // 統計標籤使用次數
    const tagCount = {};
    notes.forEach(note => {
      note.tags.forEach(tag => {
        tagCount[tag.id] = (tagCount[tag.id] || 0) + 1;
      });
    });
    return tagCount;
  }
}
```

**優點：** 簡單直接  
**缺點：** 緊耦合，難以替換實作

---

### 方法 2：依賴注入（適合中型專案）

```javascript
// src/analytics/stats-calculator.js
class StatsCalculator {
  constructor(noteService, tagService) {
    this.noteService = noteService;
    this.tagService = tagService;
  }
  
  getDailySummary(date) {
    const notes = this.noteService.getAllNotes();
    const todayNotes = notes.filter(n => isSameDay(n.createdAt, date));
    
    return {
      totalNotes: todayNotes.length,
      totalWords: todayNotes.reduce((sum, n) => sum + n.wordCount, 0)
    };
  }
}

// 使用時注入依賴
const noteService = require('../notes');
const tagService = require('../tags');
const statsCalculator = new StatsCalculator(noteService, tagService);

module.exports = statsCalculator;
```

**優點：** 
- ✅ 鬆耦合，易於測試
- ✅ 可以輕鬆替換不同實作

**測試時的好處：**
```javascript
// tests/analytics/stats.test.js
test('應該正確計算每日統計', () => {
  // 建立 mock 的筆記服務
  const mockNoteService = {
    getAllNotes: () => [
      { id: '1', createdAt: new Date('2024-12-20'), wordCount: 100 },
      { id: '2', createdAt: new Date('2024-12-20'), wordCount: 200 }
    ]
  };
  
  // 注入 mock 服務
  const calculator = new StatsCalculator(mockNoteService, null);
  
  const summary = calculator.getDailySummary(new Date('2024-12-20'));
  
  expect(summary.totalNotes).toBe(2);
  expect(summary.totalWords).toBe(300);
});
```

---

### 方法 3：事件驅動（適合需要解耦的場景）

```javascript
// src/shared/event-bus.js
const EventEmitter = require('events');
const eventBus = new EventEmitter();

module.exports = eventBus;
```

```javascript
// src/notes/note-manager.js
const eventBus = require('../shared/event-bus');

class NoteManager {
  async create(noteData) {
    // 建立筆記
    const note = { /* ... */ };
    await this.storage.save(note);
    
    // 發出事件（不直接調用其他模組）
    eventBus.emit('note:created', note);
    
    return note;
  }
}
```

```javascript
// src/search/indexer.js
const eventBus = require('../shared/event-bus');

// 監聽筆記建立事件，自動更新索引
eventBus.on('note:created', (note) => {
  indexNote(note);
});
```

```javascript
// src/analytics/tracker.js
const eventBus = require('../shared/event-bus');

// 監聽筆記建立事件，更新統計
eventBus.on('note:created', (note) => {
  updateStats(note);
});
```

**優點：** 
- ✅ 完全解耦
- ✅ 容易新增功能（只需監聽事件）

**缺點：** 
- ❌ 流程較難追蹤
- ❌ 除錯較困難

---

## 📋 完整實戰範例：標籤模組

### 規格文件

```markdown
# specs/tags.spec.md

## 模組目標
提供標籤管理功能，支援筆記的分類和組織。

## 使用者故事

### US-001: 建立標籤
身為筆記使用者，我想要建立自訂標籤，以便於分類我的筆記。

## API 設計

### createTag(tagData)
建立新標籤
- **輸入：** `{ name: string, color: string }`
- **輸出：** `{ id, name, color, createdAt }`
- **錯誤：** 標籤名稱重複、無效的顏色代碼

### getTag(tagId)
取得單一標籤
- **輸入：** `tagId: string`
- **輸出：** 標籤物件或 null

### getAllTags()
取得所有標籤
- **輸出：** 標籤陣列

### deleteTag(tagId)
刪除標籤
- **輸入：** `tagId: string`
- **輸出：** `{ success: boolean }`
- **規則：** 刪除標籤時，相關筆記的標籤也要移除
```

### 實作

```javascript
// src/tags/tag-manager.js
const validators = require('../shared/validators');
const eventBus = require('../shared/event-bus');

class TagManager {
  constructor(storage) {
    this.storage = storage || require('../shared/storage');
    this.tags = [];
  }

  /**
   * 建立新標籤
   */
  createTag(tagData) {
    // 驗證
    if (!tagData.name || tagData.name.trim().length === 0) {
      throw new Error('標籤名稱不能為空');
    }
    
    // 檢查是否重複
    if (this.findByName(tagData.name)) {
      throw new Error('標籤名稱已存在');
    }
    
    // 建立標籤
    const tag = {
      id: `tag_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      name: tagData.name.trim(),
      color: tagData.color || this.randomColor(),
      createdAt: new Date().toISOString()
    };
    
    this.tags.push(tag);
    
    // 發出事件
    eventBus.emit('tag:created', tag);
    
    return tag;
  }

  /**
   * 取得所有標籤
   */
  getAllTags() {
    return [...this.tags]; // 回傳副本，避免外部修改
  }

  /**
   * 依名稱搜尋標籤
   */
  findByName(name) {
    return this.tags.find(tag => tag.name === name);
  }

  /**
   * 刪除標籤
   */
  deleteTag(tagId) {
    const index = this.tags.findIndex(tag => tag.id === tagId);
    
    if (index === -1) {
      return { success: false, error: '標籤不存在' };
    }
    
    const deletedTag = this.tags[index];
    this.tags.splice(index, 1);
    
    // 發出事件，讓筆記模組移除相關標籤
    eventBus.emit('tag:deleted', deletedTag);
    
    return { success: true };
  }

  /**
   * 產生隨機顏色
   */
  randomColor() {
    const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8'];
    return colors[Math.floor(Math.random() * colors.length)];
  }
}

module.exports = TagManager;
```

---

## 🎯 保持程式整潔的黃金法則

### YAGNI 原則：You Aren't Gonna Need It

> **「不要預做判斷，不要過度設想」**

#### 什麼是 YAGNI？

**核心概念：** 只實作當前規格需要的功能，不要為「可能」需要的功能預做準備。

#### 常見的過度設計陷阱

❌ **錯誤做法：過度設想**
```javascript
// 規格只要求：儲存筆記標題和內容
class Note {
  constructor(title, content) {
    this.title = title;
    this.content = content;
    this.tags = [];           // ❌ 規格沒要求
    this.attachments = [];    // ❌ 規格沒要求
    this.collaborators = [];  // ❌ 規格沒要求
    this.version = 1;         // ❌ 規格沒要求
    this.history = [];        // ❌ 規格沒要求
    this.permissions = {};    // ❌ 規格沒要求
  }
  
  // 實作一堆「可能」會用到的方法
  addCollaborator() { }      // ❌ 規格沒要求
  rollbackVersion() { }      // ❌ 規格沒要求
  setPermission() { }        // ❌ 規格沒要求
}
```

**問題：**
- 程式碼變複雜
- 維護成本增加
- 可能永遠用不到
- 浪費開發時間

---

✅ **正確做法：根據規格實作**
```javascript
// 規格只要求：儲存筆記標題和內容
class Note {
  constructor(title, content) {
    this.title = title;
    this.content = content;
    this.createdAt = new Date();
  }
}
```

**優點：**
- 程式碼簡潔
- 易於理解
- 易於測試
- 快速完成

**當規格需要標籤功能時，再加：**
```javascript
class Note {
  constructor(title, content) {
    this.title = title;
    this.content = content;
    this.createdAt = new Date();
    this.tags = [];  // ✅ 現在規格需要了
  }
  
  addTag(tag) {     // ✅ 現在規格需要了
    this.tags.push(tag);
  }
}
```

---

### 預做多少？什麼做？什麼不做？

#### 這是經驗，也是要學習的

**判斷標準：**

```
1. 看規格
   ├─ 規格明確要求 → 做 ✅
   ├─ 規格暗示需要 → 確認後做 ⚠️
   └─ 規格沒提到 → 不做 ❌

2. 看專案階段
   ├─ MVP 階段 → 最小可用功能 ✅
   ├─ 成長階段 → 根據實際需求擴展 ✅
   └─ 成熟階段 → 考慮擴展性 ✅

3. 看團隊規模
   ├─ 個人專案 → 簡單直接 ✅
   ├─ 小團隊 → 適度設計 ✅
   └─ 大團隊 → 完整架構 ✅
```

---

### 實戰案例：筆記系統的演進

#### 階段 1：MVP（第 1 週）
**規格：** 能建立和查看筆記

```javascript
// 只做規格要求的
class NoteManager {
  constructor() {
    this.notes = [];
  }
  
  create(title, content) {
    const note = { id: Date.now(), title, content };
    this.notes.push(note);
    return note;
  }
  
  getAll() {
    return this.notes;
  }
}
```

**不做：**
- ❌ 標籤系統（規格沒要求）
- ❌ 搜尋功能（規格沒要求）
- ❌ 權限管理（規格沒要求）

---

#### 階段 2：新需求（第 2 週）
**新規格：** 需要標籤功能

```javascript
// 現在加入標籤
class NoteManager {
  constructor() {
    this.notes = [];
  }
  
  create(title, content, tags = []) {  // ✅ 加入標籤參數
    const note = { 
      id: Date.now(), 
      title, 
      content,
      tags  // ✅ 加入標籤欄位
    };
    this.notes.push(note);
    return note;
  }
  
  getAll() {
    return this.notes;
  }
  
  getByTag(tag) {  // ✅ 新增標籤搜尋
    return this.notes.filter(n => n.tags.includes(tag));
  }
}
```

**還是不做：**
- ❌ 搜尋功能（規格還沒要求）
- ❌ 權限管理（規格還沒要求）

---

#### 階段 3：再次擴展（第 3 週）
**新規格：** 需要全文搜尋

```javascript
// 現在加入搜尋
class NoteManager {
  // ... 前面的程式碼 ...
  
  search(keyword) {  // ✅ 現在規格需要了
    return this.notes.filter(n => 
      n.title.includes(keyword) || 
      n.content.includes(keyword)
    );
  }
}
```

---

### 如何培養這種判斷力？

#### 1. 嚴格遵守規格
```
問自己：
「這個功能在規格裡嗎？」
  └─ 沒有 → 不做
  └─ 有 → 做
```

#### 2. 使用 TDD
```
測試驅動開發：
只寫讓測試通過的程式碼
  └─ 測試沒要求的 → 不寫
  └─ 測試要求的 → 寫
```

#### 3. 定期重構
```
當規格改變時：
  └─ 不是「早知道當初就...」
  └─ 而是「現在加入剛剛好」
```

#### 4. 觀察實際使用
```
上線後：
  └─ 使用者真的需要 → 加入 ✅
  └─ 使用者沒用到 → 不加 ❌
```

---

### 常見問題

#### Q1：「但我覺得以後會需要...」

**A：** 以後再說！
- 需求會變
- 技術會進步
- 現在做可能白費
- **等真的需要時再做，更準確**

---

#### Q2：「現在做比較簡單，以後改很麻煩」

**A：** 真的嗎？
- 現在做：花時間 + 增加複雜度
- 以後做：根據實際需求，更精準
- **好的架構讓擴展變容易**

---

#### Q3：「我想展示我的技術能力」

**A：** 簡潔就是最好的展示
- 過度設計 = 不成熟
- 簡潔有效 = 專業
- **解決問題 > 炫技**

---

### 實戰練習：判斷練習

**情境：** 你在開發個人部落格系統

**規格要求：**
- 發布文章
- 編輯文章
- 刪除文章

**以下功能該做嗎？**

| 功能 | 做？ | 原因 |
|------|------|------|
| 文章分類 | ❌ | 規格沒要求 |
| 留言功能 | ❌ | 規格沒要求 |
| SEO 優化 | ❌ | 規格沒要求 |
| 多語言支援 | ❌ | 規格沒要求 |
| 文章草稿 | ⚠️ | 可能需要，確認規格 |
| 發布時間戳記 | ✅ | 基本需求 |

**原則：** 當不確定時，**問規格制定者**，不要自己猜！

---

## 🔍 模組 3 總結

### 模組化設計原則

1. **單一職責原則 (SRP)**
   - 每個模組只做一件事

2. **開放封閉原則 (OCP)**
   - 對擴展開放，對修改封閉

3. **依賴反轉原則 (DIP)**
   - 依賴抽象而非具體實作

4. **介面隔離原則 (ISP)**
   - 不要強迫使用者依賴不需要的介面

5. **YAGNI 原則 (You Aren't Gonna Need It)** ⭐
   - 不要預做判斷，不要過度設想
   - 根據規格實作，需要時再擴展

### 核心關鍵字

| 術語 | 說明 | 實際應用 |
|------|------|---------|
| **Modularity** | 模組化 | 將系統拆分成獨立模組 |
| **SoC** | 關注點分離 | 每個模組專注自己的職責 |
| **API Design** | API 設計 | 定義模組間的介面 |
| **Loose Coupling** | 鬆耦合 | 減少模組間的依賴 |
| **Dependency Injection** | 依賴注入 | 提升可測試性 |
| **YAGNI** | 你不會需要它 | 只做規格要求的功能 |

### ✅ 檢核清單

完成模組 3 後，你應該能夠：
- [ ] 將大型專案拆分成合理的模組
- [ ] 設計清晰的專案資料夾結構
- [ ] 定義模組間的 API 介面
- [ ] 實作符合 SoC 原則的程式碼
- [ ] 選擇適當的模組通訊方式
- [ ] 使用依賴注入提升可測試性
- [ ] 應用 YAGNI 原則，避免過度設計
- [ ] 根據規格判斷該做什麼、不該做什麼

---

### ✍️ 實戰練習

**練習 1：線上教師管理系統架構**  
設計一個線上教師管理系統，包含：
- 課程管理
- 學生管理
- 作業批改
- 成績統計

任務：
- [ ] 劃分功能模組
- [ ] 設計專案結構
- [ ] 定義模組間 API
- [ ] 實作其中一個模組

**練習 2：小型工作室 CRM 系統**  
設計客戶關係管理系統，包含：
- 客戶資料管理
- 專案進度追蹤
- 報價與帳單
- 溝通記錄

**練習 3：重構現有程式碼**  
找一個你之前寫的專案：
- [ ] 識別違反 SoC 的地方
- [ ] 重構成模組化設計
- [ ] 改善測試覆蓋率

---

**🎯 下一步：** 進入模組 4，學習進階 AI 協作技巧，成為 AI 的導演！

