開発実況中継中 (2)

テーマ:

このブログの読者になった方が「TDDでなんか作ってみる 」というブログを開いていることを知った。TDD というのは何かというと、Test Driven Development か? 「か?」というのは心もとないが、Test first というアレに近い。

Test first と何が違うかという話は、@IT の特集「テスト工藤開発」はプログラマのストレスを軽減するか? に書いてあった。ただ、申し訳ないが私はこの記事を読んでも Test first と TDD の違いが分からなかった。というか、これでは同じだと思うわけだが、余談はさておき、

先に紹介したブログにある「ハッシュを使おう 」という記事には、「恋するプログラム」という本が出てくる。この本、「裏の裏ページ 」でも紹介したのだが、買った。これはいい本だと思う。ただ、Ruby というマイナーの帝王のような言語で書かれているので、そこに抵抗がなければという話になってしまうが。

先に紹介した記事には、Java でハッシュはどう書くのかという話が出てきて、TDD で作ってみた、という話が出てくる。

Java では Ruby や Perl や PHP のハッシュ(連想配列)のことをおおむね Map と呼んでいる。おおむねというか、Map の方が圧倒的に多機能である。私がよく使うのは TreeMap だ。TreeMap は要素がキーでソートされた状態で格納されるので、Iterator を使って順番に取り出すことができる。Ruby のハッシュにはそのような機能はなかったと思う。

Mawou は掲示板の投稿を扱うので、投稿を順に取り出すという機能が必須、ということで TreeMap の出番になるのだが、これで Comparator を使ってハマった話は FPL の Java の掲示板に書いた通りである。

さて、例の本に出てくるハッシュの例は、

sound = {
'ジャブ' => '「バシッ」',
'ストレート' => '「ビシィッ」',
, 'ローキック' => '「ベチィッッ」',
}

である。(p.33)

これをテストケースでテストするには、こういうコードを書く。一部抜粋だが。

String key1 = "ジャブ";
String value1 = "「バシッ」";
assertEquals(value1, sound(key1));

リテラルを直接書かない理由は、後で修正したときに、各所に別々に書いてあったら、一部だけ直してもいいのかどうか分からないからだ。実際、同時に修正しなければならない場合には、必ず一部が修正漏れになることが経験的に知られている。

修正漏れでなくても、テストケースの中でリテラルのタイプミスというか打ち間違いが原因でヘンな結果が出ることは防ぎたい。前回書いたように、テストケースのロジックがバグっているというヘンな話になると、何のためにテストしているのか分からない。しかし実況チャットでよくあるのが、コレである。

本当は、このようなテスト用のキーと値のペアは、Map で持っていてもよさそうなものだが、Map でハッシュを実装する処理を作っているのに、テストケースの中で Map を使ってハッシュを実装しているというのは何かがおかしいのだ。

さて、このように、必要な値が出てくることのテストは誰でもやるのだが、もう一つ重要なテストだ思っているのが、false を返すテストである。前回の記事で「逆を書かない」という話だ。もっと具体的にいえば、sound("ジョブ"); の結果はどうなって欲しいか、というのもテストしろと言っているのだ。つまり、こういうテストである。

String keyX = "ジョブ";
assertEquals(null, sound(keyX));

Java では Map に対して登録していないキーを使って値を取り出そうとしたら null を返すことになっている。当たり前だが null に equals というメソッドはないが、assertEquals はこういう比較もしてくれる。そうなると、もう一つありそうな場合をテストしておいた方がいいことに気付く。この sound というメソッドに null を与えたらどうなるのか?

assertEquals(null, sound(null));


これは、もしかすると NullPointerException を出すという仕様でもいいかもしれない。そういうコードを書く方の責任だというのだ。とりあえず、今回はこの程度あればかなり十分だろ、というテストの内容は、例えば次のようになる。

String key1 = "ジャブ";
String value1 = "「バシッ」";
assertEquals(value1, sound(key1));

String keyX = "ジョブ";
assertEquals(null, sound(keyX));

// 特殊な場合
assertEquals(null, sound(""));
assertEquals(null, sound(null));

sound("") って何だ? いや、こういうのがどうも気になるというか、気になりませんか、そうですか。この程度、入れておいてもいいんじゃないの、ということで。

こういう特殊な状況下だけ思わぬふるまいをすることが結構ある。文字列だと、長さが0の場合。テストしたいメソッドが引数にCollection を要求しているなら、要素が一つもない状態で呼んでみる。そういう境界条件のテストも重要である。Mawou のテストでも、掲示板に発言が1つもないときにやられてしまった。まだテストしてないが、多分、フォーラムに掲示板が1つもないとヤバいと思うが、それってソフトじゃなくてそのフォーラムがヤバくないか、ということで気が付かなかったことにしている。

なお、紹介したページには、(String) というキャストが無作法ではないかという話が出てくるが、その感性は正しいと思う。Java としてもずっと気になっていたようで、J2SDK 5.0 (1.5) から、Generics という仕様が追加された。多分、それを使えば、

Map hash = new HashMap<String, String>();


とか書けて、

return hash.get(string);


と書けるんじゃないかな、5.0 の環境が手元にないので、試してないけど。

AD

開発実況中継中 (1)

テーマ:

久しぶりにプログラミングの話。FPROG では Mawou という開発コードで、@nifty のフォーラムを自動的に巡回するというプログラムを作っている。企画会議室もある。ただ、今のところ実際にコードを書いているのは私だけである。つまり、気楽に作れる状態である。

これは Java で Eclipse を使って書いているので、とりあえず、Junit でテストしながらの開発をしている。前回紹介した、@nifty の新チャットで、開発中の様子を生中継しながらである。フォーラムのチャットは6/15に入れ替えになるので、今のところ、とりあえず、フリートークCという特設チャットを使っている。フリートークCだが、言語はCではなくJava なのだ。

そういえば、C++ の場合は Junit みたいなのが何かあるか、という質問がかなり前にFPL であったのだが、私がちょっと探しただけだと見つからなかった。手ごわい。ないはずはないのだが。

さて、実況chatというか一人で勝手に喋っているのを見ているのは面白いらしいが、実況をみたことのある方はご存知のように、要するに Junit はokなのに、その後に実行したら

_| ̄|○

ということがよくあることが分かる。どういうことかというと、単体テストは通っても結合テストというか、全体の処理がダメなのだ。Junit には限界があるのは当然で、そのためのテストプログラムも結構あったはずだから、そちらを行えばいいのかもしれないが、Mawou はまだそこまで進んでいない。最近ハマったのは cookie の処理で、結局 cookie の処理をかなり書き直したのだが、こういうのは相手サーバーがいないとなかなか先に進めない。

もっとすごいのもある。テストケースがバグっているとか。まあそれはすごすぎるのだが、まさかテストケースのテストを書く人もいないだろうし。

冗談はさておき、テストケースを書くときに、「何をテストすべきか」ということが、よく問題になる。完璧なテストはもちろん無駄なことが多い。ある程度テストできれば吉、というアプローチは正しいのである。要は、コストの問題だ。このあたり、最近のJAVA WORLD でも出ていたと思うが、回収できないようなコストをかけて製品作っても意味ない、という話。

でも、最低限しなければならないテストは何か。「こうすればこうなるはず」というテストを忘れる人は、さすがにいないのだが、逆を書かない人は結構いそうだ。というか私がそうか。

(つづく)

AD
UIの話を書いたので、ついでにプログラミングの話を。条件を満たすかどうか調べる処理を書くときに、途中で返す値を trueかfalseか、どちらかに統一できれば分かりやすくなる。こんな感じである。

// true だと中断する場合
boolean isHogeHoge() {
 if (条件1)
  return true;
 if (条件2)
  return true;

 …略

 return false;
}

// false だと中断する場合
boolean isHogeHoge() {
 if (条件1)
  return false;
 if (条件2)
  return false;

 …略

 return true;
}

もっとも、常にこううまく書ける訳ではないのだが。
AD
前回の続きです。前回と同じく、Ameba Blog ではプログラムを本文内に書くのは現実的に無理なので、Jugem の方に置いたものをご覧ください。

あまり特殊なテクニックは使わない (2)

ちなみに、ここで出てくる「ふ」というのは私、「U」というのは、私のアシスタントでPC-286U というパソコンで動作している人工知能という設定のUちゃんです。その昔、プログラマーズフォーラムという所で会話形式の発言を書く時に登場してから十数年たちますが、まだ健在です。

@niftyにあるフォーラムも何と健在で、前夜のchatはN-BASICの話とかで超少人数で盛り上がっていたような感じです。プログラムの変数名はどう付けるかというネタなのですが、そういえば DEFINT というのがあったな、とかいう話。
27日から、HTMLタグの一部が使えるようになったというのですが、肝心の<pre>が使えないとプログラムを書くのは殆ど不可能に近い状況になるわけで、今回のは流石に Ameba Blog に書くことに挫折しました。ということで、Jugem にある「裏表」というブログの、「あまり特殊なテクニックは使わない」をご覧ください。

というか、&とかタグを表す文字列そのものを文章中に書きたい場合にどうすればいいのか分からないので困ったものです。以前は、普通に&amp; と書いておけば&と表示してくれたのですが、「<」「>」あたりの文字を使っても本文が途中消滅しなくなった頃から使えなくなったような気がします。
今回はプログラミングの話。ココメモのアルゴリズムに関するクイズである。図がないと説明が難しいので書きにくいのだが、とりあえず書いてみる。

既にバックナンバーに登録してある各月のページをご覧になった方はご存知のはずだが、ココメモで作成したページは、それぞれの記事が、次のような表の形式に変換されている。

―――――――――
本文1   注釈1
―――――――――
本文2   注釈2
―――――――――

あまりきれいな図ではないのだが、多分 Ameba Blog では表が書けないので、とりあえず分かればいいということで、気にしないことにした。で、このページの変換前のデータはどうなっているかというと、段落ごとに、こんな感じだ。

本文1
注釈1
本文2
注釈2

本文とそれに対応する注釈を、表の同じ行に割り振るのである。そうすることで、本文と注釈を横方法に同じ位置に揃えて表示できる、という仕組みだ。

プログラムを作るときには、それぞれの段落を、どのような規則で表の行に割り当てるか、というのがポイントとなる。それを考えるというのも面白い問題になるのだが、今回は次のような規則を考えた。

・本文の段落の次に注釈の段落が来たら、その本文と注釈を同じ行に配置する。

・本文なしで注釈の段落が現れたら、注釈だけの行を作る。

・本文に対応する注釈がない場合、本文だけの行を作る。

ただし、少し凝ったことをしている。本文や注釈が連続して現れた場合には、できるだけ同じ行に詰め込むのだ。

・注釈の次に注釈の段落が現れたら、同じ行に配置する。

・本文1に続けて本文2が現れたら、本文2に注釈が対応しない場合は、本文1と本文2を同じ行に配置する。

少しややこしい例を示しておこう。

注釈1
注釈2
本文3
注釈4
本文5
本文6
本文7
注釈8
本文9

この記事は、次のような表になる。

―――――――――
      注釈1
      注釈2
―――――――――
本文3   注釈4
―――――――――
本文5      
本文6      
―――――――――
本文7   注釈8
―――――――――
本文9      
―――――――――

このように本文と注釈を割り振るためには、どのようなアルゴリズムにすればよいか、というのがクイズ。ひっかけ問題ではないから、どこが難しいか先に書いておくと、本文6と本文7を別の行に入れるというのが難しいはず。

ココメモ (3)

テーマ:
ココメモ (3)

記事に < を入れるとそれ以降が消えるという記事の中で、本文に「<」を入れたらそこから後が消えてしまったという話を紹介した。アメーバブログの FAQ を見ると、この障害は解消したことになっている(10月10日現在)。入力しても問題ないそうだ。試してみると、実際問題なさそうだ。

ところで、以前は「<」を書きたいときには「&lt;」と書けばいい、という回答だった。だから、ココメモ (2) はそのような書き方をしたのだが、何と、「&lt;」と書いたら、「&lt;」という文字列がそのまま表示されるという、新たな問題が発生しているようだ。「&amp;」や「&quot;」のような、通常、HTML で使われるようなクオート文字列も、解釈されずにそのまま表示されている。(プレビュー画面では正しく(?)表示されている)。

仕様としてそのように処理するというのなら、別にそれはそれで構わないが、以前は「&lt;」と書けとヘルプに指示してあったのだから、そう書いても「<」と表示しなくなったのなら、一言何かあるべきだと思う。それと、「&lt;太字&gt;」と書くにはどうすればいいのか、誰か教えて欲しい。

※「&lt;」と「&gt;」のところは、実際は「<」と「>」を書きたいという話である。単純にその通り書いたら、とてつもなくおかしな画面になってしまうので。

本題に戻る。W3Cの validatorにかけると、URLに「&」が入っていると警告されたという話である。これは実はbk1 というオンライン書店サイトへのリンクに出てきたのだが、一般に、CGI を使ったサイトへリンクするときには、URL の後の方に、付加情報を & で区切って追加するという書き方があるのだ。

このとき、URLの最後の方が

 …?gu=01010000&aid=個人提携ID

のような感じになる。この & という文字を、&amp; と書きなさい、という警告が出るだ。つまり、次のように書くのが正しいのである。

 …?gu=01010000&amp;aid=個人提携ID

このようなURLの中で & をそのまま書くやり方は、割と普通に行われていて、実際、この例は bk1 のサイトに出ていた例をそのまま使ったのだ。書き方としては間違いかもしれないが、現に通用してしまうのである。

考えてみると、私のページには最初から &amp; と書いてあったはずだ。つまり、どこかでこれが & という1文字に変わってしまったのである。調べてみると、どうも私の書いた Java のプログラムが、&amp; という文字の並びを & に置き換えてしまうらしい。そこまで分かれば、ファイルに結果を書くところで、& という文字が出てきたら &amp; に置き換えるように、処理を追加してやればいい。

何をやっているのだか自分でも訳が分からないが、とりあえず、そういう処理を追加して、警告は出ないようになった。

目的のものが得られたので、今もそのような状態で使っているのだが、これは何か釈然としないというか、本来は &amp; を & に置き換えずに処理するような方法があるはずだと思う。その方法が分からないので、こういうことになったのである。プログラミングとしては正しくないというか、面白くないやり方なのである。

ココメモ (2)

テーマ:
コンピュータにできることを人間にやらせてはいけない、という原則がある。このことを言うと、「やらせてはいけない」と「やってはいけない」の違いが理解できなくて「なぜ俺がやってはいけないのだ」と食って掛かる人がいるのだが、やりたきゃ勝手にやりなさい。人間様が何をしようとその人の勝手だが、この原則は「コンピュータよ、サボるなよ」と言っているのだ。ただそれだけである。

という訳で、今回はプログラムを書いて、この面倒な作業をやらせることにした。つまり、本文の右段に注釈が表示されるように、対応する注釈をテーブルの要素として囲み、適切な位置に配置する、という面倒な処理である。こんなことは人間様がいちいちやってられないから、コンピュータにやらせようというのだ。このプログラムがあれば、次のような手順で HTML 形式のデータを作ることができる。

1. 目的のページをファイルに保存する。ココログには、1か月分の発言を集めたページを表示する機能がある。残念ながら Ameba Blog にはその機能がないので、数ページに分かれてしまうようだ。いずれにしても、最初の作業は、ブログの内容を手元のパソコンに保存するという処理になる。

2. 保存したファイルを編集して、注釈を付けたい段落の直後に、注釈を書く。この時に、本文と注釈をプログラムで識別させるために、注釈は<p class="note"> と </p> で囲むか、あるいは注釈の最初の文字が「※」になるようにしておく。

※こんな感じに書いておけば、勝手に注釈にしてくれるということ。

3. 注釈を追加したファイルに対して、今回作ったプログラムを実行する。その結果、本文と注釈が横並びになったデータが生成される。

4. 出来たデータを多少加工してから、サイトに公開する。先の手順でできたデータは HTML のページの一部なので、タイトル・見出しや、トップページへのリンクなどを追加して、Web上で独立したページとして表示したときにおかしくないようにするのだ。この処理も結構面倒で、かつ機械的な作業になるから、プログラムで実行できるようにしてある。

こうやってできたページが、前回紹介した2003年12月のアーカイブだ。ちなみに、3の処理はJava、4の処理は perl で実行しているのだが、このあたりを一発でできるようにしたいというのが課題だったりする。

それで、何とここまでが伏線で、ここからが本題だ。こうやって作ったデータを、W3C の validator にかけると、エラーが出てしまったのである。何でだろうと調べると、URLの中に「&」が出てくるという警告なのだ。

(つづく)

ココメモ (1)

テーマ:
W3C の validator というものがある。Web ページが HTML や XHTML の定義通りに作られているかどうかをチェックしてくれるというもので、サイトの作成者にとっては便利なものだ。とあるページをこれでチェックしたらエラーが出たとか書いたのだが、ではオマエのページはどうよ、という話もあるかなということで、ココメモ(仮称)で生成した試作ページに validator をかけたら、若干エラーが出てしまった。

※ココメモというのは、ココログに過去に書いた内容にメモを追加したページを作るソフト。
「ココログメモ付けソフトウェア」の略で、ココメモと呼んでいる。決してココログメモリアルの略ではない。そんなの見たことないという人は当たり前で、まだ試作中で公開していない。

※「ここメモ」というソフトがあるが、それとは関係ない。ココログというのは @nifty のブログサービスで、@nifty 会員なら無料、非会員なら毎月263円で開くことができる。

ココメモで作ったページがどのような感じになるかというと、試験的に2003年12月のアーカイブを公開しているから、興味のある方はご覧ください。なお、まだこれは試作段階なので、今後大規模な変更が行われる可能性はある。(2004-10-07 現在)

ブログにはアーカイブの機能があるのに、どうしてこういうページをわざわざ作るのかというと、単に、横に注釈を表示したかったのだ。それだけだ。

ここのところを chat でも突っ込まれたのだが、注釈を書きたいのなら、例えば本文の後に書けばいいのでは、という発想もある。ココメモというのは、そうではなく、
「注釈を本文の横に書く」ということが目的のプログラムなのである。

本文に注釈を付けるためのスタイルはいくつか考えられる。Webサイトの場合は、注釈を一箇所にまとめておいて、それぞれ注釈を付けたい箇所からリンクするという手がある。書籍の構成として、注釈が一箇所に集められているものがあるが、あのスタイルである。

このように作れば、本文だけを注釈抜きで読めるし、注釈だけをまとめ読みすることもできる。しかし、書籍だと、その2つを関連付けるのが問題になってしまう。Webサイトの場合は、リンクを張るという方法でこの問題を解決することができる。本文中の注釈のついた文や語をクリックしたら注釈に飛べるようにするのだ。これは便利といえば便利だ。逆方向のリンクも作っておけば、なおよい。ただ、リンクをクリックしないと注釈が読めないし、それで別ページに行ってしまったら、戻らないと本文の続きが読めない。別ウィンドウを出すというのもわずらわしい、というデメリットもある。

他には、注釈を本文の段落の後に続けて書くという方法がある。注釈の表示を工夫すれば、本文と区別できるように書くことは可能だ。ただ、Ameba Blog では、フォントを変えたりスタイルを変えたりするのが面倒なので、私の場合、※で始まるパラグラフを注釈のつもりで書いている。

このスタイルは、書く時に割と楽だし、手間もかからないという特徴を持っている。その反面、本文と混ざってしまうという欠点がある。本文だけ読みたい場合に、注釈が邪魔になってしまうのだ。また、先に紹介したページのように、過去のアーカイブに対して後で注釈を付けたい場合には、本文そのものが修正されてしまうため、公開時点の姿が分かりにくくなる。

そこで、本文と並べて注釈を付けようという結論になったのだ。ある種の本では、このような注釈とか解説が、本文の横(縦書きの場合は下側)に書いてあるものがある。これは、割といい感じだと前から思っていたのだ。

しかし、そのようなドキュメントを作るのが意外と大変なのである。

(つづく)

ループの書き方

テーマ:
プログラムを書くときにループという考え方は基本中の基本なのだが、最近、どうも回りくどいというか、あえて難しく書いてしまっているプログラムを何度か見かけたわけで、考えてみれば、ループに使う変数の判定をどうすればよいか、というような基本すぎることを、きっちり解説したプログラミングの本を読んだ記憶がない。

例えば、1から10までを画面に表示する処理は、C言語だと次のように書く。

for (i = 1; i <= 10; i++)
  printf("%d\n", i);

この1行目を、次のように書いてはいけない。

for (i = 1; i < 11; i++)

いや、いけなくもないが、あまりよくない。動作は最初の書き方と同じなのだが、意味が違ってくる。最初のは「1から10まで」だが、次の書き方は「1から、11未満(の最大の数)まで」という意味になっている。この微妙な差が、修正したときの誤りの入りやすさに大きく影響するのだ。

次のように書くのはどうか?

for (i = 0; i < 10; i++)
  printf("%d\n", i + 1);

これも結果は同じになる。この処理をあえて正確に解釈するなら、「0から始めて、その数に1を足した数を、10個表示する」という感じか。C言語に限らず、配列のインデックスが0から始まる系のプログラミング言語では、0から始めて継続条件を「<」で判定するようなループの作り方をよくする。だから、ループの表現としては慣れている人が多く、誤解は生じにくいのだが、後の方で「i + 1」というものが出てくるのが曲者というか、この距離が後になって問題になることが結構あるのである。

自分のやりたいことをストレートに表現すると後で困らない。プログラミングに限らず、様々なことに当てはまる基本である。