# Day 14:JavaScript - TDD 與模組化重構(續) **Published by:** [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake/) **Published on:** 2025-12-09 **URL:** https://paragraph.com/@gcake/day-14 ## Content 這兩天花了不少時間在練習「輸入驗證」、「CLI 互動」與「核心邏輯」的責任分層上,並持續透過 Test-Driven Development (TDD) 的流程來實作演算法。這篇文章整理了我今天在實作過程中的一些設計決策與心得,特別是如何利用 Vitest 進行參數化測試,以及從命令式程式碼走向聲明式重構的過程。一、三層責任分工:Parse, don’t validate在處理使用者輸入時,很容易把「檢查邏輯」和「運算邏輯」混在一起。為了讓程式碼更乾淨,我嘗試將系統切分為三層,讓每一層只專注做一件事。1. 驗證層 (Validation Layer)這一層的責任是「守門員」:接收外部傳來的髒資料(例如使用者輸入的字串),負責清洗、轉型,最後只吐出「合法的原始型別值」或直接丟出錯誤。 例如,我們常需要檢查一個輸入是否為「正整數」。與其在業務邏輯裡到處寫 if (val < 0),不如抽成一個專用的 parsePositiveInt 函式。這裡我用到了一個好用的技巧:預設參數 (Default Parameters)。可以設計成 parsePositiveInt(input, min = 1)。預設只檢查是否 >= 1。如果某些情境需要更嚴格(例如大於 5),也可以彈性傳入 min 參數。相關 MDN 參考:Default parametersNumber.isInteger()2. CLI 互動層 (CLI Layer)這一層負責跟使用者「對話」。它的工作很單純:問問題、拿答案、交給驗證層檢查。若驗證通過:就把乾淨的值往後傳。若驗證失敗(丟錯):直接 catch 起來印出錯誤訊息,然後再問一次。這樣的設計讓 CLI 層完全不需要知道「什麼是合法的整數」,它只負責「重試」這個行為流程。3. 核心邏輯層 (Core Logic Layer)這是最乾淨的一層。它假設收到的參數一定是合法的(因為髒資料早在驗證層就被擋掉了)。 這樣做的好處是:核心函式裡不再充滿防禦性的 if-else,可以專心處理演算法或業務規則。這也讓這一層特別容易進行單元測試。二、TDD 實戰:從寫死到演算法在實作演算法(例如「判斷質數」或「陣列搜尋」)時,我採用了 TDD (Test-Driven Development) 的節奏:紅 (Red) → 綠 (Green) → 重構 (Refactor)。目前還在熟悉 Uncle Bob 提倡的「最小化測試與重構」這個每次只推進幾行程式碼的思考流程。1. 善用參數化測試 (Parameterized Tests)與其寫十幾個 it(...) 來測不同的數字,不如使用 Table-Driven 的方式把測資列成表格。在 Vitest 中可以使用 test.each:describe('質數索引搜尋', () => { test.each([ // [輸入陣列, 預期結果] [, [{ val: 3, idx: 1 }, { val: 13, idx: 3 }]],​ [, []], // 沒找到的情況​ [[], []], // 空陣列邊界測試 ])('輸入 %j,應回傳 %j', (input, expected) => { expect(findPrimes(input)).toEqual(expected); }); }); 這種寫法讓測試與資料分離,新增案例時只需要在陣列裡多加一行,非常直觀。 Vitest 參考:Test API2. 實作演化的三部曲以「找出陣列中所有質數與位置」這個需求為例,我的 TDD 過程是這樣的:第一版(寫死): 為了先確認測試架構沒問題,我會先直接 return 一個固定的陣列讓測試通過。這聽起來很蠢,但能確保「測試程式本身」是對的。第二版(命令式:For Loop + Push): 這是最直覺的寫法。建立一個空陣列,跑迴圈,遇到符合條件的就 push 進去。 這時候的重點是「把邏輯寫對」,通過所有測試案例。第三版(聲明式:Map + Filter): 在測試「保護」下,我可以放心地重構程式碼,把 for 迴圈改成更具語意的高階函式 (Higher-Order Function)。三、從命令式到聲明式:HOF 的重構在前端開發(尤其是 React/Vue)中,我們非常依賴 map、filter 這類高階函式 (HOF)。這次練習中,我也嘗試把原本的 for 迴圈重構成資料流管道 (Pipeline)。什麼是 HOF (Higher-Order Function)?簡單來說,只要一個函式能接收函式當參數,或回傳一個函式,它就是 HOF。命令式寫法 (Imperative): 「建立空陣列 → 跑迴圈 → 判斷條件 → push 進陣列」。這是在告訴電腦怎麼做 (How)。聲明式寫法 (Declarative): 使用 filter 篩選、用 map 轉換。這是在告訴電腦我要什麼 (What)。MDN 參考:Array.prototype.map()Array.prototype.filter()透過 TDD 的保護,我可以確認重構前後的行為完全一致,但程式碼的可讀性與維護性卻得到了提升。四、小結這兩天的練習讓我體會到,寫程式不僅僅是把功能做出來,更重要的是架構的整潔度與測試的保護網。責任分層讓每一段程式碼都好測、好改。TDD 讓我敢於重構,不怕改壞舊功能。參數化測試 讓邊界案例的測試變得輕鬆且完整。從小型的練習題開始,每次都把整個架構流程完整跑過一遍,建立好的開發習慣。在實際的專案開發中,也是維持程式碼品質的重要基石。 ## Publication Information - [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake/): Publication homepage - [All Posts](https://paragraph.com/@gcake/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@gcake): Subscribe to updates