リファクタリング:Rubyエディション
献本いただきました。リファクタリング:Rubyエディション。
内容的には、コードをRubyで書き直しただけかなと読み進めるも、6.17あたりから様相が違ってくる。動的メソッド定義をどういう時に使うのか、とかevalをそのまま使うと遅い時もあるから、evalでメソッド定義をしようとか、動的言語を使う場合のリファクタリング技法も追加されているのだ。動的な言語を使っている時は、ともすると機能を使い過ぎて、どうしてもカオスの道へと転がり落ちる危険が増すので、こういう指標をベストプラクティスとしてまとめていくことが、重要となるのだろう。
個人的には、IDEやコンパイルエラーを活用することで機械的に行えたリファクタリングを、動的言語でどう解決するのか、という点に興味があったのだけど、この本の中では、まぁ普通にテストで乗り切るという感じだ。やはりこの点は静的な型付言語に分がありそうだ。
元のリファクタリング本は、人にあげてしまったんで、元からそうだったかは覚えていないのだが、この本では全く正反対のことがリファクタリングとして取り挙げられていることが面白い。例えばローカル変数を抽出すること、逆に問い合わせメソッドに置き替えて削除してしまうこと。ローカル変数を抽出し、そこに意味のある名前を与えることで、コードが読み易くなるなら、抽出すれば良いし、無意味なiとか、valueみたいな短命ローカル変数が存在するなら、削除すれば良い。ただ、どちらが良いのかはやはり個々のプログラマのセンスが試されるところだろう。
そういえば、メソッドチェインも良いとされる場合もあれば、悪いとされる場合もあるね。でも最近は積極的に使用される場合の方が多い気がする。傾向としてはデータの連鎖がある(つまり外部にデータの構造が晒されている)のは悪く、処理が連鎖するだけであれば良いのだと思う。つまりデータ構造は隠蔽すべきなのだ。
感心したのは、Extract Surrounding Methodに対する「サンドイッチメソッドの抽出」という訳語。なるほどね〜。これはセンスの勝利だ。
内容的には、Clean Codeと、激しくデジャブだったりする。Clean Codeはより実装寄りで、この本は、もう少し設計寄りかな。ただ、いずれにせよ、どちらの本もコードを読めないければ理解は困難だろうという点は同じだ。その点では「高尚なアーキテクチャ本」では無いが、その手の本よりも、ずっと重要なことが書かれており、これをないがしろにした開発プロジェクトは100%失敗するだろう。
Akumaで、Javaプログラムをデーモン化
Akumaを使用した、Javaプログラムのデーモン化を実験してみた。環境は、Ubunt 9.10 AMD64 IBM版Java 1.6.0(build pxa6460sr5-20090529_04(SR5))。
Akumaは、https://akuma.dev.java.net/からakuma-1.3-jar-with-dependencies.jarを入手。ファイル名が長いんで、akuma-all-1.3.jarにリネーム。
サイトに掲載されているサンプルは、クラス名が間違っているようで、Demonizerというのを、Daemonに直してやると動くようになった。とりあえず、以下のように/tmp/test.logにHelloと書き続けるデーモンを作成。
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import com.sun.akuma.Daemon;
public class Test {
public static final File PID_FILE = new File("/var/run/test.pid");
public static void main(String[] args) throws Exception {
System.err.println("(1)");
Daemon d = new Daemon();
if (d.isDaemonized()) {
System.err.println("(2)");
d.init(PID_FILE.getAbsolutePath());
}
else {
System.err.println("(3)");
d.daemonize();
System.exit(0);
}
System.err.println("(4)");
BufferedWriter wr = new BufferedWriter(new FileWriter("/tmp/test.log"));
while (true) {
wr.append("Hello");
wr.newLine();
wr.flush();
Thread.sleep(2000);
}
}
}
プロセスを終了する時に、自動クローズされるので、ファイルのクローズは省略。気になる人は、Runtime.addShutdownHook()してください(かなり面倒になるけど)。これを、以下のコマンドで起動。
sudo java -cp bin:akuma-all-1.3.jar Test
init()にファイルパスを与えると、デーモンのpidが記録される。/tmp/test.logを観察すると、サイズが増えていくので、動いている模様。ちなみに動かした時の画面は、こんな感じ。
shanai@shanai-laptop:~/java/akuma-test$ java -cp bin:akuma-all-1.3.jar Test
(1)
(3)
shanai@shanai-laptop:~/java/akuma-test$ (1)
(2)
動きとしては、最初は、デーモン化されていないから、elseの方に入り、demonize()が実行され、ここでforkして終了。forkされた方は、if()の方に入った後、(4)を実行する。ただ、init()で標準入出力が閉じられるので、表示は行われない、という感じのようだ。psで確認すると、
shanai@shanai-laptop:~/java/akuma-test$ ps auxw | grep akuma
shanai 17509 3.2 0.8 690292 33180 ? Ssl 19:07 0:01 java -Dcom.sun akuma.Daemon=daemonized -cp bin:akuma-all-1.3.jar Test
システムプロパティを確認すれば、デーモンとして動いているかどうか、Javaで確認することも可能のようだ。あとは自分で、psとkillで運用しても良いのだけど、ちょっと面倒だ。これだと二重起動を防ぐこともできない。そこでデーモン起動、終了のスクリプトを作ってみる。Ubuntuには、start-stop-daemonというのが用意されているので、これを使うのが簡単そう。以下は開始用のスクリプト。
--- start.sh ---
#!/bin/sh
start-stop-daemon --oknodo --pidfile /var/run/test.pid --exec /home/shanai/bin/java --start -- -cp /home/shanai/java/akuma-test/bin:/home/shanai/java/akuma-test/akuma-all-1.3.jar Test
--oknodoは、すでにプロセスが上がっていた時でも終了ステータスを0にするもの。
--pidfileは、上でTestデーモンが作成した、pidが書かれたファイルだが、start-stop-daemonは内容については関知しない。何に使うかというと、start-stop-daemonが、デーモンのプロセスを特定するために使用する。このファイルを作ったプロセスであれば、start-stop-daemonが対象とするデーモンプロセスであると識別するのだ。
--execは、実行するプログラムの指定であると同時に、デーモンの特定にも使用される。もしも、上で説明した--pidfileが指定されていないと、--execに指定した/home/shanai/bin/javaだけで識別してしまうので、javaで動かすデーモンが複数あったら、識別できなくなってしまう
--startの指定は、デーモンの起動を指定している。--オプションの後に書いた内容は、デーモンプロセスにそのまま渡される。
これを、sudo ./start.shで起動すると、
shanai@shanai-laptop:~/java/akuma-test$ sudo ./start.sh
[sudo] password for shanai:
(1)
(3)
shanai@shanai-laptop:~/java/akuma-test$ (1)
(2)
直接起動した時と同じように、起動される。もう一度起動しようとすると、
shanai@shanai-laptop:~/java/akuma-test$ sudo ./start.sh
/home/shanai/bin/java already running.
このように二重起動を防いでくれる。次に状態を調べるコマンドを作成する。
--- status.sh ---
#!/bin/sh
set -e
. /lib/lsb/init-functions
status_of_proc -p /var/run/test.pid /home/shanai/bin/java akuma-test
/etc/init.dを眺めてみたら、status_of_procという関数があるらしいので、ありがたく使わせてもらう。これは、/lib/lsb/init-functionsに入っている。第3パラメータは、このコマンドが表示する時に使う名前なので、好きなものを使えば良い。これを起動すると、
shanai@shanai-laptop:~/java/akuma-test$ sudo ./status.sh
* akuma-test is running
最後にデーモンを止めるためのスクリプト。
#!/bin/sh
start-stop-daemon --oknodo --pidfile /var/run/test.pid --stop --exec /home/shanai/bin/java
起動の時と同様に、start-stop-daemonを使う。今度は、--stopを指定して、停止する。停止した後に、status.shを起動してみると、
shanai@shanai-laptop:~/java/akuma-test$ sudo ./status.sh
* akuma-test is not running
ちゃんと停止していることが分かる。akumaのjarを見ると分かるように、様々な環境用の、dllやsoが同梱されているので、このjarファイルだけでok。この点、とても手軽で良くできているなと思う。
可変引数
可変引数を使っていると、可変引数を受け取りつつ、別のメソッドに引数を追加しながら引き渡すようなコードを書きたくなることが良くある。
static final String[] defaultArg = {...};
Foo(String... arg) {
super (defaultArg, arg);
}
コンパイラがこれ、許してくれればいいんだけど、残念ながらコンパイルエラー。super()呼ぶ前に自分で配列を作ろうかと思っても、super()呼び出しは先頭に置かなければならないという掟があるため、うまくいかない。でまぁ、こういう場合は、やはりビルダパターンかなと。
public class TypedVarArgBuilder<T> {
List<T> args = new ArrayList<T>(8);
public TypedVarArgBuilder<T> add(Collection<T> itr) {
args.addAll(itr);
return this;
}
public TypedVarArgBuilder<T> add(T... array) {
args.addAll(Arrays.asList(array));
return this;
}
public T[] build() {
...
はうっ、Generic array生成か。new T[size]というのは無理なんで、Array.newInstance()とか使うことになってしまう。引数リスト1つ作るたびにリフレクションは避けたいよねぇ。どうもうまくいかないな。やはり配列をnewするファクトリを渡してもらうというあたりが落とし所かなぁ。
public T[] build() {
return args.toArray(createArray(args.size()));
}
abstract protected T[] createArray(int size);
class Foo {
Foo(String... arg) {
for (String s: arg) System.out.println(s);
}
}
public class Bar extends Foo {
static final String[] defaultArg = {"Hello", "World"};
Bar(String... arg) {
super (new TypedVarArgBuilder<String>() {
protected String[] createArray(int size) {
return new String[size];
}
}
.add(defaultArg)
.add(arg)
.build());
}
public static void main(String[] args) {
new Bar("Foo", "Bar");
}
}
う〜んメンドイな。あとは実行時にバイトコード生成するか。さすがにやり過ぎな気がする。
サンシャイン牧場データ
jQueryの勉強を兼ねて、ついカッとなって、サンシャイン牧場データを作成。今後はサーバサイドはデータだけ作って、プレゼンテーションは、JavaScriptって感じになるんかな。
猫カメラ復活
猫カメラ復活。Zaurusを引退させてから、無線のセキュリティをようやくWEPから、WPA2に切り替え。そのままライブカメラの設定を変えるのを忘れていたのだけど、今日やっと切り替えた。
さっそく、うっとんを乗せてみたら、そのままうたた寝。寝ぼけて頭がずり落ちていたりして、おもしろい。









