最近、String#split
と String#chars
まわりの Cop をメンテナンスしたり実装した際の、それらメソッドに関する知見を書き残しておきます。
String#split
の小咄
RuboCop Performance に string.split(/re/)
より string.split('re')
の方が速いので、Cop にどうかという提案があったのがはじまり。
split
の引数が文字列で済む場合は、正規表現ではなく文字列を渡すのは良いとして、正規表現と文字列で振る舞いが異なるものがある。
たとえば split
の引数が空白なしの //
である場合は ''
と結果は同じなので、安全に置換できる。
'a b c'.split(//) #=> ["a", " ", " ", " ", " ", "b", " ", " ", " ", " ", "c"] 'a b c'.split('') #=> ["a", " ", " ", " ", " ", "b", " ", " ", " ", " ", "c"]
しかし split
の引数が空白一つの / /
である場合は ' '
と結果が異なり、戻り値に互換性がなくなる。
'a b c'.split(/ /) #=> ["a", "", "", "", "b", "", "", "", "c"] 'a b c'.split(' ') #=> ["a", "b", "c"]
なお、空白2つ以上は、互換性があるため安全に置換できる。
'a b c'.split(/ /) #=> ["a", "", "b", "", "c"] 'a b c'.split(' ') #=> ["a", "", "b", "", "c"]
このあたり戻り値の違いへの考慮に関して、つい今しがたリリースした RuboCop Performance 1.10.2 で Performance/RedundantSplitRegexpArgument
の既知の誤検知については修正しておいた。
String#chars
の小咄
たとえば split
の引数が空白なしの //
である場合は ''
と結果は同じなので、安全に置換できると前述した続き。Twitter での @onk のツイート。
chars があるのに split(//) を使い続けてしまう気持ちは分かるんだが、後続のために読みやすい方を広めたい
— Takafumi ONAKA (@onk) 2021年3月17日
Ruby 1.9 以前は戻り値に違いがあった。
% ruby -v ruby 1.9.3p551 (2014-11-13 revision 48407) [x86_64-darwin13.0.2] % ruby -e "p 'foo'.split(//)" ["f", "o", "o"] % ruby -e "p 'foo'.chars" #<Enumerator: "foo":chars>
Ruby 2.0 以降は戻り値に差異がなくなったため、String#chars
を使った方が明確な名前になると思う。
% ruby -v ruby 2.0.0p648 (2015-12-16 revision 53162) [x86_64-darwin13.0.2] % ruby -e "p 'foo'.split(//)" ["f", "o", "o"] % ruby -e "p 'foo'.chars" ["f", "o", "o"]
こういった歴史的経緯があるとき、アンラーニングしきれず手癖で split
の方を使ってしまったりするケースもあると思う。
このあたり次の RuboCop のリリース (たぶん 1.12.0) で、Style/StringChars
cop として追加される予定です。
なるほどと思ったので書いてみました。https://t.co/7uBEOQQbY3
— Koichi ITO (@koic) 2021年3月18日