ソフトウェア品質のジレンマ
以前、「バグがでないのは良い知らせなのか」という記事(http://dev.ariel-networks.com/Members/inoue/bug-and-test)で、横軸に時間、縦軸にバグの報告件数を取ってバグの収束を見ることへの疑念を書きました。
同じ問題意識を持っている発表資料を見つけました。
横軸が経過時間では良くない、という主張です。その代わりとして、テスト項目数(実施数)を横軸に書く案を挙げています。
この辺の数値はそれなりに記録しています。グラフにするのも(gnuplotとシェルを使えば)簡単です。問題はグラフの解釈です。これが実に難しい。どれぐらい難しいかと言うと、もう7年ぐらい考えているのに未だに分からないぐらいです。
テストがまだ充分ではないという判断ができることは多々あります。しかし、人間の疲労度とのトレードオフがあります。もし、10人でテストをして一日で見つかった有効バグが2,3件だとします。ソフトウェアの品質の観点から言えば、一日に有効バグが2,3件も見つかるならまだテストが不十分と判断するかもしれません。しかし、作業する人の立場になってみると、10人のうち、ほとんどの人は有効バグの発見数がゼロだったということです。これが数日続くと実に辛いです。ひどい徒労感に襲われます。最後のソフトウェア開発なら、とことんまでテストするのも手かもしれませんが、今後も開発を継続するつもりなら、適度なところで切り上げる必要があります。人が疲弊して辞めたり意欲を失う損失と、多少のバグを見逃してリリースしてしまう損失を比較するなら、ほとんどの場合、前者の方が損失が大きいからです。
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/software-quality-dilemma/tbping
久々に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ページ参照
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/c-bug/tbping
久々に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";
}
}
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/c-bug-answer/tbping
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は発行されません)。
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/activerecord-for-join/tbping
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上でコミュニケーション(フォーラムに何か書き込んだり)をしなければいけない理由が思いつかず、招待するのはやめました。
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/ning-app/tbping
最近アリエルで流行ること、グリーンコンピューティング
正直に言うと、今までグリーンなんとかはIBMが自社の高いハードを買わせるためのいかがわしいバズワードだと思っていました。グリーンなんとかを言い出したのはここ数年ですが、高いハードを買わせるために様々な謀略を行っているのは昔からです。
今、アリエル社内ではグリーンコンピューティングが流行っています。
Windows用ですが、消費電力の見える化では、次のLocalCoolingが面白そうです。
1アプリで消費電力を下げるってどういうことだ?、と思いましたが、(実際に使っていませんが)Windowsが元々提供している低消費電力用の設定を利用しているだけのようです。
グリーンコンピューティングを徹底するなら
- X Window Systemは禁止
- エディタはvi
- Webブラウザはw3m
ってところでしょうか。
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/green/tbping
結論から言うと、ORMは使った方がいいです。SQL(SELECT文)を書くのが例え簡単でも、結果セットからオブジェクトを生成するコードを書くのは不毛だからです。SELECT文と同等のfind呼び出しを考えるのも不毛に感じますが、これは結構楽しいのです。一方、結果セットからオブジェクトを生成するコードを書くのは、不毛なだけでなく、つまらないのです。この違いは決定的です。