我有在思考程式碼到底該放哪裡
這篇文章不是要介紹完整的 Feature-based Design,而是記錄我看完一場分享後,重新理解自己過去在專案中做過的檔案拆分與模組設計。若你有不同的實務經驗,也歡迎交流。
為什麼要拆分程式碼?
一開始,我也不太清楚為什麼要把程式碼拆開。心裡常想:「拆成這麼多檔案,維護時不是得跳來跳去地看嗎?一個檔案不能把事情都做完嗎?」
直到某個畫面同時包含彈出視窗、依按鈕切換的表單,以及依狀態變化的內容,卻全都塞在同一個檔案裡,我才發現問題。出了錯時,光是找到問題所在就得花不少時間。
把程式碼依用途拆成不同檔案後,發生問題時,我可以直接從相關檔案開始找,不必先讀過一大段與這次修改無關的內容。拆分能縮小需要理解與檢查的範圍。
更抽象地說,判斷要不要拆分時,與其只問「這段程式碼長不長」,不如問:「這些程式碼的責任邊界在哪裡?」
例如,驗證規則可能會隨商業規則改變;而「什麼狀態下啟用驗證」則可能隨流程改變。兩者的變更原因不同,把它們分開就有意義。
好處
- 修改範圍更明確:把驗證規則與套用時機分開後,調整驗證規則時,只需要修改負責規則的檔案,不必連帶處理流程。
- 更容易與 AI 協作:每個檔案的責任清楚,就能把修改範圍限制在特定模組,例如只處理 validation,不必讓 AI 同時理解一個幾千行的 component,也能降低提供 context 的成本。
需要留意的地方
- **理解檔案關係需要時間:**其他人接手維護時,可能得先了解各檔案如何組合、彼此如何協作。
- **避免為拆而拆:**如果拆分沒有讓責任更清楚,反而增加跳轉與理解成本,就值得重新考慮。
我怎麼做?
我在公司的專案中也有實踐這種做法,從整體專案到單一模組都有一些嘗試。雖然還稱不上完整的方法,但在後續維護時,我確實感受到它帶來的好處。
整個專案
整體而言,專案最外層仍以 Technical-based 的方式分類;在既有資料夾中,我再依模組細分,並依不同職責拆開檔案。這樣的做法有些接近 Feature-based 的組織方式。
以前 pages/ 底下只有主題分類,沒有再細分各個模組,頁面檔案散落在不同主題資料夾中,不容易一眼找到負責呈現某個畫面的檔案。
後來,我改為替各個畫面建立資料夾,再依單一模組整理相關程式碼。
單一模組
以訂單功能為例,畫面可能依不同狀態顯示不同表單;狀態也會決定欄位是可填寫,還是只供檢視。
這些表單、初始化 API、驗證邏輯和送出流程,原本都可以寫在同一個檔案裡。畫面照樣能運作,只是檔案可能逐漸膨脹到幾千行,讓後續理解與修改變得困難。
Before
pages/
└── OrderPage.vue
After
order/
├── OrderPage.vue
├── components/
│ ├── DraftForm.vue
│ ├── ReviewForm.vue
│ └── CompletedForm.vue
└── composables/
├── useOrderInit.ts
├── useOrderSubmit.ts
└── useOrderValidation.ts
我先依照邏輯與畫面拆分出 composables/、components/ 和主程式。主程式則負責把這些部分組合起來。
Composables
我依照不同功能拆出幾個 composable:
init:依進入畫面時的狀態,決定要呼叫哪支初始化 API。submit:依目前狀態,決定呼叫哪支 API 送出表單。validate:依目前狀態,決定套用哪些驗證規則。main:管理共用變數與 API。
現在回頭看,main 的責任或許還能再拆得更清楚;以目前的需求來說,則暫時沒有必要。
Components
我把不同狀態的表單拆成各自的 component,讓它們盡量只處理表單欄位與畫面呈現,不直接改變流程。流程相關的決策則交由主程式負責。
主程式
主程式負責組合 composables 與 components,決定目前狀態下要呈現哪個 UI,以及使用者操作後如何進入下一個流程。具體的驗證、資料轉換與 API 操作,則交給各自負責的模組。
結論
我目前的體會是,設計檔案結構真正要解決的,並不是替每段程式碼找到唯一正確的位置,而是透過結構,清楚表達程式的職責、邊界與依賴關係。
好的結構應該讓人看得出:這段程式碼為什麼放在這裡、哪些內容通常需要一起修改,以及哪些部分不應彼此依賴。團隊也需要能用一致的方式理解這些安排。
如果一種結構能降低理解與修改程式碼的成本,那麼對目前的團隊與專案而言,它就是有價值的結構。