関連記事
- 『Effective Ruby』を読んだのでまとめました - 第1章 Rubyに体を慣らす -
- 『Effective Ruby』を読んだのでまとめました - 第2章 クラス、オブジェクト、モジュール - 前編
- 『Effective Ruby』を読んだのでまとめました - 第2章 クラス、オブジェクト、モジュール - 後編
Ruby界で評価の高い『Effective Ruby』ですが、内容が込み入ってる上に海外書籍独自の言い回しがあり、初心者の頃に読んだらあまり頭に入って来ませんでした。
そこで、少し経験を積んだ今、リベンジしようと思い読み返してみました。
この本は、全8章48項目という構成で、ボリュームも多く、更にアウトプットする際にソースコードも少し変更しなければなりません。アウトプットに時間がかかりそうなのですが、少しずつまとめ記事にしていこうと思います。
では以下1章のアウトプットです!
第1章 Rubyに体を慣らす
項目1 Rubyは何を真と考えているかを正確に理解しよう
Rubyでは、falseとnilを除く全ての値が真となっています。
他の多くの言語では、ゼロは偽として扱われているのに対し、Rubyは真として扱います。
falseとnilは偽というルールなのですが、Rubyを使っているとfalseとnilに関しても区別しなければならない場合があります。
そんな時はnil?
メソッドを使いfalseがnilかの区別しましょう。
irb(main):005:0> a = nil
=> nil
irb(main):006:0> a.nil?
=> true
以下のように左の被演算子をfalseとする「==」を使って、falseかどうかで区別する方法もあります。
irb(main):010:0> activated = false
=> false
irb(main):011:0> false == activated
=> true
irb(main):012:0>
何故**「左」**に限定しているかというと「==」演算子は、左被演算子から呼び出されるからです。
左被演算子が自作したオブジェクトの場合、「==」演算子をオーバーライドして比較基準を変えてしまう可能性があります。
例えば、以下のコードの場合、Activation.new ==
は必ずtrueを返してしまいますね。
class Activaton
def == (params)
true
end
end
falseを左被演算子にしておけば、FalseClass#==
となり、右被演算子もfalseオブジェクトでなければtrueを返さないので、falseを左に置いて比較する事を心がけた方が安心です、
項目2 オブジェクトを扱うときにはnilかもしれないということを忘れないようにしよう
Rubyを扱う際は、**「オブジェクトが実際にはnilかもしれない」**という前提でコードを書く事が必要です。
Rubyでコードを書いているとNoMethodErrorをよく目にしますが、このエラーは、nilオブジェクトからメソッド呼び出しした場合に発生します。このNoMethodErrorを防ぐ手っ取り早い方法は、項目1でも使用したnil?
メソッドを使用する方法です。nilかどうかを確かめて、エラーが発生しないようにコードを書きましょう。
def name
return if @person.nil? #nilの場合はreturnするなどして、@person.nameでNoMethodErrorが発生しないようにします
@person.name
end
to_メソッドを使用して目的の型に明示的に変換する事も有効で、例えば文字列を取得するのが目的の場面でオブジェクト.to_s
と明示的に文字列変換しておけば、オブジェクトがnilでもString型の空文字を返してくます。
状況によって、Stringかnilが返って来るみたいな状況がなくなり、Stringに統一する事が出来ます。
#nameがnilでもnilを返さずにString型の''(空文字)を返す
@person.name.to_s
配列に関しては、Array#compactメソッドが自身からnilを取り除いた配列を生成して返すので便利です。
irb(main):003:0> ['a', nil, 'c', nil, 'e'].compact
=> ["a", "c", "e"]
項目3 Rubyの暗号めいたPerl風機能を避けよう
Rubyは元々Perlの影響を受けており、Perl風の機能が取り入れられています。
しかし、Rubyが成熟するにつれて代わりの方法が追加され、さらに時間がたてばPerlから受け継いだ機能は使わない方がよくなったり、完全に取り除かれる可能性があります。
Perl風機能で出くわす可能性が高いのは暗号めいたグローバル変数です。
例えばString#=~
は文字列に対して正規表現のパターンマッチを行い、成功した時は$&
、$1
、$2
などに値がセットされます。
if '123<abcde>456' =~ /<(\w+)>/
p $1
end
上の例では正規表現が最後にマッチした中で、1番目の括弧にマッチした文字列を$1
に格納しており、それを出力しています。
ここで注意が必要なのは、=~
で作成された$1
はグローバル変数に見えるのですが、実際には現在のスレッドとメソッド内でしか使えないローカル変数のような振る舞いをします。これを特殊グローバル変数と呼びます。
=~
を使うのであれば、String#match
を使う方がはるかにRubyらしい上に、わかりにくい特殊グローバル変数も使わなくて済むので、こちらを使って書いた方が良いでしょう。
irb(main):011:0> neko = '我輩は猫である。名前はまだない。'
=> "我輩は猫である。名前はまだない。"
irb(main):012:0> neko.match(/猫である/)
=> #<MatchData "猫である">
その他にも$:
もよく見るPerl風機能で、これはrequireメソッドでロードされるライブラリを探す時に参照するディレクトリを表す文字列配列です。
しかし、こちらも$:
を使わずに$LOAD_PATH
を使用した方がわかりやすいです。
項目4 定数がミュータブルなことに注意しよう
Rubyでは定数がミュータブル(変更可能)なため、定数なのに変更出来るの?と思うかもしれません。
*注(『Effective Ruby』では定数がミュータブルという表現になっていますが、定数が指すオブジェクトがミュータブルです。
定数自体にはミュータブルもイミュータブルもありません。)
以下のコードで引数に何も与えずaddを呼び出すとYokohamaが追加されてしまいます。
CITY = %w[Saitama Shibuya Asakusa]
def add(city=CITY)
CITY << 'Yokohama'
end
イミュータブル(変更不可能)にするのであれば定数をfreezeしましょう。
CITY = %w[Saitama Shibuya Asakusa].freeze
ただ単にfreezeするだけでは不十分な場合もあります。
例えば以下のメソッドを追加して
def mokumoku(extension, city=CITY)
city.map { |city| city << ".#{extension}"}
end
mokumoku('rb')を呼び出すと
["Saitama.rb", "Shibuya.rb", "Asakusa.rb"]
定数の要素に.rb
が追加されてしまいました。
このように定数をコレクションにして参照する場合は、以下のようにコレクションもフリーズしてあげなければなりません。
CITY = %w[Saitama Shibuya Asakusa].map!(&:freeze).freeze
「定数を再定義する場合」を考慮するのであれば、これでもまだ不十分なんです。
以下のようにfreezeしていても定数を再定義する場合は、上書き出来てしまいます。
module City
CITY = %w[Saitama Shibuya Asakusa].freeze
end
p City::CITY = %w[Yokohama]
結果
city.rb:16: warning: already initialized constant City::CITY
city.rb:10: warning: previous definition of CITY was here
["Yokohama"]
警告は出るものの上書き出来てしまうので、上書き出来ないようにするには、moduleをfreezeしましょう。
City.freeze
「定数を再定義する場合」を考慮するのであれば、定数は常にmodule内で定義し、moduleをfreezeして禁止して置く方が良いと言えます。
項目5 実行時の警告に注意しよう
コンパイル時の警告に注意をはらう事で、エラーの原因になり得るコードを発見することが出来ます。
コンパイル時の警告を有効にするには、ruby -w ファイル名
のように-w
オプションをつけて実行します。
Rubyがプログラマーの意図を解釈してくれているのでバグにならないケースがあるのですが、そういったRubyにプログラマーの意図を推測させる書き方は避けるべきです。例として以下のコードを見て下さい。
p '090-xxxx-xxxx'.split /-/
実行すると電話番号を-で区切ってくれるコードです。
ruby phone.rb #まずは普通に実行してみる
["090", "xxxx", "xxxx"]
これを-w付きで実行すると
ruby -w phone.rb
phone.rb:1: warning: ambiguous first argument; put parentheses or a space even after `/' operator
["090", "xxxx", "xxxx"]
警告が出ました。これはRubyがコードを先頭から読む際、最初の/に到達した時点で、正規表現の/とも割り算の/とも取れる場合に出る警告です。
Rubyはプログラマーの意図を推測して、無事正規表現として処理してくれたわけですが、このような書き方はバグに繋がります。
今後Rubyのバージョンが上がり、コンパイル時の警告に厳密な仕様になったら、エラーを吐くようになるかもしれないので日頃から注意しましょう。
ちなみに今回のコードはカッコを使って曖昧さを除く事が出来ます。
p '090-xxxx-xxxx'.split (/-/)
今回まとめ
『Effective Ruby』を読み返してみて、最初に読んだ時より理解度が向上して読みやすくなっている気がしました。その点は少しだけですが成長を感じられて良かったです。
ただ、アウトプットするとなると結構大変ですね。しかも今回のまとめって48項目中の5項目ですから、まだ先が長い。
なんとか頑張って全項目アウトプットしたい。