感謝大家陪伴來到最後的10天,不管有沒有參賽,能走到這裡真的非常不容易!希望最後這段時間,我能帶給大家一點不同的程式設計上面的思維,雖然看起來可能跟要原本要介紹的 Vue 框架可能較沒直接關係~~~。
只是我最近讀到一篇文章,其中提到一段話特別有感:
開發軟體最難的其實不是『寫程式』,而是『改程式』,特別是修改那些已經運作正常但可能缺乏測試的程式碼。
剛入職時常接手舊專案,常常會碰到這樣的情況:當我們試圖修改功能 A 時,卻意外地影響了功能 B。這通常跟當初的開發時是否考慮了 耦合度(coupling)
和 責任分配(responsibility assignment)
有關。
其實在學習 Vue 框架的基本用法和避免常見陷阱的同時,更需要關注如何在架構規劃時先做適當的思考。這比起單純追求業務需求的快速實現,在技術層和良好的架構規劃更重要,才能減少後續修改程式碼時帶來的潛在風險,工程師也不用為了陳年老碼(legacy code)而改得很痛苦。
SOLID原則
單一職責(Single Responsibility Principle, SRP)
SOLID 原則是一組軟體設計原則,用於指導軟體開發人員設計和實現高質量的、易於維護和擴展的軟體。它是由羅伯特·C·馬丁(網路稱他Bob大叔,也是Clean Code作者)提出的,細節很多不是本篇重點。
SOLID 原則有五個面向對象設計的基本原則,用來幫助開發者設計更靈活、可維護且可擴充的軟體系統。這些原則主要是對於 減少系統中的耦合性(解偶)
特別有幫助,使得程式碼更易於管理和擴充。
在元件開發(如 Vue、React 等前端框架中)中,SOLID 原則應該也可以應用,幫助建立可重用、可測試的元件。雖然SOLID 原則與比較跟物件導向(Object Orient Programing)
相關,但我們可以試著想想怎麼跟日常前端開發去做連結,先從最基本的單一職責(SRP)開始慢慢了解吧~。
一個類別應該只有一個引起變更的原因,換句話說,一個類別應該僅負責一個特定的職責或使用者角色需求。
透過這種思考設計方式,可以減少類別或函式之間的依賴和耦合
,確保在同一模組修改某一功能時,不會對其他功能造成影響(改A壞B)。
在前端元件開發中,遵循單一職責原則意味著應該將組件的職責劃分得更加清晰。避免將過多的責任混合在同一個組件中,雖然有時候程式碼看起來很簡單,似乎不需要拆分,但這樣可能導致邏輯分岔,產生維護和擴展上的不協調
。
最近,我很喜歡用漫畫圖來加深對這些概念的印象。例如,想像一個多功能的機器人,它就像我們的 UI 元件一樣,看起來多工處理能力很強。
但當它出現故障時,你很難確定是那個模組出了問題。這正如一個元件如果承擔了過多的責任,未來修改時可能會牽一髮而動全身,造成難以預測的問題。
(圖片出處)
我們可以用一個簡單寄送會員Email功能為例,需要篩選出資料名單中是活躍會員,最後透過API寄送會員優惠卷,但會發現它同處理了兩種邏輯- 過濾名單
和 非同步API寄送過程
// 原本寫在一起的寄送email功能
function sendEmailToActiveClients(clients) {
clients.forEach((client) => {
if (client.isActive) axios.post('send-email-api-url', data)
})
}
可以針對各別的需求另外拆分功能,雖然功能很簡單,但可以養成拿到需求要開始製作時,會不會想到簡單功能中暗藏多工職責,這樣做在開發完成以後,維護修改上都會比較方便。
const sendEmailApi = (clientData) =>
request.post('send-email-api-url', clientData)
const getActiveClients = (clients) => clients.filter((client) => client.active)
const sendEmail = (activeClients) => {
activeClients.forEach((activeClient) => sendEmailApi(activeClient.emailData))
}
雖然大部分探討SOILD的案例的文章少之又少,但我們還是試著用一些元件來練習思考單一職責的思維:
這個元件功能滿簡單的,但是同時處理了顯示數據
、編輯數據
、以及控制編輯狀態
的邏輯,並且輸出使用者資訊
,看起來有點違反單一職責原則(Single Responsibility Principle, SRP)的風險,一個組件做了三件事?
但上面SRP定義:「一個模組應有且只有一個理由會使其改變。」。
我們細看有2個成因影響著這個元件改變,一個是UI狀態
,另一個是按鈕資料提交的邏輯
:
以目前 input元件輸出的變化來說,會忠實反映用戶輸出的資料,只會有單一個資料輸和輸出,沒有任何改變Data的副作用存在,但UI編輯狀態上兩種改變
。
雖然實務上這種簡單少量的商業邏輯不影響維護和擴充,但如果輸入框綁定邏輯
變得比較多,例如大量驗證提示和編輯狀態的邏輯變多時
,可能會導致save資料輸出有條件變化時,就可考慮拆分元件,
也就是說分析有哪一部分會讓元件的輸出資料流分岔出現不一樣的時候,或者已經開始有複雜UI狀態,該適時分離元件開發。
我們把編輯
、顯示資料
和處理編輯狀態
切分成3個部分:
這種開發模式在後續擴充元件加入更多的驗證邏輯時,只會更動到某一層的元件檔,而不用修改原本捆在一起的程式碼。
創建一個元件來同時處理創建
和編輯資料
的表單,隨然UI元件的外觀常常長的一樣,但可能會違反單一職責原則(SRP)。
因為這樣的元件同時承擔了「創建資料」
和「編輯資料」
的兩種不同的職責。這會導致元件的複雜度增加,並使維護變得更加困難,雖然案例中只是兩欄的輸入欄位,不過實務上一個頁面(page)設定上的表單資料可能會有6~7欄以上。什麼時候要判斷使用者進入新建資料流程,什麼時麼又要判斷有API傳過來的預設資料,看到一堆條件判斷式的切換真的會讓人頭痛XD。 同時,API發送資料的方法通常一個是POST,一個是PATCH,送出按鈕後的資料儲存邏輯也不一樣
。
為了保持頁面元件的樣板清晰,我們會將造成元件行為分岔的部分,也就是業務邏輯(business logic)
,分配給外圍區塊組件(block components)
另外組裝達到行為層面的單一職責,雖然JavaScript前端框架比較少人提及下面這張圖的觀念(大部分好像看到都是Flutter或Java為主)。
也有另一位開發者在2015年提出類似表現層(Presention Layer)和容器層(container layer),不過後來作者本人認為Vue組合式函式的出現,將資料邏輯抽離,這種模式就慢慢式微了。
(圖片出處)
很多時候開發下來反而發現到是大家想讓相似的UI共用,卻沒想到隨著後續需求變化增加,原本的功能又不能壞掉,便出現改不動的失控情況。
不過確實用這樣的SOLID單一職責思維,可以讓部分元件更專注於組織視圖結構,而不是將具體的業務邏輯處理捆在一起
,造成元件的功能太多職責或變動的因素增加,而有助於提高代碼的可讀性、可維護性和模塊化。