こんにちは、hachi8833です。「Rubyスタイルガイドを読む」シリーズもやっと終わりが見えてきました。
まとまりのよさから、今回は「数値、文字列、日付・時間編」といたしました。
- Rubyスタイルガイドを読む: 総もくじ
- 前回: Rubyスタイルガイドを読む: コレクション(Array、Hash、Setなど)
- 次回: Rubyスタイルガイドを読む(最終回): 正規表現、%リテラル、メタプログラミング
Rubyスタイルガイド: 「数値、文字列、日時(日付・時刻・時間)」
数値
8-01【統一】整数値のチェックにはInteger
を使うこと
Use
Integer
check type of an integer number. SinceFixnum
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での動作に即していて明快だと思います。
今回の内容の多くが、上の記事でも言及されています。
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, useString#<<
.
Concatenation mutates the string instance in-place and is always faster thanString#+
, 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
overTime.new
when retrieving the current system time.
RubyリファレンスマニュアルのTimeではどちらも動作が同じです。
その時点の現在時刻を取得するという意図を明確にするためにTime.now
を使う、と理解しました。
#new
だと単なるTimeオブジェクトの生成と思われそうです。それが必要な場合もあると思うので、意図に応じて使い分けましょう。nowとnewはぱっと見に似てるといえば似ていますが。
補足: Railsでの現在時刻のとり方は異なる
上のTime.now
を使うルールはRubyのみの場合です。
Railsでは、ActiveSupportで用意されている、よりtimezoneコンシャスなTimeWithZoneを使うために以下のいずれかを使うのが定番です。これらを使うと、実際にはTimeWithZoneオブジェクトが使われます。
Time::zone#now
Time::current#now
--Time.zone.now
を返すので上と同じ
逆に、RailsでRubyのTime#nowやDateTime#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 thestart
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)
今回はここまでです。最終回「正規表現、%リテラル、メタプログラミング」編にご期待ください。
関連記事
- Rubyスタイルガイドを読む: ソースコードレイアウト(1)エンコード、クラス定義、スペース
- Rubyスタイルガイドを読む: ソースコードレイアウト(2)インデント、記号
- Rubyスタイルガイドを読む: 文法(1)メソッド定義、引数、多重代入
- Rubyスタイルガイドを読む: 文法(2)アンダースコア、多重代入、三項演算子、if/unless
- Rubyスタイルガイドを読む: 文法(3)演算子とif/unless
- Rubyスタイルガイドを読む: 文法(4)ループ
- Rubyスタイルガイドを読む: 文法(5)ブロック、proc
- Rubyスタイルガイドを読む: 文法(6)演算子など
- Rubyスタイルガイドを読む: 文法(7)lambda、標準入出力など
- Rubyスタイルガイドを読む: 文法(8)配列や論理値など
- Rubyスタイルガイドを読む: 命名
- Rubyスタイルガイドを読む: コメント、アノテーション、マジックコメント
- Rubyスタイルガイドを読む: クラスとモジュール(1)構造
- Rubyスタイルガイドを読む: クラスとモジュール(2)クラス設計・アクセサ・ダックタイピングなど
- Rubyスタイルガイドを読む: クラスとモジュール(3)クラスメソッド、スコープ、エイリアスなど
- Rubyスタイルガイドを読む: 例外処理
- Rubyスタイルガイドを読む: コレクション(Array、Hash、Setなど)
- Rubyスタイルガイドを読む: 数値、文字列、日時(日付・時刻・時間)
- Rubyスタイルガイドを読む: 正規表現、%リテラル、メタプログラミング(最終回)