去年對接一個跑了七年的老系統,我在同一個欄位上看見四種寫法:資料庫裡是 created_time,介面文件寫 CreateTime,前端型別定義裡是 createTime,埋點表格裡叫 Created Time。第一版程式碼上線後取不到值,我花了兩個多小時懷疑序列化、懷疑閘道、懷疑時區,最後發現是 ORM 裡 mapUnderscoreToCamelCase 這個開關沒打開。從那以後我把「命名風格轉換」當成一件需要工具的事,而不是「順手改一下」的事。
一、同一個詞的幾種長相
命名風格不是審美問題,是不同技術棧的歷史約定。同一個「使用者名稱」,各處的合法寫法並不相同:
| 風格 | 範例 | 主要出現在 |
|---|---|---|
| camelCase | userName | JavaScript / Java 變數與方法、GraphQL 欄位 |
| PascalCase | UserName | 類別名稱、React 元件、C# 公開成員 |
| snake_case | user_name | Python 變數與函式、資料庫欄位名稱 |
| CONSTANT_CASE | USER_NAME | 常數、環境變數 |
| kebab-case | user-name | URL 路徑、CSS 類別名稱、CLI 參數 |
| Title Case | User Name | 表頭、按鈕文案、報表欄位名稱 |
外部 API 也沒有統一標準:GitHub 的公開介面大面積用 snake_case,Google 風格的介面用 camelCase。於是同一份資料經過三層,就要轉三次。手工轉的風險不在慢,而在於不一致——同一批 42 個欄位裡,只要有一個手抖寫成 userName 與 username 兩種,它就跟其他欄位的對應規則不同,而這類差異通常到執行期才暴露。
二、命名衝突一般在這四個地方炸
- 資料庫識別碼大小寫。PostgreSQL 對未加引號的識別碼會折疊成小寫,你按
CreateTime建欄,實際存的是createtime,之後每次引用都得加雙引號。MySQL 表名是否區分大小寫取決於伺服器設定與檔案系統,Linux 上區分、Windows 上通常不區分,所以「我本機能跑」和「伺服器說報表不存在」完全可以同時成立。 - HTTP 標頭。標頭名稱按規範是大小寫不敏感的,很多執行環境讀出來一律小寫(
Content-Type變成content-type),閘道重寫後拿不到值,多數人會先去查驗證。 - 簽章與校驗字串。參與簽章的欄位名要和對方約定逐字元一致。把
AppKey轉成appkey之後驗簽失敗,錯誤訊息只會給你 "invalid signature",不會告訴你是大小寫問題。 - 縮寫和數字。
userID還是userId、QrCode還是QRCode、utf8mb4轉駝峰成Utf8Mb4,機械轉換的結果常常比原寫法更難看。這類詞需要一份人工白名單,而不是讓規則去猜。
還有一個少有人提前防的坑:大小寫轉換在個別語言環境下不是可逆的字母對應。土耳其語裡大寫 I 轉小寫得到的是無點的 ı,而不是 i,Java 裡 toLowerCase() 不帶 Locale.ROOT 就會踩到,欄位名在測試環境好好的,部署到設定了區域設定的機器上就匹配不上。
三、我現在的批次轉換做法
- 先把分隔符統一。欄位名裡可能混著空格、底線、連字號,我習慣先用批次尋找取代加一條正規表示式規則,把
[_\-\s]+全替成單一底線,讓後續轉換有穩定的輸入。 - 整欄貼進大小寫轉換,分別取全大寫、全小寫、首字母大寫和駝峰、底線、連字號幾種輸出。42 行一次處理完,三份目標命名各取一欄,比在編輯器裡逐個改省掉的不只是時間,還有出錯率。
- 結果先過一遍正規表示式校驗再落文件。用正規表示式測試匹配
^[a-z][a-z0-9]*(_[a-z0-9]+)*$這類模式,凡是列出不合格行的,就是轉換沒能識別分詞的欄位(多半是縮寫或黏在一起的數字),單獨人工處理。 - 把規則固化到檢查裡,而不是靠約定。前端加 ESLint 的
camelcase規則,Python 側用 linter 的命名檢查,資料庫欄位名在 migration 評審清單裡寫死 snake_case。轉換工具解決存量,lint 解決增量。 - 改名要往下游同步:檢視表、BI 報表、匯出的範本、給營運用的對照表,都按新名字重新產生一次。老系統裡最容易漏的是別人手工維護的 Excel 取數範本。
四、幾個邊界
大小寫和命名風格只作用於英文字母。中文、全形字母、程式碼裡的中文註解不參與轉換,也不會被破壞,但混排文字整批處理時註解會跟著變格式,所以我一般只貼欄位清單,不貼整段原始碼。
不要為了「統一」把整份原始檔全轉成大寫或小寫。程式碼的可讀性和 diff 噪音都是代價,正確範圍是識別碼、表頭、常數名這一層級,正文文案另按 Sentence case 處理。
對外契約改名要留過渡期。欄位層級的更名對呼叫方是破壞性變更,常見做法是新名字與舊名字並行輸出一個版本週期,文件裡註明廢棄時間。
五、常見疑問
Q:camelCase 和 PascalCase 到底怎麼選?
看它是不是「型別」。類別、元件、介面名用 PascalCase,實例、變數、方法、函式參數用 camelCase,這是 Java、JavaScript、C# 社群共同的約定。
Q:資料庫欄位該用什麼命名?
snake_case 更穩:跨資料庫可移植、不依賴引號包裹、在 SQL 裡可讀性最好。定下來之後,讓 ORM 的自動轉換去負責對應到程式碼裡的 camelCase,而不是兩邊手工對齊。
Q:為什麼我本機正常的程式碼,發到伺服器上就報欄位不存在?
大概率是大小寫敏感性不同。Windows 檔案系統不區分、Linux 區分,資料庫表名和欄名的行為跟著設定與平台走。把識別碼統一成小寫加底線,可以從根上避免這類「平台依賴」。
Q:幾十行表頭能一次轉完嗎?
可以,多行整批處理正是這類工具存在的理由。要點是先統一各種分隔符,再選目標風格,最後用一條正規表示式校驗輸出,三步都不能省。
Q:轉換會不會影響已經加密或參與簽章的字串?
會改變參與計算的原文,結果就完全不同。簽章、雜湊、Base64 這類字串永遠保持原樣,只轉換人寫的識別碼,兩者不要放在同一次批次裡。
那個老系統後來我做了一次全量重命名:資料庫裡全部 snake_case,介面輸出參數統一 camelCase,前端型別不再手寫而由介面定義產生。現在回頭看,當初那兩個多小時排查的根源,不是任何一層寫錯了,而是沒有任何一處明確寫下「這個欄位在這一層應該長什麼樣」。命名風格本身沒有對錯,混著用才有代價。