跳到主要內容

FE Day 43 文件與交接

FE Day 43 文件與交接

執行需求:CPU 可跑。今天是「前端開發實戰:React 與 Next.js 全套」系列的第四十三天,貫穿專案「預約管理系統前端」進入交付階段。今天不寫產品功能,只做一件事:把 42 天累積的知識從「在某個人腦子裡」變成「在專案裡」。具體交付八份資料:一份主說明(README)、一份架構與路由地圖、一份 API 契約對照表、三份決策紀錄(ADR)、一份環境變數清單、一份值班手冊(Runbook)、一份貢獻指南加 PR 模板、一份新成員上線檢核表;再寫一支自動產生路由文件的腳本,讓文件不會跟程式碼脫節。今天所有資料都是虛構示範,沿用 Day 31–42 的共用設定,不連任何外部服務。

引言

大部分 side project 不是死在「做不出來」,而是死在「做出來之後沒人敢動」。三個月後你想加一個新欄位,打開專案才發現:不知道 AuthProvider 為什麼把 access token 放非 httpOnly cookie、不知道 NEXT_PUBLIC_API_MODE 有哪幾種值、不知道 mock 帳號在哪、不知道部署失敗時該先看哪個儀表板。於是你花了半天重新讀程式碼,才敢下第一行修改。這段「重新理解」的時間就是交接成本,而它的高低完全取決於今天這一天有沒有做。

貫穿專案的「預約管理系統」是個小型服務業線上預約平台(情境涵蓋攝影棚、健身教練、諮詢工作室,所有資料都是虛構示範)。專案到 Day 42 為止的樣貌是:Next.js 15 App Router、React 19.x、TypeScript 5.9、Tailwind CSS 4.x;11 個頁面、兩種角色、5 個 reducer 動作的登入狀態機、一套 components/ui/ 設計系統、三層錯誤處理、Vitest 3.x 加 Playwright 1.5x 的測試、以及 Day 41 的部署設定與 Day 42 的品質檢查。

今天的內容分四段:第一段把 Day 31–42 的共用設定再次統整;第二段說明「交接要交付哪四種資料」以及為什麼「為什麼」比「怎麼做」重要;第三段實作八份交接資料與一支自動產生腳本;第四段講文件維護的節奏與常見錯誤。讀完之後你會拿到一套可以直接放進自己專案的資料骨架。

貫穿專案共用設定(Day 31–44 沿用)

今天的交接資料要把從 Day 31 到 Day 42 的設定一次講清楚,這份設定本身就是要交付的核心內容:

  • 框架:Next.js 15(App Router、Server Components 預設)、React 19.x、TypeScript 5.9
  • 樣式:Tailwind CSS 4.x、CSS variables 主題、Day 32 的 components/ui/
  • 後端:FastAPI 預約管理系統(Web 系列 Day 35–44 定義的 REST API);NEXT_PUBLIC_API_MODE=mock 時走 lib/mock/*.ts fixture,live 時走 /api/proxy 轉發
  • 認證:JWT access token(12 小時,bf_access cookie)、refresh token(14 天,httpOnly cookie bf_refresh);沿用 Day 38 的 AuthProvider 與 5 個 action(BOOTSTRAP / LOGIN / REFRESH / LOGOUT / EXPIRE)
  • 角色:admin(管理者)、customer(客戶);公開頁面 /、/services、/services/[id]、/login、/register;客戶頁 /bookings、/bookings/new;後台 /admin、/admin/bookings、/admin/services、/admin/services/[id]/slots
  • 狀態管理:TanStack Query 5.x(伺服器狀態)、useState 與 useReducer(UI 狀態)、Context(使用者狀態)
  • 品質工具:Vitest 3.x、Playwright 1.5x、axe-core 4.x、ESLint 9 flat config
  • 環境變數:NEXT_PUBLIC_API_BASE_URL、NEXT_PUBLIC_API_MODE、NEXT_PUBLIC_SENTRY_DSN、SENTRY_AUTH_TOKEN、SENTRY_ORG、SENTRY_PROJECT、NEXT_PUBLIC_APP_URL

這份設定從 Day 31 到 Day 44 不變。今天的工作是把這份設定「從口耳相傳變成可查閱」,而且刻意只寫事實與理由,不寫教學。教學在系列裡有 40 多篇可以回頭查;交接資料要回答的是「這個專案現在長什麼樣、為什麼這樣、要改的時候去哪裡改」。

原理解念:交接的四種資料與「為什麼」優先

交接資料可以分成四類,每一類回答一個不同的問題。第一類是導覽型:回答「這是什麼專案、怎麼把它跑起來」。對應 README 與上線檢核表。第二類是規格型:回答「系統由哪些部分組成、彼此怎麼溝通」。對應架構與路由地圖、API 契約對照表、環境變數清單。第三類是決策型:回答「為什麼當初這樣選、什麼情況下應該重新考慮」。對應 ADR。第四類是操作型:回答「發生問題時第一件事做什麼」。對應值班手冊與貢獻指南。四類各有各的讀者與時機:新人第一天讀導覽型,第一週讀規格型,遇到爭議時讀決策型,半夜被叫起來時只讀操作型。

四類之中最常被省略、卻最有價值的是決策型。原因很直觀:程式碼本身會告訴你「做了什麼」,但不會告訴你「為什麼不做另一種」。以 Day 38 的 token 儲存為例,程式碼寫著把 access token 放進非 httpOnly 的 bf_access cookie,如果沒有決策紀錄,接手的人很可能「順手修正」成 localStorage 或純記憶體,結果把 BFF 的中介層打壞。把「因為我們用 Route Handler 做轉發,server 端需要讀得到 token,而純記憶體做不到,所以選擇 SameSite=Lax 的短效 cookie」這一段寫下來,就能保護這個決策不被誤改。

決策型資料的標準格式是 ADR(Architecture Decision Record)。一份 ADR 只需要五個欄位:標題與編號、狀態(提議中/已採用/已取代)、背景(當時面對什麼問題)、決策(選了什麼)、後果(得到什麼、犧牲什麼)。篇幅通常半頁到一頁,不需要寫成論文。ADR 的價值來自「累積」而不是「單篇」:當專案有 20 份 ADR,新成員只要依序讀完,就能重建整個系統的思考脈絡。我們今天會寫三份:token 儲存位置、mock 與 live 雙模式、以及 Server Component 與 Client Component 的分界。

另一個要建立的心態是「文件是程式碼的一部分」。文件放在同一個 repo、同一個 PR、跟著程式碼一起被審查;改動行為的 PR 如果沒有同步更新對應文件,就是未完成的 PR。這條規則聽起來嚴格,但它反而讓文件變少:因為你不再需要維護一份「獨立於程式碼之外、遲早會過期」的文件站。今天寫的 scripts/gen-docs.mjs 就是這個原則的具體落實——路由清單不是手寫的,而是從 app/ 目錄自動產生的,程式碼改了、文件就重跑一次。

完整實作:八份交接資料與自動產生腳本

第一步,先把文件的位置定下來。我們不把所有東西塞進 README,而是用 docs/ 目錄分類,讓每一份有自己的責任:

# 交接資料的目錄結構(全部跟程式碼同一個 repo)
booking-frontend/
├── README.md                    # 導覽:這是什麼、怎麼跑起來
├── RUNBOOK.md                   # 操作:出事了先做什麼
├── CONTRIBUTING.md              # 操作:怎麼送 PR
├── docs/
│   ├── ARCHITECTURE.md          # 規格:架構、路由、資料流
│   ├── API-CONTRACT.md          # 規格:前端用到的所有端點
│   ├── env-vars.md              # 規格:每個環境變數的用途與來源
│   ├── ONBOARDING.md            # 導覽:第一週的檢核表
│   ├── routes.md                # 自動產生:路由清單(勿手改)
│   └── adr/
│       ├── 0001-token-storage.md
│       ├── 0002-mock-and-live-mode.md
│       └── 0003-server-vs-client-components.md
└── .github/
    └── pull_request_template.md

第二步寫 README。README 是最多人讀、也最常被寫砸的一份。常見的錯誤是「把 README 寫成技術手冊」:貼上完整的架構圖、所有 API、所有環境變數,結果新人讀完還是不會跑。好的 README 只回答三件事:這是什麼、怎麼在本機跑起來、接下來去哪裡看更多。以下用註解形式呈現 Markdown 的內容結構。

# 預約管理系統|前端(booking-frontend)
#
# 小型服務業的線上預約平台前端。客戶可以瀏覽服務、選擇時段、建立預約;
# 管理者可以管理服務品項、確認或取消預約、設定可預約時段。
#
# ## 快速開始
#
#     pnpm install
#     cp .env.example .env.local     # 預設 NEXT_PUBLIC_API_MODE=mock
#     pnpm dev                       # http://localhost:3000
#
# 沒有後端也能跑:mock 模式內建三筆服務與兩筆預約的示範資料。
# 示範帳號:admin@example.com / admin-pass、alice@example.com / alice-pass。
# 這些帳號只存在於 lib/mock/,不會被打包進 production。
#
# ## 常用指令
#
#     pnpm dev          # 開發伺服器
#     pnpm build        # production 建置
#     pnpm typecheck    # TypeScript 檢查
#     pnpm lint         # ESLint 9
#     pnpm test         # Vitest 單元與元件測試
#     pnpm test:e2e     # Playwright 端對端與 a11y 檢查
#
# ## 更多資料
#
# - 架構、路由與資料流:docs/ARCHITECTURE.md
# - API 契約對照:docs/API-CONTRACT.md
# - 環境變數清單:docs/env-vars.md
# - 事故處理:RUNBOOK.md
# - 決策紀錄:docs/adr/

第三步寫架構與路由地圖。這份資料的目標讀者是「要在這裡改東西的人」,所以重點不是畫得漂亮,而是「從畫面找到檔案」與「從資料找到來源」兩條路徑都通。我們用文字樹與表格,不用複雜的圖表工具,因為文字在終端機、在 diff、在 PR 描述裡都能讀。

// docs/ARCHITECTURE.md
//
// ## 分層
//
// app/           路由與版面(Server Component 為主)
//   └── 只負責讀參數、取資料、組版面;不放業務邏輯
// components/ui/ 設計系統(Button、Input、Card、Badge、Dialog、Toast)
// components/    功能元件(booking/ 客戶端、admin/ 後台)
// lib/api/       資料層;依 API_MODE 切換 mock 與 live
// lib/mock/      離線示範資料(fixture)
// hooks/         共用鉤子(useMediaQuery、useToast)
// tests/         Vitest 與 Playwright
//
// ## 一條資料流(客戶看服務清單)
//
// 1. app/services/page.tsx(Server Component)讀 URL 的 searchParams
// 2. 呼叫 lib/api/services.ts 的 fetchServices(filter)
// 3. fetchServices 依 API_MODE 決定走 mock 或 /api/proxy/services
// 4. 資料以 props 傳進 components/booking/ServicesList(Client Component)
// 5. ServicesList 用 router.replace 把篩選條件寫回 URL
//
// ## 為什麼這樣切
//
// - 讀取與渲染交給 Server Component:少一段 client 的載入狀態
// - 互動與 URL 同步交給 Client Component:需要 useSearchParams 與事件
// - 兩者的交界只有 props;不互相 import 對方的內部檔案

第四步寫 API 契約對照表。這份資料是前端與後端的交接點,最重要的原則是「唯一來源」。我們不在前端自己發明端點,而是照抄 Web 系列 Day 35–44 的定義,並標上出處,讓兩邊永遠對得起來。表格同時標明每個端點需要的角色,這樣寫權限檢查時不必再回去翻規格。

// docs/API-CONTRACT.md
//
// 後端來源:FastAPI 預約管理系統(Web 系列 Day 35-44)
// 前端一律透過 /api/proxy/* 轉發;mock 模式不走網路。
//
// | 方法   | 端點                              | 角色     | 用途               |
// | ------ | --------------------------------- | -------- | ------------------ |
// | POST   | /auth/register                    | 公開     | 註冊客戶帳號       |
// | POST   | /auth/login                       | 公開     | 登入並取得 token   |
// | POST   | /auth/refresh                     | 公開     | 換發 access token  |
// | GET    | /services                         | 公開     | 列出啟用中的服務   |
// | GET    | /services/{id}/slots?from=&to=    | 公開     | 查可預約時段       |
// | GET    | /bookings                         | customer | 自己的預約         |
// | POST   | /bookings                         | customer | 建立預約           |
// | POST   | /bookings/{id}/cancel             | customer | 取消預約           |
// | GET    | /admin/bookings                   | admin    | 所有客戶預約       |
// | PATCH  | /bookings/{id}                    | admin    | 確認或改期         |
// | POST   | /services                         | admin    | 新增服務           |
// | PATCH  | /services/{id}                    | admin    | 編輯服務           |
//
// 錯誤格式:{ "detail": "找不到服務" },前端在 lib/api/client.ts 統一轉成 ApiError。
// 時間格式:ISO 8601 含時區位移;顯示時一律用 Intl.DateTimeFormat 轉當地時間。

第五步寫環境變數清單。這份資料最常被需要、又最容易缺漏:新人拿到專案,第一個卡住的地方永遠是「少一個環境變數」。每一列都必須寫清楚「用途」「哪個環境要填」「去哪裡拿」,尤其不能只寫變數名。敏感值本身絕對不進 repo,只寫來源與取得方式。

// docs/env-vars.md
//
// | 變數                        | 環境            | 用途                            | 來源                     |
// | --------------------------- | --------------- | ------------------------------- | ------------------------ |
// | NEXT_PUBLIC_API_BASE_URL    | 全部            | 後端基底網址                    | production / staging 網址 |
// | NEXT_PUBLIC_API_MODE        | 全部            | mock 或 live                    | 固定值                   |
// | NEXT_PUBLIC_APP_URL         | production      | 產生絕對網址(分享、信件連結)  | 自訂網域                 |
// | NEXT_PUBLIC_SENTRY_DSN      | production      | 前端錯誤回報                    | Sentry 專案設定          |
// | SENTRY_AUTH_TOKEN           | build 階段      | 上傳 source map                 | Sentry 使用者設定        |
// | SENTRY_ORG / SENTRY_PROJECT | build 階段      | 指定要上傳到哪個專案            | Sentry 專案設定          |
//
// 規則一:只有 NEXT_PUBLIC_ 前綴會被送進瀏覽器,其餘只在 server 端可讀。
// 規則二:SENTRY_* 四個變數要嘛全填、要嘛全不填,lib/env.ts 會在啟動時檢查。
// 規則三:新增變數時,同一個 PR 必須同步更新 .env.example 與本文件。

第六步寫決策紀錄。三份 ADR 各對應一個「看起來怪、其實有理由」的設計。每份都短,但每一份都能省下接手的人半天摸索。以下用第一份(token 儲存)當範本,其餘兩份依同一格式撰寫。

// docs/adr/0001-token-storage.md
//
// # ADR 0001:access token 儲存位置
//
// ## 狀態
// 已採用(2026-04-11,Day 38)
//
// ## 背景
// 我們用 Next.js Route Handler 做 BFF,由 server 端轉發請求到 FastAPI。
// server 端讀不到 React 記憶體中的 token,因此 token 必須存在 cookie。
//
// ## 決策
// - access token:非 httpOnly 的 bf_access cookie,Max-Age 12 小時,SameSite=Lax
// - refresh token:httpOnly 的 bf_refresh cookie,14 天,僅由 /api/auth/refresh 讀取
//
// ## 後果
// 好處:BFF 轉發時讀得到 token,程式碼大幅簡化;refresh token 不受 XSS 影響。
// 代價:access token 對 JavaScript 可見,XSS 成功時最長有 12 小時的冒用風險。
// 重新考慮的時機:若未來改成純 SPA(無 server 中介層),應改存記憶體。

第七步是 Runbook。Runbook 的讀者通常在壓力下閱讀,所以它的寫法跟其他資料完全不同:短句、編號、先做什麼再確認什麼,不要有背景說明。Day 41 已經寫過第一版,今天把「已知限制」補進去,讓值班的人知道哪些問題現在沒有解法、該找誰。

// RUNBOOK.md(Day 43 補充:已知限制)
//
// ## 已知限制(不是故障,不要重開服務)
//
// 1. Serverless 冷啟動:閒置後第一次請求約慢 200-500 毫秒,屬預期行為。
// 2. Preview 環境的 Sentry 是獨立專案:在 Preview 看到的錯誤不會出現在 production。
// 3. mock 模式不會寫入任何資料:重新整理後示範預約會回到初始狀態。
//
// ## 部署失敗
// 1. 開 Vercel → Deployments → 看最新一筆的 build log。
// 2. 若是 Type error,本機先跑 pnpm typecheck 重現。
// 3. 修好後 push 到 main,Vercel 會自動重新部署。
//
// ## production 出現大量 5xx
// 1. 開 Sentry → Issues,找最熱門的錯誤。
// 2. 到 Vercel → Deployments,把上一個成功的版本 Promote to Production。
// 3. 在事故頻道開一個討論串,附上 Sentry 與 Vercel 的截圖。

第八步寫貢獻指南與 PR 模板。這份資料的功能是「把隱性規範變成顯性檢查」,讓每個 PR 在送出前就自我檢查一次。我們刻意把檢查寫少,只留真的會被擋下的幾條,否則模板會變成沒人看的例行公事。

// .github/pull_request_template.md
//
// ## 這個 PR 做了什麼
// (一句話說明,並附上相關的 issue 編號)
//
// ## 我怎麼驗證
// - [ ] pnpm typecheck 與 pnpm lint 通過
// - [ ] pnpm test 通過(新增行為有對應測試)
// - [ ] pnpm test:e2e 通過(含 a11y 與四斷點檢查)
// - [ ] 手動走過受影響的流程(桌機與 390 寬)
//
// ## 需要同步更新的資料
// - [ ] docs/ARCHITECTURE.md(若改動分層或路由)
// - [ ] docs/API-CONTRACT.md(若改動使用的端點)
// - [ ] docs/env-vars.md 與 .env.example(若新增環境變數)
// - [ ] docs/adr/(若做出難以回頭的技術選擇)

最後是自動產生腳本。手寫的路由清單一定會過期,所以我們讓它自己長出來:掃描 app/ 目錄、找出所有 page.tsx、把 route group(例如 (auth))排除在 URL 之外,再依第一層目錄推斷需要的角色,輸出成一份 Markdown 表格。這支腳本只用 Node 內建模組,可以直接執行。

// scripts/gen-docs.mjs
// 掃描 app/ 目錄產生 docs/routes.md,讓路由文件不會跟程式碼脫節。
// 執行:node scripts/gen-docs.mjs
import { readdir, writeFile } from 'node:fs/promises';
import path from 'node:path';

const APP_DIR = path.resolve('app');
const ROLE_BY_PREFIX = { admin: 'admin', bookings: 'customer' };

async function walk(dir, segments = []) {
  const entries = await readdir(dir, { withFileTypes: true });
  const routes = [];

  for (const entry of entries) {
    if (entry.isDirectory()) {
      // route group 例如 (auth) 不進 URL,所以不加入路徑片段
      const next = entry.name.startsWith('(') ? segments : [...segments, entry.name];
      routes.push(...(await walk(path.join(dir, entry.name), next)));
    } else if (entry.name === 'page.tsx') {
      routes.push(`/${segments.join('/')}`.replace(/\/$/, '/'));
    }
  }
  return routes.sort();
}

function roleOf(route) {
  const head = route.replace(/^\//, '').split('/')[0];
  return ROLE_BY_PREFIX[head] ?? 'public';
}

const routes = await walk(APP_DIR);
const rows = routes.map((route) => {
  const file = route === '/' ? 'app/page.tsx' : `app${route.replace(/\/$/, '')}/page.tsx`;
  return `| \`${route}\` | ${roleOf(route)} | \`${file}\` |`;
});

const markdown = [
  '# 路由清單',
  '',
  '由 scripts/gen-docs.mjs 自動產生,請勿手動編輯。',
  '',
  '| URL | 角色 | 來源檔案 |',
  '| --- | --- | --- |',
  ...rows,
  '',
].join('\n');

await writeFile('docs/routes.md', markdown, 'utf8');
console.log(`已產生 docs/routes.md,共 ${routes.length} 條路由`);

常見錯誤與踩雷

第一個常見錯誤是「README 寫成技術手冊」。把所有架構、所有端點、所有環境變數都塞進 README,結果新人讀完還是不會把專案跑起來。修法是把 README 的功能收斂成「讓人 5 分鐘內跑起來」,其餘內容拆到 docs/ 底下並在 README 末端放一張索引。判斷標準很簡單:如果一段內容的讀者是「已經跑起來、要改東西的人」,它就不該在 README。

第二個踩雷是「文件在獨立的地方」。把交接資料放在共用的雲端硬碟、或是另一個 wiki,短期看起來很乾淨,長期一定會過期——因為改程式碼的人不會記得去改另一份。修法是把資料放進同一個 repo,並在 PR 模板裡加上「需要同步更新的資料」檢查項。我們今天的做法就是如此:文件在版控裡、跟著 PR 一起被審查,甚至可以寫測試檢查連結是否失效。

第三個是「只寫怎麼做、不寫為什麼」。這是最容易犯、代價也最高的一個。程式碼已經寫了「怎麼做」,再抄一次只是浪費;真正需要留下的是取捨的理由,例如「為什麼用 cookie 不用記憶體」、「為什麼公開頁面用 ISR 而個人頁面強制動態」。沒有這層理由,接手的人會以為那是疏忽而「順手修好」,把一個刻意的設計改壞。今天三份 ADR 就是為了這件事。

第四個是「把 mock 示範資料當成規格寫進交接資料」。mock 帳號與示範資料是為了讓專案能離線跑的權宜設計,不是產品規格。如果交接資料只寫 mock 的形狀、沒寫真實 API 契約,接手的人會照著 mock 開發,上線後才發現欄位對不上。修法是把 mock 明確標為示範,並以 docs/API-CONTRACT.md 為唯一規格來源,同時提醒 mock 帳號不會被打包進 production。

第五個是「Runbook 寫成教學」。Runbook 的讀者在半夜、有壓力、只想解決眼前的問題,任何背景說明都是干擾。修法是把每一段寫成「先做 A、再做 B、若 C 就 D」的編號步驟,並且把「這不是故障」的已知限制也列進去——很多深夜告警其實是預期行為,先寫下來可以省下一次無意義的搶修。

效能與實務提醒

交接資料的維護成本要刻意壓低,否則寫完就過期。我們用三個機制來控制:第一,能自動產生的就自動產生(今天的路由清單),人工只寫推導不出來的部分(理由與取捨)。第二,把更新時機綁在既有流程上(PR 模板的檢查項),而不是靠「記得要更新」。第三,讓每份資料都短:README 一頁、ADR 半頁、Runbook 兩頁。短的文件才有機會被維護,長的文件一定過期。

另一件事是「寫給未來的自己」。大多數 side project 沒有第二位開發者,要交接的人其實就是六個月後的自己。判斷一份資料該不該寫有個實用標準:如果六個月後的我會多花超過 15 分鐘才想起來,就值得寫。

關於自動產生腳本,要注意它產生的是「事實」而不是「判斷」。今天的 gen-docs.mjs 能準確列出路由與推斷出的角色,但它沒辦法判斷「這個頁面為什麼需要 admin」。因此自動產生的檔案要開頭標明「請勿手動編輯」,而判斷性內容寫在 ARCHITECTURE.md 與 ADR 裡,兩者互相連結。

最後是文件的可驗證性。文件很容易腐化,因為沒有人會測它。低成本的防線有兩條:一是把 gen-docs.mjs 放進 CI,產生的內容與版控內容不一致就失敗(等同於「文件沒同步」的告警);二是把文件裡的指令抽出來當測試,例如確認 README 列的每一個 pnpm 指令真的存在於 package.json。這兩條加起來不到 30 行程式,卻能擋掉最常見的「文件寫了一個早就改名的指令」。

小結

今天把預約管理系統前端從「能跑」推進到「能交」。我們建立了 docs/ 目錄結構、寫了 README(導覽)、ARCHITECTURE.md(分層與資料流)、API-CONTRACT.md(12 個端點的對照表)、env-vars.md(7 個環境變數的來源與規則)、三份 ADR(token 儲存、雙模式、Server 與 Client 的分界)、Runbook(含已知限制)、PR 模板(把規範變成檢查項),最後寫了一支 scripts/gen-docs.mjs 自動產生路由清單。整套資料不引入任何新依賴,全部跟程式碼同一個 repo。

回頭看今天的產出,可以歸納成一句話:交接資料的價值不在「記錄了什麼」,而在「省下了誰的時間」。README 省下新人摸索環境的時間,API 契約表省下前後端對欄位的時間,ADR 省下重新論辯同一個問題的時間,Runbook 省下事故當下的判斷時間。每一份都對應一個具體的、反覆發生的浪費;沒有對應到浪費的文件,就不該寫。

也因此,今天刻意沒有做完整的端點參考手冊或逐行註解導覽——那兩件事成本高、折舊快,真正需要的讀者卻少。判斷一份交接資料值不值得寫,問的不是「這資訊重要嗎」,而是「這個問題會被重複問幾次」。

結語

今天的重點是把專案從「在某個人腦子裡」搬到「在專案裡」。我們刻意把導覽、規格、決策、操作四類資料分開寫,因為它們的讀者、時機與篇幅都不同;也刻意寫了一支會自動產生文件的小腳本,示範「文件也該有自己的自動化」。明天,我們會做整個系列的收尾:把 44 天的路徑完整回顧一次,盤點這個專案最後交付了什麼、哪些決定最關鍵、哪些地方還有明顯的改進空間;然後跑一次完整的驗收流程(型別檢查、lint、單元測試、端對端與 a11y 檢查、production build),把今天的交接資料當成驗收清單的一部分,確認這份專案真的能交給下一個人。

延伸資源

  • Architecture Decision Records 社群:https://adr.github.io/,ADR 的格式、範本與實務案例。
  • Diátaxis 文件分類框架:https://diataxis.fr/,把文件分成教學、操作、參考、說明四類的判斷準則。
  • Keep a Changelog:https://keepachangelog.com/zh-TW/1.1.0/,變更紀錄的撰寫慣例(繁體中文版)。
  • GitHub 文件 — PR 模板:https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/about-issue-and-pull-request-templates,PR 與 issue 模板的設定方式。
  • Next.js 專案結構說明:https://nextjs.org/docs/app/getting-started/project-structure,App Router 的目錄慣例。
  • Day 41 部署實戰:原文連結,環境變數與 Runbook 的第一版。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...