6.再定義と別名定義
1.再定義
再定義とは、文字通り再び定義することです。
対象がクラスの場合は処理の追加を行い、メソッドの場合は上書きを行います。
class Hoge
  def hoge
    "hoge!"
  end
  def piyo
    "piyo!"
  end
end
p Hoge.new.hoge  # => "hoge!"

class Hoge
  def hoge
    # hoge を上書き (古い hoge は消える)
    "hoge?"
  end
  # piyo が消えたわけではない
end
p Hoge.new.hoge  # => "hoge?"
p Hoge.new.piyo  # => "piyo!"
スクリプトを改変するときは、このように再定義を使用して変更します。
初心者の人は、デフォルトのスクリプトを直接変更しがちですが、それだと効率が悪いです。
<再定義の利点>
 ・関連するものを1つのセクションにまとめることができる
 ・alias が使用できるので、メソッドが短くなり必要な処理だけ見ることができる
 ・元に戻したくなった場合にコメントアウトするだけで良い
 ・スクリプト素材のアップデートが楽になる
特に1つ目と2つ目の利点は大きく、必要な部分だけがまとめられているというのは、
理解や作業時間に大きく影響してきます。
3つ目の「元に戻したくなる」ということはあまりないですが、関連箇所を1つにまとめているため
Ctrl+A と Ctrl+Q の2ステップで変更を取り消すことができます。
4つ目、スクリプト素材をアップデートする際に元のスクリプトだけを入れ替えるだけで済むことがあります。
たとえそうでなくても、また1から変更しなおすのと、合わなくなった部分だけを変更するのとでは
作業量に違いがあるのは一目瞭然ですね。
2.別名定義
すでにあるメソッドを違うメソッド名で定義する処理です。
元の古いメソッドが変更されても、別名のメソッドは元のメソッドと同じ処理を行います。
alias 新しいメソッド名 元のメソッド名
というように使用します。
メソッドだけではなく、グローバル変数にも使用できます。
def hoge
  "hoge?"
end
alias _original_hoge hoge

p hoge, _original_hoge  # => "hoge?", "hoge?"
再定義の併用することで、既存のメソッドに新しい処理を加えることができます。
def hoge
  _original_hoge + " hoge!"
end

p hoge, _original_hoge  # => "hoge? hoge!", "hoge?"
こうしておくと元々の処理が消えずに実行されるので、スクリプト素材などでは競合対策として多用されます。
3.リセット時のエラー
F12キーを押すとゲームをリセットきますが、それを行うと
SystemStackError - stack level too deep
という例外が発生したことはありませんか?

これは、メソッドの呼び出しすぎなどが原因で発生しますが、
スクリプトを見直しても原因がわからない方もいると思います。
結論から言うと、大半は alias が原因で起こっています。

まず、F12リセットは現在の状態からスクリプトをセクションの最初から読み直しているわけです。
つまり、再定義を行っているわけです。
しかし、スクリプトエディタ外で定義されたものは読み直さないようです。
エディタ外定義されているものとは、Window や Sprite クラスのことです。

例えば、次のメソッドがエディタ外で定義されていたとします。
def method
end
このメソッドをエディタ内で別名定義します。
大抵の場合は、この古い方の処理を新しいメソッドから呼び出すと思います。
alias _original_method method
def method
  _original_method
end
ゲーム開始時には、すべてのスクリプトが読み込まれます。
ですので、次のようなメソッド構成になります。
def _original_method
end
def method
  _original_method
end
このとき、ゲームリセットを行うとスクリプトを読み直します。
しかし、すでにメソッドは読み込まれているので、再定義という形になります。
また、読み込まれるスクリプトは、エディタで定義したスクリプトのみです。

最初の method メソッドは、エディタ外定義ですので読み込まれません。
そして、alias によって method メソッドは _original_method メソッドとなります。
しかし、このメソッドは _original_method メソッドの呼び出しを行うものでした。
alias によって自分自身を呼び出す処理になってしまったわけです。
この場合、自分自身を永遠に呼び出し続けるので、
メソッドの呼び出しすぎSystemStackErrorとなります。

他にも次のような場合にエラーが出ます。
class Hoge
  def method
  end
end
class Piyo < Hoge
  alias _original_method method
  def method
    _original_method
  end
end
これも上と同じ原理です。
Piyo クラスのメソッドを読み込むとき、初めは、Piyo クラスに method メソッドが定義されていないため
継承元である Hoge クラスの method メソッドを読み込んで別名定義しますが、
リセット後からは、Piyo クラスに method メソッドが定義されてあるためそれを別名定義します。
その結果、メソッド呼び出しの無限ループ(無限再帰)が発生してしまいます。
4.回避方法
[条件分岐]
コンピュータ上には、必要なメソッドが読み込まれていますので、
わざわざ2度目の定義をする必要はありません。
そこで、条件分岐して2度目の読み込みの際には、alias を実行しないという処理をしてあげます。
alias _original_method method unless $!
$! というのは、組み込み変数です。
この変数には、最近起こった例外が格納されています。
また、$@ も同じようなものですので、こちらを使用しても同様の効果が得られます。

最初にスクリプトを読み込む時には、もちろん例外など起こっていません。
つまり変数の中には、nil が入っていることになります。
そこで、上記の処理にすると nil つまり偽ですので、alias の処理が実行されます。

ここで、F12リセットを行うと、例外 Reset が発生します。
例外が発生すると、$! にその例外が代入されます。
Rubyでは、nil と false 以外は全て真ですので、
unless の条件を満たさず、alias の処理が実行されません。
つまり、2度目以降は別名定義しないという処理なわけです。

[例外処理]
同じ原理なのですが、再定義自体を行わないという方法もあります。
Main セクションの begin 文の前後に処理を追加します。
class Reset < Exception; end
begin
  (中略)
rescue Reset
  $scene = nil  # シーンのポイ捨て
  GC.start      # 廃品回収
  retry         # begin の処理をやり直す
end
リセットを行うとき、例外 Reset が発生します。
それを捕捉して、処理をやり直すようにしています。
例外 Reset は、リセットが行われるまで未定義ですので、
定義しておかないと別の例外が発生した際にエラーが出てしまいます。


begin の上に p と書いて実行してみると、条件分岐と例外処理の違いがわかると思います。
前者は、リセットするたびに p が実行されますが、後者では一度しか実行されないはずです。
5.競合
競合とは、複数のスクリプトを導入した際に、あとから追加したスクリプトで
予期しない変更が加えられ、正常に動作しなくなることです。
主に再定義などによって、処理が上書きされた場合に起きます。

この対策として、エイリアスなどを使用して回避しようとしますが、
場所によっては単なる再定義にするしかないところもあるので、
導入するスクリプトには注意が必要です。