try-with-resourcesを試してみる
少し時間ができたので、↓の記事を参考にJava7のtry-with-resourcesを試してみました。
Java7 体当たり/try-with-resources Statement - 日々常々
まあ他の言語からするとアレですが、Java6以前から比較すると地味にありがたい拡張ですね。特にcloseのチェック例外を無視できるのが嬉しい。なぜかリソース系クラスのcloseメソッドはチェック例外持ちが多いので、closeの例外処理が不要でも、finallyブロック内にtryをネストしなきゃいかんというのは気分の悪いものでした。
実行順が気になる
ところで、tryブロック内で例外がthrowされた場合、リソースがクローズされた後にcatchブロックが実行されます。従来であれば、closeはたいていfinallyブロックに記述されたので、catchブロックが実行されたあとにリソースがクローズされていました。
JDBCを使ったDB更新処理では、例外が発生するとcatchブロックの中でrollbackする実装をよく見かけますが、java.sql.Connectionをtry-with-resourcesに管理させた場合は。catchブロックでConnection#rollback()を呼んでも、すでにそのConnectionはクローズされている、ということになります。
Connection conn; try (conn = dataSource.getConnection()) { // DBを読んだり書いたり〜 conn.commit(); } catch (Exception e) { conn.rollback(); // ここでは、connはクローズ済みなので例外がthrowされる! }
まあ、JDBCの仕様上、close時に内部でrollbackされる約束になっているので、rollbackだけなら書かなければいいだけですが、システムによっては、DB上のログテーブルにエラー情報を書け、なんてケースもあるので、そういう場合は注意が必要です。そもそもDB処理で例外が発生した先でDBへ書き込めなんて、そんな実装は窓から投げ捨てるべき
まあ、大した話ではありませんが、実装パターンが若干変わってくるので、コピペテンプレ実装に慣れきっている向きは、気に留めておいたほうがいいかも知れません。
おまけ
try() の中に書けるリソースオブジェクトは、今回新設されたAutoCloseable型でなければいけません*1が、古いライブラリでも、void close() メソッドさえ持っていれば使えるようにラッパクラスを書いてみました。需要があるか微妙ですが。
import java.lang.reflect.*; public class ACWrapper<T> implements AutoCloseable { private final T resource; private final Method closeMethod; public ACWrapper(T resource) { try { closeMethod = resource.getClass().getMethod("close"); } catch (Exception e) { throw new IllegalArgumentException("close-method not found.", e); } this.resource = resource; } @Override public void close() throws Exception { closeMethod.invoke(resource); } public T getResource() { return resource; } }
こんな感じで使います。
try (ACWrapper<OldResource> acw = new ACWrapper<OldResource>(new OldResource())) { OldResource res = acw.getResource(); // OldResourceを使った処理 }
やっぱ型推論ほしいな…
(8/2 追記)
Java7では型引数を取るクラスのインスタンス生成・初期化の際、右辺の型引数を省略できることを忘れていました。上記の使用例のtry()の行は以下のように書けます。
try (ACWrapper<OldResource> acw = new ACWrapper<>(new OldResource())) {
ちなみにACWrapperのあとの「<>」をつけないとWarnが出ます。…それくらい補完してよ
ScalaでJUnitテストケースを書く
とりあえず、Scala用のテストフレームワークは使わず、素のJUnitを使った例。
Eclipseから使う場合、JUnit関連クラスのimportはクラススコープの外側に書いておかないと、右クリックからテスト実行できないようです。
あと、Scala2.8からアノテーションのパラメータの書き方が変わったようで、Javaと同じような記述ができるようになりました。(2.8で、前と同じ書き方をするとコンパイルエラーになります)
import org.junit.{ Test, After, Before } import org.junit.Assert._ class SampleTest { @Before def befor = { // 前処理 } @Test(timeout=500) //@Test{val timeout=500} //Scala2.7以前ではこう書く def testHoge = { Thread.sleep(300) assertEquals(1, 1) } @After def after = { // 後処理 } }
Scalaの文字列処理とかパターンマッチングの便利さを考えると、JavaのテストケースもScalaで書いたほうが楽かもしれない。
Monadについて
勉強中。あとでまとめる。
(2010/09/06)ざっくりまとめ
オブジェクト指向・手続き型脳味噌で理解したモナド(の概念の一部)
- モナドは、副作用の範囲を封じ込めるコンテナとして見ることができる
- 高階関数を受け取り、モナドの中身に適用して、その結果をまたモナドに詰めて返す
- 関数Aをモナドに渡し、その戻り値に関数Bを渡し、その戻り値に関数Cを渡し…と連鎖させることで、A→B→Cの評価順を確定することができる
モナドの中身を取り出すのは、Scalaではunit、Haskellではreturn←これは完全に間違い。- 値を受け取り、その値を格納したモナドのインスタンスを生成する関数を持つ(
Scalaではmap、HaskellではunitScalaではコンストラクタ、Haskellではreturn) - 関数を連鎖させた際にモナドの多重化を防ぐための仕組みが用意されている(ScalaではflatMap、Haskellではbind)
- モナドで使用する高階関数は、モナドの中身を受け取って、モナドを返す
- Scalaのfor文は、ListモナドとflatMapに展開される。
参考資料
All About Monads http://www.sampou.org/haskell/a-a-monads/html/index.html
「モナドは象だ」の翻訳まとめ - {Fight the Future => じゅくのblog} http://d.hatena.ne.jp/jyukutyo/20081111/1226392144
EclipseのJavaプロジェクトや動的WebプロジェクトでScalaソースを混在させる
「EclipseでのScala+Webアプリ開発 」の件で、とりあえずScalaコードを混ぜ込んでもビルドされるようになったので、方法の簡単に書いておきます。ただしちゃんと検証できてないので真似する際は自己責任で。
- Scala IDE for Eclipse (Scala Plugin)をインストールしておく。
- Eclipseを終了させ、対象プロジェクトのフォルダにある .project ファイルを開く。
- buildSpec要素内にある、javabuilderの設定を scalabuilderに置き換える
<!-- javabuilderをコメントアウト <buildCommand> <name>org.eclipse.jdt.core.javabuilder</name> <arguments> </arguments> </buildCommand> --> <!-- 以下追記 --> <buildCommand> <name>org.scala-ide.sdt.core.scalabuilder</name> <arguments> </arguments> </buildCommand>
- nature要素内に、scalanatureの設定を追加する。
<natures> <nature>org.eclipse.jem.workbench.JavaEMFNature</nature> <nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature> <nature>org.eclipse.wst.common.project.facet.core.nature</nature> <nature>org.eclipse.jdt.core.javanature</nature> <nature>org.eclipse.wst.jsdt.core.jsNature</nature> <nature>org.scala-ide.sdt.core.scalanature</nature> <!-- 追記 --> </natures>
- Eclipseを起動する
scalabuilderは、Javaソースを見つけると処理をjavacに渡してくれるようなので、javabuilderはコメントアウトでOKです。
機能のテンプレ化に関する考察
大規模システムのフレームワークとか作っていると、まずは「機能の標準化」というお題目で、画面の流れのモデルケースを作り(例:検索→一覧→選択して編集→DB更新→検索画面へ戻る)、個別機能の設計ではこのモデルケースに極力準拠してもらいつつ、実装作業が始まる前にモデルケースの実装例をテンプレとして提示する、ということをよくやります。
ただ、これを素直にやっちゃうと、テンプレをコピペした機能が何十と出来上がっちゃって、標準そのもののバグや仕様変更があると莫大な修正工数がかかる、ということになりがちです。
テンプレをコピペさせるくらいなら、フレームワークのAPIに封じて、個別に書く部分(画面やSQL、独自の論理チェックなど)を極小化することを考えたほうがよいのは、ちょっと考えればわかることですね。
この辺を突き詰めたのがSpring Rooだったりするわけですが、本ブログでは、一歩下がって、「プロジェクト毎モデルケースのフレームワーク化」を容易にし、かつカスタマイズ柔軟性を確保する方向で考えてみます。ScalaにおけるTraitのしくみがうまく使えそうな予感。
フレームワークとして共通化する部分の抽出
処理の整理の段階で大体分類は終わってるんですけど、基本的には、前回([id:shout_poor:20100817#1282021845])での整理のうち、「本処理」の部分が個別ロジックとなり、それ以外のところは共通化できると考えます。
以下、切り分けが微妙な部分の検討。
入力パラメータの整合性チェック
入力パラメータのチェックは、画面ごとに設計してしまいがちですが、本来は項目の性質によって横断的に決められるものです。例えば書籍の管理コードであるISBNコードは、どの画面であっても英数13桁で入力されなければ困りますし、日付型の項目であれば、その用途に関わらず、入力形式は原則アプリケーション内で一致させた方がいいでしょう。
従って、最低限の整合性チェックは共通機能として、同じ名称の項目であれば同じチェックがかかるようにします。