ソースは、いくつかのファイルに分かれていた。まぁ、多少なりとも規模が大きくなれば、当然のことだ。
案の定、どれから手をつけるべきか、迷った。というのも、それぞれのファイルが、何のためのものなのか、全く判別がつかないのだ。せめて、仕様書でもあればよいのだが。
仕方がない、片っ端から見ていくか・・・。
1つ目のファイルを開いた。拡張子は.cだったから、これはきっと、Cソースだ。C++ではないだろう(普通、C++の拡張子は.cpp)。で、開いてみて
コメントがない!!!
いや、ファイルの先頭に、大まかな説明と著作権表示はあるのだが、関数や変数の説明が全くないのだ。かといって、分かりやすい変数名でもない。あちこちに、aやbといった1文字の変数名がひしめいている。はじめはカウンタかと思ったが、そうでもないらしい。私はこれを解読するのか!? い、いかん、早くも頭痛がしてきた。
いや、それ以前に、この関数の定義は何だ?
execute_main(map_main, map_sub, col_map, map_out, pat, fp, wid, hei, rate, stat) MAPDATA *map_main; MAPSUB *map_sub; MAPRGB *col_map; MAPDATA *map_out; double *pat; int rate; float *fp; int wid, hei; char stat; {
ポインタを表す*の位置も気になるし、fp
という、名前からは使用目的がわかりにくい謎の引数も気になるが、それ以上に、この書式はK&R C(カーニハン・リッチー)の書式じゃないか!
これって、10年以上前の書式だ。
これ、書かれたのはいつだ? 89年? じゃぁ、仕方ないか。って、修正日が「99年」になっているのは目の錯覚だろうか? 修正者、何をしているんだ・・・。
「10年前」。これを甘く見てはいけない。日進月歩ならぬ、秒進分歩と呼ばれる世界での話なのだ。この比喩が、仮に本当なら、「10年前」は「86万年前」に相当する(笑)。
私は考古学者じゃない。
さて、この段階で、独自の型名が出てきている。その正体を探るために、私は、先頭のほうでinclude
してあったヘッダファイルを開いた。
あまりの素晴らしさ(もちろん皮肉)に、私は目を見張った。最初の著作権表示、適宜、散りばめられた空白行。それ以外の#
で始まっていた。いや、もっと正確には、ifdef
、ifndef
、elif
、else
、endif
命令を除き、その9割以上がdefine
命令。
define
命令は、最近は嫌われる傾向にある。単純な置き換え命令なので、意図しないバグが簡単に生じてしまうからだ。この人はよほどdefine
が好きなのだろうか。私は、このプログラマにDefine Maniaという
#define MSG_Info 0 /*_F77_s: MINFO */ #define MSG_Debug 1 /*_F77_s: MDEBG */ #define MSG_Warning 2 /*_F77_s: MWARN */ #define MSG_Error 3 /*_F77_s: MERR */ #define MSG_Fatal 4 /*_F77_s: MFATL */
ま、名前の付け方は95点だ。できれば、マクロ定義は全て大文字という慣例に従って欲しかったが、枝葉末節だから気にせず行こう。が、このコメントは一体・・・?
どうやら、フォートラン77からの移植らしい。いや、それはともかく、define
中にコメントを書いてもいいのか、D.M.氏(ディファイン・メイニア氏)は、ちゃんと確認したのかな?(不安になったプログラマに一言。define
の展開とコメントの削除(正確には、スペース文字への置き換え)は、共にプリプロセッサの仕事だが、コメント処理が先に行われることになっているので、大丈夫だ。/*
〜*/
ではなく、C++の//
〜という形式を使ったときでも、コメントはそのマクロ内で完結する。)
しかし、この書式は美しくない。ANSI Cでは、ちゃんとenum
が使えるので、活用しよう。
enum { MSG_Info, /*_F77_s: MINFO */ MSG_Debug, /*_F77_s: MDEBG */ MSG_Warning, /*_F77_s: MWARN */ MSG_Error, /*_F77_s: MERR */ MSG_Fatal, /*_F77_s: MFATL */ };
こうしておけば、いざ、定数を一つ追加したときでも、番号がちゃんと、ずれてくれる。
#define MODE_A 1 #define MODE_B 2 #define MODE_C 3 #define MODE_D 4
こんな形でも問題ない。
enum { MODE_A = 1, MODE_B, MODE_C, MODE_D, };
enum
は通常、0から数字を割り当てていくが、このように特定の場所でも、数字を与えられるのだ。enum
は、K&R Cでは無かったらしい(噂に聞いた)ので、まぁ、define
の嵐も致し方ないが。
次の命令は、なかなか独創的だった。
#define FLAG1_N 0 #define FLAG1_A 1 #define FLAG1_B 2 #define FLAG1_C 4 #define FLAG1_D 8 #define FLAG1_E 0x10
・・・。フラグだから、組み合わせて使うのだろう。でも、フラグなら、普通は16進数か8進数で書くところ。10進数だと、桁が増えたときに見づらくなってしまうからだ。現に私は、何か、フラグ以外の特殊な用途に使うものかと、一瞬、勘違いした。
案の定、いきなり16進数が出てきている。何とも言えずこの最後の1行、無計画性を示唆しているようじゃないか! まるで、この、一貫性のない増改築しすぎのプログラムの縮図だ。そうは思わないかい? ちなみに、他の部分で、
#define FLAG2_E 16 #define FLAG2_F 32 #define FLAG2_G 64 #define FLAG2_H 128
などという部分も発見。D.M.氏は、16進数に恨みでもあるのだろうか。
しかし、ここまでdefine
が多いと、うんざりしてくる。
#define WIDTH_MAX 512
const
変数に直しておけよ・・・。
#define TYPE_X 1
enum
はどうした・・・。
#define NUM_COUNT int
またdefine
か・・・。define
は別の形で書き・・・
・・・って、
ちょっと待てーーー!
あああああ!!! 型名をdefine
で定義しちゃってるよ、この人!
放っておけば、#define P_INT int *
なんてのも、平気で出てきそうな気がする・・・!(え? このマクロの恐ろしさが分からない? それじゃ、P_INT x, y;
と宣言してみよう。x
はint*
型で、y
はint
型になるのだ。)
こいつはtypedef
を知らんのかッ!
まさに、背筋が凍る思いとはこのことだ。型名の再定義はtypedef
。これは基本だよ・・・。
しばらく読み進むと、このファイルは終わった。次は、先頭でinclude
してあった別のヘッダファイルを開こう。
どうやらこっちは、グローバル変数と関数のプロトタイプ宣言のヘッダファイルらしい。が、画面をスクロールさせてすぐ、変な文章を発見した。
union { unsigned char *p_char_u; float *p_float_u; double *p_double_u; int *p_int_u; short *p_short_u; } p_union;
何コレ???
実際の使い方を見てみると、unsigned char
型ポインタをp_union.p_char_u
に放り込み、別の型のポインタとして取り出していた。
・・・何ソレ(怒)!
Cコンパイラは、ポインタの型チェックに厳しい。違う型へのポインタの代入は、エラーになるのだ。が、これはしばしば、必要になる型変換なので、Cにはちゃんと、抜け道が用意してある。こんなの、共用体を使わなくとも、キャストで充分だ。何が哀しくて、こんな共用体を、わざわざ・・・。私も、共用体をこんな用途に使っているのは、初めてお目にかかった。カーニハン博士、リッチー博士が見たら、卒倒するぞ、多分。それに、そんな一時利用の変数は、グローバルにするんじゃない。他の開発者と名前が衝突したら、どうするんだ。
しかもこの共用体、タグ名を書いていない。書いておいて損はないので、普通は書くものだ。_tag???という名前に統一しておけば、他の変数名とタグ名が衝突するという心配もあるまい。個人的には不要の共用体だが、いざ、もう一つ使うときに、タグはあると便利だ。
その後、こんな文章を見つけた。
union { unsigned char *p_char_u; float *p_float_u; double *p_double_u; int *p_int_u; short *p_short_u; } p_union_2; union { unsigned char *p_char_u; float *p_float_u; double *p_double_u; int *p_int_u; short *p_short_u; } p_union_3;
ほ〜ら、やっぱり必要になってる。D.M.さん、きっと、タグの存在を知らなかったんだな。まぁ、キャストも知らないんじゃ、無理ないか。
そして次を見た。先ほどもツッコんだが、この共用体をグローバルにしていた理由が分かった。
#define p_u_char p_union.p_char_u #define p_u_float p_union.p_float_u #define p_u_double p_union.p_double_u #define p_u_int p_union.p_int_u #define p_u_short p_union.p_short_u
もちろん、p_union_2
とp_union_3
の分も、あった。
そんなに . が嫌いかい?
無駄な構造体を15行。無駄なdefine
を15行。まったく、情けないったらありゃしない。
次は関数のプロトタイプ宣言だ。
execute_main( ... );
ん〜? 何か、見たことがある関数名だな・・・。そうそう、最初に見た関数だ。
・・・コラ。引数はドコに消えた!? プロトタイプ宣言が何のためにあるのか、分かっているのか、コイツは・・・。こんなことだと、随所に見受けられるfunction( int arg1, ... );
というプロトタイプ宣言、まさかとは思うが、こいつらも引数書くのが面倒だから省略したんじゃないよな? ましてや、コンパイラを黙らせるために省略したんじゃないよな!?(引数の...
は、printf
関数みたいに、引数の個数が不定の時に使う。決して、書くのが面倒な人のための救済措置ではないし、コンパイラの出すエラーを止めるための回避手段でもない。)
このソース、コンパイルしてまともに動くのが不思議だ。
これを手直しするなんて、正気の沙汰じゃない。他にも、K&R CとANSI Cでは、引数の型の格上げ問題もあり、混在させるのは危険だ。今までの人が、手をつけられなかった理由が、よく分かった。ここまでいい加減な書き方をされたのでは、ソースを修正できない。安心してANSI Cにも移行できないし、このソースはずっとK&R Cで行くしかないのだ。
こんなソースは、人目につかぬよう、会社の恥として厳重に封印すべき。
世の中、探せば色々なものがある。しかし、これはなかなかの破壊力だった。こんなソースでも、動けばお金が貰えるなんて、世の中、何かがおかしいよね。
最後に、
struct line_3d { int sx; int sy; int sz; int dx; int dy; int dz; int idx; int idy; int idz; };
という構造体が定義されていた、ということだけ記しておこう。
おお、ちゃんとタグを使っている!
え? この11行がどこに書かれていたのかって? それはね、7つあったCソースファイルのうち、4つのファイルに重ねて書かれていたのさ。
ねぇD.M.さん・・・。あなたにとって、ヘッダファイルの存在意義って、何ですか?