這裡是未來專案開發時可以隨時參考的筆記心得與架構指南。


一、軟體開發生命周期 (SDLC)

每個專案都應該遵循這個流程,避免走彎路:

  1. 計畫:釐清現況、找出落差並設定期待的目標。
  2. 需求分析:深度分析使用者的需求與痛點。
  3. 解法設計:設計能填補「現況」與「期待」落差的整體架構。
  4. 實作:開始撰寫程式碼並建立系統。
  5. 測試:確保系統功能正常運作,排查邊界案例與錯誤。
  6. 部署:將系統發布到正式環境,並上線提供使用。
  7. 維護:持續監控系統,更新與修復後續的問題。

二、技術與架構評估(對應 SDLC:計畫 & 需求分析)

在動手前先決定要用什麼工具,避免中途換技術造成浪費。

平台定位

  • 手機端 (Mobile)
  • 電腦端 (Desktop / Web)

資料儲存

  • Notion (透過 API 作為簡易後台或資料庫)
  • 本機上傳 (檔案儲存處理)

資料庫選擇 (DB Options)

依專案複雜度選型,不要過度工程化:

  • 輕量級 / No-Code:Google Sheets, Airtable, Notion
  • 關聯式 / 專業資料庫:MySQL, PostgreSQL(現代化開發推薦使用 Supabase)

權限管理與身分驗證 (Auth)

  • 首選:Google OAuth(快速且免去密碼管理負擔)
  • 進階防護:OTP (One-Time Password),手機或信箱驗證

三、規格驅動開發(對應 SDLC:解法設計)

架構決定後,先寫規格,再寫程式碼。這是 AI 協作開發的核心原則。

什麼是 OpenSpec?

OpenSpec 是一套規格驅動開發 (Spec-Driven Development) 的工具,核心理念是「先定義合約,再開始實作」,確保 AI 與開發者之間有共同依據。

初始化:openspec init

在新專案根目錄開啟終端機執行:

npx openspec init

執行後會引導你完成設定,並在專案中產生規格文件結構。


四、開發環境建置(對應 SDLC:實作準備)

規格確認後,建置本機開發環境。

Node.js 基礎環境

前端或全端開發皆需依賴 Node.js 生態。安裝完成後,確認是否正確掛載到環境變數:

node -v

(成功安裝時,會回傳版本號,如 v22.x.x)

⚠️ 注意:需先確認 Node.js 環境已安裝,才能使用 npx openspec init


五、網域與部署策略(對應 SDLC:部署)

購買網域平台

  • 推薦:Cloudflare, Gandi
  • 不推薦:GoDaddy(⚠️ 續約費用昂貴且推銷多)

部署平台選擇

  • 推薦(免費或低成本)
    • Zeabur(部署簡單快捷,對開發者友善)
    • Google Apps Script(適合自動化小工具與排程)
    • GitHub Pages(適合純靜態網站)
    • Cloudflare Pages(不想公開程式碼的前端或全端部署,效能極佳)
  • 較不推薦(下次可換)
    • Vercel / Render
    • 三大公有雲:GCP, AWS(設定繁瑣,不適合初期快速疊代)

💡 總結:雖然這次使用 Vercel 部署,下次強烈建議嘗試 Zeabur 或 Cloudflare Pages,配置彈性更好、長遠維護更輕鬆。


六、資料庫設計原則(對應 SDLC:解法設計 & 實作)

良好的資料庫設計能避免後期大規模重構。

設計前要問的問題

  • 資料之間的關係是什麼?(一對一、一對多、多對多)
  • 哪些欄位會被頻繁查詢?(考慮建立索引)
  • 資料量預期多大?(影響技術選型)

命名慣例

  • 資料表:小寫 + 底線,用複數case_recordsusers
  • 欄位:小寫 + 底線(created_atclient_name
  • 主鍵:統一用 id(自動遞增整數或 UUID)
  • 外鍵:{資料表單數}_{id},例如 user_id

常見欄位模板

每張資料表建議都加上這三個欄位,方便追蹤:

id         SERIAL PRIMARY KEY,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()

Supabase 快速上手提醒

  • 在 Dashboard 建好資料表後,記得開啟 Row Level Security (RLS)
  • 使用 Supabase JS Client 時,查詢語法接近 SQL,很直覺。
  • .env 中放 SUPABASE_URLSUPABASE_ANON_KEY,不要 commit 進 Git。

七、版本控制與 Git 工作流程(對應 SDLC:實作)

基本分支策略

main        → 正式環境(穩定版本)
dev         → 開發整合分支
feature/xxx → 單一功能開發
fix/xxx     → 臭蟲修復

Commit 訊息格式(Conventional Commits)

feat: 新增案件查詢功能
fix: 修正日期格式解析錯誤
refactor: 重構 API 回傳結構
docs: 更新 README 安裝說明
chore: 升級套件版本

💡 好習慣:每完成一個小功能就 commit,不要等到一大包再提交,出問題時更容易回溯。

常用指令速查

[!IMPORTANT] 🌟 特別標註:核心 Git 指令操作

指令說明
git status看狀態,找路徑複製
git add暫存變更
git commit -m '<訊息>'寫訊息 + 提交
git log看目前有的提交(長版)
git log --oneline看目前有的提交(短版)
git restore <檔案路徑>捨棄變更,回到上個提交
git restore --source <提交識別碼> <檔案路徑>捨棄變更,回到特定提交
git push推送到雲端
git checkout -b feature/xxx   # 建立新功能分支
git add -p                    # 逐段暫存,避免誤提交
git stash                     # 暫存目前工作,切換分支用
git log --oneline --graph     # 視覺化分支歷史

八、API 設計原則(對應 SDLC:解法設計)

RESTful 路由慣例

方法路徑用途
GET/api/cases取得所有案件
GET/api/cases/:id取得單一案件
POST/api/cases新增案件
PUT/api/cases/:id完整更新案件
PATCH/api/cases/:id部分更新案件
DELETE/api/cases/:id刪除案件

回傳格式統一

{
  "success": true,
  "data": { ... },
  "error": null
}

失敗時:

{
  "success": false,
  "data": null,
  "error": "錯誤描述訊息"
}

💡 回傳格式統一,前端處理邏輯更簡單,也方便之後接 AI 工具做自動化。


九、測試策略(對應 SDLC:測試)

不需要測試所有東西,但核心流程一定要涵蓋。

優先測試的三種情境

  1. Happy Path:正常情況下,系統是否如預期運作?
  2. Edge Case:邊界值(空字串、null、超大數字)會不會炸?
  3. Error Handling:API 失敗或網路中斷時,系統怎麼反應?

測試工具推薦

  • 後端 API:Postman 或 Thunder Client(VS Code 插件)
  • 前端元件:Vitest + Testing Library
  • End-to-End:Playwright(模擬真實使用者操作)

AAA 測試結構

每個測試案例都遵循三段式,邏輯清晰:

Arrange  → 準備測試資料與環境
Act      → 執行要測試的動作
Assert   → 驗證結果是否符合預期

十、OpenSpec 工作流程速查(AI 協作開發)

在這個專案中使用 Claude Code + OpenSpec 進行規格驅動開發,以下是常用指令。

終端機指令(openspec config)

查看與修改 OpenSpec 全域設定。

openspec config list          # 查看所有目前設定值
openspec config get <key>     # 取得特定設定值
openspec config set <key> <value>  # 修改設定值
openspec config unset <key>   # 移除設定,恢復預設值
openspec config reset         # 重設所有設定為預設值
openspec config path          # 顯示設定檔位置
openspec config edit          # 用 $EDITOR 直接開啟設定檔編輯

⚠️ 目前 --scope 只支援 global,尚無 project-level 設定。

強制啟動 OpenSpec(Claude Code 對話框)

在 Claude Code 對話框輸入:

/opsx

這會強制進入 OpenSpec 工作流程,即使沒有現有規格也能從頭開始。

完整指令速查表

指令用途
/opsx:new開始一個新變更,逐步建立規格工件
/opsx:ff快速產生所有規格工件(Fast Forward)
/opsx:continue繼續進行中的變更,建立下一個工件
/opsx:apply依規格執行實作任務
/opsx:verify驗證實作是否符合規格
/opsx:archive封存已完成的變更
/opsx:explore探索模式,釐清需求後再開始
/opsx:sync將 delta spec 同步至主規格

推薦工作流程順序

探索需求         → /opsx:explore
建立規格         → /opsx:new 或 /opsx:ff
實作功能         → /opsx:apply
驗證成果         → /opsx:verify
封存歸檔         → /opsx:archive

💡 原則:不確定要做什麼時,先用 /opsx:explore 思考清楚,再動手。規格先行,程式碼後行。


十一、維護與上線後監控(對應 SDLC:維護)

上線檢查清單

  • 環境變數(.env)是否全部在正式環境設定完成?
  • 資料庫 Migration 是否執行?
  • CORS 設定是否只允許必要網域?
  • 錯誤日誌(Error Logging)是否接上(Sentry 或 console)?
  • 有沒有設定自動備份?

常見維運工具

  • 錯誤追蹤:Sentry(免費方案夠用)
  • 使用者行為分析:Google Analytics 4 或 Umami(自架、隱私友善)
  • 正常時間監控:UptimeRobot(免費,定時打 API 確認服務存活)

後續疊代原則

「能上線的系統才是好系統,完美的系統是不存在的。」

每次疊代前先問:

  1. 這個改動解決了什麼真實痛點?
  2. 最小可行的改法是什麼?
  3. 有沒有可能搞壞現有功能?

十二、Claude Code 使用手冊(AI 協作開發核心技能)

這部分記錄在 Claude Code(Claude CLI + Agentic 介面)中實際操作時最常用到的功能,包含快速鍵、外掛機制、自動化 Hooks、MCP 整合、多重代理與時光倒流功能。


12.1 常用快速鍵(Keyboard Shortcuts)

在 Claude Code 的互動介面中,以下快速鍵能大幅提升操作效率:

快速鍵功能說明
Ctrl + C中斷目前正在執行的代理任務
Ctrl + L清除對話畫面(不影響上下文記憶)
↑ / ↓瀏覽歷史指令
Esc取消目前輸入,回到前一狀態
Enter送出訊息
Shift + Enter在訊息中換行(不送出)
Tab斜線指令自動補全(/ 後按 Tab)

💡 補充:在 VS Code 或 Cursor 的捷徑鍵環境下,Ctrl+Shift+P 開啟指令面板後輸入 Claude 可以直接呼叫插件功能。


12.2 實用資源連結

工具 / 資源說明連結
Claude MarketplacesClaude Code Plugin 社群市集,2900+ 插件一鍵安裝claudemarketplaces.com
Context7即時抓取最新套件文件注入 LLMcontext7.com
Claude Code 官方文件Anthropic 官方 Claude Code 完整說明docs.anthropic.com

12.3 斜線指令速查(Slash Commands)

在對話框中輸入 / 開頭的指令可觸發內建功能或自訂 Workflow:

內建系統指令

指令功能說明
/init初始化專案:Claude 會自動掃描整個 Codebase,生成 CLAUDE.md 作為專案記憶檔
/plugins列出所有目前已載入的插件(MCP Servers)清單,確認連線狀態
/memory查看與管理 Claude 的專案記憶內容(來自 CLAUDE.md
/cost顯示目前對話累計消耗的 Token 與費用
/model切換使用的 AI 模型(如 Sonnet / Opus)
/help顯示所有可用指令的說明
/clear清除對話歷史,開啟全新上下文

/init 詳細說明

# 在專案根目錄執行,讓 Claude 認識你的 Codebase
> /init

執行後 Claude 會:

  1. 掃描目錄結構、主要檔案與 package.json / pyproject.toml
  2. 識別技術堆疊(Next.js、FastAPI、Supabase 等)
  3. 自動生成或更新 CLAUDE.md,記錄專案架構摘要
  4. 後續對話中 Claude 可直接引用這份記憶,無需每次重新說明背景

⚠️ 注意:每當專案結構有重大改變(新增模組、更換框架)時,重新執行 /init 讓記憶保持最新。

/plugins 詳細說明

# 確認目前載入哪些 MCP 插件
> /plugins

輸出範例:

✅ filesystem  - 本機檔案讀寫
✅ github      - GitHub 倉庫操作
✅ supabase    - 資料庫直連
✅ puppeteer   - 無頭瀏覽器自動化
❌ slack       - 未連線(缺少 Token)

12.3 Hooks(自動化觸發機制)

Hooks 是 Claude Code 的事件驅動自動化功能,讓你可以在特定時間點自動執行腳本,無需手動介入。

核心概念

事件發生 → Hook 被觸發 → 自動執行腳本 → 結果回饋給 Claude

支援的 Hook 事件類型

Hook 類型觸發時機常見用途
PreToolCallClaude 準備呼叫工具安全審查、記錄請求
PostToolCall工具執行完畢後自動格式化、送出通知
PreCompact對話即將壓縮備份關鍵上下文
StopClaude 完成回覆自動執行測試、送 Slack 通知

設定方式(settings.json

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "yarn test --passWithNoTests"
          }
        ]
      }
    ],
    "PostToolCall": [
      {
        "matcher": "Write|Edit|Create",
        "hooks": [
          {
            "type": "command",
            "command": "yarn lint --fix"
          }
        ]
      }
    ]
  }
}

💡 實戰建議

  • Stop Hook 在每次 Claude 完成任務後自動跑測試,確保沒有破壞現有功能。
  • PostToolCall 配合 matcher: "Write|Edit" 自動 Lint,省去手動格式化步驟。

12.4 MCP(Model Context Protocol)

MCP 是 Claude 與外部工具、服務之間的標準化通訊協議,讓 AI 能直接操作真實世界的系統。

一句話理解MCP

MCP = AI 的 USB 插槽。你插入什麼工具(MCP Server),AI 就能操作什麼系統。

MCP 的組成

┌─────────────┐         MCP 協議          ┌──────────────────┐
│  Claude     │ ←─────────────────────→  │  MCP Server      │
│  (Host)     │   標準化的 Tools / Prompts │  (外部服務介面)    │
└─────────────┘                          └──────────────────┘
                              ┌───────────────────┼──────────────────┐
                              ▼                   ▼                  ▼
                         GitHub API         Supabase DB         本機檔案系統

常用 MCP Server 一覽

MCP Server功能設定關鍵字
filesystem讀寫本機檔案allowedDirectories
githubRepo、PR、Issue 管理GITHUB_TOKEN
supabase直接下 SQL、管理 RLSSUPABASE_URL + SERVICE_KEY
puppeteer無頭瀏覽器操作無需額外設定
postgres直連 PostgreSQL 查詢DATABASE_URL
slack發送訊息到 Slack 頻道SLACK_BOT_TOKEN
memory跨對話的持久記憶圖譜自動管理

加入 MCP Server(claude_desktop_config.json

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "ghp_xxx"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/yourname/Projects"]
    }
  }
}

⚠️ 安全提醒env 中的 Token 不要 commit 進 Git,應從 .env 或系統環境變數讀取。


12.5 Agents 與 Subagents(多重代理架構)

Claude Code 支援主代理 (Agent) 呼叫子代理 (Subagent) 的多層架構,讓複雜任務可以拆分並行執行。

概念圖

主代理 (Orchestrator)
  ├── 子代理 A:分析現有程式碼
  ├── 子代理 B:研究外部文件
  └── 子代理 C:執行測試驗證
    彙整結果 → 主代理輸出最終答案

Agent vs Subagent 差異

特性Agent(主代理)Subagent(子代理)
角色任務規劃、協調決策執行具體的單一任務
上下文持有完整對話歷史有獨立的隔離上下文
工具使用可呼叫所有工具與 Subagents受限於指派的工具範圍
典型任務「幫我開發一個新功能」「讀取這個檔案並分析依賴」

實際應用場景

場景一:並行分析多個檔案
主代理:「幫我 Review 這個 PR 的所有變更檔案」
  ├── Subagent 1:分析 api/users.ts
  ├── Subagent 2:分析 components/UserCard.tsx
  └── Subagent 3:分析 tests/users.test.ts
→ 主代理彙整三者的 Review 意見
場景二:研究 + 實作分工
主代理:「整合第三方支付 API」
  ├── Subagent 1:爬取 API 官方文件,整理欄位規格
  └── Subagent 2:分析現有程式碼,找出整合切入點
→ 主代理擁有完整資訊後開始實作

在 Claude Code 中觸發多代理

複雜任務下,Claude Code 會自動決定是否啟用子代理。你也可以在 Prompt 中明示:

請用並行方式分析 src/api/ 目錄下的所有路由檔案,
各自獨立評估後,彙整安全性建議。

💡 最佳實踐:涉及多個獨立模組、多來源資料整合、或獨立的驗證步驟時,主動提示「並行」讓 Claude 分工處理,速度可大幅提升。


12.6 Rewind(時光回溯)

Rewind 是 Claude Code 的對話狀態回滾功能,讓你可以「倒帶」到某個特定對話節點,從那個時間點重新出發,不用整個對話重來。

什麼時候用 Rewind?

情境說明
Claude 走偏方向連續幾輪後發現思路不對,想回到分叉點重試
測試不同方案想從同一起點嘗試 A 方案和 B 方案,比較結果
誤操作補救Claude 意外刪改了不該動的檔案,回到操作前的狀態
功能範疇擴大原本只改一個 Bug,現在想從頭重新規劃整個修法

使用方式

在 Claude Code 介面中,每一輪對話的左側會有一個「時鐘圖示」,點擊即可進入 Rewind 模式:

  1. 選擇要回滾的節點:點選對話歷史中任意一輪訊息旁的 Rewind 按鈕
  2. 確認回滾:Claude Code 會提示你即將捨棄回滾點之後的所有對話
  3. 重新出發:從選定節點繼續,輸入新的指令嘗試不同方向

⚠️ 注意:Rewind 只回滾對話上下文,不會自動撤銷已執行的檔案修改或終端機指令。如果需要同時回復檔案,要搭配 git stashgit checkout 使用。

搭配 Git 使用(推薦工作流)

# 在啟動複雜任務前,先建立 Git 快照
git stash  # 或
git commit -m "chore: snapshot before AI session"

# 若 Claude 走偏,先 Rewind 對話,再回滾程式碼
git checkout HEAD  # 回到最後一個 commit
# 或
git stash pop      # 還原 stash 的工作狀態

💡 黃金組合Rewind(對話回滾) + git stash(程式碼快照) = 完整的 AI 開發保險機制,讓你可以大膽嘗試,出錯也能完整復原。


12.7 快速鍵完整速查(更新版)

除了 12.1 的基礎快速鍵,以下是 Claude Code 互動介面的完整操作快速鍵:

快速鍵說明使用場景
!進入 Bash 模式,直接輸入 shell 指令臨時跑指令、不想切換終端機時
@提及特定檔案或資料夾要 Claude 讀取某個檔案時,直接選取比打路徑快
\換行,輸入多行訊息(不送出)撰寫包含程式碼區塊的複雜 Prompt
Esc中斷目前操作Claude 走偏時立即停止
Esc x2開啟 Rewind 選單,回溯對話狀態方向不對時快速倒帶
Ctrl+R顯示完整輸出 context確認 Claude 看到哪些資訊、除錯時用
Ctrl+V貼上圖片直接把截圖或 UI 稿貼給 Claude 分析
Ctrl+T查看目前 Tasks / Todos確認 Claude 目前的工作進度清單
Ctrl+B把目前任務移到背景執行長時間任務(跑測試、建置)丟背景,繼續問其他問題
Shift+TabAuto-accept,自動核准工具呼叫信任 Claude、要快速連續執行工具時開啟

12.8 Slash 指令完整速查(補充)

補充 12.3 中未收錄的指令分類:

Context 管理

指令功能使用場景
/compact壓縮 context,釋放 token對話很長、context 快滿時,保留重要資訊繼續工作
/context視覺化 context 使用量評估還剩多少空間,決定是否壓縮
/resume恢復上一次對話隔天重開 Claude Code,接回昨天的工作
/branch從目前對話建立分支同一問題想試兩種方向,各自實驗
/rewind回溯到之前的對話狀態某次修改搞壞了,回到之前的乾淨狀態

模型與效能控制

指令功能使用場景
/fast快速模式(同模型、更快輸出)輸出速度慢時切換,不換模型
/effort調整推理強度簡單任務降低強度省 token,複雜架構決策提高強度

開發輔助

指令功能使用場景
/diff查看未 commit 的變更commit 前確認自己改了哪些東西
/pr-comments讀取 GitHub PR 評論收到 code review,讓 Claude 直接讀評論並修改
/btw附帶問題,不消耗主要 context順便問個小問題,不想浪費對話空間
/loop排程重複執行任務定期跑某指令,例如每 5 分鐘監控 build 狀態
/voice語音模式(支援 20 種語言)雙手忙碌、口述需求時使用

環境管理

指令功能使用場景
/add-dir新增工作目錄同時管理多個 Repo 或 Monorepo 子模組
/sandbox沙箱環境測試危險指令前先在隔離環境執行
/vimVim 模式習慣 Vim 鍵盤操作的使用者

12.9 Headless 模式(CI/CD 自動化)

Headless 模式讓 Claude 在無互動介面的環境下執行,適合 CI/CD Pipeline 或批次自動化任務。

# 基本用法:單次執行,印出結果
claude --print "修復所有 lint 錯誤"

# 輸出為 JSON 格式(方便 pipeline 解析)
claude -p "分析程式碼品質" --output-format json

# 限制最多幾輪對話(避免無限迴圈)
claude -p "重構這個模組" --max-turns 5

# 從 stdin 讀取輸入(管線串接)
echo "解釋這段 code" | claude -p

使用場景

場景指令範例
CI 自動修 lintclaude -p "修復所有 ESLint 錯誤" --max-turns 3
自動生成 PR 說明git diff main | claude -p "為這個 diff 寫 PR description"
批次程式碼分析claude -p "分析 src/ 所有 .ts 檔的安全問題" --output-format json
自動更新文件claude -p "根據最新程式碼更新 README"

12.10 Git Worktrees(平行開發環境)

Git Worktrees 讓你在同一個 Repo 下同時維護多個工作目錄,不同分支可以同時開啟,Claude Agents 可以在不同 Worktree 中平行工作。

# 建立新的 worktree(同時建立新分支)
git worktree add ../feature-payment feature/payment

# 列出所有 worktree
git worktree list

# 移除已完成的 worktree
git worktree remove ../feature-payment

適用情境

情境做法
主線修 Bug + 同時開發新功能建兩個 worktree,各自獨立,不互相污染
並行比較兩種實作方案A 方案一個 worktree,B 方案另一個,同時跑測試比較
多個 Claude Agent 平行重構每個 Agent 指派到不同 worktree,工作完畢再合併

💡 使用 /batch 指令可讓 Claude 自動跨多個 worktrees 執行平行重構任務。


12.11 Hooks 完整事件清單(更新版)

補充 12.3 中的 Hooks 支援事件清單:

Hook 事件觸發時機典型用途
PreToolUse工具呼叫阻擋危險指令(如 rm -rf),執行前先確認
PostToolUse工具執行完畢後每次存檔後自動跑 lint 或格式化
UserPromptSubmit使用者送出提示後自動補充 context(如當前分支名稱、時間戳)
Notification通知觸發時轉發通知到 Slack 或系統桌面提示
StopClaude 完成回覆自動執行測試、自動 commit 或推送
SubagentStop子代理完成彙整子代理結果、記錄執行日誌
PreCompactContext 壓縮備份關鍵上下文到外部檔案
SessionStartSession 開始時自動載入環境變數、印出當日待辦事項
SessionEndSession 結束時自動 commit 工作進度、產生工作日誌

12.12 權限管理(Permissions)

Claude Code 提供三種權限模式,對應不同的工作場景:

模式說明適用情境
Basic常用開發指令允許,敏感檔案自動阻擋日常開發,平衡便利與安全
Strict最大限制,幾乎所有工具呼叫都需確認操作生產資料庫或部署前的最後審查階段
Enterprise透過 MCP 統一管控,企業級管理團隊共用環境,確保所有人行為一致

權限類型

allow  → 直接執行,不詢問
ask    → 每次執行前彈出確認視窗
deny   → 直接拒絕,不執行

支援 bash command pattern matching,可針對特定指令設定細粒度規則:

{
  "permissions": {
    "allow": ["git commit*", "yarn test*", "yarn lint*"],
    "ask": ["git push*", "rm *"],
    "deny": ["rm -rf *", "DROP TABLE*"]
  }
}

12.13 Checkpoint 與設定優先順序

Checkpoint(自動回溯點)

Claude Code 會自動在每次變更時建立 Checkpoint,讓你可以隨時回溯:

  • 觸發方式:Esc x2 或輸入 /rewind 開啟回溯選單
  • ⚠️ 限制:透過 Bash 指令或外部編輯器(如 VS Code)的修改不會被 Checkpoint 捕捉,需搭配 git stash 手動快照

設定檔載入優先順序(高 → 低)

當多個設定檔衝突時,以優先順序高的為準:

優先順序設定檔路徑說明
1(最高)/etc/claude-code/managed-settings.json企業 IT 管理員統一部署
2.claude/settings.local.json專案本地個人設定(不進 Git)
3.claude/settings.json專案共用設定(進 Git,團隊共享)
4(最低)~/.claude/settings.json使用者全域預設值

實際應用

團隊共用規範  → .claude/settings.json(commit 進 Repo)
個人偏好覆蓋  → .claude/settings.local.json(加入 .gitignore)
企業強制政策  → /etc/claude-code/managed-settings.json(IT 部署)

十三、程式設計基礎:函數 (Function)

函數是程式設計中最重要的積木單元。理解函數,就掌握了「讓程式可以重複使用、易於維護」的核心能力。


13.1 什麼是函數?

函數(Function) 是一段有名字的程式碼區塊,專門負責完成某一件事。你只要呼叫它的名字,它就會執行那段程式碼,並且可以重複呼叫。

生活類比:想像函數是一台咖啡機。

  • 你按下按鈕(呼叫函數)
  • 機器磨豆、加水、加熱(執行內部邏輯)
  • 吐出一杯咖啡(回傳結果)

你不需要知道機器內部怎麼運作,只需要知道「按按鈕、拿咖啡」。


13.2 函數的四個核心元素

元素說明範例
函數名稱函數的識別標籤,描述它做什麼calculateTax
參數 (Parameters)函數運作時需要的輸入資料(price, taxRate)
函數本體實際執行的邏輯程式碼{ return price * taxRate }
回傳值 (Return Value)函數執行完後吐回來的結果return result

13.3 JavaScript / TypeScript 函數寫法

基本函數

// 定義函數
function greet(name: string): string {
  return `你好,${name}!`
}

// 呼叫函數
const message = greet("Tom")
console.log(message) // 輸出:你好,Tom!

箭頭函數(Arrow Function)—— 現代 JS 常用寫法

// 箭頭函數語法更簡潔
const add = (a: number, b: number): number => {
  return a + b
}

// 單行可省略 return 和大括號
const multiply = (a: number, b: number): number => a * b

console.log(add(3, 5))      // 8
console.log(multiply(4, 6)) // 24

沒有回傳值的函數(void)

// 只執行動作,不回傳資料
function logError(message: string): void {
  console.error(`[ERROR] ${message}`)
}

logError("資料庫連線失敗")

13.4 為什麼要用函數?

❌ 沒有函數:重複程式碼,難以維護

// 計算台北的稅金
const taipeiPrice = 1000
const taipeiTax = taipeiPrice * 0.05
console.log(`台北稅金:${taipeiTax}`)

// 計算新竹的稅金(完全相同的邏輯,複製貼上)
const hsinChuPrice = 2000
const hsinChuTax = hsinChuPrice * 0.05
console.log(`新竹稅金:${hsinChuTax}`)

✅ 有函數:邏輯集中,一改全改

// 把重複邏輯包成函數
function calculateTax(price: number, taxRate: number = 0.05): number {
  return price * taxRate
}

console.log(`台北稅金:${calculateTax(1000)}`)
console.log(`新竹稅金:${calculateTax(2000)}`)
// 若稅率調整,只需改函數內部一個地方

13.5 函數的三種常見用途

1. 資料處理(轉換 / 計算)

function formatCurrency(amount: number): string {
  return `NT$ ${amount.toLocaleString()}`
}

console.log(formatCurrency(50000)) // NT$ 50,000

2. 條件判斷(封裝複雜邏輯)

function isEligibleForDiscount(age: number, isMember: boolean): boolean {
  return age >= 65 || isMember
}

if (isEligibleForDiscount(70, false)) {
  console.log("享有折扣")
}

3. 非同步操作(API 呼叫)

// async/await 函數:處理需要等待的操作(如網路請求)
async function fetchUserData(userId: string) {
  const response = await fetch(`/api/users/${userId}`)
  const data = await response.json()
  return data
}

// 呼叫非同步函數
const user = await fetchUserData("user-123")

13.6 函數設計原則

原則說明
單一職責一個函數只做一件事,不要塞太多邏輯
長度控制在 100 行以內超過 100 行通常代表函數做了太多事,應拆分成更小的函數
命名清楚函數名稱應描述它「做什麼」,如 getUserByIdsendEmail
參數不超過 3 個超過時考慮改傳物件 { id, name, role }
純函數優先相同輸入永遠得到相同輸出,沒有副作用,最易測試

函數命名慣例

// ✅ 動詞開頭,描述行為
function getUser() {}
function createCase() {}
function validateEmail() {}
function isLoggedIn() {}   // 回傳 boolean 用 is/has/can 開頭

// ❌ 名詞開頭或意義不明
function user() {}
function data() {}
function doStuff() {}

13.7 拆函數:何時該把一個函數切開?

當函數出現以下訊號,就是該拆的時候:

  • 超過 100 行:難以一眼看完整個邏輯
  • 函數名稱需要「和」字:如 fetchDataAndRenderTable,代表在做兩件事
  • 巢狀縮排超過 3 層if 裡面包 for 再包 if,可讀性急速下降
  • 同一段邏輯出現超過兩次:重複就是拆函數的訊號

拆函數前後對比

// ❌ 拆之前:一個函數做太多事
async function handleOrder(orderId: string) {
  // 第一段:驗證訂單
  const order = await db.orders.findById(orderId)
  if (!order) throw new Error("訂單不存在")
  if (order.status !== "pending") throw new Error("訂單狀態錯誤")

  // 第二段:計算金額
  const subtotal = order.items.reduce((sum, item) => sum + item.price * item.qty, 0)
  const tax = subtotal * 0.05
  const total = subtotal + tax

  // 第三段:發送通知
  await fetch("/api/notify", {
    method: "POST",
    body: JSON.stringify({ userId: order.userId, message: `訂單金額:${total}` })
  })
}

// ✅ 拆之後:每個函數只做一件事
async function validateOrder(orderId: string) {
  const order = await db.orders.findById(orderId)
  if (!order) throw new Error("訂單不存在")
  if (order.status !== "pending") throw new Error("訂單狀態錯誤")
  return order
}

function calculateTotal(items: OrderItem[]): number {
  const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0)
  return subtotal * 1.05
}

async function notifyUser(userId: string, total: number) {
  await fetch("/api/notify", {
    method: "POST",
    body: JSON.stringify({ userId, message: `訂單金額:${total}` })
  })
}

// 主函數變得像一份清單,一眼看懂流程
async function handleOrder(orderId: string) {
  const order = await validateOrder(orderId)
  const total = calculateTotal(order.items)
  await notifyUser(order.userId, total)
}

13.8 拆檔案:何時該把一個檔案切開?

函數是最小單位,檔案是組織函數的容器。當一個檔案太龐大,就要按「職責」把函數搬到不同檔案。

拆檔案的訊號

  • 檔案超過 300~500 行:難以導航與維護
  • 檔案混雜了多種職責:API 呼叫、資料轉換、UI 渲染全擠在一起
  • 多個地方都需要引用同一個函數:該函數應移至共用模組

常見的檔案職責分層

src/
├── api/          # 所有對外 API 呼叫(fetch、axios)
├── utils/        # 純函數工具(格式化、計算、驗證)
├── hooks/        # React 自訂 Hook(狀態邏輯)
├── components/   # UI 元件(只管畫面)
├── services/     # 業務邏輯(組合 api + utils)
└── types/        # TypeScript 型別定義

拆檔案前後對比

// ❌ 拆之前:所有東西擠在 orderPage.tsx(500+ 行)
// - fetch 邏輯
// - 金額計算
// - 日期格式化
// - JSX 渲染

// ✅ 拆之後:各司其職
// api/orderApi.ts      → 負責 fetch
// utils/formatDate.ts  → 負責格式化
// utils/calcOrder.ts   → 負責計算
// components/OrderCard.tsx → 負責渲染
// orderPage.tsx        → 只負責組裝以上模組(50 行以內)

💡 判斷原則:如果你需要滾動很多才能找到想改的地方,或者改一個功能要動好幾個地方——就是該拆檔案的時機。



13.9 主函數與子函數

拆函數之後,函數之間會自然形成「主從關係」:

  • 主函數(Main Function):描述整體流程的高層函數,負責「指揮」,不處理細節
  • 子函數(Helper / Sub Function):被主函數呼叫,專注處理單一細節的低層函數

關係示意

主函數
├── 子函數 A(處理步驟一)
├── 子函數 B(處理步驟二)
│   ├── 子子函數 B1
│   └── 子子函數 B2
└── 子函數 C(處理步驟三)

主函數讀起來像「目錄」,子函數才是真正執行的「內文」。

實際範例:結帳流程

// ── 子函數:各自負責一個細節 ──────────────────────

function getCartItems(userId: string): CartItem[] {
  // 從資料庫取得購物車內容
  return db.cart.findByUserId(userId)
}

function applyDiscount(subtotal: number, coupon: string): number {
  const discountMap: Record<string, number> = { SAVE10: 0.9, SAVE20: 0.8 }
  return subtotal * (discountMap[coupon] ?? 1)
}

function calcTax(amount: number): number {
  return amount * 0.05
}

async function chargePayment(userId: string, total: number): Promise<boolean> {
  const result = await paymentGateway.charge({ userId, amount: total })
  return result.success
}

async function sendReceipt(userId: string, total: number): Promise<void> {
  await emailService.send({ to: userId, subject: "訂單確認", body: `總金額:${total}` })
}

// ── 主函數:只描述流程,細節交給子函數 ───────────────

async function checkout(userId: string, coupon: string) {
  const items     = getCartItems(userId)
  const subtotal  = items.reduce((sum, i) => sum + i.price * i.qty, 0)
  const discounted = applyDiscount(subtotal, coupon)
  const total     = discounted + calcTax(discounted)
  const paid      = await chargePayment(userId, total)
  if (paid) await sendReceipt(userId, total)
}

光看 checkout 就能理解整個結帳流程,不需要看細節。

主函數 vs 子函數 比較

主函數子函數
職責描述流程、協調步驟執行單一具體任務
長度通常 10~30 行通常 5~20 行
可讀性像流程圖,一眼看懂像說明書,解釋細節
被呼叫由外部(路由、事件)呼叫由主函數呼叫
可重用性低(針對特定情境)高(可跨場景共用)

💡 設計心法:先寫主函數的「骨架」(只有呼叫子函數的那幾行),確認流程對了,再逐一實作每個子函數。這樣思路不會被細節淹沒。


💡 記住這個心法:每當你發現自己「複製貼上同樣的程式碼超過兩次」,就是該把它包成函數的時機。函數是對抗重複的最佳武器。 💡 最佳實踐:團隊在 .claude/settings.json 定義統一的 Lint 指令和權限規則,個人用 .claude/settings.local.json 覆蓋自己的模型偏好,兩者各司其職不互相干擾。


十四、標籤 (Tag)

「Tag」在開發中至少出現在兩個完全不同的場景:HTML 標籤Git 標籤。兩者概念相似但用途不同。


14.1 HTML 標籤

HTML 標籤是網頁的骨架,告訴瀏覽器「這段內容是什麼」。

基本語法

<標籤名稱 屬性="值">內容</標籤名稱>

大多數標籤有開始標籤結束標籤,結束標籤多一個 /

<p>這是一段文字</p>
<h1>這是標題</h1>
<a href="https://example.com">點我連結</a>

少數標籤是自閉合標籤,沒有內容也沒有結束標籤:

<img src="photo.jpg" alt="照片說明" />
<input type="text" placeholder="請輸入文字" />
<br />

常用標籤速查

標籤用途
<h1><h6>標題(數字越大字越小)
<p>段落文字
<a href="">超連結
<img src="" alt="">圖片
<div>區塊容器(無語意)
<span>行內容器(無語意)
<ul> / <ol> / <li>無序清單 / 有序清單 / 清單項目
<table> / <tr> / <td>表格 / 列 / 儲存格
<form> / <input> / <button>表單 / 輸入框 / 按鈕
<nav> / <header> / <footer>導覽列 / 頁首 / 頁尾(語意標籤)
<main> / <section> / <article>主內容 / 區塊 / 獨立文章(語意標籤)

標籤的屬性 (Attribute)

標籤可以附加屬性來提供額外資訊:

<!-- class:套用 CSS 樣式 -->
<div class="card highlight">內容</div>

<!-- id:唯一識別,用於 JS 選取或錨點連結 -->
<section id="about">關於我們</section>

<!-- data-*:自訂資料屬性,傳給 JavaScript 使用 -->
<button data-user-id="123" data-action="delete">刪除</button>

語意標籤 vs 無語意標籤

<!-- ❌ 全用 div,搜尋引擎看不懂結構 -->
<div class="header">...</div>
<div class="nav">...</div>
<div class="content">...</div>

<!-- ✅ 語意標籤,對 SEO 和無障礙設計友善 -->
<header>...</header>
<nav>...</nav>
<main>...</main>

14.2 Git 標籤(Git Tag)

Git Tag 是版本控制中的「里程碑標記」,用來標記某個重要的 commit(通常是發布版本)。

類比:Git commit 是書中每一頁的頁碼,Git Tag 則是「第一章開始」、「第二章開始」這樣的章節標籤貼紙。

兩種 Tag 類型

類型說明何時用
Lightweight Tag只是一個指向 commit 的指標,沒有額外資訊臨時標記、個人使用
Annotated Tag包含標記者、日期、描述訊息,有完整記錄正式發布版本(推薦)

常用指令

# 建立 Annotated Tag(推薦)
git tag -a v1.0.0 -m "第一個正式發布版本"

# 建立 Lightweight Tag
git tag v1.0.0-beta

# 查看所有 Tag
git tag

# 查看特定 Tag 的詳細資訊
git show v1.0.0

# 推送單一 Tag 到遠端
git push origin v1.0.0

# 推送所有本地 Tag 到遠端
git push origin --tags

# 刪除本地 Tag
git tag -d v1.0.0

# 刪除遠端 Tag
git push origin --delete v1.0.0

版本號命名規則(語意化版本 Semantic Versioning)

v主版本.次版本.修補版本
  MAJOR . MINOR . PATCH

v1.0.0   → 首次正式發布
v1.1.0   → 新增功能(向下相容)
v1.1.1   → 修復 Bug(不影響功能)
v2.0.0   → 不向下相容的重大改版

實際工作流程

# 開發完成,準備發布 v1.2.0
git checkout main
git pull --rebase

# 建立標籤並寫說明
git tag -a v1.2.0 -m "新增優惠券折扣功能,修復結帳金額計算錯誤"

# 推送程式碼與標籤
git push origin main
git push origin v1.2.0

💡 HTML Tag vs Git Tag:HTML Tag 標記「內容的性質」,Git Tag 標記「程式碼的版本位置」。兩者都是用「標籤」這個概念幫事物貼上有意義的名稱。


十五、沒有錯誤訊息時怎麼辦?

程式設計中最難除錯的情況不是「有錯誤訊息」,而是程式毫無反應、也什麼都沒報錯。這種狀況讓人完全不知從何下手。


15.1 為什麼沒有錯誤訊息?

常見原因:

  • 錯誤被靜默吞掉try/catch 裡面什麼都沒寫,錯誤發生了但沒人知道
  • 程式根本沒執行到那段:觸發條件不符,函數從未被呼叫
  • 非同步問題await 漏寫,函數跑完了但資料還沒回來
  • 寫入位置錯誤:程式執行成功,但結果寫到你沒在看的地方

15.2 萬用除錯句型(給 AI 或自己釐清問題)

遇到「毫無反應也無報錯」的情況,用這個格式描述問題,能快速釐清方向:

# 現況
執行之後,程式毫無反應,也找不到任何錯誤訊息。

# 期待
執行之後,Google 試算表中「測試」工作表的 A 欄應該出現 XXX 資料。

為什麼這個格式有效?

  • 現況:逼自己說清楚「現在發生什麼」,排除主觀猜測
  • 期待:說清楚「正確結果長什麼樣」,給 AI 或同事一個具體的驗證目標

沒有這兩個資訊,任何人(包含 AI)都只能亂猜。


15.3 自己動手排查的步驟

第一步:確認程式有沒有執行到

在懷疑的地方加上 console.log,確認程式流程有沒有跑到那裡:

async function writeToSheet() {
  console.log("▶ writeToSheet 開始執行")   // ← 加這行

  const data = await fetchData()
  console.log("▶ fetchData 完成,data =", data)  // ← 加這行

  await sheet.write(data)
  console.log("▶ sheet.write 完成")  // ← 加這行
}

哪一行沒印出來,問題就在那之前。

第二步:確認非同步沒有漏 await

// ❌ 常見錯誤:忘記 await,函數直接結束,資料根本還沒寫入
function saveData() {
  fetchAndWrite()  // 沒有 await!
  console.log("完成")  // 這行會在 fetchAndWrite 結束前就印出來
}

// ✅ 正確寫法
async function saveData() {
  await fetchAndWrite()
  console.log("完成")
}

第三步:確認 try/catch 沒有吞掉錯誤

// ❌ 靜默吞錯:發生錯誤也不會有任何提示
try {
  await writeToSheet()
} catch (e) {
  // 什麼都沒寫
}

// ✅ 至少要印出來
try {
  await writeToSheet()
} catch (e) {
  console.error("writeToSheet 失敗:", e)
}

第四步:確認寫入了「正確的地方」

// 明確印出你以為在操作的目標
console.log("試算表 ID:", spreadsheetId)
console.log("工作表名稱:", sheetName)
console.log("寫入範圍:", range)

有時候程式完全正確,只是寫到另一個試算表、另一個工作表去了。


15.4 除錯心法

情況第一個動作
程式毫無反應console.log 確認函數有沒有被呼叫
有執行但結果不對印出中間值,找出哪一步資料變錯了
偶爾正常偶爾不正常懷疑非同步時序問題或外部 API 不穩定
本機正常但部署後不正常比對環境變數、Node.js 版本、套件版本

💡 核心原則:除錯的本質是「縮小懷疑範圍」。從最外層開始確認,一層一層往內,直到找到第一個「不符合預期的地方」,那就是問題根源。


十六、網路爬蟲基礎

16.1 靜態網站 vs 動態網站

網路爬蟲的第一步,是判斷目標是「靜態」還是「動態」網站,因為抓取方式完全不同。

類型說明判斷方式
靜態網站資料直接寫在 HTML 原始碼裡在瀏覽器按右鍵 → 「檢視頁面原始碼」,能看到要抓的資料
動態網站資料由 JavaScript 非同步載入(AJAX / Fetch / XHR)在原始碼看不到資料,需要透過主控台 Network 分頁尋找 API 請求

16.2 靜態網站爬取方式

資料已在 HTML 中,直接發 HTTP 請求取得頁面,再解析 DOM 即可。

常用工具:Python(requests + BeautifulSoup)、Node.js(axios + cheerio

import requests
from bs4 import BeautifulSoup

res = requests.get("https://example.com")
soup = BeautifulSoup(res.text, "html.parser")
print(soup.select_one("h1").text)

16.3 動態網站爬取方式

資料不在原始碼裡,需要找到背後的 API 端點。

步驟一:打開瀏覽器主控台 Network 分頁

  1. F12 開啟開發者工具
  2. 切到 Network 分頁
  3. 重新載入頁面(或觸發資料載入的動作)
  4. 篩選 Fetch/XHR,找出實際回傳資料的請求

步驟二:複製為 cURL

找到目標請求後,右鍵 → Copy → Copy as cURL

curl 'https://api.example.com/data?page=1' \
  -H 'Authorization: Bearer xxx' \
  -H 'Content-Type: application/json'

步驟三:轉換成 Python / Node.js 程式碼

取得 cURL 後,可以:

  • curlconverter.com 自動轉成各語言程式碼
  • 或直接告訴 AI:「把這段 cURL 轉成 Python requests」
import requests

headers = {
    "Authorization": "Bearer xxx",
    "Content-Type": "application/json",
}

res = requests.get("https://api.example.com/data", params={"page": 1}, headers=headers)
print(res.json())

16.4 還是找不到 API?用無頭瀏覽器

如果資料是由前端 JavaScript 動態渲染(例如 SPA 框架),Network 分頁也找不到明確的 API,就需要用無頭瀏覽器模擬真實使用者操作。

工具語言說明
PlaywrightPython / Node.js現代首選,支援 Chromium / Firefox / WebKit
PuppeteerNode.jsGoogle 官方,只支援 Chromium
SeleniumPython / Java / 多語言老牌工具,相容性廣
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com")
    print(page.inner_text("h1"))
    browser.close()

16.5 判斷流程速查

目標網站有資料要抓
  檢視頁面原始碼(Ctrl+U)
  能看到資料?
   ┌──────┴──────┐
  是              否
   │              │
靜態網站        動態網站
requests +      開 Network 分頁
BeautifulSoup   找 Fetch/XHR 請求
              找到 API?
            ┌────┴────┐
           是          否
            │          │
        直接呼叫      無頭瀏覽器
        API 端點      (Playwright)