いけがみを召喚するには、出現予定を参考にしてください。三週間前までにメールをくだされば、日程を追加するなどしてスケジュールに組み込むことができるかもしれません。勉強会や個人的な会合、中途採用面接などに応じます。日記に書かないことはこちら。
LL魂の前日に、僕、若いこと言います。
それは正論かもしれませんよ。でも、机上の空論ですよ。gccやemacsを見ればわかるでしょう、型やテストよりも、まず実装して皆で長いこと議論して改良していけば、ソフトウエアの品質は向上し、実際、高品質です!
プログラムの要求仕様は次から次へと変化する一方で、テストを記述/変更するコストは高い。強い型付けのプログラミング言語は学習難易度に見合うだけの利点を持っていると主張できるのか?
以上、仕様記述に基づくテスト手法や、強い型付けのプログラミングの利点を友人と議論していて。
僕らがやりたいこと:
- 楽しんでプログラムを書く
- 皆が欲しいプログラムを書く
- 皆で議論してプログラムを良くすればいい
- デルゴル君(注:「データ構造とアルゴリズム」の略語) の勉強をするのはキリがない、そんなこと勉強するより unknown tips を実地で知ることのほうが重要なのでは?
結語: LLでオープンソースが正解。 テストや「長く勉強しないと身に付かない複雑な某プログラミング言語」、デルゴル君を学習する必要は、必ずしも、ない。だって、苦痛だし、犠牲に見合う保証はないし、だいいち、今すぐに作りたい物を作りだすことができないではありませんか。
以上、LL魂に行くか行かないかを友人と議論していて。
いけがみさんは理想を追い求めているふりして、実際はさぼってるだけでしょ。
以上、う〜ん、そうなのかな? そうなのかもしれないぞ。
一週間、いい仕事できたときは、金曜日の晩ご飯は大盤振る舞いするのが、いけがみ家の家訓です。正確には、男系(じいさま、とおさま)だけがずるいことに、外食する習わしとなっております。僕ら兄妹はどうなんだろう。まあとにかく、今日は3,900円のイタリアンでございましたよ。僕の代でこの旧い悪習はうちきろうと思っています。家族と行かないとねえ。
前にも書いたように、僕は厨房スタッフとお話するのも楽しみです。
- 今までいろいろな料理店で厨房に立ちました。それぞれお店には個性があって、肉が得意なところ、魚が得意なところなどです。今は野菜を生かす料理に挑戦しています。
- 野菜の形をそのまま生かすのが、私の求める料理です。特に、形が珍しい野菜は刻んでパスタのソースにしたくありません。(今日は、「へびなす」という野菜を見せてもらいました)
- 料理は味ももちろんですが、温度が重要なんです。ホールのスタッフが配膳を遅らせると、おいしい料理をお出しすることができません。
- できるだけ新鮮な素材を下ごしらえしてお出ししています。お客様が帰られてから閉店後にやることはたくさんあるのです。たとえば、この間は鹿を一匹まるごと下ごしらえしました*1。
- 結局、シェフだけではなくて、スタッフが協力し、お客様がその思いに共感して楽しい思いをしていただいてお帰りになるのが私の理想です。
以上、今日伺ったこと。前のエントリと関係あるのかな? ないのかな? 少なくとも 3,900 円以上のモトをとった気分です。
たとえ話で、話をうやむやにする人は僕は嫌いです。現状の僕の意見では、「短期間に高品質のソフトウエアを作る」という、一見トレードオフがとれてない理想を現実にするためには、仕様記述に基づくテストや強い型付けの関数型言語を使って、実装を検証すればいいのではないか、ということです。僕らは、そう、僕らは検証チームを組めばいいはずです。検証チームは、テストだけでなく、最新のデータ構造とアルゴリズムも提供致します。一方、実装そのものはどんな言語でも構いません。CでもJavaでも.Netでも。お好きなように。ネットワークがある現在(いま)、協力することにより、品質が高く、皆が欲しい物を短期間で作ることは可能ではないでしょうか。可能だと思います。
テストを楽しむ方法や、難易度の高いプログラミング言語、デルゴル君を楽しんで学習 する、しかも短期間で、という点は、ゲームが解決するでしょう。魔法言語 リリカル☆Lispは、すげえいい感じです。もっと褒められていい!
近い将来、「楽しんで学ぶプログラミングDS」は登場するでしょう。しなかったら俺が作ってやる。任天堂のファミリーBASICの志は今も残っているはず。
*1 この表現で不愉快な思いをされた方はすみません。いけがみは菜食主義者になりきれない偽善者です。
をちら見。出たばっかりです(第一版2007-06-30)。
XSS や Buffer Overflow をテストで発見する case studies などが載っていて盛りだくさん(28章もある)。半日では到底読み終えること等できなかった。各章の終わりの文献リストが、二次テキスト探すのに便利そう。私はセキュリティ方面も素人なので、この本を評価することはできないのだけれど。
仕様記述駆動テストで security を調べるというテーマは魅力的なので、いつか買う(か読む)と思う(ヨワ。
五月山に登ったのですが、さすがにぐったりです。8月はPM4時くらいから行動し始めたほうがよさそう。
オーガニックキッチンVERDEでお昼ご飯。季節の有機野菜を使ったイタリア料理屋です。 今日は、Pranzo B のコース(2,400円)[写真あり]でした。
このお店は、池田市立体育館の中にあるということもあり、隠れた名店です。体育館の広い駐車場を利用できるので自家用車で来ることもできます。2時間までは駐車場が無料です。
本当は誰にも教えたくなかったんだけど、そろそろ市民に知られてきたみたい。VERDE が本領を発揮するのは、むしろディナーです。Cena A (3,900円)、この質でこの値段はむしろ安いと思います。梅田や難波につい目が行きがちですが、北摂にお住まいの方は一度お越しあれ。宝塚、箕面、池田、豊中は隠れた場所にいいお店があるので、なかなかどうして侮れません。
土日の晩も平常通り 17:30 〜 21:00 (ラストオーダ) だそうです。[2007-08-30 修正]
[2009-08-18 追記] 一時期閉店していた VERDE ですが、箕面市船場西に Renewal Open しました。La liberta (ラ・リベルタ) というお店です。
ポストスクリプトはソフトウェアが出力するDTPデータや画像向けのフォーマットだが、ちょうどHTMLのように中身はテキストファイルで、その気になれば手書きも可能だ。変数や演算を用いたプログラミング言語的な処理も可能なことから、世の中には“ポストスクリプトを手書きするハッカー”という人種が存在する。
[日本初のハッカー、和田先生が語る「ハッカー気質」より引用]
僕の周りにも、ふっつーにいました。こういうこと書くと、私が旧世代の人間と思われてしまうだろうけど。ウチにも PostScript 紫本があって、サンプルを描画して楽しんだものです。NeXT は DisplayPostScript という PostScript を拡張した言語でGUI(ウィンドウなど)を表現していましたので、皆一時期 PostScript 手書きに夢中になりました。
via programming.reddit.com. イカス。Haskell は parser 書きやすい(Parsec or Happy)から、こういうのがサクッと作れる。
Karplus-Strong Algorithmというのは初耳でした。
世評は科学者の経歴を台無しにするだろうか。人気が出るにつれ、研究から足を洗うことにならざるを得ないのだろうか。いや、そうではない。
並の科学者ですら、その研究活動において、一般大衆に科学を伝えるための初歩訓練を受けている。「並の科学者は優秀な科学者ではない」このように考えてきた私は間違っていた。
『雪』を読んでゆくと、まるで自分も中谷といっしょに仕事をしているかのような気持ちになって、研究の道筋をたどり、それを通して、自然を見る目、現象について考える態度が身につき、自然科学の研究の面白さがわかる。
[「雪」中谷宇吉郎 【解説 樋口 敬二】 p.173, 岩波文庫より引用]
この書にかかれていることの中で、比較的役に立ったのは、結晶の一般分類である。雪の結晶については、「複雑精緻をきわめた美しい六花」という言葉が、昔から使われてきた。そしてその言葉につい惹かされて、六花以外の「美しくない」結晶の方が、つい度外視される傾向にあった。
実際には、しかしこの美しくない結晶の方が、数も多く、種類もたくさんあって、学問的には、もっと重要なのである。
(中略)
理論は略するが、これで氷晶の成因は一応分った。このアメリカでの近年の研究と、私たちの実験とを併せると、雪の結晶の人工製作の問題が、初めて首尾一貫して解けたことになる。もちろん、この「併せると」という言葉が曲者で、まだ追求しなければならない点は、たくさんある。
[「雪」中谷宇吉郎, 附記 第十一刷に際して p.167, 岩波文庫より引用]
オープンソースカンファレンス2007 北海道のときに、中谷(なかや)先生の本をいくつか入手した。前から気になっていた人で、寺田寅彦同様追いかけたい人なのだ。9月に北海道に(おそらく)行くので、その折に半世紀にわたる人工雪の歩みを辿ろうと思う。
中谷先生は人柄も良かったそうである。冬の北海道でひたすら雪の結晶を観察するために自ら学生を集めて建てた小屋に、付近住民が食料を届けたそうである。孤独な研究者もいてもいいだろうが、私は魅力的な人物になりたい。私は並の科学者ですらないが、今まで学んできた事柄を伝える側に立つときが来ればよいのだが。
うちの家族はみんな後のこと考えない人達ですね。今、思うと。あれは小学校のときだったか、母と妹と私と3人で出かけようってことになって。JR で2つほど駅が離れた町でしたが。自転車で行こうってことになったのです。妹はまだ補助輪はずれてなかったんじゃないだろうか。
今の僕なら、帰りも自転車かよ体力保つのかな、とか、事故ったらどうしようとかチラリと頭をよぎるわけですが、あのときは絶対そういうこと考えてなかったな。行けば帰れる、届かなければターンする、そういう単純な話なわけです。結局、僕らは町に辿り着き、ご飯を食べて、帰りも自転車。
卒業のテーマとして、誤り訂正を選び、一発目の就職のテーマとして、プログラムの誤りをどうするかを選んだのは、振り返れば原点から一直線だ。
話は続きますが、今度は高校の頃。当時やさぐれていた私は、授業をサボり、川へ洗濯に自転車で行きました。何故か、父が買ってくれた2台目はマウンテンバイクだった。故郷には山一つないくせにです。タイヤが太くても何の役にもたちやしない。
川を見ているうちに、この先はどこまで続いているのかなと思った。下流は海に直結しているはずなわけであり、これはもう行かなければと思ったら漕ぎ出していた。
川沿いに走りたくても道がないこともあるのです。でも、目的は川がどこに流れていくのかなのだから、迂回してでも川沿いに戻ることを繰り返した。そうして、当然海にたどり着くわけです。相模湾だった。着いたときは、もう夕方で、足がガタガタいっていたこともあり、砂浜では何もできなかったな。
そして、帰り道があるわけです。ずっと登りです。そりゃ水は高いところから低いところに流れるんだもん。最初にそこ考えろよ、自分。
この話をクラスで自慢したら、俺も行く俺も行くという話になった。その週の休日に我々は結集し、俺の発見した経路で海へ一直線。
一番かわいそうなやつは、八王子だか橋本に住んでた子で、帰りに別れた後、独りでひたすら漕ぐのが辛かったと言っていた。そうだろうなあ。お前も俺と同類だよ。類は友を呼ぶとやらで、私の周りは後先考えないどうしようもない子ばっかりです。30台になっても、いくつになっても、そうだろう。
川を前にしたとき、帰りが楽な上流へ行くことを目指すだろうか。手段と目的のどちらを選ぶか。
For whosoever hath, to him shall be given, and he shall have more abundance: but whosoever hath not, from him shall be taken away even that he hath.
-- Matthew's Gospel 13:12
それ誰にても、有てる人は與へられて愈々豐ならん。されど有たぬ人は、その有てる物をも取らるべし。
-- 新約聖書 文語譯 1917年(大正6年)改正譯新約聖書 日本聖書協会, 馬太傳福音書 13:12
(中略)
この転機に対するメッセージは、本章の初めに引用したターコフの述べているところによれば「キミは結構」か「キミは駄目」の二つであって、その中間はない。だが、別のもっと穏やかなメッセージもある。すなわち「たとえ転機の克服にしくじっても、自分の美点は失わず、内面の蓄積は続けるように。」
[続 サイエンティストゲーム ー 若き科学者のための生き残り戦略, 著:カール・J.シンダーマン, 訳:山崎 昶, p.142より引用]
来年のAAECC目指す一方で、任期切れの次の職も探さなくては。が、研究者は往々にしてこの手の失望やストレスにも対処して生きている。他のプロフェッショナル(たとえば医者や弁護士など)よりもはるかに旨く切り抜けていかねばならない。
なんにせよ、私は前向きに間違える性格に生まれついてしまったので、今後の目標も誤り訂正でいきたいわけです。デジタル通信における誤り訂正の科学はもはや枯れている(という一個人の認識)ので、次は何を訂正するかなあ。いくつかテーマは隠し持ってはいます。持ってないと取りあげられる一方だ、こんちくしょうめ。
エクストリーム・プログラミング(XPとして知られている)は、ビジネス側と開発側の両者が共通の達成可能なゴールに集中するための、ビジネス及びソフトウェア開発の規律である。
Kent Beck
[XPエクストリームプログラミング実行計画(The XP Series), Kent Beck and Martin Fowler, Foreword by Tom DeMarco 序言より引用]
このエントリでは、開発側の夢を実現するためのプログラミング技術 Concrete and Specific Programming を提唱します。
趣味としてのプログラミングは、ビジネスとは無関係です。それは、人生に与えられた有限の時間の使い道として、あなたが選んだ選択肢です。なぜ、プログラミングを趣味とするのか、その理由は様々でしょう。欲しいものを作るだけでなく、スキルの向上や、単に興味をそそられたという理由のみでプログラムを書く人は大勢います。コードのサイズを短くすることのみを目的としてプログラムを書く人もいます。XP は時として extreme すぎるので、趣味にするのは辛すぎます。
そうでなくとも、趣味としてのプログラミングにおける苦痛は、主に3つ挙げられます:
修行僧のような人は別として、これらの 3 つの項目はどれも「めんどくさい」ものばかりです。趣味として始めたものが、皆に期待されて、いつのまにか義務になってしまっている、そういう人は世の中にたくさんいます。それは紛い物の義務であり、義務感を感じてはいけないと私は思います。
これらの3つの苦痛を取り除くにはどうしたらいいでしょうか。人狼を打つ銀の弾丸がないことはすでにご存知のことでしょう。したがって、何か引き換えにするかわりに、できるだけ苦痛を取り除く方法を考える必要があります。それは、トレードオフです。
Concrete and Specific Programming では、プログラムを書くこと、すなわち実装を作業の最後に回します。プログラマが、本来、楽しむところ、つまり、「実装」を最後にとっておくという方法。それが、苦痛を取り除くためのトレードオフです。
実装をしないのなら、何をするのでしょうか。Concrete and Specific Programming では、最初に「夢」を書くことを提唱します。次に、設計書を書き、次に、仕様に基づいたテストスイーツを書き、最後に実装します。「実装が仕様を満足するまでは、リポジトリにコミットしない」という唯一の原則を守ります。箇条書きにすると、次のようになります:
まず、最初に行うことは、「自分の貴重な時間を『この』プログラミングに割くのだ」ということを認識することです。その時間を、他のことに割り当てたほうがより豊かな人生を送ることもできるはずです。再考してみましょう。
決断したら、その夢をノートやメモにまとめます。必要最小限の項目は以下の通り:
プログラミングにかかる工数を見積もることは、現実的に不可能です。そこで、その代わりに、〆切を設けます。たとえば「40歳の誕生日まで」でもいいし、究極の選択肢として「死ぬまで」でも構いません。ただ、〆切のない趣味プログラミングは、人生を無駄遣いすることが、経験的に知られています。無駄遣いをした人生も、また一つの人生です。否定的に捉えるのはやめておきましょう。
Concrete and Specific Programming では、実装を最後に回すため、モチベーションが下がりがちです。夢ノート・メモは、そのためにも書いておきます。モチベーションが下がったり、気がついたらその作業を放置していた、それもまたあなたの人生です。繰り返し言いますが、人生に与えられた時間は有限であり、普通、その時刻を知ることはできません。
「夢」を公開すれば、誰かがそれを実現してくれるかもしれません。それを不射の射と言います。古代の伝説的な弓の名手は、弓をもたずに構えて放つだけで、飛ぶ鳥を落としたといいます。あなたが、プログラムを書かなければいけないという義務はありません。誰かが代わりにやってくれるのなら、それは儲け物です。その時間をあなたは有意義に使いましょう。
プログラムは、各パーツに分けることができます。できるだけ細かいパーツにすること、またそれらをグループ化することが必要になります。オブジェクト指向では、メソッドを設計することと、クラスを設計することが、それぞれ対応します。
Concrete and Specific Programming では、一つのメソッド、一つのクラスを一つずつ設計することを推奨します。全体像は、夢ノート・メモに書いてあるはずなので、心配は無用です。逆に言えば、全体像と、その部分的構成を夢ノート・メモに書くまでは、プログラムの設計をしてはいけません。
設計図はすでにあるので、ソースコードに適切なコメントを書き加えます。近代の言語では、ソースコードにコメントを埋め込めば、自動的に HTML などの可読性の高いドキュメントを生成することができるはずです。実装はまだです。まだやってはいけません。
オブジェクト指向では、次のような手順を踏むことになるでしょう。
実装は空白のままです。実装は最後です。この段階で、実装をしてはなりません。
ドキュメントを書いてしまえば、ほっとするはずです。なぜなら、もう金輪際ドキュメントを書くことはないからです。ドキュメントがないまま、放置されたプロジェクトはたくさんあります。それらを使うために、ソースを読まなければならないというのは馬鹿馬鹿しいことです。
Concrete and Specific Programming に、今までない新しい特徴があるとすれば、それは「仕様に基づいた」テストスイーツを書くことでしょう。これは、XP が提唱するユニットテストとは関係がありません。Concrete and Specific Programming ではユニットテストと結合テスト、負荷テストなどのテストの分類をしません。テストの全ては「仕様に基づいている」ことを前提とします。
仕様とはなんでしょうか。先日のエントリでも触れましたが、ここで言う仕様とは、要求仕様、言いかえると、「プログラムのパーツが満たすべき性質は何か?」ということです。
プログラムのパーツが満たすべき性質は、考え出すといろいろあります。パーツ一つのみならず、複数のパーツが絡んだ性質もあることでしょう。ユニットテストと結合テストを区別しない、と言ったのはこの点にあります。「十分な負荷に耐えうる」というのも一つの性質ですから、負荷テストも含まれます。
この文章を読んでいる人のほとんどが同意しないと思いますが、「仕様」を考えることは、プログラムを実装することよりも楽しいことなのです。どうやって楽しんだらいいのか、私も教えた経験が不足していて、うまく言い表すことができません。
仕様に基づいたテストスイーツは「富豪的」に書き下します。つまり、どれだけメモリを食いつぶしても構いませんし、どれだけ遅くても構いません。今、思いついた方法でテストスイーツを書きます。一方、実装ではそうはいきませんから、神経を尖らせる必要があります。この張りつめた空気を楽しむ人が大勢いるのは知っていますが、手を抜くこともまた楽しいことだと主張します。
確かに、仕様に基づいたテストスイーツを書くのは簡単ではありません。ここで、Haskell の QuickCheck を見てみましょう。 QuickCheck は仕様記述を支援します。この支援はとても強力で、一度は試すことをお勧めします。QuickCheck は「一階述語論理」に似た記述を行うことができます。一階述語論理が何かを知る必要はありませんが、QuickCheck が提供する表記法を調べてみてください。「任意の」という記述が、強力なテストスイーツを作り出すための道具となります。その他、QuickCheck はランダムテストという近代的手法と、できるだけ小さい反例を探すというタクティクスも用意されています。つまり、QuickCheck は 3 つの斬新なテスト手法を提供してくれるというわけです。
仕様に基づいたテストスイーツは一通りとは限りません。この手順に最も重点をおくのが Concrete and Specific Programming の主張です。実装を始める前に、まず仕様を決定せよ。できあがるのは、ハリボテではなくコンクリート製です。 テストカバレッジを心配する必要はありません。何故なら、100% カバレッジするコードをこの段階で書くからです。ランダムテストのおかげで、カバレッジが広くても、プログラマが頑張る箇所は非常に少ないのです。Concrete and Specific Programming に QuickCheck は不可欠です。幸い、Haskell でなくても、無名関数と遅延呼び出しと型(静的でも動的でも構いません)があれば QuickCheck は実装可能です。Ruby にも RushCheck という名前のライブラリを用意しました。モナドを使うことで、QuickCheck そのものを短く精密に記述することが可能になります。RushCheck でもそのことは了解していて、Ruby でモナドを書いています。ただ、モナドを採用したせいで、RushCheck はRuby風の記述でなくなってしまっているのが欠点です。これは将来誰か(私も含む)がなんとかしなければなりません。
趣味のプログラミングですから、実装はお好きなようになさってくださればよいと思います。ただし、せっかく時間をかけて作ったテストですから、どうぞ通してください。Ruby なら例えば Rake を使えば、Ruby風に定形処理をこなすことが可能です。私は darcsの一機能である darcs test を使うことにしています。
バグの理由は、typo、勘違い、勢いに任せてうっかり、疲れていた、などの心理的要因が大きいと考えられます。また、大きなプロジェクトでは「想定外」、つまり人間が考えられる思考の許容を超えた複雑さが産み出すバグもあります。幸い、仕様は全て書き下されているはずですから、Concrete and Specific Programming に沿っていれば、複雑さを乗り越えることができるはずです。
テストに通って、初めてリポジトリにコミットします。リファクタリングとリグレッションテストはテストスイーツの変更なしに行えるはずです。
Concrete and Specific Programming では、できるだけ小さな実装を書き終えてから、次の小さい実装へ移ります。ここで、もし、手順が大きくなったならば、クラスやメソッドの設計がよろしくないことを意味します。夢を実現することが目標ですから、いままでの作業をすべてやりなおすことも選択肢の一つです。
仕様を固めて行く段階、あるいは実装を行う段階で、最初の夢ノート・メモを見直したくなるときがあるかもしれません。夢ノート・メモは、自由に追加ないし編集可能です。だから、日付を附記しておくことを推奨しています。作業の途中でやめることも自由です。現実を知りましょう。
ウォーターフォール型の開発スタイルの場合、実装を全部すませたあとでやり直すことは非常に困難です。早めに現実を知るために、夢ノート・メモの見直しと追加、変更が必要になります。
実装に関する作業メモは、すべてリポジトリのコミットログあるいは ChangeLog に記述します。まだ足りていない実装は TODO に書いておくと便利かもしれません。これらは使い古された手法ですね。
長くなりましたので、そろそろまとめさせていただきます。Concrete and Specific Programming の利点は、ドキュメントが必ずある、仕様記述に基づいたランダムテストという強力なテストファースト、夢と〆切を設けたことで現実に向き直ることができるという 3 点です。その引き換えに差し出したのは、実装の前準備に時間をかけすぎているということです。また、新しいテスト手法 QuickCheck の学習に時間を要します。QuickCheck がまだない不幸なプログラミング言語では、新たに QuickCheck を実装する必要があるかもしれません。
Concrete and Specific Programmingは、後戻りによるコストをできるだけ避けること、および、実装が終わったあとのメンテナンスを放棄(〆切が来たらさようなら)することを特徴としています。
CSP に不向きなジャンルとしては、人工知能などの自己発展する対象、カオスなど初期値によって変動が予測できないなにか(上司の言い分や要求仕様がころころ変わるのもカオス)、夕日に向かって走る系、作りっぱなしは許されないなどなど。
なお、最初に決めた〆切を延長するという裏技が存在します。それにより、あなたの人生の貴重な時間をより浪費するということは再考してみてください。
昨日のエントリから引き続いて。設計とテストスイーツを先に書き、実装を後に書く Concrete and Specific Programming を実際に行ってみます。ただ、私も人に教えた経験がないものですから、何を対象にすればいいものやら、わかりません。
そこで、プログラマの誰もが馴染んでいる単純な「データ構造とアルゴリズム」から具体例を取りたいと思います。
一つ目の具体例としてスタックを取り上げます。
まず、夢メモを書きます。
Stack 夢メモ 日付:2007-08-23 作るもの: スタック(メソッドはnew, pop, pushのみ)。 どのように作るのか: 読者層に配慮して Ruby で。 Ruby についてくる Array を一つインスタンス変数にとり、 その array を用いてつくる。CSPの解説のためなので、深い意味は無い。 喜ぶ人は誰か: 俺。 いつまで作業するか: 今日まで。
つぎに設計とドキュメントを書きます。クラスは1つで十分、メソッドは new と pop と push の3つを作ることにします。
# The class Stack provides a simple stack, which has pop and push. # 2007-08-23 # あと、License とか作者名とかを書いたりするが、省略。 class Stack # the new method does not have any argument. def initialize end # take the top of the stack if the stack is not empty. # otherwise, return nil. def pop end # put x on the top of the stack; # where x is any object. def push(x) end end
もう、実装見えていると思いますが、実装を書かずに、ここで仕様記述に基づくテストスイーツを書くのが Concrete and Specific Programming です。
さて、Stack が満たすべき性質は何があるでしょうか。今までのテスト手法だと、適当に作ったスタック、そうですね、たとえば [1, 2, 3] をスタックと思い、pop すると 3 が出てくるとか、そういうテストだと思います。Concrete and Specific Programming の手法は、主にランダムテストを使いますが、そうでない従来のテストも一件につき特別に認めます。つまり、こういうことです。
require 'test/unit'
require 'stack'
class TC_Stack < Test::Unit::TestCase
def test_simple
stack = Stack.new
stack.push 1
stack.push 2
stack.push 3
assert_equal(3, stack.pop)
end
end
まあ、こういう例も一つはあったほうがわかりやすいですから。
ここでテストを終わらせないで、仕様記述に基づくランダムテスト QuickCheck を使うのが Concrete and Specific Programming の主張です。次のような仕様を列挙します。
- 空の Stack を pop すると nil が返る (帰納法の基底段)
- 任意の Stack に任意の object x を push した後で pop すると x が返る (帰納法の帰納段)
これで、Stack のpop と push に関する仕様はほぼ決まりです。
さて、1番目のテストを Ruby でどう書くかは明らかです。しかし、2番目はどうしたらよいでしょうか。問題は「任意の」という言葉をどのようにして Ruby で表現するか、にあります。
RushCheckライブラリを用いると、「任意の」を書き下すことができるのです。2番目のテストを Ruby と RushCheck で書くとこうなります。
begin
require 'rubygems'
require_gem 'rushcheck'
rescue LoadError
require 'rushcheck'
end
require 'test/unit'
require 'stack'
def forall(*cs, &f)
assert(RushCheck::Claim.new(*cs, &f).check)
end
class TC_Stack < Test::Unit::TestCase
def test_induction_step
forall(Stack, Integer) { |s, x| # 任意のスタック s と、任意の整数 x に対し
assert_equal(x, (s.push x).pop) # (s.push x).pop と x は等しいはず(帰納段)
}
end
end
こうすると、(デフォルトの)100回、任意のスタック s と任意の x がランダムに生成され、テストが行われます。100回のランダムテストに合格して初めて一つのテストが合格したことになります。[追記:forall は自前で書かないといけないようになっています。これは、test/unit と独立していたいという希望からなのですが、いつもいつも書く羽目になるのなら RushCheck に梱包してしまうのもいいかもしれません 2007-08-25]
どうでしょう、仕様記述スイーツを書き下すことは、実装と同様か、もしくはより平易であるということに納得していただけるでしょうか。
任意の Stack とは、ぼんやりと考えると様々なオブジェクト(Integer, String, Float, ...) が詰まった Stack のように思えます。クラスはいくらでも考えられますから、埒があきません。妥協する必要があります。
そこで、問題をより易しくするために、「任意の Stack」を修正して「任意の Stack、但し全ての要素は nil」と仕様記述を書き換えることにします。修正された帰納段のテストスイートを書き下すことにしましょう。
(2'). 任意の Stack (但し、メンバは全て nil) に任意の Integer x を push して、pop すると x が返る。
なお、QuickCheck/RushCheck は oneof という関数/メソッドを持っていて、Integer や String などの中から選び出すためのツールをあらかじめ用意しています。より一般のテストを書くことは可能です。このエントリでは、説明を平易にするために oneof という選択肢を敢えて選びません。
脱線から戻ります。RushCheck では、クラスメソッド self.arbitrary を追加することで「任意の」を表すことになっています。 self.arbitrary は引数を持たず、返り値として RushCheck::Gen オブジェクトを返すという約束になっています。Gen はランダムなオブジェクトを生成する(Generate)ための仮想クラスで、RushCheck の中に含まれています。
Integer はよく使われるので、RushCheck の中にすでに self.arbitrary が埋め込まれています。一方、Stack はユーザが新しく作ったクラスですから、テストするプログラマが新しく追加する必要があります。
class Stack
extend RushCheck::Arbitrary # お約束その1.
include RushCheck::Coarbitrary # お約束その2. (今回は、不必要)
def self.arbitrary
Integer.arbitrary.bind do |n| # 任意の長さ n をもらう (bind って何?)
stack = Stack.new # 空の stack
n.abs.times do { stack.push nil } # n 回 nil を stack に詰め込む
RushCheck::Gen.unit stack # stack を RushCheck 流に返す(って何じゃそりゃー)
end
end
# 以下は気にしない(付け加えなくても、今回の例では動作する)
def coarbitrary(g)
@value.coarbitrary(g)
end
end
Stack の self.arbitrary には、2カ所説明していない部分があります。つまり、bind メソッドと unit メソッドです。Haskell を理解している人はお気づきのことと思いますが、これは両方ともモナドの演算子です。
モナドを気にしていると話が先に進まないので、脇においておきます。規約みたいなものだと思ってもらって差し支えありません。
こうして、仕様記述を終えたあとで実装に移ります。実装がテストスイーツを満足すれば、次の段階に進みます。empty? などを拡張するもよし(当然「任意のスタックに push したあとは empty? が false を返す」などの仕様駆動テストスイーツが追加されるのは言うまでもありません)、ここで満足するもよし、です。
Stack についての言及の〆切がきました。途中、self.arbitrary を自前で書く部分は RushCheck に含まれている「任意の Integer (Integer#arbitrary)」や「任意の String (String#arbitrary)」などを参考にしてください。この文書は、あくまで Concrete and Specific Programming の説明が主旨ですので、RushCheck 独自の記法に深入りはしません。
次の具体例は、suffix array を選ぶことにします。 Suffix array は、与えられた文字列に対し、その文字列の接尾辞を辞書順でソートした配列のことです。長大な文字列の中から一部分を検索するのに優れているという利点があります。
Suffix array に関する夢メモは、さきほどと同様なので省略します。が、実際は、夢メモこそがこの手法とモチベーションを支えているので抜かすことはできません。
次にクラス設計とドキュメントを書きます。しつこいようですが、実装はこの段階では行いません。
# The class SuffixArray provides an array giving the suffixes of a string in lexicographical order # 日時、ライセンス、氏名省略 class SuffixArray # assume that the argument should be a string. # return the suffix array of the string. def initialize(str) end end
Suffix array に対する要求仕様は次の通りです:
- 空文字列に対する suffix array は空の配列 (基底段)
- 与えられた文字列が空でなければ suffix array のどこかにある
- 与えられた文字列の「任意の」接尾辞が suffix array のどこかにある
- Suffix array の「任意の」要素は、与えられた文字列のある接尾辞
- Suffix array の長さは、与えられた文字列の長さに等しい
- Suffix array の各要素は、辞書順に sort されている
以上の 6 つのテストスイーツは、全て、「任意の」という言葉さえ使うことができれば、全て Ruby で書き下すことができます。
要求仕様は「かつ」で結ぶことができるため、うっかりすると長くなりがちですが、そこをできるだけ分割するのがキモです。最後の6番目の要求仕様は、お好きなソートアルゴリズムの結果と比較すればいいでしょう。 CSP が要求するテストは富豪的な実装で構いません。テストの比較対象を得るために用いるソートアルゴリズムは高速でなくても構いません。
一方、suffix array の実装は平易なものもあれば、いくつかのアルゴリズムを組み合わせたハイブリッドなものもあります。ここで強調したいのは、要求仕様のためのテストスイーツを作るほうが、実装よりもずっと平易だということです。昨日のエントリで、実装よりもテストのほうが楽しいと書いたのは、ここにあります。計算量や使用メモリ量などのナイーブなことを気にせずに、サクッとテストスイーツを記述することができるのです。もちろん「任意の××」をランダムテストで書き下せるところがポイントです。
Suffix array を生成するアルゴリズムの理論上の計算量は、array のサイズが増大するときの振舞いを表しているにすぎません。最も適切なアルゴリズムは、実際に実装してみなければわかりません。というのは、検索する文字列が Web ページなのか、それとも遺伝子なのかなど、与えられたデータ構造に依存して検索スピードやメモリ使用量が変わるからです。そのためのリグレッションテストは、実装よりも先に書き下しておいたほうが優れていると主張します。
私が知る限りの suffix array の実装は以下のリストです。これらはどれも、上記のテストスイーツを満足します。テストスイーツのほうが実装よりもはるかに単純明快である、ということです。
- Udi Manber and Gene Myers (1991). "Suffix arrays: a new method for on-line string searches". SIAM Journal on Computing, Volume 22, Issue 5 (October 1993), pp. 935-948.
- Juha Kärkkäinen and Peter Sanders (2003). "Simple linear work suffix array construction." In Proc. 30th International Colloquium on Automata, Languages and Programming (ICALP '03). LNCS 2719, Springer, June 2003, pp. 943-955.
- D. K. Kim, J. S. Sim, H. Park, and K. Park. "Linear time construction of suffix arrays." In Proceedings of the 14th Annual Symposium on Combinatorial Pattern Matching (CPM 2003), volume 2676 of Lecture Notes in Computer Science, pages 186–199. Springer Verlag, June 2003.
- Pang Ko and Srinivas Aluru (2003). "Space efficient linear time construction of suffix arrays." In Combinatorial Pattern Matching (CPM 03). LNCS 2676, Springer,June 2003, pp 203-210.
- W.-K. Hon, K. Sadakane, and W.-K. Sung. "Breaking a time-and-space barrier in constructing full-text indices." In Proceedings of the 44th Symposium on Foundations of Computer Science (FOCS 2003), pages 251–260. IEEE Computer Society, October 2003.
- D. K. Kim, J. Jo, and H. Park. "A fast algorithm for constructing suffix arrays for fixed-size alphabets." In Celso C. Ribeiro and Simone L. Martins, editors, Proceedings of the 3rd International Workshop on Experimental and Efficient Algorithms (WEA 2004), volume 3059 of Lecture Notes in Computer Science, pages 301– 314. Springer Verlag, May 2004.
- Klaus-Bernd Schürmann and Jens Stoye (2005). "An incomplex algorithm for fast suffix array construction". In Proceedings of the 7th Workshop on Algorithm Engineering and Experiments and the 2nd Workshop on Analytic Algorithmics and Combinatorics (ALENEX/ANALCO 2005), 2005, pp. 77-85. [PDF]
Suffix array(en.wikipedia.org)より孫引き。順不同です。(まだあるので注意すること)
citeseer.ist.psu.edu による "suffix-array & construction" の検索結果(約150件)
Stack や SuffixArray はデータ構造であって、あなたが作りたいものとは違うかもしれません。しかし、あなたが作りたいものには何かのデータ構造が必要になります。あなたが作りたいものが動作するためには、パーツとしてデータ構造が必須です。
Concrete and Specific Programming が主張する、実装を最後に回すという点は「利用するデータ構造を慎重に選ぶ」ことも含みます。あなたが思いついた実装は実はすでに知られている実装に比べて劣っている、そういう経験がおありの方も多いのでは?
単に動けばいい、という目標は夢としてもつにはあまりにもちっぽけです。プログラミングに割く時間は、人生のなかでかなりの部分を占めてしまいます。たとえば、リレーショナルデータベースやWiki、プログラミング言語などは世界中にたくさんあります。それらのほとんどは「最適」ではありません。プログラマの貴重な時間を割いてまで作ったソフトウエアが最適でないのは馬鹿げています。
プログラマの視点ではなく、ユーザの視点に立ち戻ってみましょう。ユーザは何を求めているのか?ユーザが求めているものは、ユーザの環境で、ユーザが求めていることを、ユーザが満足するスピードとメモリ使用で、「快適に」動くソフトウエアです。ユーザビリティやデザインも重要な要素です。
そうでないものはプログラマの自己満足と顕示欲以外にありません。 プログラマがむなしさを感じるときは、作り終えた最後の瞬間にやってきます。 あなたは夢から覚めて現実を知るでしょう。
脇に置いておいた疑問を解消しておきましょう。どうして、QuickCheck/RushCheck はモナドを使うのでしょうか。それはランダムテストにおけるタクティクスが関係してきます。
ランダムなテストケースを生成してバグを発見するということは、入力をでたらめに作っては試行することを繰り返すイテレータです。自動的にでたらめにつくられた入力は、人間が読めるようなものではありません。文字列なら、"%!#SAqerefndjkvfop4e,34e956-="のようなものが作られます。仮にこれがバグを引き起こすとしましょう。そのとき、バグの原因を究明し、プログラムを修正するのは難しいことです。
QuickCheck/RushCheck では、「ランダムに入力を生成する、ただし、サイズの小さいものから順番に作る」というタクティクスを採用しています。先ほどの例だと、"%!#SAqerefndjkvfop4e,34e956-=" よりも前に "sda89%" のような長さの短いランダム文字列を試すことになります。このタクティクスにより、バグを生成するインスタンスをできるだけ短いものにすることができます。小さな入力例でバグが見つかれば、原因をより突き止めやすくなります。
サイズの小さいものから順番に作るというタクティクスを実装するためには次のようなことを考えなければなりません:
Haskell のような純粋関数型言語では、副作用はモナドとして表現されます。一方、Ruby は副作用を起こすことが可能なのでモナドは必ずしも必要ではありません。にもかかわらず、RushCheck の実装にモナドを使ったのは、RushCheck ライブラリ自身の可読性と精密さを追求したからです。バグ発見のためのライブラリそのものにバグがあるのでは、お話になりませんからね(にもかかわらず、RushCheck にはまだバグがあるかもしれませんので、そこはご容赦願いたい)。
昨日のエントリに引き続き、Concrete and Specific Programming というプログラミング手法を提唱しました。まとめると、「実装を一番最後にまわすことにより、後手間を減らす」「仕様記述に基づいたランダムテストを併用することにより、テストスイーツを平易に書き下す」「夢と〆切を見て現実を知る」以上、3点です。
今まで提唱されてきたプログラミング手法と比較するつもりはありません。また、銀の弾丸ではないことも前に触れた通りです。楽しくないプログラミング手法かもしれませんが、私にとっては、今のところうまく働いています。
昨日のエントリから引き続いて。私がCSPに言及するのはこれで最終回です。これらの文章を読んだ人からの反応があれば、御応えするかもしれません。
もしそれが夢ならば、覚めるときがやってきます。幼少時の頃の夢を実現できた人は、そうでない人にくらべてずっと少ないです。夢はかなわないからこそ夢なのであり、白昼夢にうつつを抜かしている暇は現代にはありません。
夢ノートを付けてしまったその日から、歯車は回り始めました。 油を注すことなしに、いつまでも回り続けることはできません。現実は concrete ではなく discrete です。
最初からプログラムの全貌を書き起こすことが、いつも可能なわけがありません。現実には、夢を描いてはぶち壊し、プログラムを書いては消し、テストから見逃されたバグは潜み続けるものです。残念ですが、誠に残念ですが、趣味のプログラムは、神のいう完全という意味で、完成することがありません。現実を知るときがやってきます。
ひずみをむしろ佳しとするのが侘びであり寂びでありましょう。無駄がなければないほど、そこに価値観を求めるのが道ではありますまいか。僕は、お茶やお花を習い始めてから、「佳いもの、美しいもの」の捉え方が変わりました。完全完備多機能でなんでもできるアプリケーションは、「渋さ」がないです。いつでもどこでも繋がり合えるコミニュケーションも、「一期一会」がないです。主が客をおもてなしする、という観点が抜けています。客が主のおもてなしに感動するという観点も抜けています。僕はもう現代のプログラミングをやめて、禅寺にでも行ったほうがいいのかもしれません。
人は完全でない、という前提のうえで、破綻しないプロジェクトを運営するには、失敗しては失敗を取り戻すという小さなイテレーションを繰り返すことになります。Concrete and Specific Programming という考え方は、その小さなイテレーションをより改善するために考えたものです。
私の経験で言えば、「強い型付け」「仕様駆動ランダムテストQuickCheck」「遅延評価」を持つ Haskell は、早い段階で設計の矛盾をあばいてくれます。誤り訂正を生業とする私に取って、いちばんしっくりくる道具です。一旦 Haskell でプロトタイプを書いてから、その後必要になればその他の言語(Cとか)で書き直すのは簡単です。私が隠していた結論とは、失敗を取り戻す小さなイテレーションでプロトタイプ実装するには、今のところ、Haskellが一番いいんじゃないかなあ、ということでした。これは個人的な意見で、Haskell を知らない人には同感できないことでしょうね。
「もし、夢が叶わなかったとき、その代わりに、自分は何を得るのか」を考えておくことです。そのときがやってきたとき、がっかりする代わりに、「スキルが向上した」「別の目的に発展した」「彼女ができた」などの望ましい選択肢を、その都度、予期しておくのはよい心がけです。
RSS feed を再開しました。RSS の思想を尊重するために全文配信はしません、あしからず。