# 問題: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) 版本號控制

這個模式用「複用便利性」換「可見性與可控性」。
功能穩定、團隊分工明確時,是降低重複成本的利器;
功能還在快速迭代時,耦合的代價會讓開發速度反而更慢。

請我喝[茶]~( ̄▽ ̄)~*

Young 微信支付

微信支付

Young 支付寶

支付寶