Island Life

2010/06/18

コードに対するテストも要るんじゃないかなあ

From RSpec のすごいところ:

結局、テストコードは何をもとにして書くのか、ということだよね。Test::Unit を使ってた頃は、テストコードはソースコードに対して書くものだと思ってた。でもそれは間違いで、テストコードは仕様に対して書くものということなんだ。

TDDで最初に書くテストは仕様に対するテストと考えて良いと思うのだけれど、 実装が済んだ後で (もしくは、実装がある程度具体化した後で) 改めてその実装に対するテストを書く ことは必要なんじゃないだろうか。

というのは、特定の条件を満たさないと通らないコードパス、っていうのは ほとんどのアルゴリズムにあって、 そういうパスを通すためのテストデータは実装が具体化してからでないと作れないから。 経験上、アルゴリズム内のパスが切り替わる境界条件付近にバグが潜んでることが 多いので、境界条件をつつくテストは仕様のテストよりずっと重視している。 (例えば浮動小数点数絡みで、計算結果に桁上げが生じて54bit目が 丸められる条件を突く、とか。外から見えない内部のテーブルがいっぱいになって 次のデータ追加でテーブルの拡張が起きる条件を突く、とか。)

実装が変わったら改めて境界条件を突っつくデータを用意しないとならない。 最適化すると大抵場合分けが増えるのでテストすべき境界条件も複雑になる。 最近はカバレッジ分析みたいなツール使って ある程度自動化するものかな? 私はロートルなんでいまだに手でデータを用意してるけれど。

そんで、そういうテストを書く場合、入力セットも期待すべき出力も 複雑になることが多いので、assert_equalをずらずら並べるテストというのは すぐに限界に達する。assert_equal並べるのは楽だからつい自分も書いちゃうけど。 どんな入力に対しても結果が満たすべき条件、というのを検査するテストルーチンを 書いて、それに実装の境界を突っつく色々な入力を与えるというのが良いと思っている。 簡単な目安として、assert_equalのexpectedに定数を書いてたらたぶんそれは 良くないテスト。(自分もそういうテストをたくさん書いてるんで、自戒込みで。)

たまに、テストしたい機能と、条件検査に使ってる機能が対でバグってて、 テストを通っちゃうこともあるので、別の処理系や言語で計算したexpectedを入れる ことはある。その場合はexpectedを計算するコードをコメントに書いたり しているけど、もっとうまい方法はあるかなあ。

(追記2010/06/20 10:56:49 UTC): From http://twitter.com/tyt/statuses/16608793012

http://tinyurl.com/2b86bkq 最適化のコードのテストは最適化されてないバージョンのコードを使えばいいのではと思った。入力の型以外はほぼ自動でできそうな感じ。(一回そうやってテストしたことあるけどテストはすぐ終ったし最適化後に仕様変更は無かったなあ。)

そうだなあ。 問題は、最適化されてないバージョンをテストで走らせられる 状態で持っとかないとならないってことなんだけど (ただ最適化前の状態を取っておくのではなく、最適化後にもコードは進化するので その後の変更を最適化前のコードにも統合してゆく必要がある)、 回避できるケースもある。

  • 納品したらおしまい、という仕事で、最終ステージでががっと最適化する場合。 コンソールゲームとか。イベント向けの一発仕事とか。
  • 仕様が明確で安定している場合。ランタイムオプションやコンパイルフラグで 常に最適化のあり/なしを切り替えられるようにしておける。 Gaucheでも-fno-inlineとか持ってるので、最適化オプションあり/なしでの 結果をダンプしといて比較することは自動化できそうだ。

でもやっぱり、最適化後のコードのパスを全部カバーするようなテストセットは 改めて作る必要があるよなあ。

Tag: Programming

2010/06/18

Kumu Kahua Theatre: The Hilo Massacre

もう今週で終わりだけど、昨日Kumu Kahua Theatreの The Hilo Massacreを観た。 これはおすすめだ。

Epic Theatre的演出 (観客を芝居に感情的に巻き込むことを避け、常に舞台を作り物として、考えながら観ることを仕向ける)が効果的で、完成度の非常に高い舞台だった。 というかEpic Theatreをちゃんとやるとこんなに面白いのか、と初めて理解した気分。

1938年にヒロで起きた、労働者のデモ隊と警官隊の衝突 ("Massacre"というけれど、実際には死者はなし。ただし警官の発砲により50余名が負傷。) のシーンから始まり、モンタージュ的に時系列を前後しながら短いシーンが提示される。 シーンの合間には役者がナレーターとして、シーンの背景や、 時には意図までも(「このシーンは原作にこうあったんだけどこういう意図で削除した」)説明する。

それによって観る方は常に舞台を観ていることを意識させられるのだけれど、 一度シーンが始まるとその完成度が高いため、その時代、その場所での出来事を 目撃している感覚にとらわれる。 もちろん、それがひとつの解釈であることをわかった上で、 「あり得たかもしれない現実」を限りなくリアルに感じられたのだ。

ブレヒトが言ってたのはこういうことだったのかあ。今まで何にも分かってなかったよ。

Tag: 芝居

2010/06/11

目をつぶって見てみる

ここ数年、ショパンの練習曲(Op10と25)をちゃんと弾いてみようと 思い立ち細々とさらってきて、メトロノーム指定の6-7割くらいの 速度までは曲がりなりにも止まらずに弾けるようになったのだけれど、 そこから速度を上げようとするとどうしても力が入ってだめだ、 というところで壁にあたっていた。手首のリラックスが不十分なのだと 思ってひたすら脱力に気をつけてさらっていたのだけれどやっぱりだめで、 1日に最大1時間くらいの練習じゃこれが限界なのかなと半ば諦めかけていた。

それが、3-4ヶ月前にあることに気づいて、ひとつブレークスルーがあった。

譜めくりが面倒という理由から、できるだけさっさと暗譜して弾くようにしていて、 それはちょっとした隙間時間に練習するのにとても便利 (楽譜を用意する時間が 省ける) だったのだけれど、譜面を見ないせいで、難所にかかると手を見て 弾く癖がついていたのだ。試しに目をつぶって弾いてみたらよっぽど速度を 落としてもぼろぼろである。

手を見て弾いていると、単に鍵盤のポジションを目で確かめるというだけでなく、 暗譜自体を手の形の視覚的な記憶で行なうようになるようだ。 だから手を見ないと、どの音を鳴らしたらいいのかというイメージが 突如として曖昧になる。速度が上がらなかったのは指の運動の限界ではなく、 視覚のパターン認識によって運動を補正する回路が律速になっていたのだった。

また、視線を固定することは上体の自由な運動を制限することにつながり、 これも上体にに不要な力が入る原因になっていた。 しばらく目をつぶってスローモーションでさらうのを繰り返したら、 驚くほど弾くのが楽になった。

まだ速度は足りないけれど、ひとつ大きな障害が取れたことで なぜ弾けないかという原因のひとつひとつがよりはっきりと分かるようになってきた。

必ず間違える場所というのはそこだけ集中してさらえばいいのでわかりやすい。 一曲通して弾いて、5箇所間違えるんだけど、間違える箇所が毎回違う、 というのが、これまでどうやったら良いのかよくわからなかった。 目を閉じていると、ちょうど画像にブラーがかかるように、 ある箇所にさしかかるとイメージがぼやけるところがある。 そういう箇所は、惰性と手の記憶でだいたいは弾けるんだけれど、 確率的に間違えることが多くなる。多分、第三者に間違えたところを チェックしてもらっていると、特定の領域でチェックの密度が濃くなるのだろう。 量子力学のダブルスリットの実験で、ひとつひとつの光子の点を見てるだけじゃ わからないけど、たくさん感光させると干渉縞が出てくるみたいに。 自分一人でさらっている時は、だいたい直前に間違えたところくらいしか 覚えていないので、あまり当てにならない。 頭の中のイメージに気をつけていれば、どこが不完全なのか、かなりはっきりわかる。

これは他の分野でも同じではないか、と思う。

例えばプログラムを書いている時、書きながら全体の動きが頭の中で はっきり見えている時と、なんだかもやもやしているのをえいやっと コードに落とす時がある。後者のコードはほっとくといつかバグを出す。 ほんとは、コードに落として動かしてみながらイメージを明確にしていかないと ならならいんだけど、コードに落として安心しちゃってほっとくと後で 痛い目を見る。

何かを作りたいんだけれど、どうもうまく作れない、という場合、 それは才能だのスキルの不足だのではなく、 作りたいものが細部まできっちり見えていないからであるということが 結構ありそうな気がする。

Tags: ピアノ, ものつくり

2010/06/05

分けて考える

「評価と実行を分けて考える」というのがうまい表現だなと思った。

本来、別々に考えられる概念XとYとが、実装の都合から たまたまいっしょくたになっていることがある。 そういうモデルを先に学んでしまうと、 XとYを別々に扱うモデルに出会ったとき、ちんぷんかんぷんに思える。 新しい何かを理解しようとするとき、 人はまず自分が知っているモデルに当てはめようとするからだ。 自分の中のモデルでXとYが区別されていないと、 対象のXとYをうまく自分の中に対応づけられないので混乱してしまう。

理解の鍵は、自分の中のモデルとの対応関係を探しつづけるのをあきらめて、 逆に出会った概念でもって自分の中のモデルを再構築してみることかもしれない。

今まで理解してきたことを振り返ると、このパターンに何度も出会っている気がする。

インタプリタとコンパイラはそのひとつだった。電卓プログラムまではわりと簡単にたどり着けて、 インタプリタの動作は納得できたんだけれど、そこからコンパイラが何をやっているかを 理解するのにひとつハードルがあった気がする。 ここで分ける必要があったのは、プログラムの「解釈」と「評価実行」だったと言える。

C言語にどっぷり浸かった後でのLisp/Schemeのクロージャの理解にも、 似たようなハードルがあった。C言語でのauto変数は、 ローカル変数のスコープとエクステントが一緒になっている。 自分はそれをスタックモデルで理解していた。 C言語でもstaticを使えば一応ローカルスコープで無限エクステントな 変数になるけれど、それはdata領域に静的に確保されてるから、 当時の理解としてはグローバル変数の仲間という感覚。 つまり、静的に確保されるメモリとスタック、というモデルで理解してたもんだから、 動的に作られてローカルスコープなんだけど無限エクステントな変数、 というのがよく分からなかった。

Schemeの継続も、制御フロー、アクティベーションレコード、スタック という要素を分けて考えられるまで、理解に苦労した。

Lisperは、プログラムの構文解析(read)と「解釈評価実行」(eval)を分けて考えるのを 当然だと思っているけれど、evalを持つ動的言語でこの二つが分かれているものは あまり多くない。JSONが現れた時、簡易プログラムがいきなり受け取った文字列を evalしているのを見てびっくりしたものだ。そしたら今度はevalは危ないからと わざわざパーズしたりしている。ひどく遠回りをしているように見えたものだ。

Lisp初心者が、「シンボル」の存在につまづくことがある。 プログラムをプログラムとして見ているだけでは、シンボルは不必要だ。 C言語のソースコード上の int x; というのはあくまでソース上に記された記号であって、 プログラムの実行中に触れる対象ではない。プログラムをデータとして読み込むことで、 ソース上の同じ記号に、 「プログラムとして見た場合の識別子」と「データとして見た場合のシンボル」という 二つの違った意味が付加される。

実は、Schemeのhygienic macroのとっつきにくさ、というのも、 「シンボル」と「シンボルが示すもの」を分けて考えることに起因しているように思える。 たとえば下のコードにはxが4回現れるけれど:

(define x 10)

(let ((x (+ x 1)))
  (* x 2))

区別するために番号をふって:

(define x{0} 10)

(let ((x{1} (+ x{2} 1)))
  (* x{3} 2))

Lisperは、x{0}とx{2}があるグローバルな変数を示していて、 x{1}とx{3}はローカルな変数を示している、ことを直ちに了解するけれど、 あんまりに自明なものだから、それらにxという共通のシンボルを使うことを 不思議に思わない。けれども、マクロによってスコープを超えて識別子の挿入が 起きると、「シンボル」と「シンボルが示すもの」を意識する必要が出てくる。

(define x{0} 10)

(define-syntax k
  (syntax-rules ()
    ((_) x{4})))

(let ((x{1} (+ x{2} 1)))
  (* x{3} (k)))

この場合、(k)の展開によって出てくるx{4}は、x{0}が示すものと同じものを 示すべきか、x{1}が示すものと同じものを示すべきか。 Schemeのhygienic macroは、「シンボルが示すもの」について一貫している べきだとの立場をとる。つまり、x{4}はそれが出現するところのスコープによる 規則に従ってx{0}と同じものを示す。シンボルだけを考えていると hygienic macroが何をやっているのかよくわからなくなる。

今、自分の中で分離の必要を感じているのは、「値」と「型」と「評価」かなあ。 Lispでコードを書いている時に、例えば (foo <...>) という式を見たとすると (<...>の中は適当な式)、 この時自分の頭の中では、まず <...> が評価された「値」というのが存在して、 それにfooを適用する、というふうに考える。でもこうやって「そこにあるはずの値」 を追っかけて行く方法だと、ポイントフリースタイルみたいに引数を省いて 関数の組み合わせだけで書かれたコードが理解できない。 あと、「型」もまず手元に「値」ありきで考えちゃうので、 型だけの組み合わせで考えを進めるのが苦手。 多分、頭の中の実在感のとっかかりを「値」から引き離すことが必要だろうなと感じている。

Tag: Programming

2010/06/04

選べる人が選べばいい

昨日、 Ron Wayneの記事をきっかけに「選ばなかった人生」について思いを馳せたのだけれど、 あれを書いた時にもう一つ頭にあったのは、 やはりシリコンバレーが舞台となった、別の「選んだ人生」についての悲劇だった。

フリーランスなどやってると、「来月仕事があるかどうかわかんない」 なんてことは日常で、そういう生活が不安ではないか、と聞かれることもある。 でも正直に言って、自分が食えるかどうかという不安など、 「従業員に来月の給料を払えるかどうか」という不安に比べたら、 吹けば飛ぶような悩みにすぎない。 その点で、私は人を雇って事業をしている人は尊敬している。

だからといって、そういう道を選ばない人生はだめなのか、つまらないのか、 というと全くそんなことはなくて、 リスクを取って事業を始めるような人は全体から見れば極小数で、 平均から外れてるという意味で変人であっていいのだと思う。 個人的には変化がある方が好きなのでリスクテイカーが全体として 増える方が面白い世の中になるとは思っているけれど、 みんながみんなそうならないとだめだとは露ほども思っていない。

「このチャンスに乗ることを選んだら大変な思いをして、 きっと選んだことを後悔する。 けれども、選ばなかったらもっと後悔するだろう。」

そう心底思える人だけが、選べば良いのだと思う。

Tag: 人生

More entries ...