Tech Racho エンジニアの「?」を「!」に。
  • 開発

Rubyスタイルガイドを読む: 数値、文字列、日時(日付・時刻・時間)

こんにちは、hachi8833です。「Rubyスタイルガイドを読む」シリーズもやっと終わりが見えてきました。
まとまりのよさから、今回は「数値、文字列、日付・時間編」といたしました。

Rubyスタイルガイド: 「数値、文字列、日時(日付・時刻・時間)」

数値

8-01【統一】整数値のチェックにはIntegerを使うこと

Use Integer check type of an integer number. Since Fixnum is platform-dependent, checking against it will return different results on 32-bit and 64-bit machines.

Fixnumの結果はプラットフォームに依存するため、32ビット環境と64ビット環境で結果が同じにならないことがあるためです。

Ruby 2.4の整数はIntegerに一元化されましたが、2.3以前のRubyを使う場合は引き続き注意が必要ですね。

timestamp = Time.now.to_i

# 不可
timestamp.is_a? Fixnum
timestamp.is_a? Bignum

# 良好
timestamp.is_a? Integer

8-02【統一】乱数生成では範囲演算子...の利用が望ましい

Prefer to use ranges when generating random numbers instead of integers with offsets, since it clearly states your intentions. Imagine simulating a role of a dice:

以下のサイコロのシミュレーションの例でわかるように、整数値にオフセット値を足すよりも意図が明確になるというのが理由です。

# 不可
rand(6) + 1

# 良好
rand(1..6)

文字列

8-03【統一】文字列の結合は+ではなく、式展開と文字列フォーマットが望ましい

Prefer string interpolation and string formatting instead of string concatenation:

+で結合するのは一見わかりやすいのですが、「Rubyでの文字列出力に「#+」ではなく式展開「#{}」を使うべき理由」で解説したようにレシーバーによって動作が影響されるので思わぬ地雷を踏むかもしれません。

# 不可
email_with_name = user.name + ' <' + user.email + '>'

# 良好
email_with_name = "#{user.name} <#{user.email}>"

# 良好
email_with_name = format('%s <%s>', user.name, user.email)

式展開(interpolation)という訳語はRuby方面でよく使われますが、それにしてもうまい訳ですね。数学方面で「補間」と訳されてしまったせいか補間が使われるのをよく見かけますが、式展開はRubyでの動作に即していて明快だと思います。

Rubyの文字列連結に「#+」ではなく式展開「#{}」を使うべき理由

今回の内容の多くが、上の記事でも言及されています。

8-04【統一】一重引用符(シングルクォート: ')と二重引用符(ダブルクォート: ")の用法をいずれかに統一すること

Adopt a consistent string literal quoting style. There are two popular styles in the Ruby community, both of which are considered good—single quotes by default (Option A) and double quotes by default (Option B).

Rubyコミュニティでは以下の2つのスタイルが見られますが、それぞれに利点があるので、どちらが正しいとか間違っているということではありません。
プロジェクトで変更点が少ない方を採用するとよいでしょう。

8-04a【選択】(オプションA) 一重引用符'を優先するスタイル

式展開を使わない場合と、特殊なシンボル(\t\n'など)の表記には一重引用符を使う
(二重引用符は式展開が必要な場合のみ使うことになる)

# 不可
name = "Bozhidar"

# 良好
name = 'Bozhidar'

8-04b【選択】(オプションB) 二重引用符"を優先するスタイル

二重引用符そのものを含む文字列や、エスケープしたい文字がある場合にのみ、一重引用符を使う

# 不可
name = 'Bozhidar'

# 良好
name = "Bozhidar"

なお、本スタイルガイドではオプションAを採用しているそうです。

8-05【統一】?xのような文字列リテラル構文は使わないこと

Don't use the character literal syntax ?x. Since Ruby 1.9 it's basically redundant—?x would interpreted as 'x' (a string with a single character in it).

?xという文字列リテラルの書き方があること自体初めて知りました。見かけた覚えがありません。

# 不可
char = ?c

# 良好
char = 'c'

8-06【統一】文字列にインスタンス変数やグローバル変数を式展開で挿入する場合に{}を省略しないこと

Don't leave out {} around instance and global variables being interpolated into a string.

{}を省略するとわかりにくくなるからだと思います。
やったことがありませんでしたが、インスタンス変数やグローバル変数は{}なしでも式展開できてしまうんですね。

class Person
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  # 不可 - 間違いではないが見苦しい
  def to_s
    "#@first_name #@last_name"
  end

  # 良好
  def to_s
    "#{@first_name} #{@last_name}"
  end
end

$global = 0
# 不可
puts "$global = #$global"

# 良好
puts "$global = #{$global}"

8-07【ヒント】式展開するオブジェクトでObject#to_sを呼ばないこと

Don't use Object#to_s on interpolated objects. It's invoked on them automatically.

式展開ではObject#to_sが自動で呼ばれるので、明示的に呼ぶ必要はありません。

# 不可
message = "This is the #{result.to_s}."

# 良好
message = "This is the #{result}."

8-08【ヒント】大きなデータの作成にはString#+ではなく、String#<<を使うこと

Avoid using String#+ when you need to construct large data chunks. Instead, use String#<<.
Concatenation mutates the string instance in-place and is always faster than String#+, which creates a bunch of new string objects.

String#<<は文字列のインスタンス自体を変更するので、実行のたびに文字列オブジェクトを作成するString#+よりも速度面で常に有利です。

ループ内で#+を使うのは完全に悪手ですね。

# 不可
html = ''
html += '<h1>Page title</h1>'

paragraphs.each do |paragraph|
  html += "<p>#{paragraph}</p>"
end

# 良好(しかも速い)
html = ''
html << '<h1>Page title</h1>'

paragraphs.each do |paragraph|
  html << "<p>#{paragraph}</p>"
end

8-09【統一】【ヒント】文字列置き換えや削除は可能な限りString#gsub以外のメソッドを使うこと

Don't use String#gsub in scenarios in which you can use a faster more specialized alternative.

Writing Fast Rubyというスライドに、#gsubがパフォーマンス上不利であることが記載されており、これに基づいているようです。

#gsubは正規表現が必要な場合にのみとどめ、#trなどの適切かつ高速なメソッドを使いましょう。

url = 'http://example.com'
str = 'lisp-case-rules'

# 不可
url.gsub('http://', 'https://')
str.gsub('-', '_')

# 良好
url.sub('http://', 'https://')
str.tr('-', '_')

8-10【ヒント】複数行に渡る文字列をヒアドキュメントで扱う場合は、各行冒頭のスペース文字も読み込まれていることに注意する

When using heredocs for multi-line strings keep in mind the fact that they preserve leading whitespace. It's a good practice to employ some margin based on which to trim the excessive whitespace.

以下はgsub(/^\s+\|/, '')を使って、|とその前のリーディングスペース文字を削除しています。

code = <<-END.gsub(/^\s+\|/, '')
  |def test
  |  some_method
  |  other_method
  |end
END
# => "def test\n  some_method\n  other_method\nend\n"

ヒアドキュメントに余分なスペースを含めたくない場合は検討するとよさそうです。

8-11【統一】ヒアドキュメントで複数行のインデントを保つにはRuby 2.3の「<<~終了文字列」記法を使うこと

Use Ruby 2.3's squiggly heredocs for nicely indented multi-line strings.

最初の2つの不可例は従来の手法です。いろいろと涙ぐましいことしていたんですね。

# 不可 - PowerpackのString#strip_marginによる手法
code = <<-END.strip_margin('|')
  |def test
  |  some_method
  |  other_method
  |end
END

# 同じく不可
code = <<-END
def test
  some_method
  other_method
end
END

# 良好
code = <<~END
  def test
    some_method
    other_method
  end
END

<<~終了文字列記法は通常のコードインデントとの整合性が取れているのがありがたいです。

日付・時間

8-12【統一】システム時間の取得はTime.nowが望ましい

  • Prefer Time.now over Time.new when retrieving the current system time.

RubyリファレンスマニュアルのTimeではどちらも動作が同じです。

その時点の現在時刻を取得するという意図を明確にするためにTime.nowを使う、と理解しました。

#newだと単なるTimeオブジェクトの生成と思われそうです。それが必要な場合もあると思うので、意図に応じて使い分けましょう。nowとnewはぱっと見に似てるといえば似ていますが。

補足: Railsでの現在時刻のとり方は異なる

上のTime.nowを使うルールはRubyのみの場合です。

Railsでは、ActiveSupportで用意されている、よりtimezoneコンシャスなTimeWithZoneを使うために以下のいずれかを使うのが定番です。これらを使うと、実際にはTimeWithZoneオブジェクトが使われます。

逆に、RailsでRubyのTime#nowDateTime#nowを使うと、せっかくRailsに用意されているTimeWithZoneが使われないので、避けましょう。

特にRubyのDateTimeについては、次の項目でもみだりに使うべきでないというスタイルになっています。

参考: RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い -- Rail 4の日付・時刻の扱いを中心とする良記事です。特に環境変数とRails設定でのタイムゾーンの注意点について非常に参考になりました。ありがとうございます!

8-13【統一】DateTimeはカレンダーを過去に遡って再編成する必要がない限り使わないこと

Don't use DateTime unless you need to account for historical calendar reform -- and if you do, explicitly specify the start argument to clearly state your intentions.

DateTimeを使いたい場合はstart引数で意図を明確にすること、だそうです。

これについても上記記事が参考になりました。RubyのDateTimeは暦法を変える場合に使うのが適切であるようです。

# 不可 - 現在時刻の取得にDateTimeを使っている
DateTime.now

# 良好 - 現在時刻の取得にTimeを使っている
Time.now

# 不可 - 現代の日付取得にDateTimeを使っている
DateTime.iso8601('2016-06-29')

# 良好 - 現代の日付取得にDateを使っている
Date.iso8601('2016-06-29')

# 良好 - 歴史的な過去のカレンダーにDateTimeとstart引数を使っている
DateTime.iso8601('1751-04-23', Date::ENGLAND)

今回はここまでです。最終回「正規表現、%リテラル、メタプログラミング」編にご期待ください。

関連記事



CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。