在所有 camel case 到 snake case 轉換中,tokenization 是最重要的一個步驟,因為在大寫字母前插入分隔符會把 XMLHttpRequest 變成 x_m_l_http_request,而非預期的 xml_http_request——這是純粹的 sed 和 awk 替代在識別符含有縮寫詞時會立刻碰到的失敗模式。線上工具處理同樣任務的方式則截然不同:它們先套用一條明確的 tokenization 規則,然後把產生的詞彙清單重新格式化為六種常見命名風格中的任何一種。tokenization 與格式化之間的拆分,正是一行 shell script 默默搞壞你的 codebase,以及一個能跨專案安全重新命名識別符的工作流程之間的差別。換句話說,「命令列 vs 線上工具」這個問題其實是兩個問題。哪一種 tokenization 政策符合你的識別符?而哪一種交付形式——shell pipeline 或瀏覽器分頁——符合你實際的工作方式?多數開發者想在 script 內快速重新命名時,會選擇 sed、awk 或一個 npm 套件。當識別符夠不規則、讓人想先看清它是怎麼被切開的才敢信任輸出時,多數人則會選擇瀏覽器工具。兩者各有其誠實的優點,正確答案取決於你的 codebase、團隊以及 CI pipeline 能容忍什麼樣的重新命名政策。

camel case to snake case command line vs online
Camel Case 轉 Snake Case:命令列 vs 線上工具

命令列 script 何時管用——又為何力有未逮

教科書式的 camelCase 轉 snake_case sed 替代,會把每個接在小寫字母後面的大寫字母,換成底線加小寫形式。對 getUserName 會得到 get_user_name,完全正確。這個 pattern 很短、不依賴任何外部套件,也能毫無摩擦地塞進任何 CI 步驟或 git hook。然而同一個 pattern,一旦碰上縮寫詞就立刻失效。

以 XMLHttpRequest 為例。天真的 sed 替代會在每個大寫字母前插入底線,產出 x_m_l_http_request,任何人類審查者一看就會打槍。修法則是再跑一輪,把一連串大寫字母後接著一個大寫開頭單字時所留下的尾端底線拿掉。Perl one-liner 比 sed 更乾淨地表達這個兩段式流程,因為 Perl 能在一個表示式中同時處理 lookbehind 與 lookahead。團隊仍然得先讀懂並理解這個 pipeline 才敢信任它,而且兩個工具而非一個,代表在 bash、zsh、PowerShell 與 cmd 之間會多出一層 escape 差異的接觸面。

含有數字的識別符暴露了另一個怪癖。小寫到大寫的分隔能乾淨地處理 utf8Encoder,因為數字 8 後面接著大寫字母;但 iso8601Date 想要正確分隔,得讓 script 把數字與其後的大寫字母視為同一個邊界。JavaScript 與 Node.js 專案通常會選擇 change-case 套件或類似函式庫,免得在 shell script、PowerShell script 與 CI YAML 檔之間搬著一段手寫 regex 跑來跑去,因為跨平台維護那段 regex 的負擔,不值得省下那一個相依套件。

任何命令列作法背後的隱藏成本,是每個 shell 對 escape 序列的解讀方式不同。同一段 sed 表示式在開發者筆電上能跑,在 Windows CI runner 上卻可能失敗或產出不同的結果。線上工具則沒有這個問題,因為根本沒有 shell 需要 escape。

瀏覽器版 Camel Case 轉 Snake Case Converter 做了哪些不一樣的事

Camel Case 轉 Snake Case Converter 先進行一輪 tokenization,然後把產生的詞彙清單一次格式化成六種命名風格,因此從 snake_case 切換到 CONSTANT_CASE 或 PascalCase 不會重新執行 tokenizer。這個特性在你想要比較輸出時很重要:snake_case 與 kebab-case 之間的差異,應該只有連接符的不同,絕對不該是不同的切詞結果。

tokenizer 依序套用兩條規則。第一,它會把「一個小寫字母或數字」後接的「大寫字母」切開,因此 userId 會變成 user 與 Id 兩個 token,utf8Encoder 會變成 utf8 與 Encoder。第二,它會把一段大寫序列後、緊接著的大寫開頭單字切開,因此 XMLHttpRequest 變成 XML、Http 與 Request,而 HTTPSConnection 則變成 HTTPS 與 Connection。其他所有分隔符——標點連珠、空白、混合符號——都會被當成詞界,而不是存活在輸出中。空白與重複分隔符會被收合,不會產出空字詞。

實作上會明確指定英文 locale 來處理大小寫轉換,讓輸出在設定為土耳其文、亞塞拜然文或立陶宛文的使用者 locale 的瀏覽器中仍保持穩定——這些 locale 中,有點與無點的 I 會以出乎意料的方式影響大小寫折疊。Unicode 字母與數字在 tokenization 過程中會被保留,但大小寫折疊遵循英文規則,因此當你為了測試組建而切換 CI runner 的 locale 時,輸出不會跟著翻轉。

這個工具完全在瀏覽器中執行。不會上傳任何貼上的內容、不會保存任何結果,也不需要登入或安裝套件。輸入上限為 5,000 個 code unit,讓非常長的片段也不會卡住渲染或剪貼簿操作;空字串或只有標點的輸入會被拒收,因為它根本產不出可用的 token。即便是最長的類別或常數名稱,也遠低於這個上限,所以這個上限是有意為之的產品決定,而不是對識別符名稱的實質限制。

用轉換器走一遍一個識別符

如何用四個步驟,把一個棘手的 camelCase 識別符轉成經驗證的 snake_case 值:

  1. 把識別符或片語貼進轉換器的輸入框。XMLHttpRequest 是個有用的壓力測試,因為它一次會逼出所有邊界規則。
  2. 檢視工具推導出的 token 清單。確認 XML、Http、與 Request 是分開的三個 token,而不是 X、M、L、Http 與 Request。
  3. 並排讀取六個輸出,挑一個你的 codebase 會使用的。Python 或 Ruby 用 xml_http_request,slug 或 URL 路徑用 xml-http-request,JavaScript 用 xmlHttpRequest,C# 類別或 TypeScript 型別用 XmlHttpRequest,常數用 XML_HTTP_REQUEST,而文件標籤則用 Xml Http Request。
  4. 複製選定的值,在合併到任何可能打壞消費端的位置之前,先進行專案層級的重新命名與相容性檢查。

六種風格與各自的用途

這六個輸出只是機械化的格式,並非通用的風格規則——語言、框架、資料庫、API 與團隊都可能對開頭數字、保留字、最長長度或可接受字元再加限制。下表把每種風格對應到它的連接符與典型用途。

輸出風格連接符大小寫模式典型用途
snake_case底線全部小寫 tokenPython 函式、Ruby 符號、PostgreSQL 欄位
kebab-case連字號全部小寫 tokenURL slug、CSS class hook、檔名
camelCase無第一個 token 小寫,後續 token 大寫開頭JavaScript 變數、JSON 屬性名稱
PascalCase無每個 token 都大寫開頭TypeScript 型別、React 元件、C# 類別
CONSTANT_CASE底線全部大寫 token編譯期常數、環境變數
Title Case空白每個 token 都大寫開頭文件標籤、錯誤訊息、標題

想更深入地並排比較全部六種風格,Camel Case 轉 Snake Case 速查表:六種命名風格用實際範例逐一走過每種輸出的 tokenization 規則。

命令列仍然是對的工具的時機

除了上述種種限制之外,確實有些工作流程是 shell pipeline 勝過瀏覽器分頁的。橫跨數百個檔案的 repo 級重新命名、使用 jscodeshift、ts-morph 或 codemod 進行的語言感知重構,以及用 git grep 串接 xargs sed -i 進行的大量 find-and-replace,這些都活在命令列上,需要文字層級的轉換,而不是人工的複製貼上迴圈。關於設計素材上該選命令列還是瀏覽器的務實觀察——CSS Stripes Generator:命令列 vs 線上工具比較——也提出了類似的論點:當工作量擴大成 script 時,命令列勝出;當一個刁鑽的識別符需要仔細端詳時,瀏覽器工具勝出。

對 camelCase 轉 snake_case 而言,誠實的答案是混合做法。先用瀏覽器工具對 codebase 中那些奇形怪狀的識別符驗證 tokenization,再把規則烤進全團隊都能跑的 script 或 codemod。當你要重新命名一個公開欄位的那一天,要走你那個語言的 rename refactor,而不是全域字串取代,並且保留一個 deprecated alias,直到所有消費端都遷移完畢。

超越字串本身的安全重新命名

一旦名字看起來乾淨了,困難的部分才正要開始。更動一個資料庫欄位、一個環境變數、一個公開 API 路徑、一個 JSON 屬性,或一個匯出的函式,即使新拼法看起來更整齊,也仍然是 breaking change。在改名的過程中把 userID 跟 userId 搞混,可能打壞一個區分大小寫的檔案系統,或一個不區分大小寫的資料庫;而僅僅因為大小寫不同而有所區別的重新命名,可能需要一個中間名稱,才不會讓消費端在寫入新值的同時讀到舊值。

這個轉換器的範圍是有意嚴格設定的:它不會轉寫 script、不會把單詞單複數化、不會拼字檢查輸入、不會保留標點,也不會猜測語意縮寫。像 user ID 這種片語可能會輸出成 userId,而不是專案特定的 userID,而這正是工具刻意凸顯出來的一個政策選擇,讓審查者在重新命名公開符號之前注意到它。重新命名之後,請跑 formatter、linter、型別檢查、整合測試,並檢視產生的程式碼、reflection、設定檔、模板與外部消費端,看看有沒有你那個語言感知 rename 找不到的參考。