No Bugs, No Life

読んだ本や、プログラミング、システム開発等のねたを中心に。文章を書く練習なので少し硬派に書くつもりだけど、どうなることやら。

例外設計の見直し

MementoWeaver開発記
一応、MWも一通りの機能はコーディングが完了したので、少し気になっていたところの見直しを行い始める。
まずは例外設計の見直しから。
Javaの例外設計については、皆様一家言あるかもしれないが、MWについての課題認識と対応方針を以下に記載する。

現状の課題認識

現状では、アプリで送出する例外クラスとしてMWException(チェック例外)を定義しているが、以下の点でいけていない。

  • ほぼ全てのメソッドシグニチャにthrows MWExceptionが含まれていて、
    • 結局何もハンドルしていない
    • コードが冗長
  • ミソもクソも全てMWExceptionなので、ビジネスロジック観点からの例外とシステム環境に起因する例外が区別できていない。

MWにおける例外設計の方針

上記課題認識を踏まえて、MWの例外設計については以下の方針で見直すこととする。

  1. ビジネスロジック観点からの例外はチェック例外とする。システム環境に起因する例外は非チェック例外とする。
  2. システム環境に起因するわけではないが、バグ的な例外(ユーザーが制御できない例外)は非チェック例外とする。
  3. ビジネスロジック観点からの例外はUIのcontrollerレイヤー群でキャッチしてDialog処理する。re-throwは行わない。
  4. システム環境に起因する例外は元例外がチェック例外であろうと非チェック例外としてre-throwする。特にcatch等は行わない。
  5. MW用の例外クラス群に非チェック例外の元クラスとしてMWRuntimeError*1(非チェック例外)を定義する。
  6. MWException、MWRuntimErrorは双方ともabstractとしてそのまま用いることは行わず、適切な粒度でサブクラスを定義して使用することにする。

例外設計の方針を受けた具体的な対処

例外クラスの再設計をトップダウン的に行おうと思ったけど、既にコードがあるのでそこからボトムアップ的に必要な例外クラスを導出することにする。
そのためにも、まずは"throws MWException"となっている箇所を全て削除してコンパイルエラーになるところを順につぶしていく作戦とする*2

画面遷移系の例外

AppMainのthrow MWExceptionの削除(+MWExceptionをabstract化)により、問題になるところは2箇所。

  1. FXMLLoader#load()の時のIOException
  2. FXMLLoader#getControllerがnullだった場合の自前throw。

最初のIOExceptionはFXML定義ファイルが読み込めなかったときにthrowされるものなので、非チェック例外のMWFxmlResourceErrorを投げることにする。2つ目も同様とする。
ついでに、InputStreamのclose()をfinallyで書いていたのを、Java7で導入された"リソース付きTry"(こちらを参考とさせていただきました。ありがとうございます。)でこっそり書き換えてしまったのは、MylynをActivateしていないときにやったのは間違いだった、、、。

File読み書き系の例外

次、Derivatizer系。ここはFileのIOに関するIOExceptionをどう扱うか。
Derivatizer系ではファイル名の指定等をユーザーが直接制御することはないので、基本的にはここで発生するIOExceptionは所謂バグ。
ということでここも非チェック例外MWResourceIOErrorを新設して置き換える。

Reflection系の例外

次、Generator系。ここはリフレクション系でジェネレータを作成したりしているので、そこら辺で固有のMWExceptionを吐いたりしていた。リフレクション系ではコードテーブル(PredefinedTag)の誤りか環境設定系の誤りが考えられるためMWConfigurationErrorを新設して置き換える。
上記の他には、TemplateWrapper系で事前にsetされているべき属性がsetされていない場合にMWExceptionを送出していたが、これはバグ以外には考えられないので、MWImplementationErrorを新設(まぁ、要するにバグのときに使う)。

画面入出力系に伴う例外

かなりイマイチなことになってしまっていたInstallProcessingがここに該当。
現在はInstall時のソースディレクトリが存在しない場合も、ターゲットディレクトリが存在しない場合も何故かDialogを表示してしまっていたがこれは全然駄目。
ソースディレクトリが存在しないのは、ユーザーによる入力によってはありえるので、チェック例外MWInvalidUserInputExceptionを発生させる。
ターゲットディレクトリについてはプロパティファイルで指定しているので、MWConfigurationErrorの非チェック例外が適当。

見直し後の例外クラス構造

最終的に以下のような構造とした。
f:id:kazyury:20130428224825p:plain

*1:Java界隈ではこの命名は怒られそうだが...

*2:仕事ではこんなこと怖くて出来ないので、きちんと最初に考えておくのがスジ