JUnit4のテストとコンストラクタ

Spiderの開発では、JUnit4による自動テストを行っています。オブジェクトの検索処理が心臓部となるので、その正確性を確認しておかなければなりませんし、性質上テストしやすいものでもあるからです。

JUnit4は、アノテーションを使ってテストメソッドなどを指定します。具体的には、@Testを付したメソッドはテストされ、@Before@Afterを付したメソッドはテストメソッドの実行前後に呼び出されます。

ここで、見落としがちなのは、テストメソッドを有するテストクラスのコンストラクタがいつ呼び出されるのか、という点です。

わたしの認識では、何度もテストクラスのオブジェクトを構築するのは無駄ですから、テストクラスのオブジェクトが構築されると、以後、そのオブジェクトが使い回されるものと思っていました。しかし、実際には、テストメソッドを実行するたびに新たなテストクラスのオブジェクトが構築されます。これを見落とすと、初期化されているはずのフィールドが初期化されていなかったり、値を設定したはずのフィールドに何も設定されていないという事態が生じることになります。

なお、JUnit4のAPIリファレンスには、「To run the method, JUnit first constructs a fresh instance of the class then invokes the annotated method.」と書かれており、アノテーションが付されたメソッドを呼び出すときには、JUnitは新しいインスタンスを生成するということが明記されています。その他に、このことについて言及しているドキュメントはあったのでしょうか。自分としては、見かけませんでした。

GoogleアプリケーションとRAMディスク

Google ChromeやGoogle Earthのインストーラを起動しても、何も起こらず、インストールできないという問題が生じました。

しばらく原因が分からなかったのですが、こちらのページによれば、一時ディレクトリをRAMディスク上に設定している場合(さらに一般的にはCドライブ以外に設定している場合)に生じる問題であるようです。セキュリティ上の問題なのか、なんなのか、よく分かりませんが、一時ディレクトリ(環境変数TMP)の指し示す位置をCドライブ上(デフォルトの値が望ましい)にすることで解決することができます。

こんなケースがあるとは思いもよりませんでした。

なお、Google Chromeのヘルプページにもインストールができない場合の項目が存在するのですけれども、まさか「RAMディスクに割り当てている場合には一時ディレクトリを利用できない」とは考えないので、あまり解決になっていないような気もしますね。

NVIDIAのOptimusとPlayOnline Viewer

ThinkPad X60の熱問題に耐えられなくなり、ThinkPad T420を購入しました。届いたのでFF11をインストールしたのですが、PlayOnline Viewerが起動しない(原因不明のエラーにより「動作を停止しました」状態になる)という不具合に遭いました。まだ根本的には解決していないのですが、対症療法的なものを見つけたので、ここにメモしておきます。

こちらの記事によれば、PlayOnline Viewerが起動しないのはNVIDIAのOptimusに問題があるとのことです(ThinkPad T420固有の問題というわけではないらしい)。何が原因なのかわかりませんが、PlayOnline Viewerを起動するとNVIDIAのドライバ(nvumdshim.dll)でエラーが生じて、そのままハングアップしてしまうようです。このハングアップが厄介で、これまた理由は分からないのですが、シェルを巻き添えにしてしまいます。タスクマネージャすら応答しなくなりプロセスの終了もさせられなくなるので、Ctrl+Alt+Deleteでログオフを選ぶしかありません。

対症療法的な対策として、C:\Windows\SysWOW64\nvumdshim.dllを一時的にリネームするなどして除いておくというものがあります。前記DLLが存在しない状態ならばPlayOnline Viewerを起動することができるようになります(これまたこれまた理由が分かりません)。ただし、そのままではFF11が起動しなくなるので、PlayOnline Viewerが起動した後は元の状態に戻しておかなければなりません。

また、nvumdshim.dllを置き換えるという手段があります。古いドライバのDLLに置き換えることで、不具合を避けることができるとのことです。置き換えると、確かに、PlayOnline Viewerがエラーで停止することはなくなります。ただし、他に影響が生じない保証はありませんし、リンク先のファイル自体が安全である保証もありません(いちおうAvira Antivirusでスキャンしましたが何も検出されませんでした)。くれぐれも自己責任で試してみてください。また、既存のファイルはリネームするなどして保存しておくようにしましょう。何らかの問題が生じても、ファイルを元に戻せば復旧できるものと思われます。こちらの手段をとる場合、次のようなスクリプトを使う必要はありません。

ファイルを置き換えた場合、他のDirectX使用アプリケーションが正常に動作しなくなるおそれがあります。実際、Google EarthのDirectXモードは起動しませんでした(OpenGLモードならば起動できます)。

Continue reading

Radio SureとEclipseを使うときの注意事項

Radio Sureという、インターネットラジオプレイヤーがあります。有料版もありますが、無料版でも十分に常用できる、完成度の高いソフトウェアです。海外の局しか登録されていませんが、洋楽をメインに聴いているのであれば、むしろその方が望ましいでしょう。iTunesにもインターネットラジオ機能が搭載されていますけれど、Radio Sureの方が軽く、また、録音機能も備えているので高機能だと思います。

このソフトウェア、Eclipseと同時に使うと、ちょっとした不具合を引き起こします。それは、Eclipseで名前変更(リファクタリング)をキーボードから実行することができなくなってしまうというものです。メニューから選べば、問題なく実行することができます。

ある日突然、キーボードから名前変更のリファクタリングができなくなってしまったので、どこかで何か起こっているのかなあ、と思っていたところ、Radio Sureで記憶にない録音データが作られていたので、原因が分かりました。

Radio Sureの設定に、「Hotkeys」という項目があります。この項目に、「グローバルホットキーを有効」というオプションがあります。これをチェックした状態にしておくと、Radio Sureがアクティブになっていない場合でも、録音などの操作ができるようになります。そして、ほかのアプリケーションのキー入力をフックして奪い取ってしまうようで、ほかのアプリケーションではそれらのキーが使えないことになってしまいます。

Eclipseで名前変更のリファクタリングに割り当てられているのは、Shift + Alt + R。そして、Radio Sureで録音に割り当てられているのもShift + Alt + R。ということで、このチェックを外せば衝突が回避されます。

アプリケーションを越えてのキーフックは危険ですね。

Firefoxが大量にメモリを使用する現象への対策(続報)

以前の記事の続報です。

おそらく、「It’s All Text!」というアドオンがメモリリークを起こしています。ただ、何が原因なのかは、まったく分かりません(ソースを見ることはできるので自分で突き止めることも不可能ではありませんが…)。このアドオンを有効にした場合、有効にしていない場合よりもメモリ使用量は増加しますし、タブを閉じて減少すべき場合にも減少しないという現象を確認しています。

ただ、他にもメモリリークを起こしているアドオンはたくさん存在するでしょうし(上記アドオンしか調べていません)、日頃見ているサイトによってもメモリ使用量は変わるものと思われます(特定の場合しかメモリリークを起こさない可能性もある)。

そのため、「このアドオンを切ればメモリリークが一般的に解消される」という結論を導くことはできません。しかし、不要なアドオンをオフにすることで、メモリ使用量を軽減することができ、安定的に運用することができるということは言えると思います。

EclipseでSubversion(リポジトリー作成編)

Subversiveのチーム・プロバイダーとコネクターをインストールすると、Subversionを使ったバージョン管理をEclipse上で行うことができるようになります。

すでにSubversionを使ったプロジェクトを進めている場合は、「ファイル」→「インポート」から「SVN」→「SVNからプロジェクト」を選ぶことで、ソースコードなどをローカルにダウンロードできます。

これから新しくプロジェクトを作る場合は、「ウィンドウ」→「ビューの表示」→「その他」から「SVN」→「SVN リポジトリー・ブラウザー」を選びます。すると、同名のビューが開かれるので、リスト上で右クリックをしてメニューを表示し、「新規」→「リポジトリー」を選びます。小さなダイアログが表示されるので、テキストボックスにリポジトリーを作成する場所を入力します(「参照」でディレクトリツリーから選べます)。ここで指定した場所に、Subversionのリポジトリが作成されます。なお、リポジトリーとは、ソースコードなどプロジェクトの情報を蓄積する場所で、Subversionを使ううえで必須のものです。

リポジトリーの用意ができたら、パッケージ・エクスプローラーなどで、バージョン管理したいプロジェクトを選んで右クリックし、「チーム」→「プロジェクトの共用」を選びます。方式を選ぶダイアログが表示されるので、「SVN」を選んで進めます。使用するリポジトリーには、先ほど作成した、ローカルのリポジトリーを指定します。

EclipseでSubversion(インストール編)

唐突ですが、EclipseでSubversionを使うための方法について書いていきたいと思います。

なぜSubversionか。

なんででしょうね。

すみません、よく知らないんです。なにやら、このごろ流行っているらしいということくらいしか。このごろ? だいぶ前からありますね。CVSよりも優れたバージョン管理システムという話を聞いており、最近はSubversionが使われることも多いらしいということで、使ってみようと思いました。

EclipseでSubversionを使うためには、いくつかのプラグインを導入する必要があります。プラグインそのものにもいくつかの種類があるようですけれども、EclipseプロジェクトのひとつになっているSubversiveを導入することにします。なお、使うEclipseは3.6(コードネームHelios)です。また、MergeDocにより日本語化がなされていることを前提とします。

ホームページのDownloadsにはHelios用のリリース版が用意されているので、これを使います。zipをダウンロードするのもよいですが、アップデートサイトを利用した方が簡単でしょう。プラグインを配布しているサイトはEclipseに組み込まれているので、特別な追加処理は必要ありません。「ヘルプ」→「新規ソフトウェアのインストール」でインストールというダイアログを開き、「作業対象」の横にあるコンボボックスで「Helios – http://download.eclipse.org/releases/helios」を選びます。すると、下のリストにプラグインのカテゴリが表示されるので、「コラボレーション」を開き、「Subversion SVN チーム・プロバイダー(インキュベーション)」の横をチェックします。あとは、そのまま決定をしてインストールしていけば完了です。

次に、SVNコネクタが必要となります。Subversiveのドキュメントによると、チーム・プロバイダーのプラグインをインストールしてEclipseを再起動するとSubversiveコネクタを導入するためのダイアログが表示されるとのことです。しかし、わたしの環境では表示されなかったので、自前でインストールすることにしました。

Polarionというサイトに書かれているとおり、「http://community.polarion.com/projects/subversive/download/eclipse/2.0/helios-site/」を更新サイトのリポジトリーに追加して(「ヘルプ」→「新規ソフトウェアのインストール」→「追加」)(リポジトリーの名前は適当、たとえばSubversive SVN Connectors)、「Subversion SVNコネクター」の下にある「Subversion SVNコネクター」と「SVNKit 1.3.5 実装」にチェックをして、インストールします。ほかにJavaHLというものもありますが、実装方法の違いにすぎないので、SVNKitと好きな方を選んで構いません。ちなみに、SVNKitはPure JavaなSubversionの実装です。

チーム・プロバイダーとコネクターをインストールすれば、EclipseでSVNが使えるようになります。具体的な使い方については、次回ということで。

Spiderの実装 (2)

Spiderを実装するうえで頭を悩ませたのは、細かな違いを吸収し、統一された方法でデータベースにアクセスするためのDatabaseインターフェイスです。

インターフェイスのメソッドを実行するたびにSQLをコンパイルしていたのでは動作が重くなって仕方ありませんので、Connection#prepareStatement()でプリコンパイルしておかなければなりません。このタイミングとして、(1) Databaseの実装オブジェクトを構築するときにプリコンパイルする、(2) 各メソッドを実行するときにプリコンパイルする、という方法が考えられます。効率の面を考えれば、(2)ではメソッドを実行するたびにPreparedStatementのフィールドが初期化されているかを調べなければならないので、(1)の方が優れています。(1)は初期化に時間を要しますが、すべてのメソッドが使われるのならば、総計では変わりありません。nullチェックが入る分、(2)の方が(若干ですが)重くなります。

ただ、効率よりも考えるべきなのは、コードの見やすさです。(1)はコンストラクタ、または初期化メソッドにすべてのSQLを書き込まなければならず、そのSQLを使う部分とは離れてしまうことになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Database1 implements Database
{
    public Database1 ()
    {
        this.prepared = connection.prepareStatement("SELECT ...");
        // 初期化するSQLが増えていけば、ここがどんどん伸びていく
    }
    public int getData (long id)
    {
        this.prepared.setLong(1, id);
        ResultSet rs = this.prepared.executeQuery();
        // ...
    }
}

もっとも、エディタの上下分割機能を使えば解決するという問題でもあります。しかし、個人的には、そのような機能を使わずとも、SQLと、そのSQLを利用するコードは近くに配置したいという気持ちです。

一方、(2)の方法を採るのならば、SQLと、そのSQLを利用するコードは近くに配置することができるようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
public Database2 implements Database
{
    private PreparedQuery prepared;
    public int getData (long id)
    {
        if (this.prepared == null) {
            this.prepared = connection.prepareQuery("SELECT ...");
        }
        this.prepared.setLong(1, id);
        ResultSet rs = this.prepared.executeQuery();
        // ...
    }
}

SQLが遠く離れていると、何番目のパラメータに何のデータを入れればよいのか分かりにくくなってしまいますが、このようなコーディング方法であれば必ず近くに配置されるので分かりやすさが向上します。しかし、この方法では、メソッドが実際に呼ばれるまでSQLがコンパイルされません。そのため、SQLに文法エラーがあるとき、発見が遅れることになります(先にコンパイルしておけばオブジェクト構築時にエラーが検出される)。もっとも、すべてのメソッドについてテストするようにしておけば、これは問題になりません。開発スタイルとしても、ちゃんとテストは行うべきです。次に、毎回nullチェックが入るため、動作速度が(きわめてわずかですが)遅くなります。フィールドは最初に初期化された以降nullになることがないので、このチェックは最初の一回以外すべて意味を持ちません。個人的には、このような無駄が気になって仕方ありません。

以上から、(1)のように、あらかじめSQLをプリコンパイルするようにしつつ、(2)のように、SQLと、そのSQLを使うコードを付近に配置するという方針を考えることにします。

Firefoxが大量にメモリを使用する現象への対策

しばらく前まで、Firefoxが異様にメモリを消費して困っていました(約600MB)。そのおかげで頻繁にスワップが発生し、Firefox自体はもちろんのこと、システム全体のパフォーマンスも低下してしまっていました。

どこに原因があるのかハッキリとは分からなかったのですが、最近、あまり使っていないアドオンを無効化することで劇的な改善がみられました。おそらく、停止したアドオンがメモリを浪費する原因になっていたものと思われます。

停止前は増えるばかりだったメモリ使用量が、停止後は増減を繰り返して一定の値を保つようになっています。アドオンにメモリリークがあって、確保したオブジェクトを解放していないのだと考えられます。

どのアドオンが原因かは確認していないのですが、確認が取れ次第、続報として書き込みたいと思います。

どうもFirefoxの動作が重いなと感じている方は、使っていないアドオンをいったん無効化してみることをオススメします。メモリ使用量はタスクマネージャを使うことで確認することができます(firefox.exeのプロセスのワーキングメモリ量をみればよい)。

Spiderの実装 (1)

インターフェイスだけ定義してもプログラムは動作しませんので、実装をしなければなりません。

Spiderシステムは情報を蓄積して扱いやすいインターフェイスを提供するためのものですから、情報を蓄積するストレージが必要になります。フリースタイル(自前でディスクへの保存などの機能を実装する)でもいいのですが、効率の良いものを構築する自信はありません。また、かなりの手間がかかるので、完成がいつになるのか分かりません。そこで、リレーショナルデータベースをストレージシステムとして利用することにします。幸いなことに、JavaにはJDBCという仕組みが用意されているので、データベースの操作は比較的に楽です。また、著名なデータベースのJDBCドライバも公開されています。

どのデータベースを利用するか悩むところですが、まだシステムが成熟しておらず、テスト段階であることを考えれば、手軽に使うことのできるものが望ましいといえます。とはいえ、いつまでも簡易なデータベースに頼っているようでは、システムやデータの規模が大きくなってきたときに対応できなくなるおそれがあります。そのため、特定のデータベースに依存しない設計を目指すことにし、最初は扱いやすいデータベース(SQLiteなど)で開発し、徐々に本格的なデータベース(MySQLなど)へ移行していくことにします。具体的には、NetworkやConceptはデータベースを抽象化したオブジェクトを介してデータベースと情報をやり取りします。そして、そのデータベースごとに仲介オブジェクトの実体を実装していきます。

簡略的に記述すれば、次のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package jp.blacksoft.spider_jdbc;
public interface Database
{
    /**
     * @param id
     *            調べるオブジェクトの概念ID
     * @return オブジェクトの作成日時, オブジェクトが存在しなければ{@code null}
     */
    public Date getConceptCreateDate (long id);
    // etc...
}
public class JdbcNetwork implements Network
{
    private final Database objDatabase;
    // etc...
}
public abstract class JdbcConcept implements Concept
{
    // ...
    @Override
    public Date getCreateDate ()
    {
        return this.getDatabase().getConceptCreateDate(this.numConceptID);
    }
    // ...
}

各データベース用の実装クラスでは、Databaseで定義されたインターフェイスに合わせた情報を返すように、各データベースに応じたSQLを記述します。