思うだけで学ばない日記 2.0

思うだけで学ばない日記から移転しました☆!よろしくお願いします。

例外問題まとめ(?)

だいたいわかった(本当カヨ;

まず基本的想定として、オブジェクトの解体担当は、当のオブジェクトに限定して良い*1
この前提は、オブジェクトをコレクションに登録してコレクションから解体トリガを与えるコレクションで管理する、と言う場合でも一般性を失わない*2。つまり、オブジェクトの解体担当=当のオブジェクト、とする設計は妥当。

じゃあ例外発生時にオブジェクトは即自己解体していいか?というとそうは行かない。他から使用されているのに勝手に自己解体して自殺するわけにはいかない*3。自殺せずに以降使用されなくなるまでじっとする、でも全体の動作に支障を来しかねないからNG*4

よって、例外発生時のオブジェクトの動作の選択肢は、動作継続するか、さらに上に例外をスローして処理してもらうかの2者択一と言える*5

例外発生しても動作継続可能なクラスに統一したなら、一番すっきり解決する。適当な階層で例外を補足してその場で処理し、例外発生した事実をログにでも残した後、プログラムの稼働を再開できる。

例外発生すると動作継続不能なクラスが混じっている場合であっても、解体処理の正常終了完遂さえ保証されるなら、プログラム起動時に0から構築できた以上、適当な階層まで遡れば0から再構築して稼働を再開できるはずだ。(最悪プロセスを一旦停止して再び起動するとか!)*6 つまり、任意のクラスは例外発生しても動作継続可能なクラスに変換できる。このとき、遡る階層数をできるだけ小さくとか、動作再開までのタイムラグを小さく、とかいった要求を満たすことは、設計に特定条件での各論を持ち込まざるを得ず、例外対処の一般論の議論にそぐわないのでここでは除外する*7

で、このように考えれば、コンストラクタ内で発生する例外を恐れる理由は全くない。構築中かつ使用中というオブジェクトは論理的に存在し得ないから、コンストラクタ内ではいつでも勝手に自殺できる*8。つまり解体トリガを与えるタイミングの問題は考慮の対象から除外でき、例外を生じた当のオブジェクトの解体をいかに確実に行うかの一点に注力すれば良い。

GCがあれば何も考えなくても良い。*9

GCがない言語なら、少ない記述量・高い可読性・高い安全性記述時のフェイルプルーフを総合的に満たす好きな記述方法を選べば良い。特にコンストラクタ内での例外については、どの記述方法を選んでも解体→動作再開までのタイムラグに与える影響は無視し得る、
んジャマイカ、

追記

>例外発生すると動作継続不能なクラスが混じっている場合であっても、解体処理の正常終了完遂さえ保証されるなら、
>解体処理の正常終了完遂さえ保証

さらっと書いたがこれ案外難しいか、、orz

*1:ただし、他への委譲も解体担当の仕事に含めて良いものとする。自身のメンバの解体についてメンバ自身に委譲するとか、基底クラス部分の破棄を基底クラスのコードに任せるとかのケース。

*2:対象とするオブジェクトの中身をよく知り解体まで面倒を見るコレクションのかわりに、対象オブジェクトに解体を委譲するコレクションは言語によらずいつでも書けるハズ。つまりオブジェクトの解体担当=当のオブジェクト、と言うモデルの範囲内でどんなオブジェクトの社会も記述できる。

*3:ここで言う「使用」とは、参照カウントやリンクリストで記述される関係だけでなく、オブジェクトAの自殺によりオブジェクトBの整合性が破壊される状況全てを言う。

*4:例えばそいつが他のオブジェクトの使用権を握ったまま黙り込むと解体処理や使用権の管理でデッドロックしかねない。一方不用意に解放しても、今度は外に不整合をまき散らしかねない。黙り込んでもデッドロックや不整合が生じない保証はプログラムの詳細に立ち入った各論になる。

*5:上に例外をスローすることが即オブジェクトの自殺とは限らないことに注意。例外は関数を抜ける手段の1つにすぎない。一般メソッド内で例外が発生しても、当のメソッドは抜けるが、オブジェクトとしての完全性整合性は損なわれず、引き続き使用可能なケースがある。この性質は設計可能な事項である。

*6:その際再構築したクラスより上のクラスは破棄されないのだから動作継続していると言える。

*7:各論が任意に混じり出すと議論の範囲が定まらないからそりゃまあ結論が割れざるを得ない。

*8:オブジェクトAがオブジェクトBを使用しようとしてBを構築中にコンストラクタで例外が発生したらどうすんじゃい!?という指摘が有り得るが、それにはAやBより上の階層に遡ってそのレベルからやり直すんですよ、と答えることが出来る。

*9:もっとも、GCに資源解放の全権まではない場合は考慮が要るが。例えばC#ではDispose()を呼び忘れたときアンマネージなリソースがリークする、というクラスを書くことができる。