2009年02月06日

変数の初期化ミス

昨年末にリリースしたドールズイングラムですが、ユーザーからの報告で時々上下スクロールしなくなるというバグが発生していました。原因を調査しようにもウチのデバック環境ではまったく再現しなかったのでほとほと困り果てていたのですが、このたびやっと原因が分かって1.02の修正パッチを出すことができました。

そこで、反省と今後の対処も踏まえて原因を振り返ってみます。

問題の箇所はスクロールの状態を示すメンバ変数でした。これを設計した段階ではコンストラクタ時に必ずA,B,Cのいずれかの状態に設定され問題なく動作していたのですが、後の機能拡張時に初期化処理をコンストラクタではなく、専用の初期化関数のなかでやるように設計を変更したのです。こうすることによってスタート時や再スタート時、コンティニュー時等どの開始時にも同じ箇所で初期化できるのでコード的には良いリファクタリングだったのですが……。
その代りに内部で条件分岐しながら初期化処理を行わなければならないという複雑さがプラスされてしまったのです。

ちなみにC言語は未初期化の変数の中身は何が入っているか分からないというのが仕様なので必ず自分で初期化するのが常識です。しかしそのための初期化関数がいろんな箇所から呼ばれる場合、例えば偶然にもたまたま「初期済み」という状態がその変数に入っていたとしたら……。

はい、今回のバグの原因はその偶然でした。

それでもちゃんとリファクタリング時にチェックして正常に処理することは確認していたのですが、そのテストがデバックモードだったので本番のリリースモードでのコンパイルとは少し条件が違ってしまったのです。
再現性が低いのとデバッガー使用時にはまったく再現しなかったので原因を見つけるのが困難でした。

このバグの対処方法は、コンストラクタ初期化子で「未初期化」という状態を入れておくことで解決しました。今後この手のバグが起きないようにするには、すべてのメンバ変数を必ずコンストラクタ初期化子で一回初期化しておくという方法で解決できます。
しかし、これだと二回初期化することになってしまい、コードも2箇所に書かなければいけないというのはエレガントではない気もします。
でも、次期C++仕様(C++0x)ではメンバ変数の初期化を
int state = 0;
のように宣言時に書けるのでこれならば、スマートのまま今回のようなバグを未然に防ぐことができそうです。

つか早くC++0x実装のコンパイルでコードを書きたい……。
posted by 鶴 at 05:29| Comment(0) | TrackBack(0) | プログラム系

2008年11月09日

c++でメッセージ送信

ゲーム中に発生するイベントを、発生した場所から処理するオブジェクトへ伝えるために、c++では基本的にメンバ関数を使う。
だが、単純な構造の時は全然問題ないのだが、システムが複雑になってくると自機やステージみたいな重要なオブジェクトへのアクセス種類が増えてしまい、その結果インターフェースとしてのメンバ関数も増えてしまうことが良くある。
設計の教科書だとこういう場合は、ひとつのオブジェクトに処理を手中させずに処理ごとにクラスを分けるべきと書いてあるかもしれない。ただ素人じゃないので簡単に分割できるような処理だったら最初から別クラスで設計してあるだろう。そうでないものを無理やり分けてしまうとオブジェクト間の依存率が高いものになってしまい逆に全体の複雑さが増してしまう。そういう問題を解決するためのデザインパターンは……

っていうお堅い話じゃなくて、メンバ関数が多いなら入り口を一つにまとめちゃえば?ってのが今回思いついたアイディア。
もうイベント毎に新しいメンバ関数を追加するのは懲り懲りにゃ、基底にメッセージを受け取るインターフェースを一つ作っておくから今後はそこにお願い。

class Object {
public:
virtual void message(string s){
throw runtime_error("message失敗");
}
};


こんな感じ?後はメッセージの内容を継承先で動的に解析して処理しちゃえばいいのです。今時のC++は正規表現も使えるし……と思ったけどせめて解析を楽にするために関数名と引数ぐらいは分けておくか(あと引数を参照に)
virtual void message(const string& function, const vector<string>& arguments){...

なんかすべてのメッセージをこれで処理できそうな気がしてきた。それってどこかで見たことあるような……Smalltalkみたいなオブジェクト指向の原点に近いのか?

とは言え、さすがにすべてをこれで処理しちゃうと何がなんだか分からなくなりそうなので、ゲーム中におきるイベントや通信系イベント、Windowsメッセージみたいに用途を決めて使えば多少はコードがスッキリしないだろうか?と願いつつ、成功するのか微妙な結果に終わるのかはまた今度。
posted by 鶴 at 04:53| Comment(0) | TrackBack(0) | プログラム系

2007年05月16日

C++のforeachが混乱中?

先日Boost 1.34.0がリリースされました。
1.33が2005年の夏だったから久しぶりな気がします。

私的に今回の目玉と言えばTR1でしょう。http://ja.wikipedia.org/wiki/Technical_Report_1

今までTR1を完全に実装しているのがDinkumwareだけだったので、1.34のTR1実装が楽しみです。今までboost::shared_ptrだったのが晴れてstd::tr1::shared_ptrと書けるようになります。
って余計名前長くなってないか?将来的にはstd::shared_ptrになるのか?
まあ名前はともかく正式に「標準」を名乗れるのは良いことです。

1.34でもう一つ気になるのはNew LibrariesのBOOST_FOREACHとやらです。
まず名前が怪しい。全部大文字だからマクロ?と思ったらやっぽりマクロ。そもそもテンプレート&ジェネリックプログラミングの最先端を行っているBoostで何故マクロ?あと名前空間も無視してBOOST_なんて名前だし。
それくらい便利でありリスクもあるということを名前で啓示しているのかも。

そもそも問題の根本は、他の言語には標準でついているforeachがC++に実装されていないということにあるのかもしれません。
STLのアルゴリズムを使えばfor_eachは使えるのですが、実際に使うには関数やファンクタを使わなければ意味がなく、そしてややこしいです。

// 敵を全て描画
std::for_each( v.begin(), v.end(), std::mem_fun_ref(Enemy::draw) );

mem_fun_refという名前がまた読みにくく理解しにくいです。この辺りの歴史的経緯はEffective STLの41項に書いてありますが、実に建て増し住宅的に実装されてきたのだなと実感します。そして引数が使えなかったりと使い勝手も悪かったりします。
そこで登場したのがboost::mem_fnです。これは今回のTR1に含まれているので、こっちが標準化されればstd::mem_fun_refの存在価値がなくなる気もするのですがどうでしょう?

posted by 鶴 at 20:42| Comment(0) | TrackBack(0) | プログラム系

2006年11月25日

美人秘書Rubyの憂鬱

最近Rubyにはまっている。
最初は前任者から引き継いだ仕事のために渋々やり始めた言語なのだが、
使いこなせるようになると、どうしても他の言語の醜さにウンザリし、Rubyのコードを眺めてはウットリする変質者な日々をすごしている。

第一にRubyで書かれたソースは美しい。一遍の無駄もないそのコードは芸術品とさえ思えてしまう。
正規表現やハッシュも手足のように使えて、その他のライブラリも豊富だ。
例えるなら国際派で語学堪能、教養も豊富で護身術も使える有能な美人秘書のような言語なのだが

そんな彼女にも苦手なものがあった。
それはUnix出身の彼女がWindowsとかMicrosoft Officeやらの仕事が苦手なことだ。


「ちょっとルビ子君、cgiから顧客データ読み込んでよ」
Ruby「お安い御用です、出力先はどこにしますか?」
「ああ、じゃあExcelワークシートにまとめといて、グラフ化もお願い」
Ruby「えっエクセル?!、ちょっとお待ちください、comオブジェクトをリンクして、いやその前にコンポーネントに対応したProgIDを探して……」
「なんだか大変そうだね、じゃあその仕事はVBAオバちゃんに回すよ」
VisualBasic for Applications(通称Excelマクロ)「あいよっ!まかせときんしゃい!」
Ruby「申し訳ありません、他にお仕事はありますか?」
「ああ、じゃあそのWAVファイルの再生時間を調べておいて」
Ruby「うぇっ!WAVの再生時間ですか?えーっとヘッダ部分を読んでフレーム数をサンプリングレートで割って……」
「ちょっ!再生時間見たいだけなのに、なにも新しいモジュール作らなくても」
Ruby「ではMCIコントロールを呼び出して調べさせます。えーっとWin32APIをコールしてmciコマンドを生成、って戻り値はどうしよう……」
「る、ルビ子君?もういいから。それもオバチャンに任せるよ」
WindowsScriptHost(VBScript)「あいよっ。ついでに再生もするかい?フンフンフ〜ン♪」

未だにVB系オバチャンが最強である
posted by 鶴 at 05:47| Comment(0) | TrackBack(0) | プログラム系

2006年09月08日

知識の値段

books.jpg
ビックカメラの書籍コーナーに立ち寄った時ふと、そういえば正規表現についての本が一冊欲しいと思っていたことを思い出した。
何年もやってる割にいまだに苦手意識をもつ分野だからである。
しかしリファレンスレベルの本ではなく、かといって入門書でもないとなると選ぶ本は自然と限られてしまう。
オライリーの詳説正規表現 第2版 5,400円。高けぇ、だが自分の知となり技となるならばケチるわけにはいかない。
ついでにRubyの本も一冊欲しいとあれこれしてるウチに1万近くなってしまった。買おうか買うまいか30分近く悩む。
だが最後には

「高いお金を出したからには元を取ろうと必死に勉強するに違いない」

とマジメなのかダメ人間なのかわからない結論でレジに持っていくことにした。

そしてレジで1万円をだそうとした時。
カランカランカラン「おめでとうございます。レシートに当たりが出ましたので無料になります」

え?!30分の覚悟は一体……。
posted by 鶴 at 00:00| Comment(3) | TrackBack(0) | プログラム系

2006年08月27日

仕事はスタック?キュー?

昔、同僚が「仕事のスタックが溜まってきた」と言ったことがある。
私はその時「スタックだと最初に受けた仕事が何時までも片付かない、この場合キューの方が正しいのでは?」と返した。

スタックとはFIFO(First In, First Out)と言い、洗わなければいけない皿がどんどん上に積まれていくのを上から順に片付けていくイメージである。

一方キューはFILO(First In, Last Out)と言い待ち行列理論で使われる。冷蔵庫に入れた食べ物を古いものから食べていくイメージだ。

溜まった仕事を片付けるのに理想はキューだと、最初は思っていた。
だが、実際は時間が無いので緊急度が高いものや簡単なものから片付けていくことが多い。
つまりそういうジョブはやらなければいけないリストに載ったらすぐ実行されるので構造的にはFIFOになってしまう。

結局、優先度が低く難しかったり面倒なジョブがどんどんスタックの地層に溜まっていく。みなさんはどうでしょう?積みゲーとか積み小説をためたりしてませんか?

結論
仕事はスタックである
posted by 鶴 at 06:07| Comment(0) | TrackBack(0) | プログラム系