history

青木日記 RSS

<前の日 | この月 | 次の日>

2003-04-06

遅延初期化

メール出し忘れてた。

消える前に「バカが征く」ミラー (もう消えたけど)。

無闇に遅延初期化っつーのはどうかと思います。
 
def getFoo
  if @foo.nil?
    @foo = createFoo
  else
    @foo
  end
end
 
こういうの。初期化しておくんならコンストラクタとかでやっとくのが本道じゃ
ないですかね。計算に時間がかかるとかがあれば別でしょうけど。

想像してみると、確かに initialize で初期化するほうがよいような気がします。 ではそれはなぜ「本道」なのかをできるだけ論理的に考えてみました。

まず、そういうもんだって言えば確かにそうです。 コンストラクタって言うくらいなんだからデータ構造を作るのに使うべきでしょう。 また読むほうもコンストラクタにはそういう役割があるだろうと期待しているので、 その期待に沿って書いたほうが読みやすいと感じるはずです (消極的な理由)。

またもう一つ、動的な構造をソースコードから想像するのが簡単になるという点が 挙げられます。データ構造を理解するのはコード読みの中でも根幹にあたりますから、 データ構造を想像しやすいコードは読みやすいでしょう。 従ってインスタンス変数はコンストラクタで初期化すべきです (積極的な理由)。

Ruby のように変数型のない言語では第二点は特に重要です。 変数が型ありの場合は、コンストラクタで代入していなくとも 変数定義さえ上のほうにあれば何が入るのかはとりあえずわかるからです。

逆に言うと、変数に型のない言語で読みやすいコードを書こうと思うと コンストラクタだけでデータ構造がわかるようにするほうがよい、 そうするように矯正される。かもしれません。単にわかりにくいコードに なるだけかもしれません。

遅延初期化 (2)

ちなみに Ruby で遅延初期化をするときは次のように書くのが簡潔です。

@ivar ||= make_object()

また ||= を使う場合に限り @ivar を初期化していなくても警告が出ません。

~/c/tmail % ruby -vwe '@ivar = 0 unless @ivar'
ruby 1.8.0 (2003-03-28) [i686-linux-gnu]
-e:1: warning: instance variable @ivar not initialized
 
~/c/tmail % ruby -vwe '@ivar ||= 0'
ruby 1.8.0 (2003-03-28) [i686-linux-gnu]

ただしこれはモジュール関数の中などでやむなく遅延初期化せざるを 得ない状況を救済するためではないかと思われます。

# 例
module FileUtils
  module_function
  def verbose_output( msg )
    @output ||= $stderr   # initializeが使えないので遅延初期化できない
    @output.puts(msg)
  end
end

initializeの位置

そうだ、initialize はクラス定義の最初に置くべきでしょう。 最初のころは「private だから」つーことで一番下に書いてたんですが、 前述のような理由を考えると initialize が先頭にないとまず意味が ありません。せっかく initialize が自動的に private になるように なってるんだから、その機能を活用しましょう。

あ、でもクラスの特異メソッドは initialize より前に置いてます。

include はいまだにブレがあるんですが、 だいたいクラス定義文の冒頭か initialize の直前ですかね。 冒頭に置く理由は「スーパークラスの記述と近いから」で、 initialize の前に置く理由は 「include はインスタンスメソッドの記述 (のショートカット) だから」です。 だから一部でしか使わないメソッドのために include するときは 例外的にその直前で include する場合もあります。

requireの位置

require はファイル先頭。 最初の最初にファイル間の関係を理解するとき、 require が先頭にないと理解しづらいです。

本日のツッコミ(全6件) [ツッコミを入れる]
ささだ (2003-04-07 03:18)

私は、遅延評価のことを忘れてしまったりすることが多多あるため、initializeで初期化します。require は、メソッドの中に書いちゃうことが結構あるんですが ^^;

ただただし (2003-04-07 09:14)

じゃあ、「遅延初期化は最後から一個前の武器だ」ってことで。

あおき (2003-04-07 10:02)

とか言いつつも遅延初期化使いまくってますけどね、ぼくは。
今日の話題は自戒を込めて書いてみました ^^;;;

はら (2003-04-07 10:45)

attr_ 系は initialize の前ですかね。

moriq (2003-04-07 18:59)

おお。タイムリー。
昨日このようなコードを書きました。

module DateOld
  def roku
    @roku = ほにゃほにゃ
    instance_eval {
      def self.roku
        @roku
      end
    }
    @roku
  end
end

でも、こう書いたほうがいいですね。

module DateOld
  def make_roku
    ほにゃほにゃ
  end
  def roku
    @roku ||= make_roku
  end
end

moriq (2003-04-07 19:02)

あ、でも make_roku は nil になることがある(@roku=nilでrokuは以後nilを返す)ので @roku ||= make_roku は採用しにくいな。

名前
メールアドレス

<前の日 | この月 | 次の日>
2002|04|05|06|07|08|09|10|11|12|
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|04|05|06|09|10|
2009|07|
2010|09|

Copyright (c) 2002-2007 青木峰郎 / Minero Aoki. All rights reserved. LIRS