過去看過不少專案試圖透過 properties 集中管理業務欄位名稱,目的是避免 Hard Coding、減少重複字串、提高複用性與可維護性。但以我多年經驗來看,這類設計通常沒有帶來理想中的好處,反而隨著時間逐漸累積重複定義、命名混亂難以追蹤,最終疏於管理導致荒廢爛掉。
尤其在沒有多語系 (i18n) 需求、業務欄位名稱複用性又低的情況下,這種抽象增加的維護成本遠大於帶來的好處。
原始寫法
假設要檢查「操作項目」是否為空,專案已經有 messages.properties 管理錯誤訊息模板
messages.properties
validation.required={0} 為必填
Java 程式碼:
if (opName == null) {
throw new ServiceException(
msgUtils.get("validation.required", "操作項目")
);
}
錯誤訊息:
操作項目 為必填
這樣的程式碼很直觀,開發人員及 Code Reviewer 都能直接理解錯誤訊息的內容。
抽離業務欄位名稱
如果進一步要求將「操作項目」抽離到另一份 fields.properties:
fields.properties
field.op-name=操作項目
Java 程式碼就會變成:
if (opName == null) {
throw new ServiceException(
msgUtils.get(
"validation.required",
msgUtils.get("field.op-name")));
}
如果又要求將 properties key 宣告為常數,最後變成:
private static final String FIELD_OP_NAME = "field.op-name";
if (opName == null) {
throw new ServiceException(
msgUtils.get(
"validation.required",
msgUtils.get(FIELD_OP_NAME)));
}
原本直接寫「操作項目」就能理解,現在卻必須經過:
FIELD_OP_NAME
↓
"field.op-name"
↓
fields.properties
↓
"操作項目"
為了消除一個中文字串,增加了好幾層間接參照。
可讀性與維護成本
這種設計最大的問題,是降低了程式碼的直觀性。原本在 IntelliJ 中閱讀 Java 程式碼,就能直接看到業務欄位名稱。現在必須先找到常數定義,再查詢 properties,才能確認實際顯示的文字。在 GitLab 、 GitHub 或 Bitbucket 頁面 Code Review 時更麻煩,Reviewer 看到的只有常數名稱,還得跨檔案追蹤,才能確認錯誤訊息是否正確,要不然只能checkout下來review。
雖然 IDE 可以透過搜尋、導航等功能協助追蹤,但這些操作本身就是額外成本。
程式碼少了幾個重複字串,每次閱讀、修改及審查都需要更多跳來跳去察看的步驟,這會比較好維護嗎?
為了共用而共用
另一個問題是複用性。
假設模組 A 與模組 B 都有「操作項目」欄位:
// Module A
msgUtils.get("validation.required", "操作項目");
// Module B
msgUtils.get("validation.required", "操作項目");
如果只因為名稱相同,就要求兩個模組共用:
fields.properties
field.op-name=操作項目
未來模組 A 的業務需求改變,要求將欄位名稱改成「工作項目」,模組 B 卻仍然使用「操作項目」,這時怎麼辦?
最後還是必須拆成:
fields.properties
field.module-a.op-name=工作項目
field.module-b.op-name=操作項目
字串內容相同,不代表業務語意相同,更不代表兩者具有相同的變更原因。如果兩個模組的需求可以獨立演進,強行共用反而產生不必要的耦合。
真的好維護嗎?
以過去參與大型專案的經驗來看,這類集中管理的 properties、錯誤代碼及錯誤訊息,最後經常變得難以維護。隨著系統擴充,不同開發人員陸續新增 key,逐漸出現重複定義、命名不一致、廢棄項目及語意不明等問題。例如:
fields.properties
field.op-name=操作項目
field.op=操作項目
field.operation-name=操作項目
field.module-a.op-name=工作項目
時間一久,開發人員不知道哪些 key 還在使用,也不確定能不能修改既有定義,最後乾脆繼續新增。原本希望透過集中管理降低維護成本,結果反而形成另一個需要維護的地方。
當然,如果系統有多語系需求、業務術語必須統一,或確實存在大量共享且需要同步變更的名稱,使用 properties 有必要性。但如果沒有多語系需求,欄位名稱固定,複用性又低,那麼這些抽象究竟解決了什麼問題?
結論
我不反對使用 MessageSource,也不反對抽象與集中管理,而是認為每一層抽象都應該有明確的目的與收益,且必須考量抽象背後的成本。為了消除少量重複字串,增加 Java 常數 properties key 與 MessageSource 等間接參照,未必能提高可維護性。尤其當這些字串分屬不同業務模組時,刻意共用甚至可能造成不必要的依賴。
Clean Code 不是讓程式碼看起來沒有重複,而是讓程式碼容易理解、修改與維護。
重複不一定是壞事,抽象也不一定是好事。真正應該衡量的,是這項設計究竟降低了多少未來的變更成本,又增加了多少理解與維護成本。
沒有留言:
張貼留言