トップ «前の日記(2004-04-06) 最新 次の日記(2004-04-08)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2004-04-07

_ いやなに日記

いや、なに、その、あの、……と続く。

_ なんとなく違和感

nullがオブジェクトじゃないのがいかんのかな。
class Foo
 def initialize()
   @bar = nil
 end
 attr_accessor :bar
end
 
foo = Foo.new
if foo.bar.nil?   #foo.barがnilという状態か判定
  puts 'nil'
else
  do_something(foo.bar)
end
いちいち変数に代入しなければどうか?
 class Foo {
     Bar bar;
     Bar getBar() {
         return bar;
     }
 }
 ...
 foo = new Foo();
 if (foo.getBar() == null) {
    System.out.println("null");
 } else {
    doSomething(foo.getBar());
 }
 ...
ゲッタで戻されるのは常にBarのインスタンスであり、そのインスタンスが自分はnull(アクセス不可能な存在しない状態である)と答えるということだ。
どこにもモデル的な破綻はない。
追記:別に元ネタに反論する気はないのだが、見方が一方的なのが違和感の正体だろう。
基本的にはnullオブジェクトを返すというのが正解(nullを取るのが例外的な状態であれば最初からIllegalStateExceptionをスロー)だとは思うが、== nullというのは、JavaにおけるRubyのobject#nil?と同義だと考えればreturn null;を排斥する理由にはならない。
コンテキストが2種類あるからだ。
  • 直接の操作対象のオブジェクトが現在その属性を返送できないという状態(元ネタで指摘されている状態)(追記:これは1---[1|1..*]の関連の場合だけのような気がしてきた)
  • 操作対象のオブジェクトの属性から返送されたインスタンスがnullだという状態(1----0..[1|*]の関連で現在0)
後者であれば、return null;で良い。まあ、配列ならば要素0の配列を使用すべきだとは思うが、それは利便性の問題に過ぎない。あと、1対0..*の関連なら最初からコレクションのインスタンスを生成しておいて常にそれを元にして(イミュータブルにして)返すのが正しい実装だろう。
追記:つまるところ、object == nullという記述は、objectに対して「おまえはnullか?」というメッセージの送信なんだから、全然、おかしくないということ。
呼び出し側がいちいちメッセージをオブジェクトに送るのが面倒くさいという理由なら別に、利便性の追求は悪いことじゃないからどうでもいいです。その場合は、#isBarNullは単なる便利な(オブジェクトの取得をスキップして直接該当オブジェクトの)状態判定が可能なヘルパメソッドかな。

_ まとめ

オブジェクトの関連に着目するならば、

1対0...1の関係ならば、return null;またはreturn インスタンス; というより何も考えずに、return フィールド;

1対{0|1}...*の関係ならば、return コレクションフィールド; ミュータブルかイミュータブルかはメソッドの定義次第。単なるゲッタならばイミュータブル。最小数と最大数が固定なら配列でも可(100万要素なら配列のほうが絶対的に早いから、配列はパフォーマンスオプションとして利用できる)。

1対1 これでnullなのは異常な状態なんだから、アクセスしたやつがNullPointerExceptionで死ぬも良し、IllegalStateExceptionをスローするのでも良し。

取得可能かどうかの判定メソッドというのは、オブジェクト間の関連とは無関係なヘルパメソッド。

もちろん、それがオブジェクト間の関連ではなく、操作しているオブジェクトの状態ならば、その状態の取得メソッドとなるからヘルパメソッドではない。

_ って言うか

最近は、TELL, DON'T ASKを再考しているところなんで、最初から違う方向で片づけるべきだという考えに近づいている。つまり、
 Bar bar = foo.getBar();
 doSomething(bar);
ってのは、fooにASKしている。これをTELLに変えればこの問題は起きない。
 foo.doSomething();
 ...
class Foo {
  Bar bar;
  void doSomething() {
    if (bar != null) {
       bar.doSomething();
    }
  }
}
なぜ、ASKする?

_ Tell, Don't Ask

プラグマティックプログラマーから。

プラグマティックに考えると値オブジェクトの場合はどうよ? とかあるけど、値オブジェクトってのは方便だからそれを基準に考えてもしょうがなかろう。

atomicityまで考えると、状態問い合わせっていうのはまずいですね。その場合は、上のが全部、やばくなる。TELLの例にsynchronizedを付ければOKかな?

_ おまけ

達人プログラマー―システム開発の職人から名匠への道(アンドリュー ハント/デビッド トーマス/Andrew Hunt/David Thomas/村上 雅章)

本そのものは読んでもいないのに書影を貼ってみたり。

プラグマティックプログラマーの本。とっても良いらしい。

_ どこまでTellできるのか

えーと、良心の問題として付け加えると、自分で完全に制御できるものについては、こんな感じになるけれど、仕様を切って渡す場合には、手続き的になるのは気にしてないです。また、できたプログラムを後はよろしく、する場合にもask, get, and do it myselfのようなものは書きます。(追記:つまりトランザクションスクリプトパターン)

仕様をプログラムに落とし込むところで、誰もがプラグマティックプログラマーのように考えられるとは想像できないし、仕様というものを箇条書きにしろ、文章で書くにしろ、手続きとして記述せざるを得ない以上、また、細粒度のオブジェクトまでダイアグラムに書くのは無意味(だと思うからTDDマンセーなのだが)だと考えているから、誰もにオブジェクトのインタラクションとして頭に浮かべろというのは、いささか無謀ではないかとも思います。そこで工夫をしながら限られた時間の中で、やってのければそれに越したことは無いというスタンスです。

ちなみに、== null が複数の意味を持つというのは理解できませんでした。C++だとオペレータオーバーロードがあるから複数の意味を持たせられるけど、そうでなければ左辺のオブジェクトにnullかどうか問い合わせるということ以外にどんな意味があるんでしょう?

ゲッタを使って取り出したオブジェクトに、お前はnullかと問い合わせるのがダメで、取り出す前に問い合わせるのがOKってのは、カプセル化を破ってると端的には思います。だって、自分が存在しているかどうかは、そのオブジェクトが知っているべきことですな。そのオブジェクトの持ち主が知っているってのは持ち主がエラ過ぎです。でも、持ち主が知っていても構わない場合もあって、それはその持ち主と持たれているオブジェクトの関連によって決定されます。一意ではありません。でも、== nullという記述は一意です。

(追記:nullが一意ではないという意味かな。nullはan invalid or uncreated objectだから2種類の意味を持つ。それにそもそも型を持たない。だから一意ではないということなのかな。とは言えある型の変数に入れられたら、その型のオブジェクトの参照という意味付けはされているわけだし、invalidかuncreatedかはその時点で問うても意味ないような気がする。)

ちなみに、マジックナンバーは大好きなので、壁に/etc/magicをプリントアウトしたものを貼っていたりします(ここまで書くとネタだと思うかな)。

_ さらにおまけ

なお、Javaでも、こういうことはできます。
public class St {
    public static void main(String[] args) {
	String s = null;
	System.out.println(s.valueOf(123));
    }
}
sは、Stringオブジェクト(追記:わけわからん。正しくは、「Stringオブジェクトの参照」)なので、StringクラスのvalueOfメソッドは呼べます。ただし、インスタンスは== nullが真なので、インスタンスメソッドは呼べません。
本日のツッコミ(全7件) [ツッコミを入れる]
_ たむら (2004-04-07 09:21)

わははははは。<br># artonさんって、一発ぎゃぐも素敵

_ なかだ (2004-04-07 14:43)

ASK & GETじゃatomicじゃないでしょ。

_ Saisse (2004-04-08 10:21)

おまけのコードはEclipse使ってると警告が出ます。

_ Saisse (2004-04-08 16:32)

== nullが複数の意味を持つというのは、人間が頭の中で解釈するときに複数の解釈方法があるという意図で使っています。例えば hoge == null をみたプログラマは「hogeが取得できる状態にない」「hogeがIllegalな状態だ」「hogeを持っていない」「hogeでない」など、どういう意味で理解するか揺れがあるということです。

_ arton (2004-04-08 16:48)

そんな解釈を許容させるということは、モデリングが失敗しているのではありませんか? その例だと、順に状態遷移があるオブジェクト、エラーでも平気なオブジェクト、hogeと1対0..1の関連のオブジェクト、単なるバグの4つの異なるhogeの持ち方をするクラスです。したがって、呼び出し側にはそれが異なるということを意識させなければなりません。したがって、単にgetHogeというメソッド名ですべてをくくろうとするのがまずいのだと思います。<br> ただ、事前に確認処理を行わせ、かつあくまでもゲッタ主体でインターフェイスを規定するのならこのうち、1番目については、isReadyForHoge()は当然あるべき、2番目はisValidHoge()があったほうが良い(というより本当にそんなオブジェクトはありか?という疑問も。例外のほうが良さそう)、3番目はnullで良し(hasHoge()を実装してももちろん良い)、4番目は問題外ということでしょう。ただし、カプセル化の破壊を言い出すと、クライアントがステートを常に意識しなければならないというのも厳密には妙だとは思います。(じゃあ、どうしろと言うのかと訊かれてもとりあえずはわかりません)<br>これは、nullが複数の意味を持つのではなく、getHoge()というメソッドが複数の意味を持っているのです。なぜなら、複数の状態を返送してしまうのだから。インターフェイスを憎んでnullを憎まず。<br>#Eclipseを使ってたんですね、なんで便利だと言うか疑問だったけど一応、氷解しました。<br>#なかなか勉強になりました。おもしろい着眼点を教えてくださってどうもありがとうございました。

_ arton (2004-04-08 17:05)

TDDが好きなのも当然、こうやってイテレーションするスタイルなんだからしょうがないね、推敲大好き。<br>モデリングが失敗ではなく、APIの決定に失敗と書くべきですね。1番目は状態があるんだから、getHogeではなくdoHogeで処理結果を返す。返るのがobjectなら、nullは処理の失敗。あるいは、例外でNotReadyExceptionなど(値オブジェクトでも良いかも。getResult()とgetHoge()を持つ値オブジェクトを返すとか)ーただし、本来はTellにするべき。2番目は例外。3番目はまさにnullを返すべき場合。4番目は問題外(getHogeでhogeでないとは?)。ということではないでしょうか。

_ Saisse (2004-04-08 23:31)

インターフェースと実装という切り口ならば...と思いましたが、もう少し煮詰めてみます。<br><br>getHoge()に機能をつめすぎてるというのはその通りだと思います。nullを返すまえにホントはartonさんのようにちゃんと考えなくてはいけないのに、nullを返してしまうことで何も考えずに逃げられてしまうことが問題なのだと思います。<br><br>null禁止はその逃げ道をふさぐことになるので、より安定して、確実なシステムに近づけるのにある程度の効果は期待できるはずです。<br><br>#Eclipseのページはそろそろ消そうかな?と思っていたのでお役に立てて幸いです。<br>#こちらこそ貴重な意見をありがとうございました。


2003|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|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|

ジェズイットを見習え