著作一覧 |
昨日の出費はでかかった(とは言え、コンタクトって卵ほどじゃないだろうが物価安定商品のような。しかもO2を透過できるようになったり機能は向上しているみたいだし)。しかし、デブサミの前にけりが付いたのだけは不幸中の幸い。
なーんか、サブクラス化したときエラーになる可能性があるから、無引数コンストラクタを書いておけとかあるのを読むと暗澹たる気持ちになるね。
設計として、引数ありと引数なしの両方の生成方法を考えたのであれば、定義するのは当然だ。
でも、引数ありのコンストラクタを定義して引数なしのコンストラクタを定義していない場合というのは、本来、必ず引数ありコンストラクタを呼べ(もちろんサブクラスであっても)ということを意味する。だから、
Parent.java class Parent { } Child.java class Child { }な状態から
Parent.java class Parent { Parent(Foo foo) { ... } } Child.java -> コンパイルエラー class Child { }
というのは、インターフェイスの変更を行ってしまったのだから、Child.javaのコンパイルはエラーになる「べき」なのだ。それによってChild.javaも修正しなければならない。
もちろん、従来の引数なしコンストラクタでの生成を認めるなら
Parent.java class Parent { Parent() {} Parent(Foo foo) { ... } }
とするのは当たり前であって、しないのは問題外で考えるまでも無い。インターフェイスの変更を行う場合、現状をどうするか(エラーか認めるか)考えてから行動するのは加算に+記号を使うのと同程度に当たり前のことだ。
それを闇雲に機械的につまり無定見に最初の時点から引数なしコンストラクタを定義したら、インターフェイス変更によってエラーになるべきChild.javaがまともな初期化なしに妙な動作をしてしまうことになるじゃないか。
もちろん、Parent.java class Parent { Parent() { } } → Parent.java class Parent { Parent() { throw new IllegalStateException("must call with Foo");} Parent(Foo foo) { ... } }
ってのもありだけど、コンパイルエラーにさせるほうが良いね。実行時まで問題の発見を遅らせても良いことなんてありはしない。
結論は、
1.インターフェイス変更はもちろん悪。でも設計当初には予測不可能だった問題によって変更せざるを得ない場合もあるから、その場合は大胆に変更。なぜなら、問題点はコンパイルエラーによって明らかにされる。2.無定見な引数なしコンストラクタの定義は悪。理由はインターフェイス変更による問題点の発見が遅れる。
ということだ。
でも、削除するって方法もあるね。引数ありを定義した時点で無引数コンストラクタを削除とか。
でも、無引数コンストラクタを省略しているってことは、記述すべき内容が無いってことでもある。そこで
class Foo { int foo = 0; long fool = 0L; ... } とか class Foo { int foo; long fool; Foo() { foo = 0; fool = 0L; } ... }
のような無意味な記述をするかどうかで切り分けるという考え方もできるかも。
ダブルスタンダードは悪の帝国の始まり。
というところから始めると、上のように無駄で無意味で冗長でまったく有意義じゃない記述をたとえば「読むのはJavaの言語仕様を知らない、つまり初期値はプリミティブはゼロ、オブジェクトはnullということがわからない人が読み書きすることを前提」として「記述必須」とするコーディング規約を作るなら、確かに「デフォルトコンストラクタを利用してはだめ。必ず空でも(というか、最初の規約からゼロ代入とかは普通あるだろうから空にはならんか)記述必須」とするのは理解できる。
理解はできるが、僕ならまずそのへんの仕様については知っていることを求めるけどね。少なくともソースを触らせるのなら。
さらに追記:ちなみにfinalで修飾した場合の取り扱いはおもしろいと思う。
さらにさらに追記:ここまでくると「オレはそれをダブルスタンダードではなくケースバイケースと呼ぶね」、ってのもありだと思うが。
ジェズイットを見習え |