Personal tools
You are here: Home ブログ 井上
« July 2008 »
Su Mo Tu We Th Fr Sa
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
Categories
カテゴリなし
 
Document Actions

AirOne v4.8.5リリースしました

リリースノート

v4.8.4から間が空いていますが、機能追加はありません。バグ修正版です。機能追加版のメジャーバージョンは開発中なのでしばしお待ちください。

バグ修正版と言っても、ブランチで作業したバグ修正版としては、過去、最大の難易度でした。

v4.8.5の作業中は、会社で、cvsレポジトリふたつ、svnレポジトリひとつ、家に帰ってからsvnレポジトリ三つをフォローする日々でした(六つのレポジトリはすべて別物。svnのうちひとつはソースコードではなくドキュメント)。頭のコンテキストスイッチが多すぎて、能率が悪い気がしました。

なにかミスりそうな気がしたので、そのために行った対策が、コンパイルしないことでした。

当然、最終的にはコンパイルしますが、普通ならコンパイルして動作させるタイミングで、そこから2,3日、コンパイルせずにコードを見直す作業をしました。コンパイルエラーがゼロだろうと思うまでコードを見直してから、やっとコンパイルしました。コンパイルエラーは2ヵ所ありましたが、ひとつはinclude洩れで、これは最初から目で見つけようと思っていないので、事実上はコンパイルエラーひとつでした。ゼロのつもりでコンパイルしたので、ひとつあったのは負けですが、修正量がそれなりにあった割にはまあまあです。

普段、コンパイラで見つけられるバグはバグではない、という立場です。つまり、コンパイラで見つけられるバグは、人間ではなくコンパイラに見つけさせるべき、という考えです。なので、今回の行動は普段の言動に反しています。

今回のように、コンパイルエラーがゼロだと自信が持てるまで徹底的にコードを見直して、ようやくコンパイルする、というのも悪くないと感じました。コードを見直している間、論理バグも結構見つけられるからです。もっとも、効率を考えると、必ずしも全員には奨められません。

Railsで書いているコードもあるのですが、対照的にいい加減です。テストコードなんて一行もありません。書いては動かして、実行時エラーがあれば直す、それだけです。実行時エラーはかわいいもので、型の不一致による、エラーが起きないバグなど、悲しい思い満載です。適当でもなんとかなっているのは、Rubyの良さかもしれませんし、単にコードが少ないからだけかもしれません。

プレゼン文化の違い

先日、salesforce.comのTour de Force Tokyo(-ツール・ド・フォース-)に行ったことを書きましたが、その場でコードを書いてすぐに動かすデモがなかなか素晴らしい出来でした。しかし、会場は静かなままです。途中、プレゼンター(外国人)が自分で拍手して、会場の拍手を促す場面すらあって涙を誘いました(これを無視するほど、会場の日本人も冷たくはなく、拍手で応えていました)。

Google Developer Dayで、ステージ上の及川氏が、「こういう風にデモが成功した時、海外では拍手で讃える文化があるので、是非、みなさんもお願いします」と言って拍手を促していたのを思い出しました。

海外ではどれほどのものかと思って参考に見たのが、これです(2008年はあまり面白くないので2007年)。

良くもまあ、こう求めに応じて律義に拍手するものです。教祖と信者なのであまり参考にならないかもしれませんが。教祖がcool?と言うたびに拍手喝采です。君たち、パブロフの犬か、と毒づきたくなります。

とは言え、拍手があるだけで、たいしたことの無い技術も世紀の大発表に聞こえてきます。参加した信者も幸福な気分を味わっているのでしょう。

こういう展開の文章で、これが日経の記事なら、日本人は拍手で讃えるマナーを身につけるべきだ、とか、素直に称賛を与えない文化が日本のソフトウェアをダメにしているのだ、など、説教臭い精神論や憂国の士をきどった論調にでもなるところですが、アリエルエリアは日経ではないので、そんな展開にはしません。

精神論が嫌いというわけではなく、単に、日本人が身の丈に合わないことを無理して不自然にやっている姿を見るのが嫌いだからです。拍手が自然にでないのなら、それはそういうものです。自然にできないことを無理して始めるのは、見ていて痛々しいので嫌いです。

でも、できないのは先陣を切って拍手をすることで、誰かが拍手をした時にそれに追随するのは、多くの日本人が自然にできるようです(観察による経験則)。なので、ここは割り切って、日本で行うプレゼンでは、拍手屋を雇ってしまうべきです。内輪の関係者に頼めばコストゼロです。バイトを雇ってもコストはたかが知れています(コンパニオンの女性のコストを削れば捻出できるでしょう)。拍手のタイミングは、上のビデオを一本見れば、だいたい理解できるはずです。拍手屋が率先して拍手すれば、おそらく他の日本人もついてきます。

そんなのヤラセじゃないか、と文句を言う人もいるかもしれませんが、別に誰が傷つくわけでも、誰が損をするわけでもありません。平凡なプレゼンを凄いと勘違いしてしまう危険性が損だ、と主張する人がでてくるかもしれません。そんな人はどうせもっと色々なところで騙されているので誤差の範囲です。

ただ、ヤラセが丸見えなのは気持ちのいいものではありません。内輪ウケは、拍手がないより興醒めです。このため、内輪の関係者に拍手をさせてコストを浮かせるよりは、部外者の拍手屋の方が良いでしょう。拍手屋にはヤラセと見えないための努力もしくはスキルが必要です。周りも気づかないフリをする優しさが必要です。

IBMには悪いですが、Notesの正統な後継者の一番手はsalesforceかもしれない、という思い

salesforceのTour de Force Tokyo(-ツール・ド・フォース-)に行ってきました。

去年のGoogle Developer Dayでは昼食も夕食もでました。今年のGoogle Developer Dayではどちらも無くなりました。salesforceのTour de Force Tokyoは昼食も夕食もでました。この論評は差し控えます。

開発者向けセッションを聞いてきました。

ApexとVisualforceを、MVC的に説明すると次のようになります。

  • カスタムオブジェクト...RDBでのテーブルスキーマ定義相当。model相当
  • コントローラ...Apex言語で記述。後述するように、伝統的MVCのcontrollerと言うより、ビジネスロジック層に見えます
  • ページ...PHPやJSPのようなファイル。サーバで処理されて結果はクライアントへのレスポンスになります。view相当
  • コンポーネント...JSPで言うところのカスタムタグ(ライブラリ)。コントローラから渡されたデータ(model)を参照します。view相当

構成はWebアプリの伝統的スタイルに準拠しています。Javaで言えば、カスタムオブジェクトがRDBを隠蔽する層で、言わばエンティティ層と思ってよいでしょう。ページがJSPファイル、コンポーネントがカスタムタグファイルです。

少し変則的なのは、ページ(MVCのviewそのもの)に、対応するコントローラを記述させる部分です。伝統的MVCでの、controllerとviewの参照関係と逆方向に感じます。これは、「コントローラ」(以下、カッコ付きコントローラはsalesforceの固有名詞のコントローラ)の用語の使い方の問題だと思います。「コントローラ」はサービス層もしくはビジネスロジック層に近くて、伝統的MVCのmodel寄りです。伝統的controllerはフレームワークに隠蔽されています。ページに記述する「コントローラ」への参照は、伝統的controllerに解釈させる宣言的記述(viewがどのmodelに依存するかの宣言)と見なせます。

ページには<apex:foo>のような独自カスタムタグを書けますが、それ以外は、普通のHTMLです。CSSもJavaScriptも書けますし、望むならばFlashも埋め込めます。仕組み自体はJSPやPHPと同じです。つまり、サーバが中身を解釈して、その出力はブラウザへのレスポンス(普通はHTML)になります。

ページの記述の自由度の高さがsalesforceを際立たせるポイントです(ビュー周りの開発環境を総称して、salesforceではVisualforceと呼んでいます)。

ユーザにページ相当のファイルをポストさせて、それをサーバサイドのビューとして使う仕組み自体は誰でも作れます。例えば、RailsでユーザにERbのコードをポストさせて、それを表示に使うアプリは10分で書けるでしょう。しかし、それを愚直にやると、普通はセキュリティホールだと認識されます。

実現できることと、それを現実的に機能させることには大きな差があります。

Googleも登場当時、サーチエンジンが特別に奇特なサービスではありませんでしたが、高速に、そしてずっと高速にやり続けていることが凄い点です。同様に、salesforceも、ユーザにサーバサイドで動くコードをポストさせる仕組みを、きちんと動作させ続けていることが凄い点です。

ちなみに、情報ソースが一ヶ所なので、信頼度はその程度と思ってほしいですが、salesforceのサーバは、月に2,3回程度はメンテナンスのために停止するようです(停止は事前予告あり)。もしsalesforceが24時間365日ノンストップだったら、負けましたとあやまるしかないですが、幸いなことにそこまで進化はしていないようです。

「ページ」相当の機能は、facebookも同様の仕組みを提供しています(FBML)。これはこれで両者とも凄いのですが、salesforceが行っちゃったなと思うのがApexコードです。これは、サーバで動作するビジネスロジック層(上に書いたように、salesforceは「コントローラ」と呼んでいました)をユーザに自由に書かせる機能です(正確に用語を使うとApexはそのための言語)。これも、仕組みだけなら誰でも作れます。ブラウザからperlのコードをポストさせて、CGIとして実行させる仕組みと言えば、実現の簡単さがイメージできるはずです。しかし、普通はできません。現実の様々な障害(セキュリティやパフォーマンス)が多すぎるからです。しかし、salesforceはやってしまいました(Google App Engineよりずっと先に実現)。

Notesもユーザがアプリを作成して、サーバ上で動作させる仕組みがあります。機能だけを見ればsalesforceを凌駕しています。しかし、今、IBMがNotesの開発実行環境をPaaS(Platform as a Service)としてインターネットに公開できるでしょうか。恐くてできないはずです。社内アプリなら開発者の自己責任ですが、インターネットへの開発環境の公開はそうもいきません。

サーバサイドで実行することの意味は、サーバのCPU資源を利用できる利点もありますが、一番の利点はサーバ上のストレージへのアクセスです。事実、Webサービス(いわゆるWeb APIを使ったアプリ)と比較した利点として、トランザクションをサポートしている点をApexコードの利点だと強調しています。

ApexはORMを持っています。SOQLはSQLに似たyet anotherクエリ言語です。

次のようにLINQっぽく書けるのがクールです。個人的には、LINQっぽい方がActiveRecordのようなメソッドで頑張る記法より好きです。

// 記憶で書いているコード
List<Contact> contacts = [SELECT FirstName FROM Contact];
for (Contact contact : contacts) {
   System.debug(contact.FirstName);
}

にあるように、SOQLはjoinを明示的には指定できませんが、限定的に似たようなことは書けるようです。

似たような話を、最近facebookのAPIで見ました(Associations)。

この辺りの話は、「クエリをどう記述するか」という問題点につながり、興味深い点がある気がしていますが、まだ自分の頭の中で整理しきれていません。整理できたら、何か書きます。現状は、SQLより優れたクエリ記法には感じていませんが。

追記

そういえばコードを書くデモがほとんど失敗しませんでした。コードを書くデモって失敗率高いのですが。今年のGoogle Developer Dayのデモの失敗率の高さと対照的でした。

質問が来たので答えてみる

「動かなくてもいいから、動きそうなコードをかいてください。」 「動きそうも無いのに動くコードはいらない。」
Q1.「動きそうなコード」とはどういうコードですか?
動きそうもないのに、動くコードとは?

元の発言はシニカルで逆説的な言い方をしています。ぼくはシニカルで逆説的な表現が好きなのです。それを説明するのは野暮というもので、ぼくは野暮なことが嫌いです。嫌いですが、聞かれたので、蛇足をつけたします。

プログラムである以上、最終的には動かないと意味はありません。これは当然です。それでも敢えて、動くことより、読めるコードに価値があると倒錯した表現を使いました。「動きそうなコード」とは、人間が読めるコードであり、人間が理解できるコードです。

読めて理解できるコードであれば、例えそれが動かないコードだったとしても、動かせます。実行速度が遅ければ、速くできるかもしれません。下手なコードでも、読んで理解できれば改善できます。読んで理解するには、読み手の技量も重要ですが、とにかく、コードは人間が読んで理解できなければ存在価値が無いと思っています。

逆に、動作はしても、読んで理解できないコードは、保守できません。

他人の書いたコードを読んで理解するには一定の技量が必要で、これは実は想像以上に難しい技術です。だから、なんでもかんでも誰が読んでも容易に理解可能にすべき、というほど理想は求めていません(人も時間も有限です)。ただ、せめて自分が書いたコードぐらいは読めて理解できてほしいと思います。部外者から見ると信じられないかもしれませんが、自分の書いたコードがなぜ動いているか理解できていないのではないか、と思えるプログラマも世の中にはいます。忘れた、という次元の話ではありません。忘れるのは誰でもあります。昔自分で書いたコードが読めなくなっていることもあります。そういう次元ではなく、今、目の前で自分で書いているコードそのものを理解していないプログラマがいるのです。そんなんでプログラムは動くのかと思うかもしれませんが、困ったことに動くこともあるのです。

Q2. エレガントなコードを求めるのはどうしてですか?

誤解があるようです。エレガントの言葉の定義にもよりますが、別にエレガントなコードは求めていません。もちろん、エレガントなコードを否定もしません。エレガントなコードが美しいコードの意味だとすると、美しいに越したことはないですが、美しさよりも読めて理解できることの方を重視します。1の繰り返しになりますが、読めて理解できれば、下手なコードでも美しくできるかもしれないからです。

ぼくはプログラミングに関して原理主義者の側面もありますが、少なくとも大規模ソフトウェア開発に関しては現実主義者です。全てのコードに完璧さと美しさを求めたりはしません。コードには、カオスから守るべき領域とカオスを内包する領域の両方があることを認めています。カオスから守るべき部分、別の言い方をすると変化の影響を受けにくい部分は徹底的に守ります。守る防波堤(境界)がインターフェースです。だからインターフェースが重要です。カオスを内包する部分、別の言い方をすると変化を受け入れる部分も必要です。ここを見誤って、コードのあらゆる箇所に完全さを求めると窮屈なコードになります。コードには手を抜く部分も必要なのです。もっとも、手の抜き方は見かけほど簡単ではありません。

Q3. 「可能性」がありそう、と思われるエンジニアに共通点ってありますか? あったらいくつでも教えてください。

社会にうまく適応できない人の方が良いプログラマになる気もしますが、偏見かもしれません。時代も変わるし、変なチェック項目を作られるのが嫌ので、回答しません。

Q4. 尊敬している人と、その理由

rms

世界を変えたから。大衆に迎合しないから。信念のために戦うことを辞さないから。

プログラマの理想の採用

小関順二の「間違いだらけのプロ野球」(何年度版かは不明)だったと思うのですが、こんなことが書かれていました。かつてダイエー(知らない人もいるかもしれませんが、ソフトバンクの前身です)が城島をドラフト一位指名したことを絶賛する記事でした。

ダイエーが駒大進学の決まっていた城島を強引に指名したことを褒めていたのではありません。こんなことは日常茶飯事ですし、褒められたことではありません。

当時、ダイエーには吉永という捕手がいました。晩年、巨人に移籍したので、それで覚えている人もいるかもしれません。wikipediaに成績が載っているので、リンクを張っておきます。

城島をドラフト指名した年(1994年)、吉永は25才、ベストナインに選ばれています。捕手でありながら、三割近い高打率と二桁本塁打を三年連続で達成しています。25才という年齢を考えると、正捕手は10年安泰と思ってもおかしくない成績です。しかし、現実は、ダイエーは城島をドラフトで指名し、城島はほどなく吉永から正捕手の座を奪います。

凡庸なフロントであれば、吉永で正捕手安泰と考え、ドラフト下位で控え捕手を指名しようと考えます。自軍に10年安泰の正捕手がいれば、ドラフト上位で投手や野手を指名しようと考えるのは不思議ではありません。しかし、当時のダイエーはそんな守りのドラフトはしませんでした。その年一番良いと思った選手(城島のことです)を果敢に採りにいきました。この姿勢を小関順二は絶賛します。

ポジションが被ろうが、才能があると見込めばドラフト指名していく姿勢こそが、強いチーム作りに必要だと小関順二は説きます。事実、その後のダイエー(現ソフトバンク)は強いチームになりました。

この点に関してプログラマも同じです。才能あるプログラマならいつでも採りに行くべきです。才能あるプログラマの採用に関して、ポジションの空きなんて関係ないからです。才能を見込めば、やらせる仕事がなくても、採りにいきます。暇でしばらく遊ばせておくことになったとしても、それがなにか問題あるのでしょうか。プログラマの理想の採用とはそういうものです。

最近アリエルで流行ること、グリーンコンピューティング

正直に言うと、今までグリーンなんとかはIBMが自社の高いハードを買わせるためのいかがわしいバズワードだと思っていました。グリーンなんとかを言い出したのはここ数年ですが、高いハードを買わせるために様々な謀略を行っているのは昔からです。

今、アリエル社内ではグリーンコンピューティングが流行っています。

Windows用ですが、消費電力の見える化では、次のLocalCoolingが面白そうです。

1アプリで消費電力を下げるってどういうことだ?、と思いましたが、(実際に使っていませんが)Windowsが元々提供している低消費電力用の設定を利用しているだけのようです。

グリーンコンピューティングを徹底するなら

  • X Window Systemは禁止
  • エディタはvi
  • Webブラウザはw3m

ってところでしょうか。

ningアプリ

家にサーバを立ててPHPを勉強したい、と言っている人がいました(プログラム経験ほとんど無し)。勉強する意欲を削ぐ気はないので止めはしません。しかし、色々と道は困難です。それなりに動くサービスにするには、RDBの勉強も必要です。それなりの見栄えにするには、CSSやJavaScriptの勉強も必要です。それに加えてPHPです。気が遠くなりそうです。

世の中には、プログラミング言語そのものを学ぶのが好きだったり、仕組み(OSやRDBなど難しいほどいい)そのものを理解するのが好きな人がいる一方、そんなことより、インターネットでやりたいサービスがあって、仕組みの勉強とかどうでもいいという人もいます。後者のような人が、PHPやRDBの勉強、JavaScriptライブラリの選択に頭を悩ますのは、実に時間のムダに感じます。

この辺のことを考えていたら、ningを思い出しました。2年半近く前に言及したことがあります。今でもPHPなのかは不明です。

ningを使うと、ウィザード形式でSNSサイトを構築できます。

外国人向けに「忍者SNS」を作ろうと思ったのですが、ninjaで検索すると大量に引っかかったので、仕方なく「中目黒SNS」にしました。

次のリンクから参加できます。メールアドレスを打ち込みますが、confirmationはありません。適当に存在するメールアドレスを書くだけでsign upできます。特に活動予定はないので、ログインしても期待はしないでください。

友達管理、フォーラム(掲示板)、イベント(いわゆるグループスケジュールではなく、グループのイベント管理)、写真共有ができます。特筆すべきはActivityでしょう。メンバーの操作が逐一表示されるビューです。今やSNSと呼ばれるサービスを特徴づける機能な気がします。かつて、招待制こそがSNSと既存サービスを隔てる唯一の機能と思っていましたが、今は、招待制とActivity機能が差別化の要因だと思っています。

Activityに近い機能は、AirOneでも新着機能として持っています。しかし、AirOneの新着機能は、文書中心主義です。文書が主語で、いつどんな文書が作られたかを表示するビューです。一方、Activityは人間が主語です。人間がいつ何をしたかを表示するビューです。得られる結果は似ていますが、この違いは大きいです。そして、今のぼくは、Activityの方が正しいアプローチだと思っています。

技術的に興味深いと思ったのは次の2点です。

  • CSSを自分で書ける
  • facebookのプロフィール画面にningアプリを表示可能

会社の誰かを「中目黒SNS」に招待しようかと一瞬考えましたが、わざわざning上でコミュニケーション(フォーラムに何か書き込んだり)をしなければいけない理由が思いつかず、招待するのはやめました。

Rails(ActiveRecord)のJOINのイディオム

旧世代と罵られそうですが、O/Rマッパー(ORM)に微妙に慣れません。基本的に、こうあるべき、というSQL(SELECT文)を思い浮かべて、それと同程度のSQLを吐き出すようにコードを書いています。実に倒錯的です。

なぜそんなことをするかと言うと、N+1問題が恐いからです。N+1問題は、以下のActiveRecordの解説で説明しています(現象的には1+N問題と呼ぶ方が適切な気もしてきました)。

あまり考えずにORMでコードを書くと、SELECTの結果セットがN行の時、追加のSELECT文がN回走る危険があります。これが恐くて、コードを書いては、いちいち発行されるSQLを確認しないと眠れません。偏執狂的ですが、Nが1でも許せません。JOIN一回で済むところを2回もSELECTが発行されるのが許せないのです。

そんなわけで、変態的ですが、SQLからActiveRecordのコードという逆引きチートシートです。形式的に引けるように、テーブル名をアルファベット一文字にしています。外部キーはRailsの流儀です(特に説明していません)。 has_manyの部分は、has_oneにしてもだいたい等価です(Rubyのコードで配列になるかどうかの違い)。

SELECT * FROM A LEFT OUTER JOIN B ON B.a_id=A.id;

class A
  has_many :B
end
A.find(:all, :include=>:B)
SELECT * FROM A LEFT OUTER JOIN B ON B.a_id=A.id LEFT OUTER JOIN C ON C.a_id=A.id

class A
  has_many :B
  has_many :C
end
A.find(:all, :include=>[:B,:C])
SELECT * FROM A LEFT OUTER JOIN B ON B.a_id=A.id LEFT OUTER JOIN D ON D.id=B.d_id

class A
  has_many :B
  has_many :D, :through=>:B
end
class B
  belongs_to :D
end
A.find(:all, :include=>:D)
または
A.find(:all, :include=>{:B=>:D})
# この書き方の場合、:through=>:B の行がなくても動きます
SELECT * FROM A LEFT OUTER JOIN B ON B.a_id=A.id LEFT OUTER JOIN D ON D.id=B.d_id LEFT OUTER JOIN E ON E.d_id=D.id;

class A
  has_many :B
end
class D
  has_many :E
end
A.find(:all, :include=>{:B=>{:D=>:E}})

最後に対して具体例を示します。

テーブルは3つです。説明に必要なカラムだけ取り出した図を示します。例によって、外部キーは暗黙にRails流儀を仮定しています。

articles
|id|descripton|

comments
|id|article_id|user_id|description|

users
|id|name|

ext_user_maps
|id|user_id|

articlesとcommentsテーブルの関係は、ありがちな関係なので説明を省略します。usersテーブルも省略します。ext_user_mapsは少し説明が必要です。このシステムが持っているユーザIDと、なんらかの外部システムのユーザIDと結びつけるテーブルだと考えてください。このテーブルを通して得られるidを外部システムユーザIDと呼ぶことにします。

ここで、やりたいことが、articlesの結果セットのコメント全体と、それぞれのコメントの作者の外部システムユーザIDだとします。分かりづらいので、具体的な数値をいれた例で示します。説明に影響は無いので、articlesテーブルのレコード数はひとつにします。

articles
|id|descripton|
|1 |'x'       |

comments
|id|article_id|user_id|description|
|1 |1         |1      |'a'        |
|2 |1         |1      |'b'        |
|2 |1         |2      |'c'        |

users
|id|name|
|1 |'A' |
|2 |'B' |

ext_user_maps
|id|user_id|
|10|1      |
|11|2      |

SELECT文と欲しい結果セットは次のようになります。

SELECT articles.id aid, comments.id cid, ext_user_maps.id ext_id FROM articles 
  LEFT OUTER JOIN comments ON articles.id = comments.article_id
  LEFT OUTER JOIN users ON users.id = comments.user_id 
  LEFT OUTER JOIN ext_user_maps ON ext_user_maps.user_id=users.id (必要ならarticlesへのWHERE句);

|aid|cid|ext_id|
|1  |1  |10    |
|1  |2  |10    |
|1  |2  |11    |

これを実現するRails(ActiveRecord)のコードは次のようになります(チートシート参照)。

class Article
  has_many :comments
end
class User
  has_one :ext_user_map
end
Article.find(:all, :include=>{:comments=>{:user=>:ext_user_map}})

ここまで苦労して報われるとしたら、上のfind()の戻り値をarticlesで受けた時、articles.comments[0].user.ext_user_map.idとして、コメントごとの外部システムユーザIDを簡単に得られることです(苦労したおかげで、この時にSQLは発行されません)。

久々にCで面白いバグ(解答篇)

昨日の書き込みの解答篇です。

バグのあるコードを再掲します。

// buggy code
*(const char**)apr_array_push(arr) = arr->nelts == 0 ? "foo" : "bar";

ヒントに示したK&R2の65ページには次のように書いてあります。

多くの言語と同様に、Cでは演算子の被演算数に対する評価順序は指定していない。(例外は、&&、?:と','である。)

代入演算子(=)の右辺と左辺のどちらを先に評価するかが決まっていません。バグのあるコードの場合、右辺を先に評価すると、一回目にarr->nelts==0が真になり、意図どおりの動作をします。左辺を先に評価すると、apr_array_push()の中でarr->neltsをインクリメントするため、意図どおりにarr->nelts==0が真になることはありません。

少し続きがあります。ヒントのひとつとして次のように書きました。

Javaでは(残念ながら)同じバグを起こせません

Javaは被演算数に対する評価順序を「左から右」と規定しています。しかし、本質的なバグの原因(式の中の副作用)がなくなるわけではありません。誤ったヒントでした。

Javaでも同様の問題があることをコード例で示してみます。Cのコードと似たようなコード(副作用のある式。メソッド呼び出しを含む左辺値を返す式)にしてみました。Cですら不格好なのに、Javaにすると嫌がらせとしか思えません。と言うか、Javaの方が分かりづらい気すらします。

class My {
    // Note that this is not a productive code
    private String[] arr = new String[32];
    private int nelts = 0;
    public String[] arrPush() {
        nelts++;
        return arr;
    }
    public static void main(String args[]) {
        My my = new My();
        my.arrPush()[my.nelts] = my.nelts == 0 ? "foo" : "bar";
    }
}

久々にCで面白いバグ(読者への挑戦状篇)

常日頃、面白いバグが無いかと探しています。見つけた時は嬉しくて、このアリエルエリアで紹介しています。

自分にとって面白いバグの条件は次のようになります。

  • 一瞬気づかないが、指摘されると、あっ、と思えるもの
  • コンパイラなど静的チェックでは見つからないもの
  • どこでも起こりうるもの

普遍性と意外性を備えたバグが、好みのバグです。単純なのに意外、という性質を備えていれば更に良しですが、理想的なバグはそうそう転がっていません。最近の不満は、社内で、Javaのコードインスペクションをしているのに、面白いバグを発見できないことです。社員が優秀すぎるのでしょうか。わざと見つけにくいバグを仕込むぐらいの男気が欲しいものです。

Javaではなく、Cで久々に面白いバグに出会いました。

こんなコードです(少し変えています)。

// buggy code
*(const char**)apr_array_push(arr) = arr->nelts == 0 ? "foo" : "bar";

背景となる説明(特にAPRのapr_array_push()の説明)がそれなりに必要です。しかし、バグの原因自体は、普遍性があります。

apr_array_push()とデータ型は次のようなコードです。説明に必要な部分だけを抜粋および少し改変してます。

struct apr_array_header_t {
    /** The amount of memory allocated for each element of the array */
    int elt_size;
    /** The number of active elements in the array */
    int nelts;
    /** The elements in the array */
    char *elts;
};
APR_DECLARE(void *) apr_array_push(apr_array_header_t *arr)
{
    //このメモリ確保のコードは改変しています
    arr->elts = apr_palloc(arr->pool, arr->elt_size * new_size); // don't care new_size
    ++arr->nelts;
    return arr->elts + (arr->elt_size * (arr->nelts - 1));
}

正直、非常に分かりづらいコードです。提供している機能は動的配列です。例えば文字列の配列なら次のイディオムで使えます(このAPIの是非は今日の主眼ではありません)。このイディオムだけ認識しておけば、内部実装にはあまり深入りしなくても大丈夫です。

apr_array_header_t* arr = apr_array_make(mp, 32, sizeof(const char*));
*(const char**)apr_array_push(arr) = "foo";
*(const char**)apr_array_push(arr) = "bar";
*(const char**)apr_array_push(arr) = "baz";

buggy codeは実際にはループの中にありました。3項演算子で参照しているarr->neltsは、動的配列の要素数です。つまり、コードの意図は配列の先頭にだけ"foo"を追加して、残りは"bar"を追加するというものです(実際のコードでは、配列に追加する値は違うものです)。

ここまでで、最初に挙げたbuggy codeのバグの原因を推理できる全ての手がかりを提示しました。推理小説であれば、読者への挑戦状を書くところです。

せっかくなので、解答篇は明日にします。

ヒント

  • ポインタ関連のバグではありません
  • Javaでは(残念ながら)同じバグを起こせません
  • K&R2の65ページ参照

ソフトウェア品質のジレンマ

以前、「バグがでないのは良い知らせなのか」という記事(http://dev.ariel-networks.com/Members/inoue/bug-and-test)で、横軸に時間、縦軸にバグの報告件数を取ってバグの収束を見ることへの疑念を書きました。

同じ問題意識を持っている発表資料を見つけました。

横軸が経過時間では良くない、という主張です。その代わりとして、テスト項目数(実施数)を横軸に書く案を挙げています。

この辺の数値はそれなりに記録しています。グラフにするのも(gnuplotとシェルを使えば)簡単です。問題はグラフの解釈です。これが実に難しい。どれぐらい難しいかと言うと、もう7年ぐらい考えているのに未だに分からないぐらいです。

テストがまだ充分ではないという判断ができることは多々あります。しかし、人間の疲労度とのトレードオフがあります。もし、10人でテストをして一日で見つかった有効バグが2,3件だとします。ソフトウェアの品質の観点から言えば、一日に有効バグが2,3件も見つかるならまだテストが不十分と判断するかもしれません。しかし、作業する人の立場になってみると、10人のうち、ほとんどの人は有効バグの発見数がゼロだったということです。これが数日続くと実に辛いです。ひどい徒労感に襲われます。最後のソフトウェア開発なら、とことんまでテストするのも手かもしれませんが、今後も開発を継続するつもりなら、適度なところで切り上げる必要があります。人が疲弊して辞めたり意欲を失う損失と、多少のバグを見逃してリリースしてしまう損失を比較するなら、ほとんどの場合、前者の方が損失が大きいからです。

facebookカレンダーの斬新(?)な仕様

以下はfacebookカレンダーの作成画面です。見た目が地味という点は無視して、何か普通と違う点に気づくでしょうか。

/Members/inoue/images/misc/facebook-cal.png

日付入力に年の指定がありません。

現在時刻より前の日付、例えば、今、5/20の予定を作成すると、暗黙に2009年5月20日の予定になります。今日より後の日付を指定すると2008年の予定になります。

それぐらいの省略は珍しくないのでは、と思うかもしれません。しかし、入力時に省略可能というレベルではなく、そもそも、年を指定するUIが存在しないのです。

過去の予定の作成には意味がないはずだという割り切り、1年以上未来の予定の作成にも意味がないはずだという割り切りです。更に、以下のような制限(1ヵ月以上長期の予定は作成できない)もあります。

A Facebook event must end within 31 days from when it starts. appears to be invalid.

予定期間の上限は、システムの都合な気がします。期間(一年+前後プラスアルファ)で(RDBの)テーブルを分けている、あたりでしょうか。


Copyright(C) 2001 - 2006 Ariel Networks, Inc. All rights reserved.