2012年9月23日日曜日

ClojureでDesign By Contract

Clojureでは、関数を定義する際に事前条件と事後条件を定義できます。
この機能を使えば、Design By Contract(契約による設計)を容易に実践できます。

事前条件を定義する
さっそく実験してみます。
「パラメータが文字列であること」という事前条件をもつ関数aを定義しました。

user=> (defn a [x] {:pre [(instance? String x)]} x)
#'user/a
user=> (a 1)
AssertionError Assert failed: (instance? String x) user/a (NO_SOURCE_FILE:1)
user=> (a "123")
"123"
view raw assert.clj hosted with ❤ by GitHub
パラメータとして1を渡したときにAssertionErrorが発生しました。

Assertionを無効にする
Assertionを無効にするには、*assert*へfalseをセットします。
この値はコンパイル時に参照されるため、REPLの場合はdefnを実行する前に設定しておく必要があります。
*assert*へfalseを設定してから、上と同じ関数を定義しました。

user=> (set! *assert* false)
false
user=> (defn a [x] {:pre [(instance? String x)]} x)
#'user/a
user=> (a 1)
1
user=> (a "123")
"123"
パラメータとして1を渡しても例外が発生しなくなりました。

例をもう少し
最後に、もう少し実践的な例として日付の比較をする関数を上げておきます。
この関数はCalendar.beforeを呼ぶため、パラメータはCalendarクラスのインスタンスでなければいけません。

user=> (import java.util.Calendar)
java.util.Calendar
;; 関数定義
user=> (defn before? [base comp]
#_=> {:pre [(instance? Calendar base)
#_=> (instance? Calendar comp)]
#_=> :post [(instance? Boolean %)]}
#_=> (.before base comp))
#'user/before?
;; 動作確認
user=> (def cal1 (Calendar/getInstance))
#'user/cal1
user=> (def cal2 (Calendar/getInstance))
#'user/cal2
user=> (before? cal1 cal2)
true
user=> (before? cal2 cal1)
false
;; Calendar以外を渡してみる
user=> (before? 1 2)
AssertionError Assert failed: (instance? Calendar base) user/before? (NO_SOURCE_FILE:1)
view raw before.clj hosted with ❤ by GitHub
事前条件と事後条件をシンプルに定義できるのが素敵だと思いました。

2012年7月30日月曜日

DropDownChoiceでenumを使用する

突然ですが、enumって便利ですよね。
業務で使用する場面としては、"○○区分"というものを表現するのによく使います。
そしてそれを画面では<select>として表示することもよくあります。
このとき、画面に表示するテキストをenum自身から分離するための方法です。

enumを定義する

普通にenumを定義します。
public enum RockPaperScissors {
ROCK,PAPER,SCISSORS;
}

アプリケーションのプロパティファイルに表示する文字列を定義する

型名.フィールド名=文字列の形式で定義します。
# for RockPaperScissors
RockPaperScissors.ROCK=\u30b0\u30fc
RockPaperScissors.PAPER=\u30d1\u30fc
RockPaperScissors.SCISSORS=\u30c1\u30e7\u30ad

DropDownChoiceのコンストラクタにEnumChoiceRendererを渡す

public class RockPaperScissorsDropDownChoice
extends DropDownChoice<RockPaperScissors> {
public RockPaperScissorsDropDownChoice(String id) {
super(id,
Arrays.asList(RockPaperScissors.values()),
new EnumChoiceRenderer<RockPaperScissors>());
}
}

画面ごとに表示するテキストを変えたい場合、以下のようにします。

  1. 画面ごとにプロパティファイルを作成する
  2. EnumChoiceRendererのコンストラクタにPageのインスタンスを渡す



2012年6月26日火曜日

AngularJSとCompojureによる簡単なサンプル

Googleが開発しているAngularJS(以下angular)のバージョンが1.0になったらしいので、サンプルを作ってみました。最近はClojureに対する愛が高まっているので、サーバーサイドにはCompojureを選びました。angular-Compojure間をAjaxで通信することを目標に、一行掲示板のようなものを作りました。clj-anglr

苦労したこと
angularから送ったJSONをCompojure側で受け取るところ。

;; Good
(defroutes
(POST request (:param (read-json (slurp (:body request)))))
...)
;; Bad
(defroutes
(POST [param] param)
...)
view raw sample.clj hosted with ❤ by GitHub
Compojureのサンプルを検索してすぐに見つかるのはBadのほうだと思いますが、この書き方ではangularから送ったJSONを受信できません。JSONはリクエストパラメータとしてではなく、リクエストのBODYに格納されるため、Goodのように自分でBodyから取り出す必要がありました。

感じたこと
angularのマッピングは快適です。特に$scopeへ追加したオブジェクトを更新すると、何もしなくても画面に反映されるのがすごいと思いました。業務で作っているシステムは頻繁に画面がちらつく・・・。
もう一つ、サーバーサイドにCompojureというのはアリだと思いました。Clojureのデータ構造はシンプルなのでJavaに比べてなんとなく不安でしたが、JavaScriptと通信する時にJSONに変換するのであれば、大して問題にならない気がしました。むしろ、無駄な情報がないために見通しがいいとも感じました。

業務で使うことはまだないと思いますが、なかなか興味深いライブラリでした。

2012年3月23日金曜日

WicketTesterで独自のSessionを使用するには

アプリケーションで独自のSessionを使用するには、WebApplication.newSessionをオーバーライドします。
WicketTesterで単体テストをするときも、そのApplicationをコンストラクタに渡せば、そのSessionを使ってテストができます。
しかしこれは単体テストとしては今ひとつ。できればApplicationに依存しないテストを書きたい。Applicationのテストをしたいのではなく、Componentのテストをしたいのだ–。

MockApplicationを使う
何か適当な解決策はないものか、と考えたところで思いついたのが、MockApplicationを継承して使うという方法でした。
MockApplicationはWicketTesterのデフォルトコンストラクタが呼ばれたときに使用されるApplicationのため、もっともシンプルな解決策ではないかと考えました。
というわけで、MockApplication.newSessionをオーバーライドすれば独自のSessionが使えることを確認する試験を書きました。WicketTesterを起動した後、Sessionを取得して型を検査しています。

import org.apache.wicket.Session;
import org.apache.wicket.mock.MockApplication;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.WebSession;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
import org.apache.wicket.util.tester.WicketTester;
import org.junit.Test;
import static org.junit.Assert.*;
public class SetSessionTest {
@Test
public void testSessionCreated() {
WebApplication app = new MockApplication() {
public Session newSession(Request request, Response response) {
return new CustomizedSession(request);
}
};
WicketTester tester = new WicketTester(app);
Session session = Session.get();
assertTrue(session instanceof CustomizedSession);
}
static class CustomizedSession extends WebSession {
CustomizedSession(Request request) {
super(request);
}
}
}
これでまたTDDの道を極めたぜ。

2012年3月18日日曜日

Javaのtrimについて

Javaで末尾の全角/半角スペースをトリムする
気になったことがあったので僕も実験してみた。
気になったのは、次の2つ。
  1. 正規表現のコンパイルを一回にしたらどれくらい改善できるのか
  2. 末尾から空白を検索した方が早くね?
というわけでメソッドを2つ追加して実験してみた。
結果は、
  1. trim2と同じか少し遅いくらいまで改善
  2. trim3と同じか少し早いくらい
  3. trim1はダントツで遅い
といった感じになりました。String.replaceAllは遅いですけど書きやすいので、問題を把握した上で使いましょう。
import java.util.regex.Pattern;
public class Main {
private static final String VALUE = "あいう    ";
public static void main(String[] args) {
long[] times = new long[6];
times[0] = System.nanoTime();
for(int i = 0; i < 10000; i++) {
trim1(VALUE);
}
times[1] = System.nanoTime();
for(int i = 0; i < 10000; i++) {
trim2(VALUE);
}
times[2] = System.nanoTime();
for(int i = 0; i < 10000; i++) {
trim3(VALUE);
}
times[3] = System.nanoTime();
for(int i = 0; i < 10000; i++) {
trim4(VALUE);
}
times[4] = System.nanoTime();
for(int i = 0; i < 10000; i++) {
trim5(VALUE);
}
times[5] = System.nanoTime();
String fmt = "trim%d: %d";
for(int i = 0; i < 5; i++){
String str = String.format(fmt, i + 1, times[i + 1] - times[i]);
System.out.println(str);
}
}
public static String trim1(String value) {
return value.replaceAll("( | )+\\z", "");
}
public static String trim2(String value) {
StringBuilder sb = new StringBuilder(value);
for(int i = sb.length()-1; 0 <= i; i--) {
char c = sb.charAt(i);
if(c == ' ' || c == ' ') sb.deleteCharAt(i);
else break;
}
return sb.toString();
}
public static String trim3(String value) {
char[] ary = value.toCharArray();
char[] trimed = new char[ary.length];
for(int i = ary.length-1; 0 <= i; i--) {
if(ary[i] == ' ' || ary[i] == ' ') continue;
else trimed[i] = ary[i];
}
return new String(trimed);
}
private static Pattern pattern = Pattern.compile("( | )+\\z");
public static String trim4(String value) {
return pattern.matcher(value).replaceAll("");
}
public static String trim5(String value) {
for(int index = value.length() - 1; 0 <= index; index--){
char chr = value.charAt(index);
if(chr == ' ' || chr == ' '){
continue;
}
return value.substring(0, index + 1);
}
return value;
}
}
view raw Main.java hosted with ❤ by GitHub

2012年3月8日木曜日

Code Year 2012 - Week 8

今週は2回目のブラックジャックゲーム。
行数が多いので見にくい。JavaScriptにはJavaのimportにあたるような機能はないのかな?

2012年3月3日土曜日

Code Year 2012 - Week 7

今週の課題はループ。
forとwhileだけじゃなく、再帰を使ったループもあった。
再帰は普段使わないからけっこう手間取った。
8週目の課題がこなかったのはもたもたしてたからかな?