なんじゃくにっき

プログラミングの話題中心。

Rubyでhot reloadしてみる

これはなんだ

Rubyでhot reloadするのを簡潔に書いてみるサンプルです。

hot reloadとは

Rubyをはじめ殆どのプログラミング言語では、通常はプログラムを実行すると(もしくは最初にソース/バイナリが読み込まれたとき以降)、実行中はソースコードの反映は行われません。

それを実行中にソースコードの反映を行うのがhot reloadです。 主に開発環境で使用され、起動に時間のかかるプログラムの開発等に恩恵があります。

実装してみる

ソースは

github.com

1. hot reloadがない状態

# a.rb
class A
  def self.say_hello
    puts 'Hello, World'
  end
end
# main.rb
loop do
  A.say_hello

  sleep 3
end

main.rbを実行すると3秒おきに標準出力に 'Hello, World'と出力される簡単なプログラムです。

2. 変更を反映させるようにする

# main.rb
loop do
  load File.expand_path(File.dirname(__FILE__) + '/a.rb')

  A.say_hello

  sleep 3
end

ループ毎にソースコードをloadするようにしてみました。 これでa.rbのソースコードを実行中に

# a.rb
class A
  def self.say_hello
    puts 'こんにちは、世界!'
  end
end

のように書き換えると途中から出力が変わります。 簡単ですね。

ちなみにloadの代わりにrequireを使うと期待通りには動きません。

https://docs.ruby-lang.org/ja/latest/method/Kernel/m/require.html

Ruby ライブラリ feature をロードします。拡張子補完を行い、同じファイルの複数回ロードはしません。

3. 問題点の解消

さて、前節の内容で上手く行ったように見えますが、実は問題があります。 一度定義したメソッドなどが消えないのです。

前節で行った内容の範囲内ではなぜうまく行くかと言うと、 Rubyオープンクラスで、既にClass, Moduleが定義されていても さらにそのClassにModuleを開いて定義しなおすことができます。 既にあるメソッドと同名のメソッドを書くと上書きが行われます。

メソッドの追加、更新の場合はそれでもうごくのですが、削除はその仕組みではうまくいきません。

なのでこうします

# main.rb
loop do
  if Module.const_defined?('A')
    Object.send(:remove_const, 'A')
  end

  load File.expand_path(File.dirname(__FILE__) + '/a.rb')

  A.say_hello

  sleep 3
end

remove_constを使うと既に定義されている定数、Module, Classを削除できます(厳密にはそうではない、ですがここでは詳しい話は省きます)

これでざっくりとhot reloadする簡単なコードができました。