跳至主要内容

分層設計(Stratified Design)入門介紹

前言:為什麼你的程式碼會越寫越亂?

你有沒有遇過這種情況:一個函式裡面,既有處理商業邏輯的程式碼,又有直接操作陣列的細節,還夾雜著呼叫 API 的動作?改一個小功能,卻要看懂整包程式碼才敢動手?

這通常不是你能力不好,而是程式碼「沒有分層」。分層設計(Stratified Design)就是解決這個問題的思考方式。

什麼是分層設計?

分層設計是一種組織程式碼的方法:把程式碼依照「抽象程度」分成一層一層,每一層只使用「正下方那一層」提供的功能

用生活化的比喻來說,就像一間手搖飲料店:

  • 最上層(店長):決定「今天推出珍珠奶茶買一送一」——這是商業決策。
  • 中間層(店員):知道「做一杯珍奶 = 煮珍珠 + 泡奶茶 + 封膜」——這是操作流程。
  • 最底層(器材):搖搖機、封膜機、煮茶鍋——這是基礎工具。

店長不需要知道封膜機怎麼運作,他只要跟店員說「出一杯珍奶」。店員也不需要自己發明搖搖機,直接用現成的就好。每一層都只跟下一層溝通,整間店才能順暢運作。

用程式碼實際看一次

假設我們在寫一個購物車功能。先看「沒有分層」的寫法:

// ❌ 沒有分層:所有細節混在一起
function addItemToCart(cart, name, price) {
const newCart = cart.slice(); // 複製陣列的細節
newCart.push({ name: name, price: price }); // 操作陣列的細節

let total = 0;
for (let i = 0; i < newCart.length; i++) { // 計算總價的細節
total += newCart[i].price;
}

if (total >= 1000) { // 商業規則
setFreeShippingIcon(true); // 更新畫面
}
return newCart;
}

這個函式同時做了太多事:複製陣列、加入項目、算總價、判斷免運、更新畫面。任何一個需求改變,都得動這個函式。

接著看「分層之後」的寫法:

// ✅ 第一層:商業邏輯層(老闆看得懂的語言)
function addItemToCart(cart, name, price) {
const newCart = addItem(cart, makeCartItem(name, price));
updateShippingIcon(newCart);
return newCart;
}

// ✅ 第二層:購物車操作層(購物車的通用動作)
function makeCartItem(name, price) {
return { name: name, price: price };
}

function calcTotal(cart) {
return cart.reduce((total, item) => total + item.price, 0);
}

function updateShippingIcon(cart) {
setFreeShippingIcon(calcTotal(cart) >= 1000);
}

// ✅ 第三層:陣列工具層(跟購物車無關的通用工具)
function addItem(array, item) {
return [...array, item];
}

你會發現:

  1. 最上層讀起來像在講故事:「把商品加進購物車,然後更新免運圖示」。不用看細節就知道在幹嘛。
  2. 中間層專注在購物車的概念:算總價、建立商品項目。
  3. 最底層是通用工具addItem 根本不知道什麼是購物車,它只會把東西加進陣列,所以待辦清單、我的最愛都能重複使用它。

分層設計的三個好處

一、好維護

需求改了「滿 1500 才免運」?你只要改 updateShippingIcon 一行。想把陣列換成其他資料結構?只要改最底層,上面兩層完全不用動。每一層的改動被隔離在自己那一層

二、好測試

底層的 addItemcalcTotal 都是簡單的純函式(給一樣的輸入,永遠得到一樣的輸出),寫測試超級簡單,不需要模擬畫面或 API。

三、好重複使用

越底層的函式越通用。addItem 可以用在任何需要「新增項目」的地方,寫一次就能到處用。

怎麼判斷程式碼該放在哪一層?

這裡提供三個實用的判斷技巧:

技巧一:看函式名稱的「語言」

  • 函式名稱出現商業用語(免運、優惠、結帳)→ 放上層。
  • 函式名稱是資料操作(新增、移除、複製)→ 放下層。

技巧二:一個函式裡的程式碼,抽象程度要一致

如果一個函式裡,一行在講「套用優惠券」,下一行卻在寫 for 迴圈跑陣列,那就是抽象程度不一致,該把迴圈的部分抽出去變成下層函式。

技巧三:問自己「這個函式知道太多了嗎?」

addItem 不應該知道免運門檻是多少;updateShippingIcon 不應該知道陣列怎麼複製。每個函式只需要知道完成自己工作的最少資訊

常見的疑問

Q:一定要分成三層嗎?

不一定。層數沒有標準答案,小專案可能兩層就夠,大專案可能四、五層。重點是「每一層的抽象程度一致」,而不是層數多寡。

Q:這樣函式會不會變太多、太瑣碎?

一開始可能會這樣覺得,但實務上「很多小函式」遠比「一個大函式」好維護。每個小函式都可以獨立理解、獨立測試,出問題也更容易定位。

Q:跟前端框架的分層有什麼關係?

概念完全相通!像 React 專案常見的「頁面元件 → UI 元件 → 工具函式(utils)」就是一種分層設計。頁面元件負責組合流程(上層),UI 元件負責呈現(中層),utils 放通用邏輯(下層)。

總結

分層設計的核心觀念只有一句話:

把程式碼依照抽象程度分層,每一層只使用下一層的功能,讓每一層讀起來的「語言」保持一致。

下次寫程式的時候,可以先問自己:「這行程式碼跟旁邊那行,是在同一個抽象層級嗎?」光是養成這個習慣,你的程式碼品質就會有感提升。


延伸閱讀:這個概念在《Grokking Simplicity》(中譯本:《簡約的軟體開發思維》)一書中有非常完整的介紹,推薦給想深入了解的讀者。