2011年11月29日火曜日

Fantom でせんだみつおゲーム

Youtube 観てたら、古いテレビの録画で、せんだみつおゲームをやっていた。やっぱりプログラマだから書いてみたくなる。言語はこないだ見つけた、Fantom にしてみる。

こんな感じ

  • Actor を5つ生成して、5名の参加者に見立てる
  • メインのスレッドでは、無限ループしながら Actor にタイミングを出しつづける
  • Actor は、"SENDA", "MITSUO", "NAHANAHA"を出力しながら、ランダムに次の番のアクターを指名する

using concurrent

class SendaMitsuoGame {
  Void main() { play }

  Void play() {
    pool := ActorPool()
    Actor[] actors := (1..5).map { Player(pool, it) }

    actors[0].send("next1")
    actors.each { it.send(actors.toImmutable) }

    messages := ["senda", "mitsuo", "nahanaha"]
    while (true) {
      message := getHeadAndRotate(messages)
      actors.each { it.send(message) }

      Actor.sleep(1sec)
    }
  }
  Str getHeadAndRotate(Str[] m) { m.add(m.removeAt(0)).last }
}

const class Player : Actor {
  const Int number

  new make(ActorPool p, Int number) : super(p) {
    this.number = number }

  override Obj? receive(Obj? msg) {
    if (msg is List) { Actor.locals["all"] = msg }
    else if (msg == "senda") doSenda
    else if (msg == "mitsuo") doMitsuo
    else if (msg == "nahanaha") doNahanaha
    else if (0==((Str)msg).index("next")) {
        setMyTurn(((Str)msg)["next".size..-1].toInt)
    }
    return null
  }

  Bool isNext(Int n) {
    diff := (n - number).abs()
    return 1 == diff || 4 == diff
  }

  Bool isMyTurn() { Actor.locals["myTurn"] }

  Void setMyTurn(Int f) {
    Actor.locals["myTurn"] = (number == f)
    Actor.locals["nextOnesTurn"] = isNext(f) }

  Void doSenda()  { callAndAssign("SENDA") }

  Void doMitsuo() { callAndAssign("MITSUO") }

  Void doNahanaha() {
    if (Actor.locals["nextOnesTurn"])
        echo("" + number + ": NAHANAHA");
  }

  Void callAndAssign(Str call) {
    if (!isMyTurn()) return

    next := nextOne()
    echo("" + number + ": " + call + " -> " + next);

    ((Actor[])Actor.locals["all"]).each { it.send("next" + next) }
  }

  Int nextOne() {
    next := Int.random(1..4)
    if (next < number) return next
    return (++next < 6) ? next: 1
  }
}

出力はこんな感じになる。ミスる事も無く、延々とせんだみつおゲームを繰り返す。適当な確率でミスさせる事も、もちろんできるけど、割愛。

$ fan senda.fan 
1: SENDA -> 2
2: MITSUO -> 5
1: NAHANAHA
4: NAHANAHA
5: SENDA -> 1
1: MITSUO -> 3
4: NAHANAHA
2: NAHANAHA
3: SENDA -> 5
5: MITSUO -> 4
5: NAHANAHA
3: NAHANAHA
4: SENDA -> 3
3: MITSUO -> 1
5: NAHANAHA
2: NAHANAHA
1: SENDA -> 4
4: MITSUO -> 5
1: NAHANAHA
4: NAHANAHA
しかし夜遅くまで、俺、何書いてんだ・・・

2011年11月25日金曜日

Groovy + POI

Groovy では、クロージャを使って若干の下準備(下例では TableBuilder)をしておくと、以下のようなコードが書ける。

static void main(args) {
   new TableBuilder().with {
     sheet(   "test"
         )(   style(header)
         )(   "No"   )(   "名前"      )( nextRow
         )(   style(normal)
         )(   1      )(   "琵琶湖"    )( nextRow
         )(   2      )(   "多来加湖"  )( nextRow
         )(   3      )(   "富内湖"    )( endTable   )
   }.write(new FileOutputStream("sample.xls"));
}

TableBuilder の中では、Apache POI を使っていて、実行すると 下のような Excel ファイルが sample.xls に書き出される。

カッコの開き方が気に入らないが、しょうがないらしい。下の A、B は等価だけど、C は別ものに解釈される。上例を C のようなカッコのに直すと、動作しない。

ABC
   ("A1")("B1")(
    "A2")("B2")
    ("A1")("B1"
   )("A2")("B2")
   ("A1")("B1")
   ("A2")("B2")

TableBuilder は下のコードのようになる。

class TableBuilder {
   def book
   TableBuilder() { book = new HSSFWorkbook() }
   def nextRow = { cell, style ->
      cursor.curry(cell.offset(1, -cell.columnIndex), style)
   }
   def endTable = { cell, style -> book }
   def cursor = { cell, style, arg ->
      if (arg instanceof Closure) { arg(cell, style) }
      else { 
         cell.setCellValue(arg)
         if (null != style) cell.cellStyle = style 
         cursor.curry(cell.next(), style) 
      }
   }
   def start(sheet){ cursor.curry(sheet.cellAt(0, 0), null) }
   def sheet(name) { start(book.createSheet(name)) }
   def style(styleClosure) {
      { cell, style-> cursor.curry(cell, styleClosure(book))}}
}

また、POI の API が Groovy コードの中で馴染むように、Mixin でメソッドを追加した。

class CellMix {
   def next() { offset(0, 1) }
   def offset(r, c) {
      sheet.cellAt(rowIndex + r, columnIndex + c) }
}
class RowMix {
   def cellAt(c) { getCell(c) ?: createCell(c) }
}
class SheetMix {
   def rowAt(r) { getRow(r) ?: createRow(r) }
   def cellAt(r, c) { rowAt(r).cellAt(c); }
}
class CellStyleMix {
   def borderMethodName = { s-> "setBorder" + s.capitalize() }
   def setBorder = { arg, edge -> 
      owner."${borderMethodName(edge)}"(arg."$edge" ?:BORDER_NONE); 
      setBorder.curry(arg) }
   def setBorder(Map arg) {
   setBorder.curry(arg)("top")("left")("bottom")("right") }
}

カッコを連鎖させるやり方にしたのは、単にクロージャとカリー化を試したかっただけで、実は、普通のクラスと leftShift() のオーバライドだけで、 下のような C++風の書き方もできて、こっちの方がむしろスッキリしたコードになる。

sheet("test") << 
   "A1" << "B1" << endRow <<
   "A2" << "B2" << endTable

2011年11月23日水曜日

Fantom

Info Q を読んでたら、Scala は先行き暗いねって記事があった。その内容自体は、まあ一理あるねくらいだけど、その問題提起をしている人が、気に入ってる言語として Scala と対比してる Fantom というのがあって、ちょっと調べてみた。

ここでサンプルコードを読んでみると、特にキャッチーな感じはしないけど、その分、実用性を念頭に置いてる気がする。知的な遊びのためよりむしろ割と泥臭い現場でも使えるように考えられてる印象。C# が分かる Javaプログラマで、面倒くさがりな人向けって感じか。言語仕様とか文法のパラダイムよりも、バイトコードやモジュールの扱いみたいなアーキテクチャ寄りの面に長所があるのかもしれない。

Linux にインストールするには、

  • オフィシャルサイトのトップページからダウンロードして、
  • 適当なところに展開して、fantom-1.0.XX フォルダの中に入る
  • adm/unixsetup を実行して bin/下のファイル群に実行権限を付ける
  • bin/fan -versionを実行して、動作確認
  • 必要に応じて PATH にbin/を追加する

以下のようにして、FWT という GUI 上の HelloWorld ができる。

  • ファイル FwtHello.fan に以下のコードを書く
    using fwt
    class FwtHello : Test
    {
      Void main()
      {
        Window { Label { text = "Hello world" }, }.open
      }
    }
  • $ fan FwtHello.fan で以下のようなちっこいウィンドウが表示される。

====

全然、Fantom とは関係ない話だけど、Fantom についていろいろググってて、Fantom で迷路の最短経路をとくパズルをやってる人がいたので、その先をたどると面白い記事を見つけた。

問題は「人材獲得作戦・4 試験問題ほか」に載ってて、この問題をどのように使ったかという顛末(「人材獲得作戦・3」)まで書かれていてとても面白い。

ただ、「問題の性質からいって、キューやリストが必要なのは明らかなのに、言語にCを使う人が相当多い。」ってところだけ残念。以下のようにすればオブジェクト指向も関数型も論理型も関係なく簡単に解ける。

  • S の上下左右に隣接する空のマスに1 を書き込む
  • 1 に隣接する上下左右の空のマスに2 を書き込む
  • ・・・
  • N に隣接する上下左右の空のマスにN+1を書き込む
  • Gに着いたら、隣接するマスを N からカウントダウンする形で戻れば完成

コードは簡単だから省略。C 言語はもちろん、多分 COBOL でも書ける。

Squeak の右クリメニュー

Fedora のソフトウェアの追加/削除から Squeak をインストールするとバージョン 3.10 の Mysqueak ってのが入ってくるけど、かなり古いものらしい。 [→参照]

で、squeak.org のここから Squeak All-in-One をダウンロードすると、バージョン 4.2 が入っている。これを適当なところで展開して、squeak.sh を実行すれば、環境が立ち上がる。

ちなみに今使っている Linux 環境では、特に設定しなくても、右クリで 'do it'を含むコンテキストメニューがポップアップされたけど、古いやつはマウスの設定を変える必要があった。

  • 何もない背景のとこで左クリック
  • World のメニューが出てくるので[open...]を選択
  • サブメニュー[Preference Browser]を選択
  • "mouse"でフィルタすると [swapMouseButtons] ってのが出てくるので、enabled にチェックを入れる

Windows 版だと、今のバージョンでもそんな設定が必要になる事もあるらしい。[→参照

2011年11月21日月曜日

LePUS3

昨日のブログを書くために、Wikipedia の Strategy パターンのエントリを開いてみたら、見慣れないものがあった。

なんかかっけえ。フリーメイソンとかみたいだ。三角のとことか。

LePUS3 というダイアグラムらしい。五十音的には、レプスさんくらいの感じだろうか。

Wikipedia によると

  • UML と同様のオブジェクト指向モデリング言語である。
  • formal specification language(形式仕様記述言語) の一つである。
  • First-order predicate logic(一階述語論理)のサブセットである
  • Codechart とも呼ばれる
  • いいところ
    • スケーラビリティ :少しの記号を用いた小さな Codecharts で、大規模なプログラムをモデリングできる
    • 自動検証 :設計と実装を同期させて、自動的に適合性を検証できる
    • 可視化 :リバースでソースコードから Codecharts を生成しやすい
    • パターンの実装 :プログラムがデザインパターンを実装しているかどうか、自動的に判断できる
    • 設計抽象化 :実装の詳細に取りかかる前に抽象的なプログラムを表現できる
    • 総称 :特定の実装ではなく、設計のモチーフとしてデザインパターンを表現できる
    • 厳密性:設計者は Codecharts によるモデルを厳密に検証できる

いつのまにか、Wikipedia のプログラム関連のエントリの、至る所で LePUS3 が使われている。全然、気がつかなかった

オフィシャルサイトはここ

この本で紹介されている。
Codecharts

自分は注文したけど、しかし高い。
7300円もか。そんなにもか・・・

2011年11月20日日曜日

OSGi で GoF の Strategy

久しぶりに OSGi でもいじってみるかと思い立つ。

Wikipedia に載ってる Strategy パターンの サンプル・コードでも OSGi で動かしてみよっかなと。今回は、Equinox でやってみる。

OSGi 環境を作る

  • 適当な場所に osgi-test フォルダを作る
  • osgi-test フォルダ 下に plugins フォルダ を作る
  • Eclipse の plugins下から以下のファイルを osgi-test/plugins フォルダ 下にコピーする。
    • org.eclipse.osgi_*.jar
    • org.eclipse.equinox.ds_*.jar
    • org.eclipse.equinox.util_*.jar
    • org.eclipse.osgi.services_*.jar
うちの環境だとこんな感じになる
$ cd /tmp/osgi-test
$ tree
.
└── plugins
    ├── org.eclipse.equinox.ds_1.3.1.R37x_v20110701.jar
    ├── org.eclipse.equinox.util_1.0.300.v20110502.jar
    ├── org.eclipse.osgi.services_3.3.0.v20110513.jar
    └── org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar

起動して、他のプラグインも追加しておく

$ java -jar plugins/org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console

osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106

osgi> install file:///tmp/osgi-test/plugins/org.eclipse.osgi.services_3.3.0.v20110513.jar
Bundle id is 1
osgi> install file:///tmp/osgi-test/plugins/org.eclipse.equinox.util_1.0.300.v20110502.jar
Bundle id is 2
osgi> install file:///tmp/osgi-test/plugins/org.eclipse.equinox.ds_1.3.1.R37x_v20110701.jar
Bundle id is 3

osgi> start 1
osgi> start 2
osgi> start 3
osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701

環境が出来たので、以降ではプラグインを作って動かしてみる。先に書いたとおり、Wikipedia にある Strategy のサンプルコードをネタにして、Strategy、ConcreteStrategyAdd、ConcreteStrategyMultiply、Context を連動させてみる。

Strategy の作成〜インストール

まずは、Strategy プラグインプロジェクトの作成

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.strategy"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Runtime]タブを開いて、[Exported Packages]に"ex1.osgi.strategy.strategy" を追加する。
  • 以下の Wikipedia から持ってきた、以下のコードを追加する
    package ex1.osgi.strategy.strategy;
    public interface Strategy {
       int execute(int a, int b);
    }
できたら以下のように Export しておく。
  • プロジェクトのコンテキストメニューから、[Export]->[Deployable plug-ins and fragments] を選択
  • [Destination] / [Directory]に、osgi-test フォルダを指定する
  • [Options] で、"Package plug-ins as individual JAR archives"のみチェックされている事を確認して[Finish]
エクスポートしたら、OSGi に install しておく
osgi> install file:///tmp/osgi-test/plugins/ex1.osgi.strategy.strategy_1.0.0.201111202011.jar
Bundle id is 4
osgi> refresh
osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 RESOLVED    ex1.osgi.strategy.strategy_1.0.0.201111202011
ss や refresh といったコマンドの意味は、OSGi コンソールから help と打てば、説明が出てくる。

ConcreteStrategyAdd の作成〜インストール

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.add"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Dependencies]タブを開いて、[Required Plugins]に"ex1.osgi.strategy.strategy" を追加する。
  • 以下のようにコード追加する。
    package ex1.osgi.strategy.add;
    import ex1.osgi.strategy.strategy.Strategy;
    
    public class ConcreteStrategyAdd implements Strategy {
        public int execute(int a, int b) {
            System.out.println("Called ConcreteStrategyAdd's execute()");
            return a + b;  // Do an addition with a and b
        }
    }
  • プロジェクト直下に OSGI-INF フォルダを追加する
  • OSGI-INF フォルダのコンテキストメニューから、[New]->[Component Definition]を選択
  • "Component Definition Information" / Class に ex1.osgi.strategy.add.ConcreteStrategyAdd を指定して[Finish]
  • component.xml の Service タブで、[Provided Services]に ex1.osgi.strategy.strategy.Strategy を追加する
これも Export して、OSGi にインストールしておく。

Context の作成〜インストール

次は ConcreteStrategyMultiply をやる前に、Context に行ってみる

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.context"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Dependencies]タブを開いて、[Imported Packages]に"ex1.osgi.strategy.strategy" を追加する。
  • 以下のコードを追加する。Wikipedia のサンプルコードでは、Context#executeStrategy() を実行するのは StrategyExample#main() だけど、面倒だから strategy が関連付けられたときに Context 自身が executeStrategy() のテストコードを実行するようにした。
    package ex1.osgi.strategy.context;
    import ex1.osgi.strategy.strategy.Strategy;
    
    public class Context {
       private Strategy strategy;
     
       public synchronized void setStrategy(final Strategy strategy) {
          this.strategy = strategy;
          testExecuteStrategy();
       }
       public synchronized void unsetStrategy(final Strategy strategy) {
          if (strategy == this.strategy) this.strategy = null;
       }
       public int executeStrategy(int a, int b) {
          return strategy.execute(a, b);
       }
       public void testExecuteStrategy() {
          final long l = System.currentTimeMillis();
          final int a = (int) (l % 10);
          final int b = (int) (l / 10 % 10);
          final int result = executeStrategy(a, b);
          System.out.printf("execute(%d, %d) = %d", a, b, result);
       }
    }
  • プロジェクト直下に OSGI-INF フォルダを追加する
  • OSGI-INF フォルダのコンテキストメニューから、[New]->[Component Definition]を選択
  • "Component Definition Information"/Class にex1.osgi.strategy.context.Context を指定して[Finish]
  • component.xml の [Service]タブで、[Referenced Services] に Strategy を追加する
  • 追加した Strategy を選択して[Edit]、"Bind"にsetStrategy、"Unbind"にunsetStrategy を指定する
できたら、これもエクスポートして、OSGiにインストールしておく。


動作確認

とりあえず、ここまでで動かしてみる。

osgi> start 4
osgi> start 5
osgi> start 6
Called ConcreteStrategyAdd's execute()
execute(3, 1) = 4
osgi> 
期待通りに動作する。(実は、start 4 は無くても良いし、start 5 と start 6 の順番は逆でも良い。)

ConcreteStrategyMultiply の追加と動作確認

最後に、ConcreteStrategyMultiply を付け加えて、ConcreteStrategyMultiply と差し替えてみる。

  • [New]->[Plugin-Project]
  • Project Name には "ex1.osgi.strategy.multiply"を指定、Activetor 無し、tempate 不使用で[Finish]
  • MANIFEST.MF の [Dependencies]タブを開いて、[Required Plugins]に"ex1.osgi.strategy.strategy" を追加する。
  • 下のようにコード追加
    package ex1.osgi.strategy.multiply;
    import ex1.osgi.strategy.strategy.Strategy;
    
    public class ConcreteStrategyMultiply implements Strategy {
        public int execute(int a, int b) {
            System.out.println("Called ConcreteStrategyMultiply's execute()");
            return a * b;   // Do a multiplication with a and b
        }
    }
    
  • プロジェクト直下に OSGI-INF フォルダを追加する
  • OSGI-INF フォルダのコンテキストメニューから、[New]->[Component Definition]を選択
  • "Component Definition Information"/Class に net.yasuabe.osgi.ex1.strategy.multiply.ConcreteStrategyMultiplyを指定して[Finish]
  • component.xml の Service タブで、[Provided Services]に ex1.osgi.strategy.strategy.Strategy を追加する
これもエクスポートして、OSGiにインストールしておく。

エクスポートしたら、Context から実行される Strategy を、Add から Multiply に変えてみる。

osgi> ss
Framework is launched.

id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 ACTIVE      ex1.osgi.strategy.strategy_1.0.0.201111202011
5 ACTIVE      ex1.osgi.strategy.add_1.0.0.201111202028
6 ACTIVE      ex1.osgi.strategy.context_1.0.0.201111202042
7 RESOLVED    ex1.osgi.strategy.multiply_1.0.0.201111202123

osgi> stop 5
osgi> start 7
Called ConcreteStrategyMultiply's execute()
execute(3, 1) = 3
osgi> ss
Framework is launched.
id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE      org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE      org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE      org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 ACTIVE      ex1.osgi.strategy.strategy_1.0.0.201111202011
5 RESOLVED    ex1.osgi.strategy.add_1.0.0.201111202028
6 ACTIVE      ex1.osgi.strategy.context_1.0.0.201111202042
7 ACTIVE      ex1.osgi.strategy.multiply_1.0.0.201111202123
osgi> stop 7
osgi> start 5
Called ConcreteStrategyAdd's execute()
execute(4, 3) = 7
osgi> 
Context を起動したまま、Strategy を差し替えられるのが確認できる。

雑感

今まで、何回か、いわゆるパッケージ開発案件に関わってきたけど、納品先個別の要件の扱い方について、何パターンかの手法がある。

一番泥臭いのだと、トランクのソースコードに個別要件対応のコードを入れてしまって、条件文で切り分けるというベタベタのローテク手法がある。「バカじゃね?」って思う人も多いかもしれないけど、実際の現場では結構多い。

それよりは、少しはマシな手法だと、納品先別にブランチを切って個別要件はそこに入れていくという事になる。まあ、これも知的な方法とは言いにくいけど、割と普通に行われている。

もっと良い方法としては、パッケージ共通部分と個別にカスタマイズ可能な部分について最初に見通しを立てた上で、カスタマイズ可能な部分がプラガブルになるようにアーキテクチャを設計する手法がある。

この手法をとると、機能の差し替えや追加、また管理面においてもリスクが少なくて、柔軟なソフトウェアになるんだけど、この場合にもさらに、プラグインの方式を自分で作るか、既存の仕様や製品を使うかで道が分かれる。

必ずしも自作にこだわる必要がないとしたら、OSGi がかなりの有力候補なのではないかと思う。(個人的には、はっきり言って、かなり高い割合で自作の道を取るのは不正解だと思うけど。)

2011年11月19日土曜日

Eclipse + Groovy + NekoHTML

Redmine や Trac の wiki などを使ってプロジェクト内で情報共有している HTMLベースのコンテンツを、プログラムで取り扱うやり方を考えていて、Groovy でも使ってみようかと思い立つ。

で、やっぱり Eclipse で使いたいのでプラグインを入れる。Indigo なら update site は ここで、Required とマークされている Groovy Eclipse ってのがあるから、これを選択してインストール。

インストールできたら、お試しプロジェクトを作る。パッケージエクスプローラ上の右クリから New -> Other と進むと、Groovy Project がリストに出てくるから、選択して適当に操作すると、Groovy Project が生成される。

適当に Hello World を書いて、main() メソッド上の右クリから[Run As]を選択すると、サブメニュー、[1 Groovy Console], [2 Groovy Script], [3 Java Application]が表示される。どれを選んでも、同じコードが同じように実行される。

これで、Eclipse で Groovy が使えるようになった。

Groovy 自体、たいして使ったことがないので、練習がてら「wikipedia の適当なページから「他の言語」にリストアップされている言語を、標準出力に書き出す」という簡単なお題をやってみる事にする。

Groovy で HTTP GET や HTML/XML を扱う方法をググりながら、以下のようなコードを書いてみた。

class Ex2 {
   static void main(args){
      new XmlSlurper(
            new org.cyberneko.html.parsers.SAXParser())
         .parse("http://ja.wikipedia.org/wiki/秋刀魚")
         .depthFirst()
         .grep{it.name() == 'DIV' && it.@id == 'p-lang'}[0]
         .DIV.UL.LI.each { println it.text() }
   }
}
Eclipse から実行すると、秋刀魚を解説している10個の言語の名前が書き出される。あまり便利ではなかったが、デバッガも一応使える。

XmlSlurper のコンストラクタに渡しているものは、NekoHtml の SAXParser というやつで、HTML にタグを補完して well-formed な XML として扱えるようにしてくれる。

使えるようにするには、ここから zip か tgz をダウンロードして、中の nekohtml.jar と xercesImpl.jar (または xercesMinimal.jar )を、~/.groovy/lib 下に置けばいい。

2011年11月13日日曜日

論理パズル で Prolog のウォーミングアップ

テストケース(開発者が作る、いわゆる DeveloperTest のテストケースじゃなくて、開発はやらないテスト部隊が使うテストケース)を作るための小道具として Prolog を使うと便利かもと思って、自宅の環境に SWI-Prolog を用意してみた。

Fedora の「ソフトウェアの追加/削除」でリストアップされてたので、選択して適用するだけでインストールできた。

本当は、与えられた条件にしたがって、縦に要因、横にテストケースを並べた直交表もどきの表を生成するような仕組みを作りたいんだけど、その前にウォーミングアップしてみる。

手元にある論理パズル集の中から簡単なものを選んで、Prolog で解いてみる事にする。

お題は以下のようなもの。

  • とあるミスコンで、好美、恵美、明美、麻美、宏美が予選通過した
  • なかよし4人組が以下のように優勝者を予想した
    • まゆみ:好美と恵美ではないだろう
    • 大貴:恵美か明美だろう
    • 崇臣:好美か麻美だろう
    • やす美:好美と麻美ではないだろう
  • 予想が的中した人数は2名だった。
優勝したのはだれか?

あまり Prolog に詳しいわけではないけど、素朴に書くと以下のようになるのではないだろうか。
% 出場者
contestant(akemi). 
contestant(asami). 
contestant(hiromi). 
contestant(emi). 
contestant(yoshimi).

% 誰が誰の優勝を予想しているか
expect(mayumi, akemi). expect(mayumi, asami). expect(mayumi, hiromi).
expect(daiki, akemi). expect(daiki, emi).
expect(takaomi, yoshimi). expect(takaomi, asami).
expect(yasumi, emi). expect(yasumi, akemi). expect(yasumi, asami).

% 結局、優勝者は2名の予想者に予想された出場者だった
winner(Contestant):- expectors(Contestant, 2).

% 優勝者と予想者数の関係
expectors(Contestant, Count):-
  contestant(Contestant),  
  countExpectators(Contestant, [mayumi, daiki, takaomi, yasumi], Count).

% 優勝者を予想した人を数える
countExpectators(_, [], 0 ).
countExpectators(Contestant, [Expector|OtherExpectors], Count) :-
  expect(Expector, Contestant), 
  countExpectators(Contestant, OtherExpectors, Count2),
  Count is Count2 + 1.
countExpectators(Contestant, [Expector|OtherExpectors], Count) :-
  not(expect(Expector, Contestant)),
  countExpectators(Contestant, OtherExpectors, Count).
これを SWI-Prolog に読ませて、以下のようにすると答えが出る。
?- winner(Winner).
Winner = emi .
ちなみに明美の優勝を予想した人数は、上で定義した述語 expectors 使って、以下のように求められる。
?- expectors(akemi, Count).
Count = 3 .
逆に、同じ expectors を使って、3名が優勝を予想した出場者を求めることもできる。これがPrologの面白いところでもある。
?- expectors(W, 3).
W = akemi ;
W = asami ;
false.

====

ソフトウェアのテストケースを作ろうとすると、あるテスト対象の振る舞いに影響を与える条件群について、すべての条件を掛け合わせると現実的でないテストケース数になったりする

なので、少ないテストケース数でなるべく各条件を網羅できるように工夫する事になるんだけど、その辺りの作業がちょっとしたパズルみたいになってくる事がある。

で、せっかくプログラミングを生業としているのだから、プログラムでなんとかしようかって発想になるんだけど、手続き型言語を使うと、やりたい事とコード上の表現がいまいち直截性に欠けていたりする。

あまり流行ってはいなけど、こういう時は Prolog が割と使えそうな気がしないでもない。

2011年11月10日木曜日

Fedora のアップグレードで平常心の訓練

Fedora 16 が昨日か一昨日リリースされたので、Fedora15 からアップグレードしてみた。

まず、Fedora のサイトに書いてあるアップグレード手順どおりにやってみる。

rpm --import https://fedoraproject.org/static/A82BA4B7.txt
yum update yum
yum clean all
yum --releasever=16 --disableplugin=presto distro-sync

うん。やっぱり、うまくいかない。
--skip-broken が使えないからとか何とかメッセージが出て、途中で止まる。

まあ、初めはいつもこんなもんだ。俺は、ぜんぜん動じない。で、ちょっと調べて以下のようにして再挑戦。

yum --releasever=16 --disableplugin=presto distro-sync --skip-broken

今度は、 「Transaction Check Error で、gnome-icon-theme-symbolicgnome-power-manager が keyboard-brightness-symbolic.svg のアレやら何やら(意訳)」とか言ってくる。 こやつめ、ハハハ!

よく分からないけど、試しに以下のようにしてみた(gnome-icon-theme-symbolicとか言うやつは、この際、忘れる)。

yum --releasever=16 --disableplugin=presto distro-sync --skip-broken --exclude=gnome-icon-theme-symbolic

うん、なんだか上手く行ってるっぽい。エラーも無く yum が終了。再起動してログインしたら、Fedora 16 のデスクトップ画像が見えた。端末から cat /etc/fedora-release とやってみたら、 Fedora release 16 (Verne)と出た。

うん、これは出来ておるね。出来たっぽいね。

まあ、いちいち書いてはいないけど本当はもっと試行錯誤して、正直つらかった。よく覚えてないけど、気がつくと頬が濡れてたから、俺ちょっと泣いてたのかもしれん。まあでも、どうやら一応、Fedora 16 にアップグレードできた。


で、まずは 「ソフトウェアの追加/削除」で Eclipse を Indigo に変える。実はこれが、そもそもの目的だった。 うまくインストールから起動まで確認できて一段落。ほっとする。

更に、他のいろんなもの全部をアップグレードしたくて、「ソフトウェアの更新」を起動すると、700個近く検出された。かまわず[Install Updates]押下。

・・・って、ま〜たかよ。また、gnome-icon-theme-symbolic と gnome-power-manager が干渉してるとか言ってきた。

今度は、 yum remove gnome-power-manager で、電力管理の何かを削除してみる。多分使ってないし、要るなら後でなんとかすればいいし。
で、再度、[Install Updates]。

すると今度は、「libnih が GLIBC とナントカカントカ」だの言ってきやがって、ギギギ、てめえコノヤ……まあ、短気は損気とも言うからな。

rpm -q --whatrequires libnih で見てみると、別にどこからも依存されてる形跡がないので、yum remove してしまう。

再び、[Install Updates]を再試行すると、今度はどんどん進む。これは今渡こそ、上手く行ってるっぽい。

小一時間で 700 近い更新が適用。清々しい気分。再起動しろというので、にこやかにOKボタン押下。

で、しばらく待ってると、いつものようにユーザ選択のポップアップが表示された。ユーザ名を選んでからパスワードを入力。

…そしたら、困った顔のマシンの画像で「ああ、何かがおかしいです、ログインし直してください」とか、メッセージを出してくる。
はあ?何だ、その言い草?しかし、何度やってもログインできず、同じ戯言を繰り返してくる・・・

何度やっても、何度やっても、何度やっても、何度やっても、何度やっても、何度やっても、何度やっても、

おぎゃーーーーーーー!!!


…ひとしきり地団太を踏んで暴れると、嗚咽も収まり、だんだん冷静さが戻ってきた。いつまでもクヨクヨしない。泣いたってログインできないよって、ばあちゃん言ってたし、俺は大人だしな。

で、なんとなく SELinux のせいだろうと思ったので、無効になるようにしたいのだけど、ログインできない状態でどうすれば良いか分からない。 幸い予備のマシンがあったからググったら、シングルユーザモードというのがあって、Windows の safe mode みたいな事ができるらしい。

なんか、起動中に良い感じのタイミングで「E」キーを押すと、カーネルの選択画面になるので、そこで single を追加指定すれば root でログインできる(詳しいやり方は、シングルユーザモードで検索するとなんぼでも出てくる)。

で、CUI ログインしたら、 vim /etc/sysconfig/selinuxで SELinux の設定ファイルを開いて、「SELINUX=enforcing」から「SELINUX=disabled」に書き換える。

で、もっかい再起動。
今度こそ、ちゃんとログインできた。
ほっとしたが、えらく手間がかかったなあ・・・。

まあ、こういった アップグレード関連のアレコレとかって、Linux そのものが好きでベータ版から使ってる人には、大半が常識的で造作ない事なんだろうけど、自分はすごい苦手。こういった作業はたまにしかしないし、してもさっぱり記憶が定着しないし、本当、弱点だわ。

# Fedora 自体は気に入っているが

2011年11月5日土曜日

FEST+JavaScript で半自動 Swing テスト

あ〜、暇だから、Swing フォームを JavaScript で動かすやり方でも考えてみっかな。

====

例えば、打鍵+目視に基づいた手動画面テストが既に実施されていたり、チェックリストとかテスト仕様書が出来ている場合に、作業負荷を減らすために、後から部分的に自動化を導入して、半自動テストにしたい場合がある。

ところでfest という、Swing アプリを Javaコードから機能テストするための API セットがあり、Javaコード からUIイベントを発生させて画面上のコンポーネントを操作するAPIが一揃い入っている(AWT の Robot をベースにしたものらしい)。

また、JavaScript コードを Javaプログラムから動かす仕組みは、Java 1.6 から備わっていて、特に何か特別なライブラリを必要とせずに、簡単に使えるようになっている。

これらを利用すれば、テスト対象フォームの外で JavaScript を実行して、フォーム上の要素を制御することができるはず。

■ 実験台

実験台として、以下のフォームを考えてみる。

こんな仕様
  • テキストボックスの名前は "field1"
  • チェックボックスの名前は "check1"
  • ボタンの名前は "button1" で、押下すると "check1"がチェックされている場合は field1 を大文字化した文字列、チェックされていない場合は field1 そのままの文字列を、標準出力に書き出す。

実験台だから単純なフォームにしたけど、実際の現場での開発対象フォームは入力項目が何十個もあって、実行ボタンを押すまでに必要な打鍵動作が多く、1回なら問題ないけど、同じようなテストケースや回帰テストをしたりすると心身ともに疲れたりする。やはり、そういうのは、何とかして省力化したい。

■ 方針

まず、テスト対象フォームを開くと同時に、以下のような別ウィンドウを開く。

で、ここで JavaScript を入力して Excecute ボタンを押下すると、手動の打鍵と同等の JavaScript が実行されるようにしたい。

■ 実装

JavaScript 入力用のウィンドウ ScriptInputWindow は、最初にテスト対象ウィンドウの FrameFixture を受け取る。

public static void main(String[] args) {
   JFrame frame = showWindow(new TestTargetWindow("Test Target"), 0);
   FrameFixture window = new FrameFixture(frame);
   showWindow(new ScriptInputWindow(window), 3000);
}
public static JFrame showWindow(JFrame frame, int xpos) {
   frame.setSize(250, 150);
   frame.setLocation(xpos, 0);
   frame.setVisible(true);
 
   return frame;
}
見ての通りテスト対象ウィンドウは TestTargetWindow だけど、ScriptInputWindow と TestTargetWindow は同じテストハーネスのプロセスから実行する事になる。もしかすると、開発対象システムに固有な何か特別な親ウィンドウとか、ランチャーとかを介さないと、テスト対象ウィンドウ単体ではフォームを開けないと心配する人もいるかもしれないが、大抵の場合は instrumentation ベースのモックツールを使えば何とかなるし、そんなに難しくもない。

ScriptInputWindow はまず最初に、テスト対象フォーム上のコントロールを、JavaScript 用の ScriptEngine に登録する。ScriptEngine も ScriptInputWindow が保持しておく。

public ScriptInputWindow(FrameFixture window) {
   super("JavaScript input window");

   initializeControlls();  

   this.jsEngine = new ScriptEngineManager().getEngineByName("JavaScript");
   registerComponents(window, window.component(), 0);
}

@SuppressWarnings("restriction")
private void registerComponents(
      FrameFixture window, Container container, int indent) {

   for (Component component: container.getComponents()) {
      String name = component.getName();
      if (null != name) {
         ComponentFixture comp = selectComponent(window, name);
         if (null != comp) this.jsEngine.put(name, comp);
      }
      if (component instanceof JComponent)
            listComponents(window, (Container)component, indent + 1);
   }
}
selectComponent() は ContainerFixture から name に一致する ComponentFixture を選んでくるメソッドだけど、面倒なので省略。

ボタンを押すと書き込んだJavaScriptコードを実行する部分は以下のようなもの。

executeButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent arg0) {
      ExecutorService e = Executors.newSingleThreadExecutor();
      e.execute(new Runnable() {
         public void run() {
            try {
               String text = ScriptInputWindow.this.textArea.getText();
               ScriptInputWindow.this.jsEngine.eval(text);
            } catch (Exception e) {
               e.printStackTrace();
             }
          }
      });
   }
});

■ 試行

以下のコードを書き込んで実行すると、テキストボックスの内容が "123abcABC" に変わり、チェックボックスがチェックされ、ボタンが押され、標準出力に”123ABCABC”が出力される。
field1.setText("123abcABC");
check1.check();
button1.click();

■ 応用

  • フォーム上のコントロールの操作以前に、まずコントロールの名前を知ってなきゃならないんだけど、必要な時に階層状に表示してテスターに見せる機能もあった方が良いかもしれない。これは registerComponents でやってる事をそのままどっかに書き出すだけだから簡単。
  • 自動打鍵の結果として得られた画面上の更新項目を、比較しやすい形式でどこか(TextArea とかクリップボードでもファイルでも)に書き出す仕組みもあると便利かもしれない。グリッドとかツリーとかあると、ちょっと手間がかかるかも。
  • 自動打鍵後に、用意しておいた期待値と自動的に比較する仕組みがあると、かなり全自動に近づいてくる。
  • 手動打鍵の記録なんかまでやるとなると、それは難しい。商用製品でも買った方がマシかもしれない(下記の参考記事をみるとFESTでも対応作業中みたいな書きっぷりだけど、どうなったのか不明)。

■ 参考サイト

2011年11月3日木曜日

仲間外れはどれか?

下の図は、仲間外れを見つける問題例だけど、小学校受験のためのものだから幼稚園児向けということになる。

仲間外れといっても、一応、少なくとも野菜ではあるのだから、問題の趣旨を一旦置いておけば、あながち間違った括りとは言えなくもない。

でも、じゃあ、「カボチャ」の代わりに、「八百屋さん」だったり「プログラマ(好きな食べ物:肉、嫌いな食べ物:野菜)」とかだったとしても、同じカテゴリって言い張る奴っているのだろうか。まあ、こんなアホな質問はした事はないけど、身の回りには多分いないと思う。つうか、いたら嫌だ。

ところが、それに相当するようなソースコードなら、プロの開発現場でもしょっちゅう見かける。今日はそんな事を書いてみる。

====

こんなコードがあったとする。

このコードには、ダメなところがいくつかあるけど、特にまずい問題点を、ちゃんと技術者っぽい言葉で説明できるだろうか。

/** 色を指定できるカーペット。 */
public class Carpet {
   private final int color;
   public Carpet(int color) {
      this.color = color;
   } 
   //…
}

/** システムで使える色の集合  */
public class Colors {
   private static final Map COLORS = new HashMap();
   static {
      COLORS.put("赤", 0xFF0000);
      COLORS.put("緑", 0x00FF00);
      COLORS.put("青", 0x0000FF);
   };
   private Colors() {} 
   public static int colorNameToRgbValue(String colorName) {
      return COLORS.get(colorName); 
   }
   public static List createCarpets(String[] colorNames) {
      List result = new ArrayList();
      for (String colorName: colorNames) {
         result.add(new Carpet(COLORS.get(colorName)));
      }
      return result;
   }
}

Colors は本当は enum を使うべきだけど、サンプルの都合上普通のクラスにした。また、そもそもユーティリティクラスがまずいってのは、取り敢えずこの際、放っておく事にする。

Colors という色のセットを表すクラスに、色に関係ない事はないけど明らかに異質でレベルの違う Carpet という概念を扱うメソッド、createCarpets() が定義されている。

こういうとき、ちょっと堅苦しい言い方だけど、「凝集性が低い」という(もちろん低いほどダメ)。

もっと言うと、暗合的凝集度論理的凝集度の間くらいになると思われ、レベルとしてはサイテーに近い。(技術者っぽくない言葉でいうと、暗合的凝集度は「なんで同じクラスに入れてんのかイミフ」なやつで、論理的凝集度は「言い訳できない事ないけど必然性に乏しい」やつ。)

この凝集性と表裏一体の概念として、結合度というのもある。

上のコードは結合度が高い(こっちは高いほどダメ)のだけど、実質的にグローバル関数といえるメソッド群がクラス変数という実質グローバルな情報を介して結びついている点で「共通結合」であり、6段階あるうちの悪い方から2番目で、やはりかなり酷いコードと言える。

さらに、凝集性や結合度はオブジェクト指向隆盛以前の、構造化手法全盛期からある設計評価基準だけど、オブジェクト指向に関連の強い言葉「責務」を用いて表現すれば、「createCarpets によって、Colors が一つ以上の異なる責務を負っている事が問題」と言う事もできる(責務:言うまでもなく CRC の R(Responsibility))。

つうわけで、上のコードは単純過ぎる例ではあるものの、技術的には「低凝集・高結合度で、複数の責務を負った下手糞なコード」だと言える。まあ、そういうコードや設計を放置しておくと、その先どうなるかについて、技術者じゃない人になら説明する必要があるけど、技術者同士なら説明不要だと思う(思いたい)。

ただし、実はクソヘタプログラマだけがこんなコードを書いているのではなくて、そこそこの技術者/チームでも、開発期間の中で生じるいろんな行きがかり上、たまたまこんな感じになってしまって、そのまま直す機会を逸してしまってる場合もある。

まあ、でも。そんな時のためのリファクタリング技術なわけで、ちゃんとテストコードを実行するという前提(当たり前だけど、無ければ必ず書き足す)さえ守れば、なんぼでも体質改善すれば良い。

だけど、そもそもダメだって事が認識できないとなると、いろんな意味でかなりやばい。説明するこっちも、クラスの責務から説明すればいいのか、結合度・凝集度から説明すればいいのか、それとも小学校受験の仲間外れ問題まで遡って説明しなければならないのかと考えてしまって、なんかもうキッツイわ…

2011年11月1日火曜日

Eclipse と JMockit でテストファースト

以前のブログで、他の2つのクラスとのコラボレーションを含む簡単なクラスのテストをJMockitを使って書いたけど、その時は、敢えてレガシーコードに後付けテストを追加するような感じで書いてみた。

やろうとしている事は以下のようなものだった。

  1. 作ろうとしているのは Foo というクラスで、文字列を返すメソッド hoge() を持っている
  2. Foo#hoge() が返す文字列は、Baz#hoge() から取得したものである
  3. ただし、Baz のインスタンスは、Bar のインスタンスを経由しないと使えない
これを以下に、テストファーストで実装してみる。ただし今日は、Eclipse を極力活用するという趣向でやってみる。

====

まず①の クラス Foo とメソッド hoge()だけど、以下の様に、まず最初にテストクラスを書く。

public class FooTest {
   @Test public void hoge() {
      Assert.assertEquals("dummy", new Foo().hoge());
   }
}
まだクラス Foo が無いから当然コンパイルエラーの赤波が表示されるので、以下のように仮実装する。
 public class Foo {
   public String hoge() {
      return null;
   }
}
ただし、これをそのままタイプしてはいけない。Eclipse を使うという前提なのだから Ctrl+1を2回(クラスに一回、メソッドに一回)使うだけでタイプせずに済むので、これを活用する。
とりあえず、ここまでで JUnit を実行して、テストが失敗するのを見てから、hoge() の戻り値を"dummy"に変えて、テスト成功に変わるのを確認しておく

で次に、② の Baz#hoge() への委譲。これもテスト・コードから書く。

public class FooTest {
   @Test public void hoge() {
      new NonStrictExpectations() {
         Baz baz;
         {
            baz.hoge(); result = "dummy"; times = 1;
         }
      };
      Assert.assertEquals("dummy", new Foo().hoge());
   }
}
ここでももちろん、Baz も Baz#hoge() も未だ無いわけだから赤波が表示されるけど、さっきと同様に Ctrl+1 を 2回使って Baz の空実装を Eclipseに生成させる。ただし今度は Baz#hoge() の中身を以下の様に書き換える。
public class Baz {
   public String hoge() {
      //FIXME [your user name] Nov 1, 2011
      throw new AssertionError();
   }
}
自分の環境では、この FIXMEコメントから AssertionError送出の2行は、Java エディターのテンプレートに登録してあるので、fixme とタイプして Ctrl+1を押せば、その日の日付とログインしているユーザ名で2行挿入される。

で、赤波が消えたら、また JUnit を実行して Baz#hoge() が呼ばれていないというアサーション例外が上がるのを確認し、Foo#hoge() を以下の様に書き換える。
public String hoge() {
   return new Baz().hoge();
}
書いたらまた JUnitを実行して、今度は緑になるのを確認。

最後に、③ の Bar インスタンスを経由して Baz インスタンスを得るところ。これもやっぱりテストコードから。

public class FooTest {
   @Test public void hoge() {
      new NonStrictExpectations() {
         Baz baz;
         Bar bar;
         {
            bar.baz(); result = baz; times = 1;
            baz.hoge(); result = "dummy"; times = 1;
         }
      };
      Assert.assertEquals("dummy", new Foo().hoge());
   }
}
またここでも Baz#hoge() と同様に、Ctrl+1 をクラスとメソッドにそれぞれ適用して空実装を生成し、FIXME付きのアサーション例外送出コードを書いておく。

赤波が消えたらJUnit実行。Bar#baz() が呼ばれてないってアサーション例外が上がったら、おもむろに Fooを以下のように書き換える。
public class Foo {
   private final Bar bar = new Bar();
   public String hoge() {
      return this.bar .baz().hoge();
   }
}
但し、これも Ctrl+1 (→ Create field 〜)を活用し、無駄なタイピングをせずに編集する。できたら、Junit を実行しになることを確認。

というわけで、完了。

====

ちなみに、この間からかじり始めた Coq のチュートリアルの第2回で、結論から1個ずつ論理を遡って証明を完成させていく手順が紹介されていたけど、テストファーストと似ているところがあると思った。