【Ruby】downcaseメソッドに全角英字渡したときの挙動が変わってた【2.4.0〜】

前置き

お師匠「お前が書いたとこのテストコケてんねんけど、どうしてくれんねん」

今日の開発中の出来事。あるissueを消化して次に取り掛かろうとしていた矢先、お師匠ことエンジニアの上司(今日はリモート出社)からslackが飛んできた。嘘ですやん!ワシはちゃんとテスト通るの確認しましたよぅ!万が一にもそんなわけあるわけないわけですわけ(狂乱)!

開発において、テストは仕様書同様の意味を持つ。「そのメソッドが何をもらって何を返すのか」が仕様であり、その仕様通りにメソッドが動いてくれるか確認するのがテストだからだ。ということは、1つのissueをこなすとき、書かれたコードが正しいものかどうかはそのコードのテストが通るかどうかで判断できる。テストがコケているということはすなわち、実装されたロジックに誤りがあるということを意味している。つまるところ、「テストコケてる == 出直せボンクラ」なわけである。ヒエッ。無能エンジニアはテストこかしまくるからツラいです。ロジックくらいちゃんと実装しやがれ(自戒)

とはいえ、ロジックのテスト書いたら(当たり前だけど)それがちゃんと通ることは確認している。というか、今手元で同じテスト実行しても全件ちゃんと通ってる。

お師匠の環境でだけコケてる…?これは一体全体どういうわけなんだ(錯乱)

親方にコード確認させてもらうね(いなり大好きおじさん)

よく分からん状況になったので、まずは当該コードの確認をば。どこでコケてるのかが分かれば何か見えてくるはずだからね、アタリを付けるのは大事だよね。ってなわけで確認したところ、どうやらコケているのは以下の部分のようだ。

text = StringUtil.half_kana_to_full_kana(text)text = StringUtil.half_to_full(text)text.downcase!

ロジックは至極簡単で、

  1. 文字列中の半角カナを全角カナに直す
  2. 文字列中の半角文字を全角文字に直す
  3. 文字列中の大文字英字を小文字英字に直す

というものだ。
上の2行については、それぞれのメソッドのテストが通っているので正しく動いているとわかった。とすると、コケている原因はどうやら3行目、RubyのStringクラスのインスタンスメソッドであるdowncase!にあるようだ。って言ったって、これRubyが用意してるメソッドなんでしょ?どうやったらコケるんだよ…

お師匠「『downcaseは全角文字扱えない』ってなってるけど」

…え?そんなわけ…えっ?!(Rubyリファレンス: downcaseのページへ)
…ん〜、たしかにそう書いてある…。3行のうち2行目までで文字列は全て全角文字になっているはずなので、ここに書いてあることが正しいとすればコケるのもわかる。でも、…ワシの環境だと…

vagrant@localhost:~$ irbirb(main):001:0> 'ABC'.downcase!=> "abc"

やっぱりちゃんと変換できるじゃんアゼルバイジャン。どういうことなの…??
と、ここまできてお師匠が一言。

お師匠「Rubyのバージョンいくつ?」

なるほど、Rubyのバージョンアップで全角文字変換ができるようになった可能性があるわけだ…。
ワシのRubyバージョンは2.4.1。お師匠のは2.2.6。あ〜…これっぽいぞ。
試しにrbenvでRubyのバージョン落として実行してみた。

vagrant@localhost:~$ rbenv versions  system  2.2.3* 2.4.1 (set by /home/vagrant/.rbenv/version)vagrant@localhost:~$ rbenv local 2.2.3vagrant@localhost:~$ rbenv versions  system* 2.2.3 (set by /home/vagrant/.ruby-version)  2.4.1vagrant@localhost:~$ irbirb(main):001:0> 'ABC'.downcase!=> "ABC"

当たり。2.2.6から2.4.1のどこかで、全角文字もdowncaseで変換できるようなアップデートが入っていたようだ。

Ruby 2.4.0 String supports Unicode case mappings

この記事にもあるとおり、Ruby2.4.0のリリースにこのアップデートが含まれていた。
これまでdowncaseメソッドで変換できる文字コードはASCIIに限られていたが、アップデートでunicodeに対応したため全角文字の変換も可能になった、ということらしい。このページによると、downcaseだけでなく小文字->大文字のupcase、小文字<->大文字の相互変換のswapcase、先頭だけ大文字であとは全て小文字に変換するcapitarizeメソッドも全角文字を扱えるようになっているようだ。
Ruby2.5.0リファレンスマニュアルを見たところ、downcaseにオプションを渡すことで変換範囲を変えることも可能らしい。ほえ〜。全然知らなかった。流石にアップデートの詳細まで追いきれないから…(呆れ)まぁ分かったことは、「Ruby2.4.0以降なら全角文字でも標準のメソッドで変換できる」ということ。
production環境はおいそれとバージョン上げられないからちょっと辛いけど、手元しか実行しないコード書いてる場合、もしくはまだ「クラウドサーバー建てては壊し」を繰り返す段階であるなら、Rubyのバージョンは上げといて損はない。

まとめ

Ruby2.4.0以降とそれ以前でdowncaseメソッドの振る舞いが違う

  • ruby ver < 2.4.0 … 全角英字は変換できない。全角英字の部分は元の文字列のまま返ってくる。
  • ruby ver >= 2.4.0 … 全角英字でも変換できる。オプションとして引数に:asciiを渡せば、2.4.0以前の挙動と同じ。
  • 上記の違いはそれぞれupcase、swapcase、capitarizeメソッドにも当てはまる。

バージョンは上げられるなら上げといたほうが幸せになれる

アップデートしてるんだから当たり前っちゃ当たり前だけど、バージョンが上がったもののほうが基本的に改善は進んでいる。あまりにも新しすぎるとバグ取り切れてなくて不安定だったりするが、stableと表記があったらそのバージョンはおおよそ問題なく動くはず。バージョンに縛りがないなら、一番新しいstableなバージョンで開発をすすめると良い。

あとがき

今回も勉強になりましたわァ〜。個人的にはメソッドのアップデート云々よりも、「お師匠でもRubyのバージョンアップデートの内容全部知ってるわけじゃない」ってところが一番の驚き&収穫。いっつもお師匠に言われてるけど、エンジニアリングは受験勉強じゃないからメソッドとか仕様とかいうのは暗記するものじゃないんだってことを再認識したゾ。
「こういうロジックが実現出来るメソッドはどっかで用意されてる」とか「なんか決まった仕様が存在しているらしい」っていうざっくりした知見だけ持っておいて、いざロジックを実装するとなったらお師匠に聞くなりGoogleで調べるなりすればいいのだ(もちろん覚えているなら聞く必要はない)。
ちょっとした考え方の違いだけど、これだけでだいぶエンジニアリングというものへのとっつきやすさが変わってくる気がする。あまり構えずに済むというか。
まぁ話が長くなってしまったが、Rubyがよしなにやってくれるようになったし身構えずもっと気楽にコード書こうやってことですわ(強引なまとめ)

\Twitterも要チェックやで!/