chofutaroメモ

ソフトウェアエンジニアによるソフトウェアエンジニアのためのメモ書き

OODBMSよりRDBMSが使われる訳

O/Rマッピングの役割とメリットより抜粋

Hibernateで理解するO/Rマッピング(1)から引用させていただきます。

リレーショナルデータベースに保存されたデータの寿命は、それを扱う個々の「アプリケーション」よりも長い場合があります。リレーショナルデータベースに対して無理にオブジェクト指向設計を持ち込むよりも、それぞれの最適な設計方法を共存させる方が得策といえるのではないでしょうか。

所感

O/Rインピーダンスミスマッチがあるなら、なぜオブジェクト指向データベースを使わないのか。OODBMSが登場してしばらく経つにもかかわらず、世間でRDBMSを使い続ける理由がどこにあるのか判りませんでした。上記My至言を拝見するまでは「実用的なOODBMSが作れないから流行らない」くらいに思っていました。

SwingWorkerを使ってみる

SwingWorkerを使って時間のかかる処理の状況をプログレスバーで表示してみました。主な課題は、この手の処理のコールドスポット(⇔ホットスポット。共通部品になりそうな部位)はどこか自分なりに感じ取ることです。

  • JDK6u24、NetBeans7.0β2

ネットを検索するとSwingWorkerに関するサンプルはたくさん見つけられるのですが、これらのコードは、ほとんどのケースで時間のかかる処理をGUI(Swing部品を取り扱っている部位)に直接埋め込んでいます。しかし、時間のかかる処理というのは本来GUIとは無関係であることが多いと思いますので、時間のかかる処理を別立てにすることを目標とします。

20110224180146

今回は、時間のかかる処理をEratosthenes#calculate()に作ってあります(MAX_NUMBER迄の素数を求める)。SwingWorker#doInBackground()内で時間のかかる処理を実行します。このメソッドはイベントディスパッチスレッド (EDT) ではありませんので、このメソッドで実行します。

EratosthenesオブジェクトはGUIやSwingWorkerに依存しません。共通部品のAbstractProgressListener(末尾”所感”参照)を通じてGUI側のオブジェクトに処理を委譲します。AbstractProgressListener派生の無名派生クラス#actionPerformed()がそれです。

actionPerformed()の中の処理は、他のサンプルにも見られる処理と同様で、Eratosthenes#calculate()にて実行中の処理状況をSwingWorker#publish(getValue())しています。それを受けて、SwingWorker#process()でプログレスバーを更新しています。これ以降は、通常のSwingWorkerの使い方同様だと思います。

public class AppPanel extends javax.swing.JPanel {

    private int MAX_NUMBER = 1000 * 1000;   //  対象数(最大側)
    private SwingWorker<Void, Integer> worker = null;

    private void startButtonActionPerformed(java.awt.event.ActionEvent evt) {
        worker = new SwingWorker<Void, Integer>() {
            @Override
            public Void doInBackground() throws Exception {
                // Eratosthenes.calculate()は時間のかかる処理でGUIとは無関係
                Eratosthenes.calculate(new AbstractProgressListener() {

                    @Override
                    public boolean actionPerformed() {
                        boolean canceled = isCancelled();
                        if (!canceled) {
                            publish(getValue());
                        }
                        return !canceled;
                    }
                }, MAX_NUMBER);
                return null;
            }
            @Override
            public void process(java.util.List<Integer> chunks) {
                setProgress(chunks.get(chunks.size() - 1));
            }
        };
        worker.addPropertyChangeListener(new ProgressListener(progressBar));
        worker.execute();
    }                                           

    private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {
        if (worker != null && !worker.isDone()) {
            worker.cancel(true);
        }
        worker = null;
    }
}

ちなみに、時間がかかる処理の本体であるEratosthenes#calculate()は、以下のようになっています。Swingには全く依存していません。

public class Eratosthenes {
    public static void calculate(AbstractProgressListener listener, int count) {
        :
        do {
            :
            //  GUIに状況を表示できるようパーセンテージをセットする
            listener.putValue(処理状況を表すパーセンテージ);
            //  GUI側に制御のトリガを与える。
            //  キャンセルならば終了するためbreak
            if (!listener.actionPerformed()) break;
        } while (まだ素数判断する数値が残っている);
        :
    }
}

これらの関係をクラス図にしてみます。

20110224180145

蛇足ですが、AbstractProgressListenerクラスはActionクラスで良いのかもしれません。今回は、私がうまくActionクラスを使いこなせなかったため、AbstractProgressListenerクラスを作成しましたが、既存のクラスで十分ならば既存のクラスを使うべきだと思います。

サンプルコード

所感

SwingWorker#doInBackground()の中で、時間のかかる処理を(が)委譲して(されて)います。その際にGUI側であるSwingWorkerと非GUI側であるEratosthenesの間を取り持つ橋渡し役は、Swingに依存しないクラスを使用すると、時間のかかる処理をSwingから切り離すことができました。
こうすることで、時間のかかる処理をCUI等Swingに依存しない部位にて再利用することができると思います。
また、SwingWorkerを包含するAppPanel側は、時間のかかる処理(ホットスポット)をGUIから引き離すことができました。今回は手が届きませんでしたが、時間がかかる処理の時に表示するプログレス・ダイアログをジェネリックなクラスでひとつ作っておけば、こちらも共通部品として再利用可能と思われます。

JMockitを使ってみる

JMockitを使ってみました。ネット上では”Java最強の単体テストライブラリ”との呼び声が散見されます。

インストール方法や簡単の使用方法は、id:torutkさんの[id:torutk:20101003]を参考にさせていただきました。以下に記すコードも、[id:torutk:20101003]に掲載されているコードをほとんどそのまま使わせてもらっています。
参考としてコードの内容をクラス図にしたのが下の絵です。

20110223110317

単体テストのテストケースとして、同値分割と境界値分析から以下のようなテストケースを挙げました。

  • 正常系
    • 30℃未満(暑くない)
    • 30℃以上(暑い)
  • 異常系
    • 委譲先のWeatherSensorが処理に失敗する

モック(今回はJMockit)を使わないとしたら、テストケース毎にWeatherSensorImplを作成して、テストケース毎にWeatherSensorImplを含むJARファイルを切り替える必要があるかもしれません。

今回は、そのような事態を避けるためJMockitを使います。それが以下のコードです。NonStrictExpectationsブロックがJMockit固有の処理になります。

@Test
public void 気温30度以上() {
    new NonStrictExpectations() {
        WeatherSensor mockSensor;
        {
            SensorFactory.getWeatherSensor();   returns(mockSensor);
            mockSensor.getTemperature();    returns(30.0);
        }
    };
    ThresholdMonitor monitor = new ThresholdMonitor();
    boolean result = monitor.isHot();
    assertEquals(true, result);
}
@Test(expected= IllegalStateException.class)
public void 委譲先のWeatherSensorが処理に失敗する() {
    new NonStrictExpectations() {
        WeatherSensor mockSensor;
        {
            SensorFactory.getWeatherSensor();   returns(mockSensor);
            mockSensor.getTemperature();    result = new IllegalStateException();
        }
    };
    ThresholdMonitor monitor = new ThresholdMonitor();
    boolean result = monitor.isHot();
}

JMockitを使えば、単体テストのために委譲先のWeatherSensorを自前で書き換える必要が無く、NonStrictExpectationsで示すブロック内に”テストのために期待する処理”を記述するだけで済みました。

サンプルコード

所感

開発費が少ないあるいは開発期間が短い場合、単体テストが薄くなる傾向があると思います。例えば、ユーザが「動けば良い」と言ってくだされば、単体テストは限定的に行い、結合試験にてビッグバンテストすることもあると思います。
ただし、開発者に任意というケースばかりでなく、例えば、ミッションクリティカルなシステムの場合はユーザが品質保証の方法について厳しいケースがあります。この場合、基本部品としてのクラスに対して、C0/C1/C2を100%網羅するような単体テストを義務づけるケースも少なくないと思います。
契約時には開発者のスキルや事情を勘案してもらえることが少ないと思いますので、単体テストまで言及しているような品質保障体制下で開発を行う場合、開発者は「いかに効率よく単体テストするか」頭を悩ませるものと思います。
モックは、そのような悩みをかなり軽減してくれそうです。
JMockitはかなりのテストケースをカバーできるように見受けられます。”JMockit最強”という説は”JMockitは他のモックライブラリに比べてカバーできるテストケースが最大”と同義だと思われます。

NetBeans7.0β2で日本語が入らない

NetBenasでバグらしき現象を発見しました。

現象

エディタで日本語が入力できなくなる。漢字変換キーが効かなくなる。

トリガ

不明

解消

問題のファイルを一旦閉じた後に再度開いて編集を開始すると、元に戻る。

swing.propertiesにシステムデフォルトのLook&Feelを設定する

Javaアプリケーションにおいて、Look&Feelを設定する方法を調べました。画面仕様を決める課程において試行錯誤できるよう、ボタン幅や背景色等の画面の共通仕様を簡単に設定・変更する方法が知りたかったためです。

id:torutkさんが[id:torutk:20080106]で触れられているように、Javaには以下の3つの方法があるようです。

  • プログラム中で記述
  • コマンドライン・オプションで指定する
  • JREのプロパティファイルで記述

試しに以下のようなファイルを作成して%JDK_HOME%\jre\lib\swing.propertiesとして保存すると、確かにWindowsっぽい見栄えになりました。

# Swing properties
swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel

すごく簡単に変更することができました。Windowsのプログラムばかり作っていた時に、X-Windowのメカニズムに感動したことを思い出しました。

サンプル

所感

画面を伴うシステムにおいて、ユーザにはJava(Swing)で作られていようが、C#で作られていようが、その点は重要ではないことが大半だと思います。ユーザにとっては見やすく、使いやすいデザインであれば良いはずです。プロとしては『JavaだからMetalなデザインにならざるを得ません』とは言えません。ユーザが『ここはもっとXXXにして』という要望をおっしゃるならそれに応えることになると思います。
一方開発者視座で考えると、ユーザの要望に応えるためにゴリゴリとプログラムを追加・修正していては開発費がいくらあっても足りません。従って、開発環境には、簡単にルック&フィールを変更する方法が備わっていることが肝要ではないでしょうか。Javaはその点が充実しているように感じます。
ちなみに、C#等、.NET Frameworkの場合において、JavaのLook&Feelのように後付け的な変更をしようとすると、テーマを作成することになるのでしょうか?

フォントの一括変更

複数の画面を使うプログラムに対して、フォント(見栄え)を統一する方法を調べました。画面仕様の調整において複数枚画面の見栄えを統一する必要は多々あり、そのデザイン要素の中でフォントが代表的な要素だと思ったからです。

@IT会議室で教えていただいたページに掲載されている方法を適用すると簡単に実現できました。

java.util.Enumeration keys = UIManager.getDefaults().keys();
while (keys.hasMoreElements()) {
    Object key = keys.nextElement();
    Object value = UIManager.get(key);
    if (value instanceof javax.swing.plaf.FontUIResource) {
        UIManager.put(key, f);
    }
}

ちなみに、この処理の呼び出しはSwingコンポーネントインスタンス化前に実行しておく必要があるようです。私は最初、以下のように記述して失敗しました。

(誤)
public AppFrame() {
    initComponents();
     //  setUIFont()はUIManagerを使ってフォントを変更する処理
    setUIFont(new javax.swing.plaf.FontUIResource("MS ゴシック", Font.PLAIN, 16));
}

(正)
public AppFrame() {
     //  setUIFont()はUIManagerを使ってフォントを変更する処理
    setUIFont(new javax.swing.plaf.FontUIResource("MS ゴシック", Font.PLAIN, 16));
    initComponents();
}

サンプル

所感

フォントは、年齢によって適切と考えられるサイズが異なり、フォントの種類は嗜好によるところが大きいと思います。よって、画面の見栄えに関する仕様調整は難しく、種類やサイズを固定するには試行錯誤が必要になりがちです。
従って、簡単に、一括して、種類やサイズが変更できる仕組みを備え付けておくことが肝要なのではないでしょうか。
C#(というか.NET Frameworkでしょうか)にそのような機能はないように思います。Look&Feelを比較的簡単に自作できる点も考えるとJavaも結構良いなと見直しました。