BeOS 版もひとまず公開できたので、前々から設計が気になっていた部分を修正中です。
それは、期待した処理が行えなかったときに発生する「例外」オブジェクトに対応するエラーメッセージを取得する部分です。
まず、例外オブジェクトの構造はクラス図にすると
こんな感じになっています。
すべて Exception というインタフェースを実装したかたちで例外の種類ごとにクラスがあります。
図には書いていませんが、Exception を実装する各クラスには、そのクラスに特化したメソッドも持っています。
たとえば、XML ファイルの解析中に発生する例外クラス XMLParseException は、例外の発生した行を得るための GetLine() を持っています。
ここで、Exception には GetErrorMessage() があるのですが、これは必ずしもユーザにお見せできるようなものではありません。また、将来、多言語対応を考えているのですが、言語によるエラーメッセージの切り替えを各例外クラスの中で行いたくはありません。
そこで、ExceptionMessageResolver というものを導入しました。クラス図にすると
こんなものです。これだけではわかりにくいので、オブジェクト図も
一緒に見てください。
ExceptionMessageResolver は Resolve() に渡された例外を見て、自分の知っているものなら対応するエラーメッセージを返します。
たとえば、MemoryExceptionMessageResolver は、渡された例外が MemoryException なら「メモリが不足しています」というようなメッセージを返します。
利用する側では、ExceptionMessageResolver の集団に対して、その 1 つ 1 つに「この例外知ってたらメッセージをちょうだい」とお願いしていきます。メッセージをもらえればそれをユーザに表示するわけです。
さて、XMLParseExceptionMessageResolver は CoverDefParseException を渡されたとき、それを XMLParseException だと判断して「XML ファイルの解析に失敗しました」のようなエラーを返しますが、本当は CoverDefParseException なんだから、そこは CoverDefParseExceptionMessageResolver に任せてもっと細かいメッセージをもらいたいものです。
そこで、XMLParseExceptionMessageResolver はまず CoverDefParseExceptionMessageResolver に処理を頼んで、解決できなかったときだけ自分が解決しようとします。そのための仕組みが ChainedExceptionMessageResolver で、あらかじめ登録されたものに処理をお願いできるようにしてあります。
オブジェクト図では各クラスオブジェクトがどういう関係で登録されているかわかるようにしています。
また、この仕組みを使って、1 つ 1 つのオブジェクトに「これ知ってる?」って聞いてまわるだけのやつも作りました。
オブジェクト図で allInOne という名前のついた HandsOffExceptionMessageResolver のオブジェクトです。
こいつは人に頼むばっかりで自分は何も解決しません。
さらに C++ なので誰かがこれらのオブジェクトの寿命を管理してやる必要があります。
そこで、すべての ExceptionMessageResolver オブジェクトの寿命は ExceptionMessageResolverStorage が管理してやることにしました。
欲しいオブジェクトの取得も Get でできます。たとえば、ある例外がすでに XMLParseException だとわかっているなら、allInOne を使う必要はなくて、直接 XMLParseExceptionMessageResolver のオブジェクトを使えばいいのです。
そして、この storage くんが makeStorage() というメソッド内で各オブジェクトの主従(?)関係を登録しています。
こういう設計にしたところ、非常に大きな問題が目の前にのしかかってきました。
1 つ例外を作ろうと思ったらえらく面倒なのです。
その例外に対応する Resolver クラスを用意して(これが面倒)、ExceptionMessageResolverStorage::makeStorage() でその Resolver を生成して、場合によっては主従関係の登録をしなおして、さらに ExceptionMessageResolverStorage::Get() でその Resolver をもらえるように、Resolver の種類を表す列挙型(EMRSClass)を追加しなければいけません。
こういうのを「懲りすぎ」といいます。全くもって無駄に複雑になっています。考えなしに作るとこうなります。
割と早いうちにうすうす気づいてはいたのですが、さっさと動くものを作りたかったこともあって、そのままがんばってました。
Windows と BeOS の両バージョンをとりあえずリリースできたので、これを大きく作り直しています。
単なる作り直しでしかないので、残念ながらここが作り直されてもユーザさんにはメリットはありません。すみません。
ExceptionMessageResolver(とその周辺クラス)にとってかわる新しい ExceptionMessageGenerator は全くもって単純なものです。クラス図は
こんな感じ。
見てのとおり、すべての例外をこのクラスがすべて扱います。たとえば、今まで XMLParseExceptionMessageResolver が行っていた機能は processXMLParseException メソッドが引き受けます。processXMLParseException メソッドはその中で processCoverDefParseException メソッドを呼んでいます。そうです。主従関係はここにハードコーディングされています。
だいたい、場合によって主従関係をが変えたくなることはまずないんです。これでいいやん!
それから、たとえそれが XMLParseExeption だとわかっていても、他の process~ を使わないといけない作りになりました。実際の例外は、XMLParseException のサブクラスが数個、CoverDefParseException のサブクラスが数個というように細かい例外ごとに 1 つクラスを作っているので全部比較していたら遅いんですが、そんなことはしませんから。そのためにクラスを階層化してあるんであって、親クラスの例外でなければ、それより下のクラスの例外ではないのでたどる必要はありません。
それなら、せいぜい、数個~十数個の比較で、処理できるはずで、そんなに時間がかかる処理でもないからこれでいいやん!
ということで、BeOS 版の公開でふぬけになってさぼっているわけではありません(笑)
一応、やってますよ、ということで。
まあ、玄箱いじったりしてて開発止まってた時期があったのは確かですけど(ぉ