Hironytic Status

ひろんの開発日誌

[CoveredCalc] バージョンアップ

2005-01-30 22:21:35 | 開発状況
CoveredCalc for Windows 1.0.4、CoveredCalc for BeOS 1.0.1 をリリースしました。
メインの修正点は、起動時に前回終了時に表示していたカバーを読み込もうとして失敗すると、すぐに終了してしまってカバーを変更することができなかった動作を変更したことです。
こんなときはデフォルトカバーを使って起動するようにしました。
ただし、デフォルトカバーが壊れていた場合はどうしようもありません。

[CoveredCalc] 例外からのエラーメッセージを取得を修正中

2005-01-28 22:37:26 | 開発状況
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 版の公開でふぬけになってさぼっているわけではありません(笑)
一応、やってますよ、ということで。
まあ、玄箱いじったりしてて開発止まってた時期があったのは確かですけど(ぉ

[CoveredCalc] バグ修正と BeOS 版

2005-01-03 22:58:12 | 開発状況
昨日の晩、MIY ちゃんとチャットで話していて、カバー切り替え時に画像ファイルが見つからないなどのエラーが発生した場合、そのまま終了すると、次回起動時にエラーメッセージが出て二度と起動できなくなることがあるというバグ報告を受けました。

見た目は切り替え前のカバーになっていますが、内部的にはエラーが発生する切り替え後のカバーに変更されている様子。それが 1 つ目のバグ。で、2 つ目のバグとして、前回のカバーが読めなくなっている状態の場合に二度と起動できないというのも関係しています。まあ、後者は既知の不具合なのですが、プログラムの構造を変更する必要があって、現在 BeOS 版を作ってることもあり、そっちができてから直そうと思ってるものです。

そういうわけで、1 つ目のバグをなんとか修正しました。
でもまだわけあってリリースはしていません。

さて、BeOS 版の方ですが、カバーブラウザのリストのダブルクリックでカバーが切り替わらないという点が Windows 版と異なっていますが、それ以外では(正常系の動作なら)ひととおり実装できたと思っています。
エラー処理になるとちょっと怪しいんですが、それはまあ Windows 版も同じということで(爆)
BeOS 版も近いうちに初回リリースができるかなと思っています。