iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
Vibe Coding

夢幻甜品師闖工程世界:Vibe Coding vs 專業開發的 0→1 冒險攻略系列 第 10

Day 10|廚房工具不能全塞一櫃:從專案結構看懂 Code 怎麼分類

  • 分享至 

  • xImage
  •  

昨天我們先把開發環境準備好,也看懂了 package.json 和套件是怎麼讓專案跑起來的。

而回到剛開始做 BuJo 的時候,我很快就遇到另一個問題:

功能寫完之後,這個檔案到底該放在哪個資料夾才是對的?

打開 Project 一看,裡面有好多不同名字的檔案和資料夾:

project/
├─ src/
│  ├─ components/
│  ├─ stores/
│  ├─ services/
│  └─ router/
├─ public/
└─ ...

有些是建立專案時就出現的,有些又像是後來自己建立的。

當時我其實很疑惑:

這些資料夾是原本就規定好的嗎?如果不是,一個專案到底是依照什麼邏輯,決定哪些檔案該放在哪裡?

所以今天就從 專案結構(Project Structure) 開始,把這張地圖從外到內拆開來看。


先從整個專案結構看起

接下來先用一個 Vue 專案可能會看到的結構來理解。

把整個專案拉遠來看,大致可以看到不同用途的區域:

project/
├─ src/             ← 主要程式碼
├─ public/          ← 靜態資源
├─ tests/           ← 測試(也可能採其他安排)
├─ package.json     ← 套件、scripts 等專案資訊
├─ vite.config.js   ← Vite 設定
└─ ...

這些不同用途的檔案與資料夾,一起構成了整個 專案結構(Project Structure)

專案結構處理的是一個比較上層的問題:

整個專案裡,不同性質的東西應該放在哪個區域?

再往下一層看,開發功能時,我們最常動到的,通常就是集中在 src/ 裡的這些程式碼。

src/ 再被打開:

src/
├─ components/
├─ stores/
├─ services/
├─ composables/
├─ router/
└─ ...

問題就又往下一層了:

同樣都是程式碼,為什麼還要分成這麼多資料夾?

接下來,就來看這些程式碼到底可以依照什麼方式分類、組織。


走進 src,這些資料夾是怎麼分出來的?

先不要急著背每個名字。

以 Vue 專案常見的情況來看,可以先按照「為什麼會出現」理解:

常見的應用程式區域
├─ components/   → 畫面元件
├─ assets/       → 圖片、CSS、字型等資源
└─ router/       → 路由設定(有 Routing 時)

依技術選擇
├─ stores/       → 使用狀態管理時
├─ composables/  → 抽出可重複使用的 Composition API 邏輯時
└─ services/     → 團隊選擇集中 API/外部服務邏輯時

依產品需求
└─ locales/      → 需要 i18n 多語系時

再翻成白話:

資料夾 通常拿來放什麼
components/ Vue 元件,也就是畫面上的 UI 單位
assets/ 圖片、CSS、字型等資源
router/ URL 對到哪個頁面,以及路由規則
stores/ 需要集中管理、讓多個元件使用的狀態
composables/ 可重複使用的 Vue Composition API 邏輯
services/ API、HTTP Client 或外部服務相關邏輯
locales/ 多語系翻譯內容
utils/ 比較通用的小工具函式

所以這些並不是:

「Vue 已經幫我準備好的標準櫃子。」

有些會受到使用的框架、套件或開發工具影響,有些則是開發者依照專案需求自己建立的。

那問題就來了:

工程師到底依據什麼,決定哪些程式碼要分開、哪些又適合放在一起?


資料夾怎麼分,背後其實有個原則:SoC

這裡會碰到一個重要的軟體工程原則:

關注點分離(Separation of Concerns, SoC)。

可以先把它理解成:

不同責任的程式碼,不要毫無邊界地全部揉在一起。

例如:

介面顯示
路由
狀態管理
API 溝通

這些程式碼負責的事情不同,所以通常會依照各自的責任整理到不同區域。

但要注意,分的是責任,不是單純把不同類型的檔案全部拆開。

Vue 官方在介紹 單一檔案元件(Single-File Component, SFC) 時也特別提醒:

關注點分離(SoC)不等於按照檔案類型把程式碼拆開。

一個 .vue 檔裡可以同時有:

template
script
style

因為它們雖然形式不同,卻都在共同描述同一個元件。

所以 SoC 真正要處理的是:

哪些責任應該靠近,哪些責任應該分開。


那這些程式碼到底可以怎麼分?

知道「為什麼要分」之後,接下來才進到實際的程式碼組織方式。

先看兩個常見方向,以及實務上也會使用的混合方式:

程式碼組織方式
│
├─ 按類型組織(by-type)
│  └─ 按技術類型/角色分類
│
├─ 按功能組織(by-feature)
│  └─ 按產品功能分類
│
└─ 混合式組織(Hybrid)
   └─ 混合使用不同的組織方式

這些不是 Vue 規定的三種標準架構,而是不同的程式碼組織思路


按類型組織(by-type):先問「它是什麼?」

例如:

src/
├─ components/
│  ├─ ActivityPage.vue
│  └─ FriendsPage.vue
│
├─ stores/
│  ├─ activityStore.js
│  └─ friendStore.js
│
└─ services/
   ├─ activityApi.js
   └─ friendApi.js

它的分類邏輯是:

畫面元件 → components/
共用狀態 → stores/
服務邏輯 → services/

也就是先問:

這個檔案是什麼類型?

同一種類型的程式碼,就集中放在同一個資料夾裡。

在專案規模還不大的時候,這種方式通常很直覺。


按功能組織(by-feature):先問「它屬於哪個功能?」

但如果換一個角度,不是要找「所有 Store」或「所有 Component」,而是:

我要修改 Activity 功能。

按照剛剛的 by-type,就可能需要分別到:

components/
stores/
services/

把所有和 Activity 有關的程式碼找出來。

另一種做法,就是先按照產品功能分類:

src/
└─ features/
   ├─ activity/
   │  ├─ components/
   │  ├─ store/
   │  └─ services/
   │
   └─ friends/
      ├─ components/
      ├─ store/
      └─ services/

這就是 按功能組織(by-feature)

它先問的是:

這段程式碼屬於哪一個功能?

所以兩者最簡單的差異可以記成:

by-type
→ 先看「它是什麼類型?」

by-feature
→ 先看「它屬於哪個功能?」

實際專案,不一定非得二選一

by-type 和 by-feature 不一定只能二選一。

例如:

src/
├─ features/
│  ├─ activity/
│  ├─ friends/
│  └─ auth/
│
├─ shared/
│  ├─ ui/
│  └─ utils/
│
├─ router/
└─ assets/

跟特定功能綁得比較緊的程式碼,可以放在各自的 features/ 裡。

真的會被多個功能共用的東西,再抽到 shared/

這就是一種 混合式組織(Hybrid)

它不是另一套固定模板,而是依照專案實際需求,把不同的組織方式搭配使用。


專案結構也會跟著程式碼一起演化

所以真正的問題並不是:

「by-type 和 by-feature 到底哪個比較專業?」

而是:

「現在這個專案結構,還適合目前的程式碼規模和開發方式嗎?」

專案剛開始時:

components/
stores/
services/

可能已經非常清楚。

但隨著功能越來越多,也可能開始出現:

現象 可能代表
修改一個功能要跨很多資料夾 相關程式碼可能離得太遠
同一個資料夾塞進大量互不相關的檔案 原本的分類開始不好找
新增檔案時常常不知道該放哪 責任邊界開始變得模糊

這時候才可能需要重新整理,逐步改成 by-feature,或採用混合式組織。

所以 專案結構(Project Structure) 並不是專案建立第一天決定一次,之後就永遠不動。

它也可能隨著程式碼規模、功能和團隊需求一起演化。


把這套分類放回 BuJo 看看

BuJo 前端目前的 src/ 大致是:

src/
├─ assets/
├─ components/
├─ composables/
├─ locales/
├─ router/
├─ services/
├─ stores/
├─ utils/
├─ App.vue
└─ main.js

從最上層來看,components/composables/services/stores/ 主要是按照程式碼的技術角色分類,所以整體上屬於 按類型組織(by-type) 的思路。

再往資料夾裡面看,也會依照實際用途繼續細分,例如:

components/
└─ ui/

也就是先有一個主要的分類方式,再根據實際需要往下整理。

這也比較接近真實專案的樣子:不一定每一層都要套上一個新的架構名稱,而是讓目前的結構維持清楚、好找、好維護。


換到後端,資料夾又是怎麼分的?

前端在整理程式碼時,會思考畫面、狀態、路由、API 等責任該怎麼分。

換到後端,問題則會變成:

誰負責接收請求?
誰負責處理業務邏輯?
誰負責和資料庫或外部服務溝通?

這些不同責任,也會反映在資料夾結構上。

先用一個常見的後端結構來看:

src/
├─ routes/
├─ controllers/
├─ services/
├─ middleware/
├─ lib/
└─ __tests__/
資料夾 通常負責什麼
routes/ 定義 HTTP 路由,決定請求要交給哪個處理程式
controllers/ 接收請求、取得參數、呼叫後續邏輯並組成回應
services/ 處理業務邏輯或外部服務整合
middleware/ 在主要處理流程前後執行共用邏輯,例如驗證、流量限制、錯誤處理
lib/ 共用的底層工具或第三方函式庫封裝
__tests__/ 測試

這背後同樣會用到 關注點分離(Separation of Concerns, SoC)

例如後端常見會看到這類責任層次:

Route
  ↓
Controller
  ↓
Service
  ↓
Database / ORM

這種按照責任層次來組織的方向,也常會談到 按層組織(by-layer)

不過這不是所有後端專案都必須照抄的固定架構,而是其中一種常見的責任切分方式。

所以前端和後端真正共通的,不是資料夾名稱,而是:

都需要把不同責任的程式碼分到合理的邊界裡。


原來資料夾不是先有答案,再把程式碼塞進去

以前我會把資料夾結構想成單純的檔案整理:

這個是元件,就放進 components/

那個是狀態管理,就放進 stores/

但現在才發現,真正重要的不是把檔案收得多整齊,而是替不同責任畫出清楚的邊界。

這樣之後要找程式碼時,會比較知道該從哪裡開始;修改功能時,也比較不容易牽動一堆原本不相關的地方。

而且這張地圖也不是一開始決定好就永遠不變。

隨著專案規模變大、功能變多,原本好用的分類方式也可能需要重新調整。

所以資料夾結構真正解決的,不只是「檔案該放哪」,而是讓整個專案之後更容易理解、修改和維護。

下一個問題就是:

每一次有人進來改過什麼,我們要怎麼留下紀錄?

下一篇,就來正式拆 Git。


參考資料

  • Vue.js 官方文件:Single-File Components — Separation of Concerns
  • Vue.js 官方文件:Composables
  • Express 官方文件

上一篇
Day 9|甜點還沒開始做,先確認廚房能不能開工:從環境建置看懂 package.json 與 npm
下一篇
Day 11|打怪前先記得存檔!從 Git 看懂工作區、暫存區與 Commit
系列文
夢幻甜品師闖工程世界:Vibe Coding vs 專業開發的 0→1 冒險攻略12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言