CODE Polyglot / よく使う書き方 / 01

スネークケースとキャメルケースを変換する

user_id と userId を行き来させます。データベースの列名と JSON のキー、定数と変数のように、名前の書き方をそろえるときに使います。HTTPServer のように大文字が続く名前で、よく見かける書き方はほとんどの言語で崩れます。

画面にこう出れば正解

どの言語で書いても、同じ入力なら画面に出る文字は1文字も違わない、というのが正解の決め方です(例は Python で動かしたときのコマンドです)。

基本(キャメル → スネーク)

$ python3 case_convert.py snake userId
user_id

大文字が続く(HTTPServer)

$ python3 case_convert.py snake HTTPServer
http_server

途中で大文字が続く

$ python3 case_convert.py snake getHTTPResponseCode
get_http_response_code

最後が大文字の続き(userID)

$ python3 case_convert.py snake userID
user_id

数字のあと

$ python3 case_convert.py snake version2Name
version2_name

先頭が大文字(パスカルケース)

$ python3 case_convert.py snake UserProfile
user_profile

もうスネークケース

$ python3 case_convert.py snake already_snake
already_snake

基本(スネーク → キャメル)

$ python3 case_convert.py camel user_id
userId

大文字のスネークケース(定数)

$ python3 case_convert.py camel MAX_RETRY_COUNT
maxRetryCount

先頭と途中の「_」が続く

$ python3 case_convert.py camel __private__value
privateValue

数字の部分

$ python3 case_convert.py camel address_2_line
address2Line

空の文字列

$ python3 case_convert.py camel ""

日本語が入っている

$ python3 case_convert.py snake userなまえ
NG: 半角の英数字と _ だけにしてください

ハイフン(ケバブケース)は扱わない

$ python3 case_convert.py camel user-id
NG: 半角の英数字と _ だけにしてください

10言語のプログラム

どれもそのまま保存して動かせます。毎朝、上の正解の例をすべて実際に動かして確かめています。

case_convert.php8.4.26 で 14 / 14 件の出力が正解と一致

<?php
// スネークケース(user_id)とキャメルケース(userId)を、指定した向きに変換する
const NG = "NG: 半角の英数字と _ だけにしてください";
[, $mode, $s] = $argv;

// preg の $ は末尾の改行の手前にも合う。D を付けて、文字列の本当の終わりだけに合わせる
if (!preg_match('/\A[A-Za-z0-9_]*\z/D', $s)) {
    echo NG . "\n";
} elseif ($mode === "snake") {
    // 切れ目は2種類。小文字か数字の次の大文字(userId)と、大文字の続きの最後で次が小文字のもの(HTTP|Server)
    $s = preg_replace('/(?<=[a-z0-9])(?=[A-Z])/', '_', $s);
    $s = preg_replace('/(?<=[A-Z])(?=[A-Z][a-z])/', '_', $s);
    // strtolower は PHP 8.2 から ASCII だけを変える(ロケールに左右されない)。ここは ASCII だけなので mbstring は要らない
    echo strtolower($s) . "\n";
} else {
    // explode は「__」のところで空の部分を作る。array_filter の既定は "0" も捨ててしまうので、空文字だけを捨てる
    $parts = array_values(array_filter(explode("_", $s), fn($p) => $p !== ""));
    $out = "";
    foreach ($parts as $i => $p) {
        // ucfirst は先頭しか変えない。残りを先に小文字にする
        $out .= $i === 0 ? strtolower($p) : ucfirst(strtolower($p));
    }
    echo $out . "\n";
}

動かし方(ファイルを保存したフォルダで)

$ php case_convert.php snake userId

よく見かける書き方で試すと

仕事でよく見かける書き方(標準の関数をそのまま使う形や、よく紹介される正規表現など)を、同じ正解表で動かした結果です。言語を出した答えごとにまとめ、赤い字はその言語が実際に出した間違った行です。

入力よく見かける書き方の答え(10言語)
基本(キャメル → スネーク)snake userId

✓10言語すべて正解

大文字が続く(HTTPServer)snake HTTPServer
  • PHP・Python・C#・Rust・Swifth_t_t_p_server
  • Go・Java・Kotlinhttpserver
  • JavaScript_h_t_t_p_server
  • Ruby✓ 正解
途中で大文字が続くsnake getHTTPResponseCode
  • PHP・Python・JavaScript・C#・Rust・Swiftget_h_t_t_p_response_code
  • Go・Java・Kotlinget_httpresponse_code
  • Ruby✓ 正解
最後が大文字の続き(userID)snake userID
  • PHP・Python・JavaScript・C#・Rust・Swiftuser_i_d
  • Go・Java・Ruby・Kotlin✓ 正解
数字のあとsnake version2Name
  • Java・Kotlinversion2name
  • PHP・Python・Go・JavaScript・C#・Rust・Ruby・Swift✓ 正解
先頭が大文字(パスカルケース)snake UserProfile
  • JavaScript_user_profile
  • PHP・Python・Go・Java・C#・Rust・Ruby・Kotlin・Swift✓ 正解
もうスネークケースsnake already_snake

✓10言語すべて正解

基本(スネーク → キャメル)camel user_id

✓10言語すべて正解

大文字のスネークケース(定数)camel MAX_RETRY_COUNT
  • PHP・C#・KotlinmAXRETRYCOUNT
  • Python・Ruby・SwiftMAXRetryCount
  • Go・RustMAXRETRYCOUNT
  • JavaScriptMAX_RETRY_COUNT
  • Java✓ 正解
先頭と途中の「_」が続くcamel __private__value
  • Python・Go・Rust・Ruby・SwiftPrivateValue
  • JavaScript_Private_Value
  • Javaエラーで止まった
  • PHP・C#・Kotlin✓ 正解
数字の部分camel address_2_line
  • JavaScriptaddress_2Line
  • PHP・Python・Go・Java・C#・Rust・Ruby・Kotlin・Swift✓ 正解
空の文字列camel ""

✓10言語すべて正解

日本語が入っているsnake userなまえ
  • PHP・Python・Go・C#・Rust・Ruby・Kotlin・Swiftuserなまえ
  • JavaScript・Java✓ 正解
ハイフン(ケバブケース)は扱わないcamel user-id
  • PHP・Go・Ruby・Kotlinuser-id
  • Python・JavaScript・Java・C#・Rust・Swift✓ 正解

この書き方の決まり

  • 引数は「変換の向き(snake か camel)」と「変える文字列」
  • snake(userId → user_id): 小文字か数字のあとの大文字(user|Id)と、大文字が続いたあとで次が小文字の大文字(HTTP|Server)を、単語の切れ目にする。切れ目に「_」を入れて全部を小文字にする
  • camel(user_id → userId): 「_」で区切って空の部分は捨て、最初の部分は全部小文字、2つ目からは先頭だけ大文字にしてつなぐ(MAX_RETRY_COUNT → maxRetryCount)
  • 扱うのは半角の英数字と「_」だけ。それ以外が入っていたら NG

解説

大文字が続く名前(HTTPServer)

よく紹介される「大文字の前に _ を入れる」書き方は、HTTPServer を h_t_t_p_server にばらします。逆に「小文字のあとの大文字だけ」で切ると httpserver とくっつきます。この表の10言語のよく見かける書き方のうち、getHTTPResponseCode を正しく変えられたのは Ruby(Rails の underscore と同じ形の2本の置き換え)だけでした。

正しくは、切れ目を2種類に分けて調べます。

  • (?<=[a-z0-9])(?=[A-Z]) … 小文字か数字のあとの大文字の前
  • (?<=[A-Z])(?=[A-Z][a-z]) … 大文字が続いたあと、次が小文字になる大文字の前

Go の正規表現(RE2)には (?<=…)(後読み)と (?=…)(先読み)がありません。Rust には標準の正規表現がありません。この2言語と Swift は、1文字ずつ前後の文字を見て切れ目を決めています。

「先頭だけ大文字」の関数は、残りを小文字にしない

camel 側で MAX_RETRY_COUNT を変えると、関数によって答えが分かれました。

関数RETRY が結果
Python・Ruby の capitalizeRetry(残りを小文字に)正しい形にできる
PHP の ucfirst・ucwords、Kotlin の replaceFirstCharRETRY(残りはそのまま)mAXRETRYCOUNT など
C# の TextInfo.ToTitleCaseRETRY(全部大文字の単語は略語とみなして残す)mAXRETRYCOUNT

小文字にする関数と、動かす場所の言語の設定

Java の toLowerCase() と C# の ToLower() は、動かす場所の言語の設定を使います。トルコ語の設定では I が点のない ı になり、ID が ıd になります。この書き方では Java は toLowerCase(Locale.ROOT)、C# は ToLowerInvariant() を使っています。

「英数字だけか」の確かめ方

\w や「英数字か」を調べる関数が、ひらがなや漢字にも合う言語があります(Python・C# の \w、Rust の is_alphanumeric、Swift の CharacterSet.alphanumerics)。userなまえ を通してしまうので、使ってよい文字を [A-Za-z0-9_] と書いて決めています(入門 第16回 と同じ問題)。

区切ったときの空の部分

__private__value を「_」で区切ると、空の部分ができます。Java の naive は空の部分の先頭を取り出そうとして、例外で止まりました(StringIndexOutOfBoundsException)。空の部分を捨ててから使います。