線上課程觀課進度管理小工具開發日誌
Day 6:JavaScript 程式碼執行排序:遞迴函數、Call Stack、Task Queue
Call Stack;遞迴函數與 call stack 的關係;Task Queue(非同步的概念再釐清)
Day 9:「修煉圈圈 Practice Rings」Web app 開發日誌 - 2025 六角 AI Vibe Coding 體驗營期末大魔王作業
這份筆記旨在記錄 2025 六角 Vibe Coding 體驗營每日作業 Day 21 的期末專案成果,從一個簡單的個人痛點出發,透過與 AI(在我這個情境中是 Perplexity)協作,在一天內完成了一個包含前後端的全端網頁小工具:「修煉圈圈 Practice Rings」。
<100 subscribers
線上課程觀課進度管理小工具開發日誌
Day 6:JavaScript 程式碼執行排序:遞迴函數、Call Stack、Task Queue
Call Stack;遞迴函數與 call stack 的關係;Task Queue(非同步的概念再釐清)
Day 9:「修煉圈圈 Practice Rings」Web app 開發日誌 - 2025 六角 AI Vibe Coding 體驗營期末大魔王作業
這份筆記旨在記錄 2025 六角 Vibe Coding 體驗營每日作業 Day 21 的期末專案成果,從一個簡單的個人痛點出發,透過與 AI(在我這個情境中是 Perplexity)協作,在一天內完成了一個包含前後端的全端網頁小工具:「修煉圈圈 Practice Rings」。
本文整理第一次 Code review 後第一次程式碼重構的歷程,歸納核心觀念與模組化架構設計,作為後續 Node.js 專案開發的基礎參考。
依責任將程式碼切分為三層,每一層只關注自己的任務:
驗證層(Validation Layer)
職責:定義資料的合法性規則(型別、格式、範圍),不涉及業務邏輯或執行環境。
特性:純函式(Pure Function),無副作用,可被任何模組(CLI、核心邏輯、前端)重用。
例子:validate.js 裡的 parseNum、parseNonNegativeInt。
核心邏輯層(Core Domain Layer)
職責:處理業務規則(例如「計算訂單總價」、「更新使用者狀態」),產生運算結果。
特性:依賴驗證層進行防禦,但不依賴執行環境(如 readline、DOM)。
設計:可選擇在入口保留防禦式檢查,讓核心邏輯在被其他模組直接呼叫時仍具備保護能力。
例子:order.js 的 calculateTotal。
執行層 / 互動層(Execution / Interaction Layer)
職責:處理與使用者的互動(輸入/輸出),串接驗證層與核心邏輯層。
特性:高度依賴特定環境 API(如 Node.js readline/promises),負責流程控制(迴圈、重試)。
例子:各功能的 main.js,以及 CLI 封裝工具 cliPrompt.js。
建立清晰、單向的相依關係,避免循環依賴與混亂:
相依方向大致為:
執行層 main.js
↓
CLI 工具 cliPrompt.js / readline.js
↓
驗證層 validate.js ← 核心邏輯層 order.js
原則:
底層模組(如 validate.js)不應依賴上層模組。
CLI 相關模組(readline.js, cliPrompt.js)不應反向依賴業務邏輯模組。
相對路徑匯入時,於 ES module 環境要使用完整路徑+.js 副檔名,例如:../util/validate.js。
採用 util/ 目錄集中通用工具,各功能資料夾(feature-a/, feature-b/)專注於該功能的業務邏輯與執行入口:
root/
├── util/ # 通用工具庫
│ ├── validate.js # 純驗證函式(Parse & Check)
│ ├── validate.test.js # 驗證函式的單元測試
│ ├── readline.js # Node.js Readline 封裝(Factory Pattern)
│ └── cliPrompt.js # CLI 互動封裝(問答、驗證、重試迴圈)
│
├── feature-a/ # 功能 A:訂單處理
│ ├── order.js # 核心邏輯(Domain Logic)
│ ├── order.test.js # 核心邏輯測試
│ └── main.js # 執行入口(CLI Entry Point)
│
├── feature-b/ # 功能 B:庫存更新
│ ├── stock.js
│ ├── stock.test.js
│ └── main.js
│
└── package.json
此結構的意義:
util/ 保留為可重用工具庫,避免與特定業務邏輯糾纏。
每個功能資料夾完整擁有「核心邏輯+測試+執行入口」,方便獨立開發與重構。
驗證模組 validate.js 採取由下而上的組合式設計:
parseNum
輸入:任意值。
行為:
轉字串,trim 空白。
使用正規表達式 /^[0-9]+$/ 確認為純數字且至少一位。
使用 parseInt(str, 10) 轉成十進位整數。
若格式不符合,丟出 Error('輸入錯誤')。
parseNonNegativeInt
輸入:任意值。
行為:呼叫 parseNum,再檢查 num >= 0。
若小於 0,丟出 Error('輸入錯誤')。
parsePositiveInt
輸入:任意值。
行為:呼叫 parseNum,再檢查 num > 0。
若小於等於 0,丟出 Error('輸入錯誤')。
此設計讓「型別/格式驗證」與「數值範圍規則」可自由組合、重用。
validate.test.js 的測試規劃遵守以下原則:
每個驗證函式至少具備:
合法輸入案例:確認回傳值正確。
非法輸入案例:確認會丟出預期錯誤(例:'abc'、'-1'、'0' 對正整數函式而言)。
對丟錯的測試一律採用:
expect(() => someValidator(badInput)).toThrow('輸入錯誤');
一旦驗證模組測試綠燈,後續核心邏輯與 CLI 僅需專注自身行為,出現異常時也能快速定位是驗證層還是核心層的問題。
readline.js 封裝 Node.js 的 readline/promises:
每次呼叫 createRl() 建立新的 Interface。
在關閉後(rl.close())不再使用舊實例,避免 ERR_USE_AFTER_CLOSE 類錯誤。
此設計允許在 while 迴圈中反覆「關閉舊介面、建立新介面」,實作多輪輸入與重試。
cliPrompt.js 抽象出「問一題+驗證+錯誤重試」的固定模式:
參數:
prompt: 要顯示的提示字串。
validator: 驗證函式(例如 parseNum、parseNonNegativeInt)。
邏輯:
使用 while 迴圈反覆提問。
每次輸入交給 validator 檢查:
驗證通過 → 回傳解析後的值,結束迴圈。
驗證失敗 → 顯示錯誤訊息,重新建立 readline Interface,進入下一輪提問。
此工具讓「互動流程」與「驗證規則」實現鬆耦合,業務端只需替換驗證函式即可復用一整套輸入流程。
將上述架構應用於兩個常見的功能開發場景。
此功能需要使用者輸入一個正整數作為訂單數量。
CLI 執行程式碼:透過 askWithValidator('請輸入訂單數量:', parsePositiveInt) 取得合法的正整數。
核心函式 processOrder(quantity):在已驗證前提下,處理業務邏輯。
// feature-a/order.js
export function processOrder(quantity) {
// 此處已假設 quantity 為正整數,直接處理業務
return `訂單已接收,數量:${quantity}`;
}
此功能需要使用者輸入商品 ID(正整數)與庫存數量(非負整數)。
CLI 執行程式碼:分別呼叫 askWithValidator 兩次,並傳入不同的驗證函式。
const productId = await askWithValidator('請輸入商品 ID:', parsePositiveInt);
const stockLevel = await askWithValidator('請輸入庫存數量:', parseNonNegativeInt);
核心函式 updateStock(productId, stockLevel):內部再次呼叫驗證函式做防禦,確保收到的參數符合預期。
// feature-b/stock.js
import { parsePositiveInt, parseNonNegativeInt } from '../util/validate.js';
export function updateStock(productId, stockLevel) {
const validProductId = parsePositiveInt(productId);
const validStockLevel = parseNonNegativeInt(stockLevel);
return `商品 ${validProductId} 的庫存已更新為 ${validStockLevel}`;
}
在此結構下,updateStock 的單元測試會涵蓋「productId 為 0 或負數」以及「stockLevel 為負數」等非法情境,間接確保了驗證模組被正確依賴。
高可重用性:驗證邏輯與 CLI 互動邏輯被抽離,新功能只需專注於核心演算法與特定的驗證規則組合。
高可維護性:修改驗證規則(例如定義何謂整數)只需修改 validate.js 一處,所有功能同步更新。
測試清晰:單元測試各司其職,validate.test.js 測格式,order.test.js 測業務規則,除錯範圍明確。
防禦式程式設計:透過「UI/CLI 即時攔截」與「Domain 最終防禦」兩層保護,讓程式碼更健壯。
本文整理第一次 Code review 後第一次程式碼重構的歷程,歸納核心觀念與模組化架構設計,作為後續 Node.js 專案開發的基礎參考。
依責任將程式碼切分為三層,每一層只關注自己的任務:
驗證層(Validation Layer)
職責:定義資料的合法性規則(型別、格式、範圍),不涉及業務邏輯或執行環境。
特性:純函式(Pure Function),無副作用,可被任何模組(CLI、核心邏輯、前端)重用。
例子:validate.js 裡的 parseNum、parseNonNegativeInt。
核心邏輯層(Core Domain Layer)
職責:處理業務規則(例如「計算訂單總價」、「更新使用者狀態」),產生運算結果。
特性:依賴驗證層進行防禦,但不依賴執行環境(如 readline、DOM)。
設計:可選擇在入口保留防禦式檢查,讓核心邏輯在被其他模組直接呼叫時仍具備保護能力。
例子:order.js 的 calculateTotal。
執行層 / 互動層(Execution / Interaction Layer)
職責:處理與使用者的互動(輸入/輸出),串接驗證層與核心邏輯層。
特性:高度依賴特定環境 API(如 Node.js readline/promises),負責流程控制(迴圈、重試)。
例子:各功能的 main.js,以及 CLI 封裝工具 cliPrompt.js。
建立清晰、單向的相依關係,避免循環依賴與混亂:
相依方向大致為:
執行層 main.js
↓
CLI 工具 cliPrompt.js / readline.js
↓
驗證層 validate.js ← 核心邏輯層 order.js
原則:
底層模組(如 validate.js)不應依賴上層模組。
CLI 相關模組(readline.js, cliPrompt.js)不應反向依賴業務邏輯模組。
相對路徑匯入時,於 ES module 環境要使用完整路徑+.js 副檔名,例如:../util/validate.js。
採用 util/ 目錄集中通用工具,各功能資料夾(feature-a/, feature-b/)專注於該功能的業務邏輯與執行入口:
root/
├── util/ # 通用工具庫
│ ├── validate.js # 純驗證函式(Parse & Check)
│ ├── validate.test.js # 驗證函式的單元測試
│ ├── readline.js # Node.js Readline 封裝(Factory Pattern)
│ └── cliPrompt.js # CLI 互動封裝(問答、驗證、重試迴圈)
│
├── feature-a/ # 功能 A:訂單處理
│ ├── order.js # 核心邏輯(Domain Logic)
│ ├── order.test.js # 核心邏輯測試
│ └── main.js # 執行入口(CLI Entry Point)
│
├── feature-b/ # 功能 B:庫存更新
│ ├── stock.js
│ ├── stock.test.js
│ └── main.js
│
└── package.json
此結構的意義:
util/ 保留為可重用工具庫,避免與特定業務邏輯糾纏。
每個功能資料夾完整擁有「核心邏輯+測試+執行入口」,方便獨立開發與重構。
驗證模組 validate.js 採取由下而上的組合式設計:
parseNum
輸入:任意值。
行為:
轉字串,trim 空白。
使用正規表達式 /^[0-9]+$/ 確認為純數字且至少一位。
使用 parseInt(str, 10) 轉成十進位整數。
若格式不符合,丟出 Error('輸入錯誤')。
parseNonNegativeInt
輸入:任意值。
行為:呼叫 parseNum,再檢查 num >= 0。
若小於 0,丟出 Error('輸入錯誤')。
parsePositiveInt
輸入:任意值。
行為:呼叫 parseNum,再檢查 num > 0。
若小於等於 0,丟出 Error('輸入錯誤')。
此設計讓「型別/格式驗證」與「數值範圍規則」可自由組合、重用。
validate.test.js 的測試規劃遵守以下原則:
每個驗證函式至少具備:
合法輸入案例:確認回傳值正確。
非法輸入案例:確認會丟出預期錯誤(例:'abc'、'-1'、'0' 對正整數函式而言)。
對丟錯的測試一律採用:
expect(() => someValidator(badInput)).toThrow('輸入錯誤');
一旦驗證模組測試綠燈,後續核心邏輯與 CLI 僅需專注自身行為,出現異常時也能快速定位是驗證層還是核心層的問題。
readline.js 封裝 Node.js 的 readline/promises:
每次呼叫 createRl() 建立新的 Interface。
在關閉後(rl.close())不再使用舊實例,避免 ERR_USE_AFTER_CLOSE 類錯誤。
此設計允許在 while 迴圈中反覆「關閉舊介面、建立新介面」,實作多輪輸入與重試。
cliPrompt.js 抽象出「問一題+驗證+錯誤重試」的固定模式:
參數:
prompt: 要顯示的提示字串。
validator: 驗證函式(例如 parseNum、parseNonNegativeInt)。
邏輯:
使用 while 迴圈反覆提問。
每次輸入交給 validator 檢查:
驗證通過 → 回傳解析後的值,結束迴圈。
驗證失敗 → 顯示錯誤訊息,重新建立 readline Interface,進入下一輪提問。
此工具讓「互動流程」與「驗證規則」實現鬆耦合,業務端只需替換驗證函式即可復用一整套輸入流程。
將上述架構應用於兩個常見的功能開發場景。
此功能需要使用者輸入一個正整數作為訂單數量。
CLI 執行程式碼:透過 askWithValidator('請輸入訂單數量:', parsePositiveInt) 取得合法的正整數。
核心函式 processOrder(quantity):在已驗證前提下,處理業務邏輯。
// feature-a/order.js
export function processOrder(quantity) {
// 此處已假設 quantity 為正整數,直接處理業務
return `訂單已接收,數量:${quantity}`;
}
此功能需要使用者輸入商品 ID(正整數)與庫存數量(非負整數)。
CLI 執行程式碼:分別呼叫 askWithValidator 兩次,並傳入不同的驗證函式。
const productId = await askWithValidator('請輸入商品 ID:', parsePositiveInt);
const stockLevel = await askWithValidator('請輸入庫存數量:', parseNonNegativeInt);
核心函式 updateStock(productId, stockLevel):內部再次呼叫驗證函式做防禦,確保收到的參數符合預期。
// feature-b/stock.js
import { parsePositiveInt, parseNonNegativeInt } from '../util/validate.js';
export function updateStock(productId, stockLevel) {
const validProductId = parsePositiveInt(productId);
const validStockLevel = parseNonNegativeInt(stockLevel);
return `商品 ${validProductId} 的庫存已更新為 ${validStockLevel}`;
}
在此結構下,updateStock 的單元測試會涵蓋「productId 為 0 或負數」以及「stockLevel 為負數」等非法情境,間接確保了驗證模組被正確依賴。
高可重用性:驗證邏輯與 CLI 互動邏輯被抽離,新功能只需專注於核心演算法與特定的驗證規則組合。
高可維護性:修改驗證規則(例如定義何謂整數)只需修改 validate.js 一處,所有功能同步更新。
測試清晰:單元測試各司其職,validate.test.js 測格式,order.test.js 測業務規則,除錯範圍明確。
防禦式程式設計:透過「UI/CLI 即時攔截」與「Domain 最終防禦」兩層保護,讓程式碼更健壯。
Share Dialog
Share Dialog
No comments yet