2011年10月23日日曜日

Fedora 15 + JavaDB

自宅マシンで javaDB を使って試したいことがあったんだけど、OpenJDK には JavaDB が入っていないらしい。「ソフトウェアの追加と削除」にもリストアップされない。という訳でRPMからインストールしてみた。

まず、ここからダウンロード

で、Oracle サイトのここを見てインストール

$ chmod +x javadb-10_5_3_0-linux-rpm.bin 
$ ./javadb-10_5_3_0-linux-rpm.bin 
# su
# cd javadb-10.5.3.0/
# rpm -ivh sun-javadb-*.rpm

ij コマンド で起動して、試してみる

ij> connect 'jdbc:derby:testdb1;create=true'; 
ij> create table t1 (col1 int primary key, col2 varchar(10));
ij> insert into t1 (col1, col2) values (100, '俺'));
ij> select * from t1;
COL1       |COL2      
----------------------
100        |俺   

まあ問題なさそう。
※実はエラーメッセージとか help とかが文字化けしてたけど、javaコードから使うのがメインだから放置。

2011年10月18日火曜日

Coq をインストールしてみた

今日、現場の昼休みに、最近噂の Coq のチュートリアルを見つけたので、早速インストールしてみる。

Fedora 15 でアプリケーションの追加と削除を開くと、Coq 8.3がリストアップされてきたので、チェックを入れて適用。

さらに、端末を開いて coqide と打ち込んでみると、ひとしきり追加のインストールが実行されたあと、CoqIDE が開いた。 とりあえず、チュートリアル 一回目の練習問題を解いてみるが、初回なんで難しくない。

結構面白い。明日もやろっと。

2011年10月17日月曜日

リファクタリング再読 4章 テストの構築

このブログで、何度も引用してきたけど、10年以上前に読んだリファクタリングが、今読んでも面白い。

面白いと言っても、当たり前の事が普通に書いてあるだけなんだけど、忘れた頃に読み返すと、自分が現場でいつも言っている事と大部分一致していて、若い頃に受けた影響ってやっぱりずっと残るんだと実感する。(さすがに、このレベルの名著ですら、やはり10年も経つとある程度の陳腐化は避けられず、全てに同意はできるわけではないけど…)

今日は 『4章 テストの構築』から、何点か引用して考えてみたい。

====

テストを完全に自動化して、その結果もテストにチェックさせること。(P.90)
xUnit Patterns の言葉で言うと、Manual Interventionは止めようねって事で、まあ、JUnitの基本中の基本。とはいっても、@Test メソッドの中で普通のアサーションで書ける比較を、標準出力に書き出して目視確認してたおバカさんを、こないだ発見した。こんなレベルの人が「JUnit 使用歴○○年です!」なんつってプロジェクトに入り込んできちゃったりするから、この業界っておそろしい。


テストを頻繁に実行せよ。コンパイル時にはテストを局所化して、一日に最低一度は全てのテストを実行せよ。(P.94)
今は普通に CIサーバでコミット時にテスト走らせたり、incremental test とかも普及してるから、一日一度はさすがに10年前って感じがするが、なるべく頻繁に実行すべしってのが、この文の趣意。まあ、なんぼ言っても、他人のテストコードをコケさせておきながら、コンパイルが通ってるってだけでテストもせず、平気でコミットする間抜けが減るどころか永遠に増え続けてるから、気が滅入るが…。


バグレポートを受け取ったら、まずそのバグを明らかにするための単体テストを書け。(P.97)
これは今も昔も良いプラクティスで、余り反論する人を見たことがないけど、数年前までは、いざバグを再現させるテストコードを書こうにも、そもそもテスタビリティが低すぎて、異常に難易度が高い場面もあった。今は、instrumentation を活用したテストフレームワークが普及しているから、そうでもないけど。(テストツールのテスト能力が高まっただけで、ソースコードのテスタビリティは大して進歩していないのが悲しいが)


ここでお話しするのは「単体テスト」です。これはプログラマの生産性を向上するために行うものです。これで品質管理部門が満足するとしても、それは副作用に過ぎません(p.96)
リファクタリング〜xUnit の文脈の中で、「機能テスト」と対置されて語られる「単体テスト(UnitTest)」って、ウォーターフォール・モデルの単体テストを単に自動化しただけのものとはかなり違うんだけど、この勘違いは今でもかなり根強い。 自分が、現場で説明する時には、「外部品質じゃなくて内部品質をあげるためのツールだから」とか言う事がある。分からない人はどう言い換えてもやはり分かってくれないけど、わかる人はなるほどそうかと納得してくれたりする。


不完全なテストでも、書いて実行する方が、実行できない完全なテストよりもましだ。
テストを書けば自分が楽になるって認識が成立しないとプログラマはテストを書きたがらないわけで、つまり下手なプログラマって、テストコードによって自分で自分の作業を楽にする事ができないプログラマなんだけど、その手の輩に無理やりテストを書かせようとするから無理なカバレッジ基準が制定されて、後付けでパスを通そうとするから、却って生産性が下がる。そんなのどうせ、まともなアサーションが書かれるわけないのにさ。

本当は、自覚して UnitTest を使えてるプログラマが、どこにどの程度テストコードを書くか自分で判断すればいい事なんだけど、最低辺も含めて、いろんな人が集まるプロジェクトでは、うーんやっぱ難しいのかな…


大事な事は、一番怪しいと思う部分をテストすることです。それが最も効率の良いテスト方法です。(p.97)
失敗の恐れのある境界条件を考えて、そこを集中的にテストせよ。(p.99)
失敗すると予想される時に、例外が上がることをテストし忘れないこと。(p.100)
境界条件や例外条件なんかをテストするにしても、テスト駆動/テストファーストでやるのが、結局は一番楽で確実なんだけど、現場のレベルによってはいくら言い聞かせても浸透しない。後付けでテスト書いたって、カバレッジだけに必死になって、どこが「怪しい」かなんて完全に眼中の外になってしまう事が分かりきってるんだけどね。


テストで全てのバグが見つからないからといって、テストを書くのを止めてはならない。ほとんどのバグはテストで補足される。
そもそも開発者が要件を誤解していたり、忘れていたりしたら、そういった認識エラーは本体コードと同様にテストコードにも織り込まれてしまうから、開発者テストだけで捕捉するのは論理的に不可能。そういった限界がある事を認識した上で、機能テストも絡めた全体的なテスト計画を立てるのが常道。


====

あと、引用ではないけどテスト関連で付け加えると、『リファクタリング』に載ってるリファクタリング・パターンのほとんどに「コンパイルしてテストする」と言う手順が組み込まれている事も、改めて確認できる。

この10数年で、リファクタリングという言葉が普及したおかげもあってか、動いてるコードに手を入れる心理的ハードルが昔より低くなったけど、「動いているコードはいじるな」っていう昔からの不文律を乗り越えて良いのは、テストコードという前提あってこそという、基本中の基本が蔑ろにされ始めてるのを感じる。テストもせずにコードをいじって、CIサーバから常時テスト失敗メールが飛んできてるのに何とも思わない連中が、どんどん増えてきている気がする。

出来ている現場の出来てる人達はびっくりするかもしれないけど、まだまだこんな感じの現場が多いんだよな… いかん、また眠れなくなってくる…

2011年10月10日月曜日

自動テストのレベル分け

最近は、「xUnit で UnitTest を書いてカバレッジとってます」なんて、どいつもこいつも謳ってるけど、お前らマジかと。それで出来てるつもりなのかと…?

というわけで、秋の朝がさわやかなので、自動テストの出来てる度合いの段階区分を考えてみたい。

  Level 3: 出来てる:常時テスト成功、高カバレッジ

ちゃんとできてるプロジェクトの出来る子ちゃん達からしたら、取り立てて言及することもない当たり前の状態だと思う。

"Clean Check-In" が普通に守られているから、当然、テストも常時全件成功する。まあ基本中の基本。

で、TDD/TestFirst で開発してるから、開発の最初期から高カバレッジ(計測対象範囲内でのカバレッジ基準充足)で始まり、進捗に伴いソースコード量が増えていく中でも、高カバレッジを維持したまま推移する。


Level 2:怪しい:ほぼ常時テスト成功、低カバレッジ

"常時"かつ"全件"、テストが成功してはいるけど、カバレッジが低いプロジェクト。まあ、後付けでテストを書こうとすると、えてしてそんな感じになる。

原因としては、単にスキルがないから仕方なく後付けになったり、確信的にテストをサボっていたり、いろいろあるにせよ、結果的に以下のような感じになる。

①:テスト・コーディングに凄い余計な時間が掛かる。テスト・ファーストでやってさえいれば自然に得られたはずの保守性・メンテ性が備わってない低テスタビリティ・コードに無理やりテストコードを書いてく事になるから、まともな生産性は無理。

② :テスティング・ポイントがウヤムヤになる。本体コードを書いていた時点では脳内にあったはずのコーディング意図がとっくに揮発した状態で、テストコードを後付けする破目になる。何をテストしてるのか本人が分かってない状態。

③ :テストコードがザルになる。①で指摘した生産性の低いテストコードを、②で指摘した曖昧状態のプログラマが書いてくわけだから、「何かをテストする」事ではなくカバレッジを通す事が自己目的化してしまい、結果、Assertion も Expectation も不十分で、単に実行経路に含まれただけの空虚なテストコードになってしまう。

まあ、後付けテストの全てがそんな糞テストコードばかりとは限らないけど、テストコードは本体コードより先に書いとくに越したことはない。それが普通なのだと認識するだけで、悪い事がいろいろ避けられて良い事がいろいろ増えてくる。


Level 1:下手糞:常時テスト失敗、低カバレッジ

低カバレッジでも、取り敢えずテストが存在して差し当たり成功していれば、まだ見どころはある。また、たまたま間違えてテストを壊す事もあると思うが、そんなのすぐ直せば良い事で別に問題じゃない。

だけど、テストが通らないコードをコミットするのが常態化していたり、CIサーバでテストが失敗してるのに放置されていたりしたら、それはかなりの低スキル・チームの疑いがある。

実は、そうしたプロジェクトでは、Level 3 の状態を志してはいたもののスキル不足で健闘虚しくって感じじゃなくて、 Level 1 の状態で普通・正常だと思ってる開発者が大勢を占めていたりする。酷いのになると「今までに経験したプロジェクトではそれが普通だったし、テストが壊れても気にしない方がリファクタしやすい。」なんて事を真顔で主張したりする(リファクタするためにこそテストコードが必要だという最低限の常識を弁えていれば、そうした発言は出ないんだけど…)。

そうやって考えてくと、そもそも技術者の○○使用経験とか○○歴とかって何なのだろうという問題に突き当たるが、これは別の機会に考えてみたい。あと、Level 1 で生じている問題には、余りにも劣化した形で普及し実践されている『リファクタリング』もあるのだけど、これも別の機会に考える。

朝から、気が滅入ってきた。山でも歩いてこよっと。

2011年10月9日日曜日

Eclipse から XSLT 2.0 を使うやり方

今更だけど、Eclipse の XSL Developer Tool で XSLT2.0 を使うやり方が分かったのでメモっとく。
  • まず、Saxon home edition の新しいやつ (saxonhe9-3-0-8j.zip)をダウンロード。
  • ダウンロードしたZIPを適当なとこに展開して、中の saxon9he.jar を出しておく。
  • Eclipse のメニューを [Window]>[Preference]>[XML]>[XSL]>[Java Processors] ってたどる。
  • Add を押して開いたダイアログボックスで、だいたい以下の様な感じで入力。追加したやつのチェックボックスを選択しておく。
これで使えるようになる。 試しに、昔のポストで書いた XSLT で平均と分散を計算するやつを、XSLT2.0 を使ってやってみる。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" />
  <xsl:template match="/list">
    <xsl:variable name="result">
      <xsl:call-template name="stat">
        <xsl:with-param name="items" select="item" />
        <xsl:with-param name="subtotal" select="0" />
        <xsl:with-param name="count" select="0" />
      </xsl:call-template>
    </xsl:variable>
    <average>
      <xsl:value-of select="$result/avg" />
    </average>
    <varianece>
      <xsl:value-of select="$result/sumOfD2 div $result/count" />
    </varianece>
  </xsl:template>
  <xsl:template name="stat">
    <xsl:param name="items"/>
    <xsl:param name="subtotal"/>
    <xsl:param name="count"/>
    <xsl:choose>
      <xsl:when test="$items">
        <xsl:variable name="t">
          <xsl:call-template name="stat">
            <xsl:with-param name="items" select="$items[position() > 1]" />
            <xsl:with-param name="subtotal" select="$subtotal + $items[1]" />
            <xsl:with-param name="count" select="$count + 1" />
          </xsl:call-template>
        </xsl:variable>
        <sumOfD2><xsl:value-of select="($items[1]-$t/avg)*($items[1]-$t/avg)+$t/sumOfD2"/></sumOfD2>
        <count><xsl:value-of select="$t/count"/></count>
        <avg><xsl:value-of select="$t/avg"/></avg>
      </xsl:when>
      <xsl:otherwise>
        <sumOfD2>0</sumOfD2>
        <count><xsl:value-of select="$count"/></count>
        <avg><xsl:value-of select="$subtotal div $count"/></avg>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

前回と同じ入力XMLから同じ結果が得られることが確認できた。

前にやった時は、結果ツリーフラグメントの制約から template の結果を文字列にして返さざるを得なくて煩わしかったけど、XSLT 2.0 の temporary tree では 普通の XML データとして扱えるので、やってる事をより直截に表現したコードになった。

2011年9月29日木曜日

リファクタリング本の Cyclomatic complexity

『リファクタリング』 を読んだ人なら、以下のコードに見覚えがあるかもしれない。第一章「最初の例」でのリファクタリング実演のために用意された架空の「長すぎるメソッド(Long method)」で、このメソッドを含む一群のコードを改善する過程で、リファクタ術が披露される。

まあ、実演のためのサンプルだから、本気でバカ長いメソッドを掲載するわけにはいかないだろうけど、それでも全然簡潔ではないし見た目も悪い。

自分なら普通、こういうのを製品コードに残したりはしないけど、それは単に自分にとって綺麗とか汚いといった審美的な感覚だけではなくて、他の開発者のミスリーディングを引き起こしたり生産性を下げたりといった、つまり他人様に迷惑をかける真似はすべきでないという、割と真面目というか普通な社会人的常識からでもあったりする。

  public String statement() {
      double totalAmount = 0;
      int frequentRenterPoints = 0;
      Enumeration rentals = _rentals .elements();
      String result = "Rental Record for " + getName() + "\n";
      while (rentals.hasMoreElements()) {
         double thisAmount = 0;
         Rental each = (Rental) rentals.nextElement();
         
         switch (each.getMovie().getPriceCode()) {
         case Movie.REGULAR:
            thisAmount += 2;
            if (each.getDaysRented() > 2)
               thisAmount += (each.getDaysRented() - 2) * 1.5;
            break;
         case Movie.NEW_RELEASE:
            thisAmount += each.getDaysRented() * 3;
            break;
         case Movie.CHILDRENS:
            thisAmount += 1.5;
            if (each.getDaysRented() > 3)
               thisAmount += (each.getDaysRented()  - 3) * 1.5;
            break;
         }
         frequentRenterPoints ++;
         if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
               each.getDaysRented() > 1) frequentRenterPoints ++;
         
         result += "\t" + each.getMovie().getTitle() + "\t" +
         String.valueOf(thisAmount) + "\n";
         totalAmount += thisAmount;
      }
      result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
      result += "You earned " + String.valueOf(frequentRenterPoints) +
      " frequent renter points";
      
      return result;
   }

ところで、このコードの扱いにくさを「計測」するとどうなるか。よく使われている尺度としては、Cyclomatic Complexity があるが、どんなもんだろう。

ちなみに、CheckStyle の解説では、

  • 1-4: considered good
  • 5-7: ok
  • 8-10: consider re-factoring
  • 11+: re-factor now!
とある。

で、実際に測ってみると出てきた複雑度は 9 だった。"consider re-factoring"という事になるから、本の中での実演のネタとしては、正に丁度良かったという事になる。

さらに本に従って最後の段階までリファクタを施したコードの CyclomaticComplexity を測ってみると、結局 2 にまで低減していた。さすがにこれくらいまでやると、変な引け目も後ろめたさもなくソース・コードを共有できる。

で、せっかくある程度客観的にコードの良し悪しがでるのだから、自分だけじゃなくチームにも、簡潔で扱いやすいコードを書く方向で前向きに頑張ってほしいと思うのだけど、いろんな人が集まる現実のプロジェクトではそうも言ってられない場合も多い。

特に、すでにある程度コードが書き貯まったプロジェクトで、途中で CyclomaticComplexity チェックを適用したりなんかすると、「11+」どころか、妥協して15以上とかで設定しても警告が大量発生して、結局「今回は諦めようぜ」って感じになる。

人生って厳しいよね。

2011年9月18日日曜日

依存 jar を変更したらどうなるか

あるアプリケーションがビルド時に参照していた ライブラリの jar を変更して、そのアプリケーション再コンパイルせずに実行したらどうなるか。

====
以下のようなライブラリ・コードを書き、コンパイルして foo.jar に丸めておく。
package p1;

public class Foo {
  public String bar() {
    return "hello";
  }
}
で、このライブラリを使う以下のようなクライアントコードを書いて、-cp に foo.jar を指定してコンパイルする。
package p2;

import p1.Foo;

public class Client {
  public static void main(String[] args) {
    System.out.println(new Foo().bar());
  }
}

コンパイルされたクラスを java コマンドで -cp に foo.jar を指定して実行すると、標準出力に hello と出力される。


さて、ここで クラス Foo を変更して、つまり foo.jar の中身を変えて、再コンパイルせずに再度 Client#main() を実行したらどうなるか。

(1) メソッドの中身を変えてみる

  public String bar() {
    return "good-bye";
  }
問題無し。標準出力に good-bye と表示される。

(2) メソッドの名前を変えてみる

  public String Bar() {
    return "hello";
  }
メソッド p1.Foo.bar()Ljava/lang/String が見当たらないって事で、NoSuchMethodError が送出される。リフレクションのコーディングでよく catch したりするNoSuchMethodExceptionではなく、NoSuchMethodError が投げられてきた。

(3) メソッドの引数を変えてみる

  public String Bar(String s) {
    return "hello";
  }
これは (2) と同じ

(4) メソッドの戻り値を変えてみる

  public Object Bar() {
    return "hello";
  }
これも (2) と同じ。戻り値も識別される。

(5) throws を追加してみる

  public String bar() throws IOException {
    throw new IOException("test");
  }
これは意外にも普通に実行されて、IOExceptionが送出されてスタックトレースされる。意外というのは、これを再コンパイルしようとすると、コンパイルエラーが出るからで、Client#main() に throws を追加するか、bar() を try-catch で囲むかしないと、コンパイルが通らない。でも、コンパイルは通らなくても実行時のメソッド呼び出しは成功する。

(6) メソッドの可視性を変えてみる

  private String bar() {
    return "hello";
  }
これは IllegalAccessError が送出される。つまり、一応 p1.Foo.bar()Ljava/lang/String の存在は識別された上で、アクセスに失敗して例外が発生した模様。

(7) クラスの可視性を変えてみる

class Foo {
…
(6) と同様だが、クラス p1.Foo の存在を識別したあとに、アクセスするところで失敗している。

(8) 定数を変えてみる

メソッドは以上のような感じで、フィールドもだいたい想像がつく。で、ここでちょっと定数を試してみる事にする。

まずライブラリコードを以下のように変える。

package p1;

public class Foo {
  public static final int C = 100;
}
次に、クライアントコードを以下の様に変える
package p2;

import p1.Foo;

public class Client {
  public static void main(String[] args) {
    System.out.println(Foo.C);
  }
}

両方コンパイルして実行すると、標準出力に100が実行される。

ここでライブラリコードを以下の様に修正し、jar を作り直す。

  public static final int C = 200; 
で、クライアントコードを再実行すると、標準出力に 200 が出力されると思いきや、実際には変更前と同じ100が出力される。
javap で見てみると、バイトコードに定数 100 が埋め込まれているのがわかる。なるほど定数はこういう扱いらしい。
public static void main(java.lang.String[]);
  Code:
   0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3: bipush 100
   5: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
   8: return 

(9) null値 を試してみる

ライブラリ・コード を以下のように変更して、両方とも再コンパイルして実行してみる。
package p1;

public class Foo {
  public static final String C = null;
}
標準出力には、null が出力される。

ここでライブラリ・コードを、public static final String C = "a"; に変更して、クライアントを実行してみる。

(8) の結果から、null が出力される結果を類推してしまうが、実際には "a" が出力される。理由は、Java 言語仕様として null 定数として扱われないためコンパイル時には byte コードに直接書き込まれず、実行時に初めて Foo.Cの値を読むことになるかららしい。javap は以下のようになる。
public static void main(java.lang.String[]);
  Code:
   0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3: getstatic #3; //Field p1/Foo.C:Ljava/lang/String;
   6: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   9: return