Effective Ruby を読んだメモをこっそり。メモなのでてきとうなことを言っている場合があります。
翔泳社
売り上げランキング: 114,152
感想
良い本だった。知ってることもそりゃあったけれど、それも含めて勉強になった。 ただ、MiniTest と ri, gem, bundler の章はツールの使い方って感じでちょっと読み飛ばしてしまったかな。他の章はよかった。
警告
ruby -w
で警告を出せるが、rspec などのように ruby コマンドにオプションを渡せないことがある。そういう場合は RUBYOPT
環境変数を使える。
RUBYOPT=-w
グローバル変数 $VERVOSE
を true
に設定しても警告を有効にできる。false
だと警告が減り、nil
だと消える。 しかし、こちらでは $VERBOSE
の設定が解釈される前の、コンパイル時の警告が出ない。
コンパイル時警告だけを出したい場合は、RUBYOPT=-w
かつ $VERBOSE=nil
として、実行時計画だけを出したい場合は $VERBOSE=true
とし、両方出したい場合は RUBYOPT=-w
とすればよい
※ しばらく、.bashrc に export RUBYOPT=-w
と書いておいて警告をつぶしまくる業をしたくなったが、警告出まくって辛い
定数
Ruby の定数は変更可能
argv = ARGV
のように小文字の変数に代人してしまえば、argv[0] = 'foo'
のように警告さえもなしに変更可能
clone と dup
clone は freeze 状態と特異メソッドもコピー。dup はやらない
Hash の初期値
Hash.new({})
と書いた場合、Hash の要素は全て同じ object_id を持つ {}
で初期化される。これは使い物にならない。
Hash.new { {} }
と書けば、要素にアクセスした時に始めてブロックが評価されるので、別の object_id を持つ {}
で初期化される。
fields = {}
chunk.msgpack_each do |tag, time, record|
channel = build_channel(record)
fields[channel] ||= {}
fields[channel][tag] = build_message(record)
end
みたいに書いていたコードは
fields = Hash.new {|hash, key| hash[key] = {} }
chunk.msgpack_each do |tag, time, record|
channel = build_channel(record)
fields[channel][tag] = build_message(record)
end
と書けるようだ。ただし、fields[key]
がデフォルトで {}
になってしまうので、if fields[key]
みたいなものは書けなくなる。if fields.has_key?(key)
ならイケるようだ。
※ しかし、このコードは他の人に伝わるのかどうか微妙だ
rescue の中での例外
rescue の中でまた例外が起きる可能性も考えてコードを書く
その際、オリジナル例外情報が消えないように、例外インスタンスにオリジナルの例外も含めるように、と書いてあるが、Ruby 2.1 以降なら e.cause
もあるのでそれを使っても良いのでは、と思った。
引数のデフォルト値
def_instance_delegator
の所で変なところに食いついてしまった。シグネチャが
def def_instance_delegator(accessor, method, ali = method)
のようになっていて、第三引数のデフォルト値を、第二引数の値に設定していた。そんなことできたのか。
method_missing ではなく define_method を使おう
これは自分もやっている。method_missing でメソッドを定義したつもりになっていると、respond_to?
が true を返してくれないし、そもそも全部 hook しはじめるので遅いし、メソッド typo してもわからなくなる。
respond_to?
の代わりに respond_to_missing?
というメソッドで調べることができるようだ。
instance_eval と class_eval
再整理
instance_eval
でメソッド定義したら、特異メソッド。 Foo.instance_eval
はクラスの特異メソッドなので、クラスメソッドになる。 インスタンスメソッド定義用に用意されたのがFoo.class_eval
。クラスでしか使えない。
効率的なテスト
- ファズテスト (クラッシュしないかどうかのテスト)
- FuzzBert gem
- 数日流す
- プロパティテスト (仕様にそったランダムテスト)
- MrProperty gem
- XML Schema とか JSON Schema で入力データのスキーマを定義して、境界値テストするみたいなやつ
ri
ri Array
モンキーパッチあてているやつのドキュメントも見れるとのこと => あれ、Hash#except とかでてこないけどな
ri File::open
ri Array#pop
ri pop
ri bundler:README.md
irb
irb> irb 'foo'
irb#1(foo)> size
のようにしてそのオブジェクトのセッションを開ける (pry みたいなことが irb だけでもできたのか)
jobs
, fg
, kill
でセッションを扱う
gem の上限指定
最新バージョンで動く保証がないのだから、自作 gem が依存している gem のバージョンは上限指定をしよう、と言っているが、反対したい。
gem.add_dependency 'fluentd', '>= 0.10.56', '< 0.12.0'
# gem.add_dependency 'fluentd', '~ 0.10.56'
のように書いてしまうと、v0.12 でも動くものが動かなくなってしまう。
gem.add_dependency 'fluentd'
とだけ書いてあれば、仮に v0.12 で動かないにしても、ユーザ側で Gemfile.lock をいじって回避しようがあるが、 gemspec でバージョン指定されてしまうと、ユーザの手で回避しようがなくなる。
「最新バージョンで動く保証がない」とか言ってないで「最新で動かないことがわかったら速やかに修正します」という姿勢をもったほうが良いと思ってる。
GC
近所の Ruby コミッタ氏に以前聞いたことが書いてあった。
- Ruby はメモリプールを確保しているので、OSにメモリを返されないことがある
- プール > ページ > スロット
- ページが全部解放されない限り、OS にメモリを返却しない. ps や top でみたメモリ使用量が減らない
- 2.1 では 1ページ 408 スロット
詰め詰め処理して、できるだけ空きページ作るような処理はやらないんだっけ?Java とかはやってた気がするが。
GC.stat の heap_length がページ数. その他 => https://gist.github.com/sonots/71277ef3f9b53fa87862
GC のタイミング
malloc_increase が malloc_limit を超えるとマイナーGC、olmalloc_increase が oldmalloc_limit を超えるとメジャーGC が走る。
- malloc_limit: これを越えて malloc すると GC が発生する閾値
- malloc_increase: GC 発生までに malloc/realloc したバイト総数 (GC 起動でリセット)
(1..1000).each do |i|
a = [i] * 10000
s = GC.stat
puts "#{s[:malloc_limit]} #{s[:malloc_increase]}"
end
超えると Minor GC が走ってその度に malloc_limit が増えていることが確認できた。
16777216 16765560
23584579 672
...
23584579 23562384
33100043 672
...
33100043 33067888
33554432 672
...
33554432 672
malloc_limit が増え続けて、ヒープサイズも大きくなるかと思っていたが、33554432 よりは増えなくなった。はて。=> RUBY_GC_MALLOC_LIMIT_MAX (32MB) にあたったからだ。けっこう小さいような気もするし、1回のGCと考えれば大きいような気もするし、チューニング難しいな。
ObjectSpace.define_finalizer
GC発動時にリソース解放処理をさせる。そんなことができるのか。ただし、けっこう嵌りそう
freeze
"foo".freeze は定数と同じ扱いになる
"foo" でオブジェクトを生成してから freeze しそうに見えるが、そうはならないようだ。以前 f リテラルを導入するかどうか議論していたのはこの辺の挙動が不思議に見えるからかな、と邪推
Q. いつ、GC されるんだ?されなくなる? Symbol と同じ扱い?であれば、Symbol GC と同様のタイミングで解放される?
ループの中でオブジェクトリテラルを避けよう
(1..100).each do |i|
%w[a b c].include?('a')
end
とかやると %w[a b c] の object が作られては GC されるので無駄処理になる。あらかじめローカル変数に保存しておくべき。これは意識してるな。
メモ化
複数行にわたるときは
@a ||= begin
end
と書ける。今まで
return @a if @a
...
...
@a = ...
と書いてたな。 ナルホド