# 問題:Copy-Paste 驅動開發
在大型組織裡,這個場景幾乎每天都在上演:
A 專案需要計算用戶用量與成本,工程師在 A 專案內實作了一套邏輯。
幾個月後,B 專案有同樣的需求,因為沒有共享機制,工程師複製了一份,稍微改了改。
C 專案來了,歷史重演。
結果:同一份商業邏輯分散在三個 repo,各自累積 bug、各自修、各自演化成不同版本。這不是技術債,這是技術分裂。
# 根本原因:缺乏模組化邊界
Copy-paste 文化的成因不是工程師偷懶,而是共享的摩擦力太高:
- 公司內部無法用 public registry
- 抽共用庫的成本看起來比複製貼上高
- 沒有建立私有模組的標準流程
所以大家選最省力的路:複製。
# 解法:私有 Go 模組 + Git 認證
Go Modules 本身支援任意 git host,只需要一次性設定 SSH 認證,之後的使用體驗和 public module 完全相同。
~/.gitconfig — 一次設定,永久生效
[url "git@git.company.internal:"]
insteadOf = https://git.company.internal/
這讓 go get 自動走 SSH,不需要額外的 token 或手動處理認證。
go.mod — 像一般依賴一樣宣告
git.company.internal/platform/billing v1.3.0
版本對應 git tag,升版本就是改這行。
# 模組設計:對外只暴露三個東西
一個設計良好的內部模組,外部 API 應該極度簡單:
// 1. Config — 宣告外部依賴
type Config struct {
DB *gorm.DB
Logger *zap.SugaredLogger
}
// 2. NewModule — 工廠函式
func NewModule(cfg *Config) (*Module, error)
// 3. SetupRoutes — 委託路由
func (m *Module) SetupRoutes(rg *gin.RouterGroup)
所有 handler、service、repository 全部封裝在模組內部,主專案零感知。
# 整合端:三行搞定
// import
import billing "git.company.internal/platform/billing"
// 初始化
billingModule, _ = billing.NewModule(&billing.Config{
DB: db,
Logger: logger,
})
// 路由
billingModule.SetupRoutes(authenticated)
A、B、C 專案都是這三行,差別只有傳入不同的 DB 和 Logger。
# 額外收益:Graceful Degradation
模組可以設計成可選的:
if apiKey := os.Getenv("BILLING_API_KEY"); apiKey != "" {
billingModule, err = billing.NewModule(...)
if err != nil {
billingModule = nil // 初始化失敗不中斷主程式
}
}
功能缺 key 就不啟用,不影響核心流程。這在功能開關、漸進上線時特別有用。
# 缺點:你用「複用便利性」換來的代價
# 1. 版本升級是「全有或全無」
需要修一個 bug,就得升整包版本。
若同一版本夾帶其他 breaking change,主專案必須一起承擔,沒有選擇。
# 2. 出問題,主專案無法自救
handler 邏輯、SQL query、錯誤處理全封裝在模組內部,主專案工程師看不到、改不到。
唯一的路:等模組那邊修完、發版、主專案升版。緊急 hotfix 的回應速度直接受限。
# 3. DB Schema 耦合
Config 傳入 *gorm.DB,代表模組的 migration 和 table 結構與主專案共用同一個資料庫。
模組改了 schema,主專案的 DB 跟著動。部署順序必須對齊,否則直接炸。
# 4. 本地開發回饋週期長
想追一個 bug、加一行 log:
去模組 repo → 修改 → commit → push → 打 tag → 回主專案升版
每次迭代都要跑這個流程,比直接寫在主專案慢很多。
緊急時可用 replace directive 暫時繞過:
// go.mod 暫時指向本地路徑
replace git.thebarkingdog.tw/go-module/rag => ../rag
但這只是權宜之計,上版本前記得移掉。
# 5. 測試邊界模糊
SetupRoutes 是黑箱,主專案的整合測試無法 mock 內部邏輯。
要測 RAG 路由,只能跑 end-to-end,需要真實的外部服務在線,CI 環境建置成本上升。
# 總結
| 做法 | 維護成本 | 一致性 |
|---|---|---|
| Copy-paste | O(n × 專案數) | 無法保證 |
| 私有模組 | O(1) | 版本號控制 |
這個模式用「複用便利性」換「可見性與可控性」。
功能穩定、團隊分工明確時,是降低重複成本的利器;
功能還在快速迭代時,耦合的代價會讓開發速度反而更慢。
