雀巽の日記帳

雀巽が綴る日常の記録

Effective Rubyを読んだので程々に感想を書いてく

Effective Ruby をやっと読みました!

Effective Ruby

Effective Ruby

薄いと思って油断したら案外重かった……!

ざっくりした感想として、Ruby がそこそこ使えるようになってきた人が読むと非常に勉強になる本、だと思いました。 読んでて「へぇ!そうなんだ!」とか「おお!やってみよう!」とか感じることが多く、面白かったです。

ただ、案外重くて、寝る前に読んでたらスヤスヤと爆睡したりもしてました。
会社で「睡眠に Effective な本なんやな」とか言われました。

素晴らしい本です。

本題

さて、実はタイトルをこちらの記事からパクリました。

web-salad.hateblo.jp

いつも隣でワイワイ喋りながら働いてる同僚の記事です!

ここまでたくさん感想を書く気力がなかったので、程々に感想を書いていこうと思います。

建前としては「本を読むべき!素晴らしい!」で、本音としては「詳細は本を読んでくれ!書くのダルい!」です。

ただ、人に教えれるレベルの理解度というのは非常に高い状態だった気がするので、やはり1度自分の言葉としてアウトプットするのは非常に重要だと思います。

というわけで、ちょっと感想書きます。
折角なので先ほどの記事で触れられてない項目を選びます!

項目6 Ruby が継承階層をどのように組み立てているかを頭に入れよう

クラスメソッドはそのクラス(クラスもオブジェクト)に対する特異メソッドとして定義されているというのは有名ですが、具体的にどこに定義されてるのかは読むまで謎でした。というか、特に考えたことがなかったです。

どうやら Ruby では、特異メソッドを作る際、暗黙的に特異クラスが作成され、それが継承階層の真上に挿入され、その特異クラスのメソッドとして定義されるそうです。 全く知りませんでしたが、どうやらクラスメソッド(クラスの特異メソッド)は実はこっそり作成された特異クラスに定義され、そしてそれをそのクラスはこっそり継承しているということです。

また、インクルード時も特異クラスが暗黙的に作られ、自分の継承関係の上に挿入されます。 なんと、モジュールのインクールドも裏では継承だったのです!なんてこった!!

さて、言葉だとわかりにくいのでプログラムを書きます。

module Foo
  :
end

class Bar
  :
end

class Foobar < Bar
  include Foo

  def self.foobar_self
    :
  end

  def foobar
    :
  end
end

雑にクラスとモジュールを定義してみました。
それでは継承関係を覗いていきます。

# まずは普通に Foobar クラスのインスタンスメソッドを見てみます
>> Foobar.instance_methods(false)
=> [:foobar] # インスタンスメソッドが正しく返ってきました

# 次に、Foobar クラスの特異クラスのインスタンスメソッドを見てみます
>> Foobar.singleton_class.instance_methods(false)
=> [:foobar_self] # ホントに特異クラスに定義されてました

# Foobar の親クラスを覗いてみます
>> Foobar.superclass
=> Bar # 普通に親クラスが返ってきました

# 次に継承階層を出力してみます
>> Foobar.ancestors
=> [Foobar, Foo, Bar, Object, Kernel, BasicObject] # インクルードしてるモジュールが出てきた!

とまぁ、ざっくりこんな感じです。

複数モジュールがインクルードされた時の継承関係も一応見ておきましょう。

class Foobar < Bar
  include Moge
  include Foo

  def self.foobar_self
    :
  end

  def foobar
   :
  end
end

このように2つインクルードすると、

>> Foobar.ancestors
=> [Foobar, Foo, Moge, Bar, Object, Kernel, BasicObject]

先にインクルードされてる方がより遠い祖先になりました。
単に「自分の直前に挿入する」ということをシンプルにしているということですね。

全体的に目から鱗

項目15 クラス変数よりもクラスインスタンス変数を使うようにしよう

クラス変数はホントにそのクラスがもつスタティックな値で、完全に一意に定まります。

例えそのクラスを継承してるオブジェクトからであろうが、それは同じ変数を参照することになります。

プログラムを書いてみます。

class Bar
  @@bar = "bar"

  def self.bar=(bar)
    @@bar = bar
  end

  def self.bar
    @@bar
  end
end

class Foobar < Bar
end

class Hoobar < Bar
end

さて、それでは確かめてみます。

# 当然どちらにも同じ値が入っています
>> Foobar.bar
=> "bar"
>> Hoobar.bar
=> "bar"

# それでは片方書き換えてみます
>> Foobar.bar = "foo"
=> "foo"

>> Foobar.bar
=> "foo"
>> Hoobar.bar
=> "foo" # こちらも変わってしまいました!

と、いった感じです。

こういう動作を意図している場合は問題ないのですが、基本的に変数スコープがデカくて嬉しいことはあまりないので、避けたいです。 それと、こういう場面では継承したクラスごとにそれぞれ別の値を持たせたいことも多い気がします。

そんな特は、クラスインスタンス変数を使いましょう。

クラスもオブジェクトなので、当然インスタンス変数を持っています。
早速使ってみましょう。

class Bar
  @bar = "bar"

  def self.bar=(bar)
    @bar = bar
  end

  def self.bar
    @bar
  end
end

class Foobar < Bar
end

class Hoobar < Bar
end
>> Foobar.bar
=> nil
>> Hoobar.bar
=> nil

およ?どちらからも見れなくなりました。 それもそのはずです、両クラスともに別のオブジェクトであるため、インスタンス変数は共有しておりません。

これで終わるのも気持ち悪いので、ちょっと書きなおしてみます。

class Bar
  def self.bar=(bar)
    @bar = bar
  end

  def self.bar
    @bar
  end
end

class Foobar < Bar
  @bar = "foobar"
end

class Hoobar < Bar
  @bar = "hoobar"
end

さて、動かしてみましょう。

>> Foobar.bar
=> "foobar"
>> Hoobar.bar
=> "hoobar"

意図した通りの動作になっていることが保証出来ました。

また、少し違う話ですが、今回の設定方法はクラス定義中にベタで書いてあって気持ち悪いですね。 このやり方での初期値の設定は事故の元に見えます。

ちょっとかっこよく設定できるようにしてみます。

# 設定項目を保持するクラス
class Configuration
  attr_accessor :bar
end

class Bar
  # 継承先で呼び出してもらう設定クラス
  def self.configure
    configuration = Configuration.new
    yield configuration
    @config = configuration.bar
    @bar = @config.bar
  end

  def self.bar=(bar)
    @bar = bar
  end

  def self.bar
    @bar
  end
end

class Foobar < Bar
  # 初期化
  configure do |config|
    config.bar = "foobar"
  end
end

class Hoobar < Bar
  # 初期化
  configure do |config|
    config.bar = "hoobar"
  end
end

こうすると、ちょっとかっこよく各クラスの初期値を渡すことが出来ます。

# ちょっとカッコ良い!
configure do |config|
  config.foo = "foo"
  config.bar = "bar"
end

閑話休題

基本的にはクラスインスタンス変数を使うのが吉だと思います。

第8章 メモリ管理とパフォーマンス

これだけ章でとりあげました。

こういう話が載ってるの、凄く良いなぁと思ったのでざっくり章でとりあげました。 ガベージコレクタのチューニングや、プロファイルを取る方法などについて乗っています。

例えば「項目46 Ruby プロファイリングツールを使おう」では、

  • プロファイリングツールを使ってからパフォーマンス・チューニングをしましょう
    • Ruby 2.1 以前 ruby-prof gem
    • Ruby 2.1 以降 stackprof + memory_profiler

といったことが書いてあります。

また、これは Effective Ruby とは関係ありませんが、Ruby のガベージコレクタの仕組みについては以下の記事*1が図もあり、非常にわかりやすかったです。

RubyPython のガベージコレクタの違いについて書いてあります。

この章とは特に関係ないですが、パフォーマンス関係で思い出した記事をもう一つ紹介します。

Ruby による並列・並行プログラミングに関する記事です。

www.toptal.com

どちらも英語ですが、読んでて非常に面白かったのでオススメです!

追記

POSTD に Visualizing Garbage Collection in Ruby and Python の日本語訳が掲載されていました!

まとめ

非常に良い本だと思いますので、1度目を通してみることをオススメします!

次の本

次に読みたいなと思ってる Ruby 関係の本はこちらです。
もっと重厚です。ヤバイです。会社で買ってもらいました。

リファクタリング:Rubyエディション

リファクタリング:Rubyエディション

Ruby 以外の本で直近読もうと思ってる本をついでに紹介しておくと、以下の2冊です。

一億人の英文法 ――すべての日本人に贈る「話すため」の英文法(東進ブックス)

一億人の英文法 ――すべての日本人に贈る「話すため」の英文法(東進ブックス)

マスタリングTCP/IP 入門編 第5版

マスタリングTCP/IP 入門編 第5版

そして読書会でちょこちょこ読み進めてる Haskell 並列並行本、はじパタ……いっぱいある!

キャパオーバーで死なないように、うまい感じにそれぞれを読み進めていきます。

とりあえずは英語の本とマスタリング TCP/IP 入門編を読んでみまーす!

月曜日

今日は早寝するって決めたのに……!

*1:追記:日本語訳がPOSTDに掲載されていました!