線上課程觀課進度管理小工具開發日誌
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 的核心目標是提升程式碼品質、分享知識、找出潛在問題。實務上常見的形式可分為:
時機:完成一段功能後,提交給別人看之前。
做法:先放下手邊工作幾分鐘,再回來用「陌生人」的視角重新審視程式碼 diff,檢查是否有明顯的邏輯錯誤、錯字或不一致的命名。
優點:能過濾掉最基本的錯誤,提升自己產出的品質,也讓後續的 review 更有效率。
時機:需要快速得到「語法、風格、明顯 bug」的檢查;或想要「其他寫法」的靈感。
做法:將一小段程式碼或一個檔案交給 AI,並清楚說明程式的目的與你的問題,例如:「幫我找出這段程式碼中可以重構的地方」或「幫我想想還有哪些測試情境我沒考慮到」。
優點:即時性高,能快速找出「機器眼」擅長發現的模式問題,也是很好的 brainstorm 夥伴。
時機:團隊開發的標準流程(例如 GitLab MR / GitHub PR)。
做法:將改動整理成清楚的 commit 或 MR,附上修改目的、核心變更與對應測試。Reviewer 會從架構、可維護性、是否符合團隊規範等角度給出建議。
優點:能藉由不同人的觀點找出自己的盲點,特別是在業務邏輯、架構設計與長期維護性上。
時機:遇到較複雜的問題,或在設計一個新功能時,需要即時討論。
做法:兩個人(或多人)共用一個螢幕或線上共編工具,一人負責寫,其他人邊看邊給建議。本質上是「即時的 code review」,隨時調整寫法。
優點:回饋最即時,適合用在釐清需求、設計 API 或解決棘手問題的階段。
不論用哪種形式,都可以從這三大塊內容檢查:
語法與風格
命名是否語意清楚?(例如 data、temp、item 這類命名是否可以更具體?)
是否使用 === 而非 == 來避免非預期的型別強制轉換?
是否可以用可選串連(?.)或空值合併運算子(??)來簡化 null 或 undefined 的檢查?
演算法與設計
是否有過於複雜的巢狀迴圈?時間複雜度是否合理?
在需要高效能查詢的場景,是否可以用 Map 或 Set 的 has() 取代陣列的 find() 或 includes()?
public API 的定義是否合理?模組與模組之間的界線是否清楚?
測試與邊界
測試是否只測了 happy path(正常情境),而忽略了邊界值(例如 0、-1、空陣列)或錯誤輸入?
測試的 mock 是否過多,導致在測「mock 本身」而不是「真實邏輯」?
測試名稱是否清楚描述「規格」(it('should return sum when given a positive integer')
AI 很適合處理「機器擅長、但人類覺得瑣碎」的情境,可以當作 pre-review 或輔助教練。
開發中「自己先過一輪」的 pre-review
情境:剛寫完一小段功能,丟給同事 review 前。
AI 用途:快速掃描語法錯誤、未使用的變數、重複的邏輯、過長的函式,並提醒缺少哪些測試情境(例如邊界、錯誤輸入)。
練習題或 side project 尋求靈感
情境:像現在練習 TDD、演算法題目。
AI 用途:問「同一題有沒有其他寫法?」、「這段程式碼可以怎麼重構?」、「這份測試規格還可以怎麼拆解?」,當作 brainstorm 夥伴。
處理大量重複、模板化的改動
情境:在多個檔案中加入同樣的 log、錯誤處理,或修改 API 名稱。
AI 用途:幫忙檢查是否有檔案漏改、拼錯字,或協助產生重構後的樣板程式。
理解複雜或老舊的程式碼
情境:接手別人的專案,或看到一段很長的函式。
AI 用途:請它用自然語言摘要「這支函式在做什麼」,或畫出 pseudo-code,幫助你快速建立 mental model。
產生安全性 / 效能的提示清單
情境:處理使用者輸入、檔案 I/O、API 呼叫等潛在風險點。
AI 用途:請它列出「這種情境常見的安全性或效能問題」,當作一份動態 checklist 來逐項檢查。
涉及複雜商業規則的決策。
需要團隊共識的架構設計或風格取捨。
在這類情境,AI 可以提供選項,但最終還是要由人類做出決策。
今天進行了第一次 code review
下面列出這次發現前兩週的程式碼可以再改善的部分:
在 CLI 裡的典型流程:
import { divideBy3Times } from './q9.js';
import readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const rl = readline.createInterface({ input, output });
async function main () {
const num = await rl.question('請輸入一個正整數:');
const n = parseInt(num, 10);
try {
const times = divideBy3Times(n);
console.log(`${n} 至少要除以 3 連續 ${times} 次,小數點後第二位四捨五入後是零`);
} catch (error) {
console.log('輸入錯誤,請輸入一個大於 0 的整數');
} finally {
rl.close();
}
}
main();
domain 函式(例如 divideBy3Times)在輸入不合法時 throw new Error(...)。
try...catch 負責把錯誤轉成「使用者看得懂的訊息」,避免未捕捉例外直接中斷程式。
finally 確保 rl.close() 一定會被呼叫,釋放 readline 的資源。
參考:流程控制與例外處理
參考:Errors | Node.js
參考:Readline | Node.js
避免 magic number:重要數字用常數或已宣告變數表達(例如 const MAX_LINES = 2)。
語意化命名:變數用名詞、函式用動詞,名稱可以長,但要一眼看得出用途。
let / const 原則:
預設用 const,只有真的需要重新指定(reassign)才改用 let。
這樣可以在語法層幫忙擋掉部分 bug。
參考:語法與型別
Number(n):將整個值轉成 number,若字串不是完整合法數字,結果會是 NaN。
parseInt(n, 10):會從左到右解析字串中的整數部分,例如 "123abc" → 123,"abc" → NaN。
在 CLI 題目中,常見做法是:
使用 parseInt(num, 10) 把輸入字串轉成整數或 NaN。
再搭配 Number.isInteger(n)、n > 0 檢查是否是合法正整數。
參考:Number
參考:parseInt()
isNaN(x)(全域)會先嘗試把 x 轉成 number 再判斷是不是 NaN,例如 isNaN('hello') 會是 true(因為 'hello' 轉數字變成 NaN)。
Number.isNaN(x) 不會做強制轉型,只有在「型別是 number 且值是 NaN」時才回傳 true。
用 parseInt 或 Number() 把值轉成 number,接著用 Number.isNaN(n) 檢查會比較直覺、也比較安全。
參考:NaN
參考:isNaN()
參考:Number.isNaN()
陣列方法(map、filter、reduce 等)在處理「整個陣列的轉換」時,語意通常比裸 for 迴圈更清楚,可讀性較好。
但在需要 break / continue、或對性能非常敏感的情況,傳統 for / for...of 仍然是很好的選擇。
可以把原則調整為:
優先考慮用陣列方法讓 intent 更清楚。
如果遇到需要細緻控制流程或性能要求,再回到 for。
參考:陣列與陣列方法
readline.createInterface({ input, output }) 裡:
input 通常綁定 process.stdin,代表從終端機讀入的資料。
output 通常綁定 process.stdout,代表在終端機顯示的提示文字。
console.log 單純寫到 stdout,與 readline 是否存在無直接關係。
rl.close() 用來關閉 Interface,釋放對 input / output 的控制,觸發 close 事件:
建議在一個互動流程結束後就呼叫 rl.close(),避免忘記關閉導致後續程式無法正常使用 readline 或卡在等待輸入。
將多支題目會重複用到的流程抽成 util 函式,例如:
數字輸入驗證
共同的格式化邏輯
要給其他檔案使用的函式,用 export 暴露出來,這些對外暴露的函式可以視為這個模組的 public API。
「最小 TDD」原則下,可以這樣安排測試重點:
優先完整測 public API(例如 rotate90、calString 或 /util 中 export 出來的函式),涵蓋正常情境、邊界情境與錯誤輸入。
檔案內部只給自己用的 helper 函式,優先透過 public API 間接被測到,不一定都要另外 export 出來寫獨立測試。
如果某個 helper 邏輯很複雜、難以透過 public API 測試,就考慮把它抽到 /util 作為獨立模組的 public API,並為它寫測試。
這樣可以在「練習 TDD」與「維持模組邊界乾淨」之間取得平衡:測試專注於對外承諾的行為,內部實作則依照需要逐步重構。
建立專門負責輸入驗證的函式或模組,例如:
validatePositiveInteger(n):檢查是否為大於 0 的整數,不合法時丟錯。
好處:
domain 函式邏輯更純粹、只關注「題目核心運算」。
驗證邏輯可以共用,測試也集中。
可以把「建立 readline、詢問問題、關閉」封裝成單獨模組,例如:
createCli() 回傳一個 ask(question) 的 async 函式,內部自行處理 rl 的建立與關閉。
這樣每一題 CLI 程式都可以只專注在:
題目運算
如何把結果印出
而 I/O 細節則集中管理在這個模組中。
參考:readline 模組
目前對多項式 function calString(n) 的測試是類似「窮舉案例」的寫法,例如:
// calString(n) 範例:
// calString(3) => "1+2-3=0"
// calString(6) => "1+2-3+4-5+6=5"
test.each([
[6, "1+2-3+4-5+6=5"],
[10, "1+2-3+4-5+6-7+8-9+10=7"],
[1, "1=1"],
[3, "1+2-3=0"],
])('輸入 %i 應印出正確算式與結果', (n, expected) => {
expect(calString(n)).toBe(expected);
});
這種做法的特徵是:
一次檢查「整串輸出」是否完全等於預期字串。
想增加信心時,自然的做法是「多塞幾個 n,多窮舉幾組 input/output」。
這已經能有效驗證「小範圍內的行為」,但還可以再往下拆成「規格層級」的測試,分別檢查不同規則。
以一個簡化版函式 sumString(n) 為例:
// 回傳 1 + 2 + ... + n 的字串與總和,例如:
// sumString(3) => "1+2+3=6"
function sumString (n) { /* ... */ }
不在乎左邊長什麼樣,單純檢查「= 右邊的數值」是否正確。
test.each([
[1, 1], // 1
[3, 6], // 1 + 2 + 3
[5, 15], // 1 + 2 + 3 + 4 + 5
])('輸入 %i 時,總和要正確', (n, expectedSum) => {
const result = sumString(n); // "1+2+3=6"
const [, sumStr] = result.split('=');
expect(Number(sumStr)).toBe(expectedSum);
});
對應回 calString(n),就是只抽出 = 右邊的 sum 來比對,而不是整串字串。
專門檢查算式格式是否符合規則,例如:
第一個數不帶 + 或 -。
後面的正數都要帶 +。
整體只包含數字、+、-。
test('格式:第一個數不帶 +,其餘正數帶 +,結尾有 =sum', () => {
const result = sumString(5); // "1+2+3+4+5=15"
const [expr] = result.split('='); // "1+2+3+4+5"
// 利用正則把每一段數字(含前面的符號)切出來
const tokens = expr.match(/[+-]?\d+/g); // ["1", "+2", "+3", "+4", "+5"]
// 規則 B1:第一個 token 不可以以 '+' 開頭
expect(tokens.startsWith('+')).toBe(false);
// 規則 B2:後面每個 token 如果不是負數,就必須以 '+' 開頭
for (const token of tokens.slice(1)) {
if (!token.startsWith('-')) {
expect(token.startsWith('+')).toBe(true);
}
}
});
對應回 calString(n),就可以檢查:
第一項沒有符號。
後面為正的都是 +數字,為負的是 -數字。
專門檢查輸入是否合法,而不是算式正不正確:
test.each([[0], [-1], [1.5], ['3']])('輸入非正整數 %p 應丟錯', (value) => {
expect(() => sumString(value)).toThrow();
});
對應回 calString(n),就是補上「n 不是正整數時要丟錯」的測試,而不是再多列幾個正常的 n。
類窮舉測試(現在已經做的)
多組 n → 比對「整串輸出」是否完全等於預期。
好處:直覺、一次看「輸入→輸出」是否正確。
規格層級測試(可以再補的)
先把需求拆成多條規則(數值結果、字串格式、輸入合法性),各自寫測試。
好處:哪條規則壞了,測試訊息更清楚;未來改格式或內部實作時,較不容易全掛。
放在現在的多項式練習上,就是:
除了「針對多個 n 比對整串算式字串」,還可以:
拆出「結果數值規則」來測 sum。
拆出「格式規則」來測字串長相。
拆出「輸入規則」來測錯誤處理。
讓測試從「多案例比對」進一步升級成「行為規格的集合」。
Code review 的核心目標是提升程式碼品質、分享知識、找出潛在問題。實務上常見的形式可分為:
時機:完成一段功能後,提交給別人看之前。
做法:先放下手邊工作幾分鐘,再回來用「陌生人」的視角重新審視程式碼 diff,檢查是否有明顯的邏輯錯誤、錯字或不一致的命名。
優點:能過濾掉最基本的錯誤,提升自己產出的品質,也讓後續的 review 更有效率。
時機:需要快速得到「語法、風格、明顯 bug」的檢查;或想要「其他寫法」的靈感。
做法:將一小段程式碼或一個檔案交給 AI,並清楚說明程式的目的與你的問題,例如:「幫我找出這段程式碼中可以重構的地方」或「幫我想想還有哪些測試情境我沒考慮到」。
優點:即時性高,能快速找出「機器眼」擅長發現的模式問題,也是很好的 brainstorm 夥伴。
時機:團隊開發的標準流程(例如 GitLab MR / GitHub PR)。
做法:將改動整理成清楚的 commit 或 MR,附上修改目的、核心變更與對應測試。Reviewer 會從架構、可維護性、是否符合團隊規範等角度給出建議。
優點:能藉由不同人的觀點找出自己的盲點,特別是在業務邏輯、架構設計與長期維護性上。
時機:遇到較複雜的問題,或在設計一個新功能時,需要即時討論。
做法:兩個人(或多人)共用一個螢幕或線上共編工具,一人負責寫,其他人邊看邊給建議。本質上是「即時的 code review」,隨時調整寫法。
優點:回饋最即時,適合用在釐清需求、設計 API 或解決棘手問題的階段。
不論用哪種形式,都可以從這三大塊內容檢查:
語法與風格
命名是否語意清楚?(例如 data、temp、item 這類命名是否可以更具體?)
是否使用 === 而非 == 來避免非預期的型別強制轉換?
是否可以用可選串連(?.)或空值合併運算子(??)來簡化 null 或 undefined 的檢查?
演算法與設計
是否有過於複雜的巢狀迴圈?時間複雜度是否合理?
在需要高效能查詢的場景,是否可以用 Map 或 Set 的 has() 取代陣列的 find() 或 includes()?
public API 的定義是否合理?模組與模組之間的界線是否清楚?
測試與邊界
測試是否只測了 happy path(正常情境),而忽略了邊界值(例如 0、-1、空陣列)或錯誤輸入?
測試的 mock 是否過多,導致在測「mock 本身」而不是「真實邏輯」?
測試名稱是否清楚描述「規格」(it('should return sum when given a positive integer')
AI 很適合處理「機器擅長、但人類覺得瑣碎」的情境,可以當作 pre-review 或輔助教練。
開發中「自己先過一輪」的 pre-review
情境:剛寫完一小段功能,丟給同事 review 前。
AI 用途:快速掃描語法錯誤、未使用的變數、重複的邏輯、過長的函式,並提醒缺少哪些測試情境(例如邊界、錯誤輸入)。
練習題或 side project 尋求靈感
情境:像現在練習 TDD、演算法題目。
AI 用途:問「同一題有沒有其他寫法?」、「這段程式碼可以怎麼重構?」、「這份測試規格還可以怎麼拆解?」,當作 brainstorm 夥伴。
處理大量重複、模板化的改動
情境:在多個檔案中加入同樣的 log、錯誤處理,或修改 API 名稱。
AI 用途:幫忙檢查是否有檔案漏改、拼錯字,或協助產生重構後的樣板程式。
理解複雜或老舊的程式碼
情境:接手別人的專案,或看到一段很長的函式。
AI 用途:請它用自然語言摘要「這支函式在做什麼」,或畫出 pseudo-code,幫助你快速建立 mental model。
產生安全性 / 效能的提示清單
情境:處理使用者輸入、檔案 I/O、API 呼叫等潛在風險點。
AI 用途:請它列出「這種情境常見的安全性或效能問題」,當作一份動態 checklist 來逐項檢查。
涉及複雜商業規則的決策。
需要團隊共識的架構設計或風格取捨。
在這類情境,AI 可以提供選項,但最終還是要由人類做出決策。
今天進行了第一次 code review
下面列出這次發現前兩週的程式碼可以再改善的部分:
在 CLI 裡的典型流程:
import { divideBy3Times } from './q9.js';
import readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const rl = readline.createInterface({ input, output });
async function main () {
const num = await rl.question('請輸入一個正整數:');
const n = parseInt(num, 10);
try {
const times = divideBy3Times(n);
console.log(`${n} 至少要除以 3 連續 ${times} 次,小數點後第二位四捨五入後是零`);
} catch (error) {
console.log('輸入錯誤,請輸入一個大於 0 的整數');
} finally {
rl.close();
}
}
main();
domain 函式(例如 divideBy3Times)在輸入不合法時 throw new Error(...)。
try...catch 負責把錯誤轉成「使用者看得懂的訊息」,避免未捕捉例外直接中斷程式。
finally 確保 rl.close() 一定會被呼叫,釋放 readline 的資源。
參考:流程控制與例外處理
參考:Errors | Node.js
參考:Readline | Node.js
避免 magic number:重要數字用常數或已宣告變數表達(例如 const MAX_LINES = 2)。
語意化命名:變數用名詞、函式用動詞,名稱可以長,但要一眼看得出用途。
let / const 原則:
預設用 const,只有真的需要重新指定(reassign)才改用 let。
這樣可以在語法層幫忙擋掉部分 bug。
參考:語法與型別
Number(n):將整個值轉成 number,若字串不是完整合法數字,結果會是 NaN。
parseInt(n, 10):會從左到右解析字串中的整數部分,例如 "123abc" → 123,"abc" → NaN。
在 CLI 題目中,常見做法是:
使用 parseInt(num, 10) 把輸入字串轉成整數或 NaN。
再搭配 Number.isInteger(n)、n > 0 檢查是否是合法正整數。
參考:Number
參考:parseInt()
isNaN(x)(全域)會先嘗試把 x 轉成 number 再判斷是不是 NaN,例如 isNaN('hello') 會是 true(因為 'hello' 轉數字變成 NaN)。
Number.isNaN(x) 不會做強制轉型,只有在「型別是 number 且值是 NaN」時才回傳 true。
用 parseInt 或 Number() 把值轉成 number,接著用 Number.isNaN(n) 檢查會比較直覺、也比較安全。
參考:NaN
參考:isNaN()
參考:Number.isNaN()
陣列方法(map、filter、reduce 等)在處理「整個陣列的轉換」時,語意通常比裸 for 迴圈更清楚,可讀性較好。
但在需要 break / continue、或對性能非常敏感的情況,傳統 for / for...of 仍然是很好的選擇。
可以把原則調整為:
優先考慮用陣列方法讓 intent 更清楚。
如果遇到需要細緻控制流程或性能要求,再回到 for。
參考:陣列與陣列方法
readline.createInterface({ input, output }) 裡:
input 通常綁定 process.stdin,代表從終端機讀入的資料。
output 通常綁定 process.stdout,代表在終端機顯示的提示文字。
console.log 單純寫到 stdout,與 readline 是否存在無直接關係。
rl.close() 用來關閉 Interface,釋放對 input / output 的控制,觸發 close 事件:
建議在一個互動流程結束後就呼叫 rl.close(),避免忘記關閉導致後續程式無法正常使用 readline 或卡在等待輸入。
將多支題目會重複用到的流程抽成 util 函式,例如:
數字輸入驗證
共同的格式化邏輯
要給其他檔案使用的函式,用 export 暴露出來,這些對外暴露的函式可以視為這個模組的 public API。
「最小 TDD」原則下,可以這樣安排測試重點:
優先完整測 public API(例如 rotate90、calString 或 /util 中 export 出來的函式),涵蓋正常情境、邊界情境與錯誤輸入。
檔案內部只給自己用的 helper 函式,優先透過 public API 間接被測到,不一定都要另外 export 出來寫獨立測試。
如果某個 helper 邏輯很複雜、難以透過 public API 測試,就考慮把它抽到 /util 作為獨立模組的 public API,並為它寫測試。
這樣可以在「練習 TDD」與「維持模組邊界乾淨」之間取得平衡:測試專注於對外承諾的行為,內部實作則依照需要逐步重構。
建立專門負責輸入驗證的函式或模組,例如:
validatePositiveInteger(n):檢查是否為大於 0 的整數,不合法時丟錯。
好處:
domain 函式邏輯更純粹、只關注「題目核心運算」。
驗證邏輯可以共用,測試也集中。
可以把「建立 readline、詢問問題、關閉」封裝成單獨模組,例如:
createCli() 回傳一個 ask(question) 的 async 函式,內部自行處理 rl 的建立與關閉。
這樣每一題 CLI 程式都可以只專注在:
題目運算
如何把結果印出
而 I/O 細節則集中管理在這個模組中。
參考:readline 模組
目前對多項式 function calString(n) 的測試是類似「窮舉案例」的寫法,例如:
// calString(n) 範例:
// calString(3) => "1+2-3=0"
// calString(6) => "1+2-3+4-5+6=5"
test.each([
[6, "1+2-3+4-5+6=5"],
[10, "1+2-3+4-5+6-7+8-9+10=7"],
[1, "1=1"],
[3, "1+2-3=0"],
])('輸入 %i 應印出正確算式與結果', (n, expected) => {
expect(calString(n)).toBe(expected);
});
這種做法的特徵是:
一次檢查「整串輸出」是否完全等於預期字串。
想增加信心時,自然的做法是「多塞幾個 n,多窮舉幾組 input/output」。
這已經能有效驗證「小範圍內的行為」,但還可以再往下拆成「規格層級」的測試,分別檢查不同規則。
以一個簡化版函式 sumString(n) 為例:
// 回傳 1 + 2 + ... + n 的字串與總和,例如:
// sumString(3) => "1+2+3=6"
function sumString (n) { /* ... */ }
不在乎左邊長什麼樣,單純檢查「= 右邊的數值」是否正確。
test.each([
[1, 1], // 1
[3, 6], // 1 + 2 + 3
[5, 15], // 1 + 2 + 3 + 4 + 5
])('輸入 %i 時,總和要正確', (n, expectedSum) => {
const result = sumString(n); // "1+2+3=6"
const [, sumStr] = result.split('=');
expect(Number(sumStr)).toBe(expectedSum);
});
對應回 calString(n),就是只抽出 = 右邊的 sum 來比對,而不是整串字串。
專門檢查算式格式是否符合規則,例如:
第一個數不帶 + 或 -。
後面的正數都要帶 +。
整體只包含數字、+、-。
test('格式:第一個數不帶 +,其餘正數帶 +,結尾有 =sum', () => {
const result = sumString(5); // "1+2+3+4+5=15"
const [expr] = result.split('='); // "1+2+3+4+5"
// 利用正則把每一段數字(含前面的符號)切出來
const tokens = expr.match(/[+-]?\d+/g); // ["1", "+2", "+3", "+4", "+5"]
// 規則 B1:第一個 token 不可以以 '+' 開頭
expect(tokens.startsWith('+')).toBe(false);
// 規則 B2:後面每個 token 如果不是負數,就必須以 '+' 開頭
for (const token of tokens.slice(1)) {
if (!token.startsWith('-')) {
expect(token.startsWith('+')).toBe(true);
}
}
});
對應回 calString(n),就可以檢查:
第一項沒有符號。
後面為正的都是 +數字,為負的是 -數字。
專門檢查輸入是否合法,而不是算式正不正確:
test.each([[0], [-1], [1.5], ['3']])('輸入非正整數 %p 應丟錯', (value) => {
expect(() => sumString(value)).toThrow();
});
對應回 calString(n),就是補上「n 不是正整數時要丟錯」的測試,而不是再多列幾個正常的 n。
類窮舉測試(現在已經做的)
多組 n → 比對「整串輸出」是否完全等於預期。
好處:直覺、一次看「輸入→輸出」是否正確。
規格層級測試(可以再補的)
先把需求拆成多條規則(數值結果、字串格式、輸入合法性),各自寫測試。
好處:哪條規則壞了,測試訊息更清楚;未來改格式或內部實作時,較不容易全掛。
放在現在的多項式練習上,就是:
除了「針對多個 n 比對整串算式字串」,還可以:
拆出「結果數值規則」來測 sum。
拆出「格式規則」來測字串長相。
拆出「輸入規則」來測錯誤處理。
讓測試從「多案例比對」進一步升級成「行為規格的集合」。
Share Dialog
Share Dialog
No comments yet