テスト・コードと行き当たりバッタリ設計の実例(その2)

その1(http://foobardam.hatenablog.com/entry/2012/12/24/060705)のつづき。

JavaBeansを全く知らない人向けのBeanの概要説明

本記事はテスト・コードと設計の説明が中心的な話題の予定なのだが、題材としてBeanの例を進めるのでBeanの話が多くなると思う。そのためJavaBeansが全く分からないという人は、これからの説明についてくるのが困難になることが予想される。ここではざっくりと説明しておく。
たとえば、以下のようなPersonクラスがあるとする。

public class Person {
	private String name;
	private int age;
	private String tel;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getTel() {
		return tel;
	}
	public void setTel(String tel) {
		this.tel = tel;
	}
}

クラスPersonは、プロパティ(name、age、tel)を持つBeanであるという。
JavaBeansには、Beanであることの厳密な必要条件があるのだけど、本筋から外れるのでここでは省略する。いずれ説明するときがあるかもしれない。実は、このPersonクラスはJavaBeansの必要条件を満たしていない*1ので、厳密にはBeanといいずらいのだが、そういった厳密さは目をつぶっていただきBeanとして扱うことにする。
詳細が知りたい人は、http://ja.wikipedia.org/wiki/JavaBeansを参照と書きたいところだけど、本当にJavaBeans初めてだと何が何だか分からないと思う。JavaBeansが出てきた背景が分からないと理解しにくいものがある。昔の自分がそうだった。
Beanとは部品であるという説明を受けることもあるが、ここではデータベースに入るエンティティ・データみたいなものだと思って欲しい。

Personの内容(プロパティ)を出力してみる

では、早速、Personの内容を標準出力に出してみる。

public class Person {
	private String name;
	private int age;
	private String tel;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getTel() {
		return tel;
	}
	public void setTel(String tel) {
		this.tel = tel;
	}
	public static void main(String... args){
		System.out.println(new Person());
	}
}

って、Personクラスにmainメソッドをつけただけ。
結果は、

Person@10b30a7

となった。Personオブジェクトのハッシュ値が表示された。
当然、Personクラスに

@Override
public String toString() {
	return "Person [name=" + name + ", age=" + age + ", tel=" + tel + "]";
}

のようにtoStringメソッドをオーバーライドして実行してあげれば

Person [name=null, age=0, tel=null]

のようにBeanのプロパティが見える結果になる。
ちなみに、このtoStringメソッドのオーバーライドは、Eclipseに入っていたソースコード支援機能を用いて半自動で作成した。

Personクラスをもう少し便利にする

ここで、オブジェクトを生成してから、いちいちset系メソッドでプロパティをセットするのが少し面倒である。その必要がないように便宜をはかる。引数でプロパティを指定できるコンストラクタを定義する。しかし、JavaBeansとして必要なのが、引数なしコンストラクタである。これがないと、JavaBeansの要件を満たさないのである*2。ここでは、引数なしコンストラクタは、呪文なようなものとしてとにかくつけておこう。本連載で困るようなことはないかもしれないが、Beanに引数なしコンストラクタを用意するのは良い習慣であると思う。

public class Person {
	private String name;
	private int age;
	private String tel;
	
	public Person(){}
	public Person(String name, int age, String tel) {
		super();
		this.name = name;
		this.age = age;
		this.tel = tel;
	}

	// getter ... setter (省略)

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + ", tel=" + tel + "]";
	}
	
	public static void main(String... args){
		System.out.println(new Person());
		System.out.println(new Person("太郎", 28, "090-1234-5678"));
	}
}

実行結果は、

Person [name=null, age=0, tel=null]
Person [name=太郎, age=28, tel=090-1234-5678]

少し脱線するが大事なことなので少し説明しておく。Javaの基礎中の基礎であるが、コンストラクタを明示的に定義しないとデフォルト・コンストラクタ(引数なしのコンストラクタ)が暗黙的に設定される。今回のように引数ありのコンストラクタを新たに定義してしまうと、デフォルト・コンストラクタ(引数なしのコンストラクタ)が設定されなくなってしまうため、JavaBeansの要件を満たすためには明示的に引数なしコンストラクタを記述する必要が生じる。JavaBeansをきちんと利用したい場合、このことを知らないと最初にハマる罠なので気をつけよう。

ここまでの成果

https://github.com/foobardam/SpyPrinter/tree/0eff9580f5159cd201e23b6d2f9d6ac0d7dfa1ef
ちなみに余談だが、本記事はWindowsで開発しているので、当初文字コードSJISで書いていた。しかし、githubでブラウザにてコードを閲覧すると文字化けするので、ソースコードutf-8で開発することにした*3

次回

今回はその1でも書いたが、toStringメソッドのオーバーライドは、(Eclipseの支援機能があるとはいっても)クラスごとに用意しなければならなかったり、プロパティが増える度に作り直さなければならない。つまり、汎用性がない。今回の目的は、汎用的に使えるprintメソッドを作ることとする。では、どうするのか?標準で入っているJDKjava.beansパッケージとjava.lang.refrectパッケージ配下にあるクラスを駆使するというのが直接的な答えになるのだが、そこまで解説していると今回のテーマからあまりにも脱線してしまう。よって、apache commonsのBeanUtilsライブラリを導入することとする。次回は、BeanUtilの導入からとしよう。

*1:直列化とか考えるとややこしくなる

*2:引数なしコンストラクタがJavaBeansの要件になっている理由をここでは説明しない。GUIのデザイナなどBeansを部品として扱うツールのときに問題が起きたりする

*3:この辺は少しだけ記事にした→http://foobardam.hatenablog.com/entry/2012/12/23/030351

テスト・コードと行き当たりバッタリ設計の実例(その1)

はじめに

統合開発環境(本記事ではEclipseを想定する)を使ったJavaでのテスト・コードについて書いてみたい。

テスト・コードというと、テスト駆動開発という言葉を思い出し、まずはテストを先に書いて、次に実装を書くものと考えている人も多いかと思う。
またテストという言葉から、品質を上げるために書くものと考えている人もいるかもしれない。

テスト・コードというのは、テストだけではない他の価値もあると、私は思っている。テスト・コードを書いている人は、実際にそう思っている人も多いだろう。
よって、テスト・コードというコードという言葉の前にテストという単語を置いただけのネーミングで、その実態をすべて表しているかは疑問である。しかし、本記事ではいい名前も思いつかないので世間に合わせて、そのコードをテスト・コードと呼ぶことにする。

テスト・コードを書いたことがあまりない人は、教条的にテスト・ファーストを守ろうとしたり、とにかく全テスト・パターンを網羅しようと意識しすぎたりして、億劫になっているかもしれない。でも、そんなふうに肩肘はらずに、もっとリラックスしてテスト・コードとつきあっていけばいいのではないだろうか?
「テスト・ケースも漏れたら漏れたで仕方ない。書かないよりはマシ」くらいの気持ちで、慣れないうちは書き始めればいいと思う。完璧主義者にはつらい心持ちだろうが、とにかく書きつづけて慣れていかないと、その良さは実感できないだろう。コストを払ってまでテストを書いたほうが良いかと聞かれれば、私の個人的見解ではコストを払ってでも良いと答える。

ただし、品質をあげるためだけではない。場合によっては、あまり品質向上に付与しないテスト・コードもあると思う。その場合でも、テスト・コードを書くべきだ。工数がないなら、ないなりに書けば良い。たとえば、調査で分かったことは調査結果としての記録コード(つまり、テストとしての意味は薄く備忘録的だが、一般的にはテスト・コードとよばれるもの)にしておけばいいと思う。自分の場合のテスト・コード(特にJava on Eclipse)に対しては以下の効用があると思っている。

  1. 条件もれに気づきやすい。nullチェックや、境界値など、メソッドの引数に対する考慮漏れに気づくことが多い。
  2. 呼ぶものと呼ばれるものの分離が自然と行われる。よって、コンポーネント化が自然と行われる。疎結合になる傾向が強くなる。
  3. プログラムを起動するエントリー・ポイントとして活用する(通常は、static void mainメソッドがそれに相当すあたるが、EclipseプラグインのJUnitを使えばmainメソッド以外からでもプログラムが起動可能になる。その他のIDEも同等の機能を持つかもしれないが私はEclipseしか使わないので、よく知らない)
  4. 最初にがんばって設計し、一発で実装を決めていかなくても、そこそこの良い実装になる。昔の設計→実装というよりは、設計と実装を何度も行ったりきたりする往復しつつ、コードが洗練されていく。


自分が、どのようにテスト・コードとつきあっているのかという実例を、これからJava言語で書いたプログラムを例にとって、プログ記事として書いて行こうと思う。もちろん、最初からきっちりと設計せずに、だらだらと書いていくのにコードが徐々に洗練されいく様子も書ければいいなとは思うが、はたしてうまくいくか。ちなみに、この記事連載ではユニット・テストのテスト・コードの話に限定する予定である。

これから作るコードの動機となるコード

とりあえず、下記のようなコードを対象としてみよう。

package hoge;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Sample {
	static class SomeBean {
		String str;
		Integer i;
		
		public SomeBean(String str, Integer i) {
			this.str = str;
			this.i = i;
		}
		public String getStr() {
			return str;
		}
		public void setStr(String str) {
			this.str = str;
		}
		public Integer getI() {
			return i;
		}
		public void setI(Integer i) {
			this.i = i;
		}
		
	}
	public static void main(String[] args) {
		List<SomeBean> l = new ArrayList<SomeBean>();
		l.add(new SomeBean("hoge1", 77));
		l.add(new SomeBean("hoge2", 78));
		l.add(new SomeBean("hoge3", 79));
		System.out.println(l);
	}

}

を実行すると

[hoge.Sample$SomeBean@1a758cb, hoge.Sample$SomeBean@1b67f74, hoge.Sample$SomeBean@69b332]

と表示される。見てもらえば分かるとおり、リストの中は、SomeBeanオブジェクトのハッシュ値が表示されていて、SomeBeanのプロパティの中身("hoge1"とか77とか)が分からない。EclipseなどのIDEを使えばデバッガが利用できるのでSomeBeanの中身は、すぐに分かる。
f:id:foobardam:20121215191947p:plain
しかし、いつでもデバッガが使えるとは限らない。また、データが大量(上記コードの例で言えば、リストの要素数、つまりSomeBeanのインスタンスが大量に100個とかになってくるとデバッガでインタラクティブに見ていくには、そうとうしんどい。では、どうすべきか?
究極的には、printで出力するのが王道である。簡単に言えば、ログとか標準出力に、SomeBeanのプロパティを出力すれば良い。
単純にSomeBeanにプロパティ値を表示するように、toString()メソッドをオーバライドすればことは解決する…

のか?

この方法だと、クラスごとにtoStringをオーバーライドにしていかなければいけなくなる。さらに、Beanのプロパティに別のBeanがあって、そのBeanのプロパティにさらに違うBeanがあって、みたいなBeanが数珠つながりでカスケードしているような構造の場合は、どうやって出力すれば良いのか?悩みは尽きない。

ここまでの成果

githubにソースコードが進化していく過程を記録しておこうと思う。とりあえず、ここまでの成果は、以下の通り。
https://github.com/foobardam/SpyPrinter/tree/cbb5fd00999b061a6fb05115f0bd4ddb82450784
Eclipseのプロジェクトが入っている。

次回は

この悩みを解決するには、汎用的なprintメソッドを作ればいいのである。さて、次回は、このネタを題材に、汎用printメソッドを作っていこうと思う。もちろん、自分がどのようにテスト・コードを作っていくのかというのも書いてみたい。

githubとgitkとWindowsと文字コード

普段、Windows上で開発しているときは、Gitはmsysgitを使っている。
文字コードsjisにしている。
コミット文章は、utf-8にしている。
自分は、gitkを多用しているが、この状態で何も困らなかった。

ところが、本日、githubにソースコードをアップして文字化けすることに気づいた。
どーしよー。
世の中utf-8に向かっているし、Windwosでさえ内部的にはユニコードで処理しているので、いつまでsjisなのよというのもあるのでutf-8にすることにした。ところが、gitkのソースコードの文字が化ける。gitkは、自分の生命線にあたるので文字化けは非常に困る。ググッて以下のように解決することにした。

git config gui.encoding utf-8

gitkは、OSの文字コードに合わせて表示するらしい。コミットはutf-8みたいなんだけどな。
とりあえず、リポジトリ単位で設定することにする。人によっては、--globalオプションで統一してしまっていいかもしれない。

HaskellとUTF8での日本語文字列の取扱い

Haskellの日本語の取扱いで困っているような同じような人(Haskell初心者さん)へ向けて。本記事では、Ubuntuで全てUTF8で統一しているにも関わらず困った事象です。WindowsでDOS窓環境だったりすると、また違った困りかたになると思います(DOS窓SJISのため)。

概要

Haskellを使ってHTMLのスクレーピングしようとしたときに、はまった日本語の取扱いについて書く。

具体的に困った事象

UTF8で保存したファイルのHaskellプログラムの中で

hoge == "次ページ"

のように、ごく普通に日本語の文字列マッチングした場合の話。
hogeは、tagSoupを通して加工した文字列が入っている変数。次ページというのが、ここでマッチさせたい日本語の文字列。
Webブラウザで保存してあったHTMLファイルをreadFileで、読み込んだ場合はTrueが返ってくる。しかし、Network.HTTP.simpleHTTPでネットから、同じホームページを直接読み込むと、マッチングにFalseが返ってくる。同じプログラムのはずなのに。もちろん、utf8で記述されているホームページですよ。

解決方法

cabalでutf8-srtingをインストールする。
そこで、一時的にファイル保存せずにネットから直接、読んできたときに、

hoge == Codec.Binary.UTF8.String.encodeString "次ページ"

とencodeString関数を通せばTrueになった。

原因追求編

環境

  • Ubuntu 12.04
  • GHC7.4.1
  • cabal install tagsoup
  • cabal install utf8-string

文字コードについて

すべてUTF8のはず。つまり、、、、

  • HaskellプログラムのファイルはUTF8で保存する。
  • 対象ホームページのエンコードもUTF8で記述されている。
  • 対象ホームページを「Webブラウザ」で、ファイルとして保存。当然、ファイルはUTF8で保存されている。

調査

GHCの日本語の扱いを把握する必要がある。後ほど、推測した原因を書くが、とりあえず、GHCiで確認できる事実を列挙する。

Main> putStrLn "日本語"
日本語
Main> print "日本語"
"\26085\26412\35486"
Main> Codec.Binary.UTF8.String.encode "日本語"
[230,151,165,230,156,172,232,170,158]
Main> Codec.Binary.UTF8.String.encodeString "日本語"
"\230\151\165\230\156\172\232\170\158"
Main> Codec.Binary.UTF8.String.decodeString "\230\151\165\230\156\172\232\170\158"
"\26085\26412\35486"
Main> putStrLn "\26085\26412\35486"
日本語

推測1(GHCとライブラリ)

GHCiで調べた事実と、http://itpro.nikkeibp.co.jp/article/COLUMN/20100406/346695/?P=5
から、以下のように推測している。

  • GHCは、デフォルトのままでUTF8を扱える。
  • printの場合は、ShowクラスでUTF8を、数値で表示する。
  • putStrの場合は、そのまま日本語として表示される。
  • readFileで、UTF8のファイルは、そのまま読み込まれGHC側に解釈される。
  • Network.HTTP.simpleHTTPは、encodingした形で読み込まれる(というより、ネットワーク越しから生バイナリを、単純に取り込んでいるということなのかな?)
  • tagSoupは素通し。
  • Webブラウザで保存した場合は、WebブラウザがUTF-8と解釈してデコードしてくれている。たぶん。

推測2(utf8とUnicode

日本語周り(というかユニコード)の知識が、曖昧なため、間違っているかも。
単なるバイナリデータをutf8としてデコード(Codec.Binary.UTF8.String.decode)すると、Unicodeに変換されるということなのではないか?
つまり、"\230\151\165\230\156\172\232\170\158"が生のバイナリで、これをutf8としてデコードすると"\26085\26412\35486"というユニコードに変換される。

わかりやすく書くと

バイナリ  = utf-8としてdecode => ユニコード
バイナリ <= utf-8としてencode => ユニコード

という感じ。

まとめ

  • 最近のGHCとしては、内部的にユニコードで扱えるようになった。
  • Sytetem.IO.readFileや、putStrは、UTF8を自動的に解釈できるようになっている。
  • System.IO.printは、Showクラスで、ユニコードの数値で表示するようにされている。つまり、たとえ内部でユニコードとして扱えても、数値で表示するということ。
  • Network.HTTP系の関数とtagSoupにユニコードを認識する能力がなくて素通ししている。

と推測しつつも、にわか仕込みの調査なんで、いまいち理解していません。あしからず。

所感

すごいHaskell本程度を読んだ程度の自分(つまり、Haskell初心者)には、本筋じゃない、こういう細かいところを調査することが面倒だな。普及しまくっているメジャー言語とかだと、日本語の取扱いごときでひっかからないと思う。
Haskellが、もっと普及して、Haskeller人口が増えれば、こういうことを気にしなくてもよいようになると思う。

正規表現からの脱出

はじめてのHaskellプログラム http://foobardam.hatenablog.com/entry/2012/07/15/000640 では、正規表現を用いてバッテリー残量プログラムを書いた。

haskellライブラリの正規表現で、=~には感動を覚えた。多相性ってこんな使い方ができるんだと感動ひとしきり。この様子は、siroccoさんの http://d.hatena.ne.jp/sirocco/20090416/1239852340 を参照すると分かる人には分かる。perlのnオプションで処理できるような行指向のテキスト加工処理(つまり、ちょっとしたテキスト加工処理)ならば、これで処理できそうだ。その代表として、バッテリー残量プログラムをあげることができるのではないだろうか。

だがしかし、一方で山本さんの http://d.hatena.ne.jp/kazu-yamamoto/20090309/1236590230 を読む。正規表現は好きなのだが、保守しにくいらしい。簡単な正規表現しか、実際の仕事では書く機会がなかったので、そういうもんなのかなと思ったけど、パーサコンビネータに、興味あったし、自分も正規表現を越えてやろうと思った。まあ、越えるというより脱出という感じ。

題材は、バッテリー残量プログラムとする。それでも、Haskell初心者(start haskellの簡単な練習課題に苦労するようなスキル)の自分には、かなり無謀な挑戦だった。モナドの理解も十分進んでいないうちに、Parsecに、いきなり挑戦してみた。結局、1週間も悪戦苦闘したことになる。

その結果が、これ。

 

import System.IO
import Text.Parsec
import Text.Parsec.String
 
 
getCapacitance :: String -> String -> Double
getCapacitance ptn content = either (\_ -> -1.0) (\x -> x) $ parse (capacitanceP ptn) "" content
 
capacitanceP :: String -> Parser Double
capacitanceP str = do
  manyTill anyChar (try (string str))
  spaces
  n <- many1 digit
  return $ read n
 
main = do
  state <- readFile "/proc/acpi/battery/BAT0/state"
  let current = getCapacitance "remaining capacity:" state
  info <- readFile "/proc/acpi/battery/BAT0/info"
  let full = getCapacitance "design capacity:" info
  putStr $ show (floor $ current / full * 100) ++ "%\n"

 

https://github.com/foobardam/practice-haskell/blob/57137863519521c88e3b9bb4dba15d88ff14fb87/batWoRegex.hs

かなり無理やり何とか書いた。Parsec2とParsec3の違いや、Parsecのソースコードを見て、モナド変換子をまだ理解していない段階で、頭が混乱し、Eitherをどうやって扱うか分からない状態で、無理やりEitherからRight値を取り出したりと、かなりひっちゃかめっちゃかだが、目的だけは達成できた苦労の結晶である。

結論は、状況に応じて臨機応変に、正規表現を使うときは使うしParsecで行くときは行くという柔軟な姿勢が一番良いようだ。

 

 

Gtk2hsのインストールメモ

WindowsにGtk2hsを入れた。忘れないようにメモ。

  1. http://www.gtk.org/download/win32.phpからall-in-one bundleをダウンロードする。
  2. zipを展開して、スペースの入らないパスになるような位置におく。
  3. C:\gtk\binのように、上記で置いたbinフォルダにパスをはっておく。
  4. cabal install gtk-buildtools
  5. cabal install gtk

でOK。重要なのは、スペースの入らないパス上に置くということ。

バッテリ残量プログラム(Java版)

昨日ブログに書いたバッテリー容量残量表示HaskellプログラムをJavaで書いてみた。

 

基本的に、Ubuntuでは、

 

 

 

$ cat /proc/acpi/battery/BAT0/info

present:                 yes

design capacity:         74880 mWh

last full capacity:      73830 mWh

battery technology:      rechargeable

design voltage:          14400 mV

design capacity warning: 3691 mWh

design capacity low:     200 mWh

cycle count: 0

capacity granularity 1:  1 mWh

capacity granularity 2:  1 mWh

model number:            42T4776

serial number:            2141

battery type:            LION

OEM info:                SONY

 

 

 

のように表示されるのを利用している。
 
 
package battery;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
 
public class Bat {
public static int getNumber(final String ptn, final BufferedReader state) throws IOException {
for(String s = state.readLine(); s != null; s = state.readLine()){
Matcher m = Pattern.compile(ptn + "\\s*(\\d+).*").matcher(s);
if(m.matches()) return Integer.parseInt(m.group(1));
}
return -1;
}
 
public static int getCapacitence(String path, String ptn){
BufferedReader state = null;
int result = -1;
try {
state = new BufferedReader(new FileReader(path));
result = getNumber(ptn,state);
state.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
 
public static void main(String[] args) {
int full = getCapacitence("/proc/acpi/battery/BAT0/info", "last full capacity:");
int now = getCapacitence("/proc/acpi/battery/BAT0/state", "remaining capacity:");
System.out.format("%.0f%%%n", now * 100.0 / full);
}
 
}


やっぱり、Javaは冗長過ぎて嫌になる。