地球ウォーカー2

Scala, Python の勉強日記

equalsとhashcodeを実装する(Eclipseで)

今日発見して感動したのでメモ。
EclipseでequalsメソッドとhashCodeを簡単に実装できる。

手順

public class Book {
    /** ISBN */
    private String isbn = "";

    /** タイトル */
    private String title = "";

    /** 著者 */
    private String author = "";

}

↑このクラスに対する実装するとして・・・。

private String author = "";

の次の行にカーソルを移動し、「ファイルメニュー > ソース > hashCode() および equals()の生成」をクリック。
出てきたダイアログで、equalsとhashCodeに使用するフィールドを選択する。
f:id:hysa:20101108223536p:image

たったこれだけで、equals()とhashCode()が実装される。

public class Book {
    /** ISBN */
    private String isbn = "";

    /** タイトル */
    private String title = "";

    /** 著者 */
    private String author = "";

    /* (非 Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((author == null) ? 0 : author.hashCode());
        result = prime * result + ((isbn == null) ? 0 : isbn.hashCode());
        result = prime * result + ((title == null) ? 0 : title.hashCode());
        return result;
    }

    /* (非 Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Book)) {
            return false;
        }
        Book other = (Book) obj;
        if (author == null) {
            if (other.author != null) {
                return false;
            }
        } else if (!author.equals(other.author)) {
            return false;
        }
        if (isbn == null) {
            if (other.isbn != null) {
                return false;
            }
        } else if (!isbn.equals(other.isbn)) {
            return false;
        }
        if (title == null) {
            if (other.title != null) {
                return false;
            }
        } else if (!title.equals(other.title)) {
            return false;
        }
        return true;
    }
}

いかにも自動生成しましたって感じのソースだけど(特にequalsメソッド)、十分実用に耐えうると思う。

ずっと放置していたWebサービスを公開してみる - Ninewords

このサービスは現在休止中です

Nine Words - Daily "9" hot words

どんなWebサービス?

1日1回*1注目ワードを取ってきて、そのワードをYouTubeで検索した動画と紐付けて表示する。
注目ワードは以下のサービスのRSSから取得している。

戯言

  • 注目ワードのRSSYouTubeを組み合わせたら何か面白いことできないかなーと思って開発した。
    • いざ作ってみたら大して面白いものができなかったのでずっと放置してた。
  • "9"に特に意味はなく、レイアウト的に1ページに9個くらいが妥当かと思っただけ。
  • 特に更新する予定は無いのでAboutページはたぶん永遠に"Under Construction"のままだと思う。
  • 2010年6月21日からキーワードの収集を開始*2

*1:AM 11:00

*2:cronがうまく動かないことがあってキーワードを収集できていない日がある

GAE + Maven2 + Eclipseの連携 (2010/09最新)

GAE + Maven2 + Eclipseの連携で参考にさせていただいた記事が更新されていたのでメモ。

どうやらEclipse3.6の登場でm2eclipseプラグインに変更があったたため*1以前の方法が使えなくなったらしい。

新しいバージョンではプロジェクトを作成するmvnコマンドのarchetypeVersion1.1.2から1.3.1に変更された。

mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-4:generate -DarchetypeGroupId=org.beardedgeeks -DarchetypeArtifactId=gae-eclipse-maven-archetype -DarchetypeVersion=1.3.1 -DarchetypeRepository=http://beardedgeeks.googlecode.com/svn/repository/releases

参照先の記事では既存のプロジェクトに対するMavenビルダーの変更方法も紹介されている。

ちなみに、プロジェクト直下のwarフォルダ(targetフォルダへのリンク)は廃止されたらしい。

*1:主にMavenビルダーが変わったらしい

GAEをJUnitで単体テストするときにはまった3つの罠

はまりまくって貴重な祝日が台無しになったので憂さ晴らしエントリ。
やろうとしたことは、GAEのURLFetchServiceを使って取得したHTTPResponseをいじくりまわすロジックのテスト。

罠1:そのまま実行できない

何も考えずにJUnitでテストコードを書いて実行するとExceptionが発生する。

The API package 'urlfetch' or call 'Fetch()' was not found.

実行したのは以下のようなテストコード。デバッグしてみると、service.fetch(url)の部分で例外が発生している模様。

import static org.junit.Assert.fail;
import java.net.URL;
import org.junit.Test;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;

public class UrlFetchServiceTest {

    @Test
    public void test() {
        doTest();
    }

    private void doTest() {
        try {
            URL url = new URL("http://feeds.feedburner.com/hatena/b/hotentry");
            URLFetchService service = URLFetchServiceFactory.getURLFetchService();
            HTTPResponse response = service.fetch(url); // ← ここで例外が発生してしまう

            // responseをいじくりまわすメソッドを呼び出す

        } catch (Exception e) {
            fail(e.getMessage());
        }
    }
}

考えてもわからないのでとりあえずGoogleの公式ドキュメント(日本語版)を見たところ、ローカルの実行環境構築にはApiProxy.EnvironmentimplementsしたTestEnvironmentクラスを作成した上でApiProxyに設定すれば良いらしい。

ApiProxy.setEnvironmentForCurrentThread(new TestEnvironment());
ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(".")){});

見よう見まねでTestEnvironmentクラスを作った。ここまではうまくいった。
ところが、ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(".")){});を記述しようと思ったら・・・。

罠2:日本版のドキュメントが古い*1

ApiProxyLocalImplクラスのコンストラクタが呼び出せない><
どうやらSDKのバージョンアップでApiProxyLocalImplコンストラクタがpublicprivateに変わったらしい*2
途方に暮れつつググってたら、英語版のドキュメントを発見。曰く、

The most important class in this package is com.google.appengine.tools.development.testing.LocalServiceTestHelper, which handles all of the necessary environment setup and gives you a top-level point of configuration for all the local services you might want to access in your tests. In order to write a test that accesses a specific local service, create an instance of LocalServiceTestHelper with a com.google.appengine.tools.development.testing.LocalServiceTestConfig implementation for that specific local service, then call setUp() on your LocalServiceTestHelper instance before each test and tearDown() after each test.

つまり、テストコード内でLocalServiceTestHelperを使いたいサービスでもってインスタンス化し、テスト前とテスト後にそれぞれhelper#setUphelper#tearDownを呼び出せと。
これを踏まえて前のコードを変更すると、

import static org.junit.Assert.fail;

import java.net.URL;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalURLFetchServiceTestConfig;

public class UrlFetchServiceTest {
    // UrlFetchServiceTestを使うのでLocalURLFetchServiceTestConfigでインスタンス化する
    private final LocalServiceTestHelper helper =
        new LocalServiceTestHelper(new LocalURLFetchServiceTestConfig());

    @Before
    public void setUp() {
        helper.setUp();
    }

    @After
    public void tearDown() {
        helper.setUp();
    }

    @Test
    public void test() {
        doTest();
    }
    // .... 以下は前のコードと同じ
}

日本語版で書いてあった古いバージョンの方法よりかなりシンプルになっている。
コンパイルに必要なappengine-testing.jarMavenリポジトリにインストールされていれば

<dependency>
    <groupId>com.google.appengine</groupId>
    <artifactId>appengine-testing</artifactId>
    <version>1.3.7</version>
    <type>jar</type>
    <scope>test</scope>
</dependency>

を記述するだけで追加できる。まだインストールしていない場合はこのサイトで入手できる。

で、いざ実行!

The API package 'urlfetch' or call 'Fetch()' was not found.

・・・同じエラーが出た。

3.ランタイムライブラリが必要

まぁこれはドキュメントに書いてあるのを見逃していただけなんだけど、ランタイムライブラリにappengine-api-stubs.jar*3が必要らしい。

<dependency>
    <groupId>com.google.appengine</groupId>
    <artifactId>appengine-api-stubs</artifactId>
    <version>1.3.7</version>
    <type>jar</type>
    <scope>test</scope>
</dependency>

ライブラリを追加したら無事動いた。

感想

解決して良かった。そしてこれからは英語ドキュメントを先に読もう、と心に誓った。

その他メモ

  • LocalServiceTestHelperのコンストラクタは可変引数になっているため、複数サービスを含むテストをするときはまとめてインスタンス化できる。
// 例)Webサイトをスクレイピングしてデータベースに格納するロジックのテストを行う。
private final LocalServiceTestHelper helper
    = new LocalServiceTestHelper(new LocalURLFetchServiceTestConfig(),
                                 new LocalDatastoreServiceTestConfig()
                                 );
  • LocalServiceTestHelperの引数に指定可能なTestConfigクラスは以下があることを確認。
    • LocalBlobstoreServiceTest
    • LocalCapabilitiesServiceTest
    • LocalChannelServiceTest
    • LocalDatastoreServiceTest
    • LocalImagesServiceTest
    • LocalMailServiceTest
    • LocalMemcacheServiceTest
    • LocalTaskQueueTest
    • LocalURLFetchServiceTest
    • LocalUserServiceTest
    • LocalXMPPServiceTest

*1:2010/09/21現在

*2:どのタイミングで変わったのかはわからないが、少なくとも1.3.7ではprivateになっている

*3:使用するサービスによってはappengine-api-labs.jarも必要かも

ウェブユーザビリティの法則について

自分用メモ。ウェブユーザビリティの法則 改訂第2版を途中まで読んだ。

ユーザーは"実際には"どんな風にウェブを使っているのか

  • 人はページ内の文章を読まない。ざっと見るのみ
  • 人は最良の選択に時間をかけるよりも、ある程度満足できるところで妥協する
    • 最善なものを選ぶ(最適化)ではなく、最初に出会った妥当なものを選ぶ(満足化)

各ページのナビゲーション

どのページでも以下がわかるようにする

  1. サイトID
  2. ページ名
  3. セクション、サブセクション
  4. ローカルナビゲーション
  5. 「現在地」表示
  6. 検索

トップページ

以下がわかるようにする

  1. サイトのポイント
    • タグラインをつけるなど。
  2. どこから始めればよいか。