最近看了劉潤的底層邏輯,書中有一個小節是流程、制度與系統,看完後突然有點想寫些什麼的衝動。
這些商業管理的抽象名詞,乍看之下可能跟寫程式沒什麼關係,畢竟我自己習慣的是討論程式碼本身,或是工具的效率... 等議題,但其實這三個概念好像可以跟軟體工程搭上邊?
▼ 這三個概念:流程、制度與系統,可以畫成一個類別圖
這三者之間的關係,為我們提供了一個強大的框架,可以用來檢視和建立複雜的前端應用。
流程 Process
流程就是一種順序,他定義了從起點到終點的一系列有序的活動,不管是使用者體驗(UX) 或是數據處理 (Data),流程都與之息息相關。一個清晰、順暢的流程,可以讓使用者的體驗 UP UP!
使用者互動流程
我們舉一個簡單卻又直觀的流程為例子:電商網站的忘記密碼流程,將每一個步驟拆分如下 ⬇️
- 使用者點擊「忘記密碼」按鈕
- 系統引導至「輸入註冊信箱」的頁面
- 使用者輸入信箱後,畫面(前端) 顯示「已寄出信件」等提示
- 使用者點擊信件中的連結,連到「設定新密碼」頁面
- 使用者輸入並確認新密碼後,系統跳出「密碼設定成功」的提示,並回到登入頁
這樣的流程設計,就符合前面的類別圖中提到的兩點:
- 有序:每個順序都是有其意義的,無法插隊,也不能跳過某個步驟
- 定義執行順序:我們將每個步驟拆開,一步步地寫出來,可以更好地釐清整個執行的順序
有了好的流程設計,我們就能搭配狀態機 (State Machine) 的概念,來管理這樣的流程。
▼ 以下用 React 框架為例,可以使用 useState 或 useReducer 來追蹤當前的流程狀態,並根據狀態渲染對應的 UI
import React, { useReducer } from 'react';
// 定義所有狀態
const initialState = {
email: '',
status: 'idle', // 'idle' | 'submitting' | 'success' | 'error'
error: null
};
// 定義狀態機 reducer
function reducer(state, action) {
switch (action.type) {
case 'INPUT_CHANGE':
return {
...state,
email: action.payload
};
case 'SUBMIT':
return {
...state,
status: 'submitting',
error: null
};
case 'SUCCESS':
return {
...state,
status: 'success'
};
case 'FAILURE':
return {
...state,
status: 'error',
error: action.payload
};
default:
return state;
}
}
function PasswordResetForm() {
const [state, dispatch] = useReducer(reducer, initialState);
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: 'SUBMIT' });
try {
await api.sendResetEmail(state.email);
dispatch({ type: 'SUCCESS' });
} catch (err) {
dispatch({ type: 'FAILURE', payload: err.message });
}
};
if (state.status === 'success') {
return <p>重設密碼的信件已寄至您的信箱,請查收。</p>;
}
return (
<form onSubmit={handleSubmit}>
{state.status === 'error' && <p className="error">{state.error}</p>}
<input
type="email"
value={state.email}
onChange={(e) => dispatch({ type: 'INPUT_CHANGE', payload: e.target.value })}
placeholder="請輸入您的註冊信箱"
disabled={state.status === 'submitting'}
/>
<button type="submit" disabled={state.status === 'submitting'}>
{state.status === 'submitting' ? '傳送中...' : '傳送重設信件'}
</button>
</form>
);
}
數據處理流程
除了畫面外,前端也需要處理數據,通常會從後端取得原始資料,再加工並顯示在畫面上。這中間的過程就包含了數據的轉換、過濾、排序... 等處理。
同樣舉個例子,我要在儀表板頁面顯示本月銷售冠軍的商品列表,步驟流程拆解如下:
- Fetch Data:向後端 API 發送請求取得資料
- Transform Data:將取得的資料進行格式化,例如將 timestamp 轉成常見的日期格式
- Sort Data:根據銷售額進行排序
- Render Data:將處理好的數據資料顯示在畫面上
這邊推薦使用 Pinia 做流程管理工具,他的 actions 負責非同步操作與相關業務邏輯,還能直接修改 state,讓數據流的定義更直觀。
import { defineStore } from 'pinia';
import { api } from '@/services/api'; // 假設有一個封裝好的 api 服務
export const useProductStore = defineStore('products', {
// 定義流程中需要管理的數據
state: () => ({
topProducts: [],
isLoading: false,
error: null,
}),
// 定義完整的數據處理流程
actions: {
async fetchTopProducts() {
this.isLoading = true;
this.error = null;
try {
// 步驟一:取得資料 (Fetch)
const rawData = await api.fetchMonthlySales();
// 步驟二 & 三:轉換 (Transform) 與排序 (Sort)
const processedData = rawData
.map(p => ({
...p,
saleDate: new Date(p.saleDate).toLocaleDateString(), // 格式化日期
}))
.sort((a, b) => b.salesVolume - a.salesVolume); // 按銷量排序
// 將最終結果存入 state
this.topProducts = processedData;
} catch (err) {
console.error("Failed to fetch top products", err);
this.error = '無法取得商品資料,請稍後再試。';
} finally {
this.isLoading = false;
}
},
},
});使用 pinia 的好處是可以避免把數據資料分散在多個元件中,這樣容易造成資料散落且不一致的問題,現在我們需要資料,就統一從 pinia 這個源頭取得,確保資料的統一與可靠性。
制度 Rules
我覺得制度可以看成是提升團隊程式碼品質的一個規則,也可以是軟體應用中使用者的權限規定 ... 等,以下就用這兩塊功能為例與大家分享我的看法。
程式碼規範制度
在團隊協作中,常常會因為程式碼的風格而吵翻天 XD,例如最經典的幾個問題:
- 結尾要不要分號?
- 縮排用 space 還是 tab?幾格?
- 變數命名是駝峰式還是字首大寫?要不要底線?
這些看似不影響程式碼開發,卻能左右開發者心情的問題,如果不好好處理,很容易造成開發者的混亂與日後維護的噩夢,所以程式碼規範是一種制度,需要我們共同遵守。
最常見的 ESLint 這套工具,我們利用這套工具將這個制度自動化,強制執行語法規則。
使用者存取的權限制度
在每個會員系統中,不同角色的使用者會擁有不同的權限,這樣的權限體系,又何嘗不是一種制度呢?
以後台管理系統為例,我們通常會有這些角色,並賦予這些角色不同的權限:
- 管理員:可以觀看所有頁面,也可以管理使用者
- 編輯者:可以觀看跟內容相關的頁面,也可以編輯內容,但不能管理或刪除使用者
- 訪客:只能看儀表板的公開資料
在做權限功能時,要擋住的第一道關卡就是頁面的存取權,這時我會使用路由守衛 (Route Guards) 來做權限檢查。
{
path: '/client/user',
label: '用戶列表',
// 對應的頁面元件,我多包一層 ProtectedRoute 做檢查,表示此頁需權限保護
element: (
<ProtectedRoute>
<ListClientUserPage />
</ProtectedRoute>
),
// 權限要求,使用者需擁有其中一個才能訪問
permission: ['read user', 'manage user'],
}系統 System
一個個的元件組合起來,就是我們熟知的系統,讓我們再一次複習類別圖,更能加深系統、流程與制度之間的關係:
- 系統可以將流程自動化
- 系統需要遵循制度
以前端開發而言,我們談論的系統小至一個元件,大致整個應用架構都是有可能的。因為一個獨立的元件,往往也有獨立的流程和獨立的制度。
前端元件化系統
現在我們的開發愈來愈趨向「元件化」,尤其使用了前端框架後,這樣的趨勢已不可避免。我們讓每個元件都有獨立的功能、狀態和邏輯,但他們又像樂高一樣要組合在一起,才能實現完整的功能。
我們會將畫面拆解成獨立、可重用的元件,透過 props 或 events 定義它們之間的接口,致力做出一個高內聚、低耦合的系統。而原子設計更是將系統化的概念推到極致,從原子、分子,到組織,一層一層的建構出 UI 系統。
前後端整合系統
我們再將視角拉遠一些,會發現前端其實只是整個業務系統的一部分,因為前端通常會和後端、資料庫、第三方服務 ... 等共同構成一個更大的系統。
例如,前後端會透過 API (像是 RESTful 或 GraphQL) 來交換資料,進行協作,這就是兩個子系統合作的方式。
以電商為例,假設在購物車系統中,前端的 AddToCart 元件透過呼叫後端的 API,再進一步與庫存管理、訂單處理等其他子系統互動,才能共同完成「將商品加入購物車」這個業務功能。
流程、制度與系統
前面我把這三個概念分開說明,但它們並非獨立作業,而是可以彼此補足、相互成就。最後我會用一個完整的例子,來跟大家分享我怎麼看待「流程、制度與系統」,以及如何讓它們緊密結合、發揮整體效益。
現在我們要做一個能讓使用者安全登入的功能:
- 流程 - 登入的步驟
- 使用者輸入帳號密碼
- 點擊登入按鈕,前端畫面顯示 Loading 狀態
- 發送請求至後端進行驗證
- 如果驗證成功,就跳到主頁;如果驗證失敗,就顯示錯誤訊息
- 制度 - 登入的規則
- 安全的制度:密碼在傳輸過程中必須加密,如果嘗試登入超過 X 次,帳號會被臨時鎖定
- 驗證的制度:帳號是合法的 Email 格式,密碼長度要大於 8 位且英數混合
- 程式碼的制度:處理登入的邏輯函式,必須符合團隊的規範(如果有)
- 系統 - 實現登入的架構與工具
- 元件系統:
<LoginForm />元件,封裝了輸入框、按鈕和錯誤提示等畫面與狀態 - 狀態管理系統:集中管理使用者的登入狀態、Token 和個人資訊 .... 等
- 路由系統:設定路由守衛,確保登入後才能看到頁面
- API 系統:封裝跟後端 API 有關的處理
- 元件系統:
這個登入的系統中,會用到登入的「流程」,而系統的實作細節 (如表單) 需要遵守「制度」,制度又反過來影響「流程」的體驗。真的是息息相關,缺一不可。
小結
在邊寫邊思考的過程中,發現自己也學到了很多。
未來當我開始一個新專案或是新功能時,也許會嘗試用這三個視角來結構化我的任務:
- 我要實現的流程是什麼?
- 我要遵守哪些制度?
- 我應該要用怎樣的系統來組合?
最後也希望這樣的分享能幫助到各位的日常開發,有任何問題都歡迎留言討論唷。