ストリートファイター4をちょいとやってきたが、結局これもマニア向けかなあ。スーパーコンボとウルトラコンボをキャラ一人につき一つずつ割り当てて、それぞれ「攻めた勢いで使う技」「一発逆転を狙う技」と役割を持たせてやることをはっきりさせたのはいい。でもそれでもまだマニア向けに凝りすぎな部分がある。
セービングアタックまわりの仕様なんてのは際たるもので、これビギナーが一朝一夕に理解できる代物じゃないだろ。乱入抑制のビギナーモードがあるといっても限界があるし、何ていうか複雑化してドツボというマニア向けゲームのスパイラルからは抜け出せてないっぽい。それでもギルティギアとかよりはだいぶ敷居が低いとは思うが。
会社で新人とかに教えていて困る質問というのは、やはり「オブジェクト指向って何ですか?」といった類の質問で、実のところこれは未だにどう答えていいかわからない。ぶっちゃけてしまえばオブジェクト指向プログラミングというのは構造化プログラミング同様にプログラミング手法の一つで、構造化プログラミング言語を用いた方が構造化プログラミングを行いやすいのと同様に、オブジェクト指向プログラミング言語を用いた方がオブジェクト指向プログラミングは行いやすく……という話を新人にできるかヴォケ!!
そもそもこういったプログラミング手法というのは基本的にソフトウェアの開発・保守の効率を向上させるためのもので、そのご利益を体感するにはある程度の規模のコードを書く必要があるのだけど、研修で書くコードなんぞたかの知れた規模でしかない。
構造化プログラミングというのはおもくそ簡単に説明してしまえば恣意的な制御のジャンプを抑制するもので、誤解を恐れずに書いてしまえば goto 文を飼い慣らすためのものだ。なので、近代的な言語は break, continue, return といった形での飼い慣らされた goto だけを採用する事が多い。例外処理は goto というよりは longjmp じゃねえのかとも思うが、これも飼い慣らされたジャンプには変わりない。
それじゃあオブジェクト指向プログラミングはどうなのかと言われると、やはり誤解を恐れずに書けば関数ポインタテーブルへのシンタックスシュガーだ。そもそも C 言語の頃から関数ポインタを用いた多態と詳細の隠蔽ってのはできたわけで、クラスベースにしろプロトタイプベースにしろ多態性ってのは本質的に関数ポインタだと思ってる(クロージャもプラスアルファで入るか?)。継承だのカプセル化だのといった枝葉末節は極めてどうでもいいし、どちらにしろ C などのレベルで実現不能ではない。ただ非構造化プログラミング言語で構造化プログラミングを行うのにそれなりのコストがかかるのと同様に、非オブジェクト指向プログラミング言語ではオブジェクト指向プログラミングにコストがかかるってだけ。
じゃあどのぐらいコストがかかるのかというと、試しに C 言語でオブジェクト指向プログラミングをやってみればわかる。今回はハッシュテーブルを利用してプロトタイプベースの実装を睨んだコードにしたが、まあ構造体を使っても面倒な事には変わりない。ソースは以下の通り。
#include<stdio.h>
#include<malloc.h>
/*****************
* ハッシュ関数など *
* 定数値は適当 *
* ***************/
const int HASHSIZE = 3000;
const unsigned int HASHSEED = 17;
unsigned int hash(char *str) {
unsigned int h = 0;
unsigned char *c;
for (c = (unsigned char *)str; *c != '\0'; c++) {
h = HASHSEED * h + *c;
}
return h % HASHSIZE;
}
/**********************
* オブジェクトの要素 *
* ********************/
typedef struct HashVal {
char *key;
void *obj;
} HashVal;
#define Object HashVal*
#define Attr HashVal*
/************************
* オブジェクトの初期化 *
************************/
Object initialize(void) {
Object obj = (Object) malloc(sizeof(HashVal) * HASHSIZE);
return obj;
}
/****************
* メンバの取得 *
* **************/
void* getAttr(Object obj, char *name) {
int h = hash(name);
Attr attr = &obj[h];
if (attr != NULL) {
if (strcmp(attr->key, name) != 0) {
return NULL;
}
return attr->obj;
} else {
return NULL;
}
}
/****************
* メンバの割当 *
* **************/
void setAttr(Object obj, char *name, void* attr) {
int h = hash(name);
Attr newattr = (Attr) malloc(sizeof(HashVal));
newattr->key= name;
newattr->obj = attr;
obj[h] = *newattr;
}
int foo(Object obj) {
puts("foo");
return 1;
}
int bar(Object obj) {
puts("bar");
return 1;
}
int main(void) {
Object obj;
obj = initialize();
setAttr(obj, "foo", &foo);
printf("%d\n", ((int (*)(Object)) getAttr(obj, "foo"))(obj));
setAttr(obj, "foo", &bar);
printf("%d\n", ((int (*)(Object)) getAttr(obj, "foo"))(obj));
return 0;
}
メモリの解放もエラー処理もやってねえし、ハッシュの衝突とかあんま考えてねえし、実はこれでも全力で手を抜いたのだ。それでこれなんだから、本格的にやろうと思ったらオブジェクト指向プログラミングのためのコードだけでもデバッグが大変だろう。さらに上記のコードは splint などの静的解析ツールにかけると警告の山になるので、そもそもコードの品質は良いとは言えない。それを直すのは面倒なんでそのままにしちゃったけど、本来はこれでよしとしちゃダメなレベルだ。つまり決して不可能ではないが、 C 言語のような言語ではオブジェクト指向プログラミングは超面倒ということ。
似たような事を Io で書くとこうなる。
obj := Object clone
obj foo := method("foo" println; return 1;)
obj foo println
obj foo = method("bar" println; return 1;)
obj foo println
あんまりナイーブに比較していいものではないけど、オブジェクト指向プログラミングのエミュレートは大変ってことはわかると思う。
なんか話がえらいことずれて行った気がするけど、とにかくオブジェクト指向プログラミングを理解させるにはこうしたプログラミング手法の進歩の話をせざるを得ず、研修中にちょこちょこと話して理解させることのできるような代物じゃないわけだ。流石に俺は 21 世紀になって「動物クラスを継承してワンワンニャーニャー」なんてごまかしでお茶を濁せるような恥知らずじゃねえし。
俺としては関数プログラミングの概念も教える必要があると思っていて、というのもクラスベースのオブジェクト指向プログラミング言語はプロトタイプベースのそれの特殊ケースであり、プロトタイプベースは関数型言語で大抵はエミュレートできる、というか関数型言語のサブセットに近いと思ってる。それと契約プログラミングとインターフェースの関係とか、オブジェクト指向プログラミングの周辺知識って意外と多いのよ。
そしてここら辺をちゃんと学んでる職業プログラマというのは今の会社ではほぼ皆無で(60人中3人もいないと思う)、実際には新人に教える前に他の社員がダメダメなのだ。というわけで他の社員にもこの辺の知識は是非とも学んでほしいというか、あまりに知識・スキルに差があると議論にならねえんだよクソッタレ。少なくとも今日書いた内容はたいしたものじゃねえんだけどなあ。
追記:よくよく見たら const を付けるべきところに付いてねえとか、最初に書き上げた時に思ったよりもツッコミどころ満載だなあ。まあいいか、手抜きだって書いてるし。
この怪文書はクリエイティブ・コモンズ・ライセンスの元でライセンスされています。引用した文章など Kuwata Chikara に著作権のないものについては、それらの著作権保持者に帰属します。