最近、仕事でとあるプログラムのソースコードを読んでいたのだが、
どうにもおかしい気がしたので詳しく解析していたら、なんとgoto文が使われていた。
それもなかなかあくどい使い方である。
goto文については多重ループを抜けるために限定的に使うような話も聞く。
for(i=0;i<loop;i++){
for(j=0;j<loop;j++){
if(condition){
goto LOOP_END;
}
}
}
LOOP_END:
ループを途中で抜ける場合、break文で抜けられるが、
ループを2つ抜けることを単純に書けないのでgotoを使うことがあると。
ただ、僕自身はこんな書き方したことはないし、今までの仕事でも見ることはなかったが。
まずあったタイプは終了処理へジャンプさせるもの。
while(1){
if(flag1){
goto CMD_END:
}
if(flag2){
goto CMD_END;
}
switch(mode){
case MODE1:
break;
case MODE2;
break;
}
CMD_END:
close1();
}
こういう書き方は比較的ある方かもしれないが。
末尾にある終了処理にいろんなところから飛んで行くと。
実際のところ、関数途中でreturn文が入る書き方もこの手のgotoの使い方に類似する面がある。
ただ、whileループの末尾に飛ばすというのが結構あくどい気がする。
次にswitch文の中で使われたgoto文。
switch(mode){
case MODE1:
exec1();
goto MODE1to3;
case MODE2:
exec2();
goto MODE1to3;
case MODE3:
exec3();
MODE1to3:
exec4();
break;
case MODE4:
break;
}
mode=MODE1,MODE2,MODE3で共通的な処理があるからと、goto文を使って書いていると。
switch文自体がクセの強い構文だと思うんだけど……
caseごとにbreak;で終わるのが基本ではあるけど、複数ラベル束ねる場合は、
switch(mode){
case MODE1:
case MODE1:
case MODE1:
exec4();
break;
}
のような書き方をすることが一般的に認められている。
ただ、この自由度の高さを逆手に取ってか、変な書き方をする人がいると。
気持ちはわかるけど、そんな変則的な書き方をするのはいかんだろうと。
後ろに飛ぶのではなく前に飛ぶパターンもあった。
POWERON:
exec1();
if(status==POWERON){
goto POWERON;
}
exec2();
if(status==POWERON){
goto POWERON;
}
while(1){
exec3();
if(status==POWERON){
goto POWERON;
}
}
フラグを検出したら初期化処理に戻るって感じの話。
関数の外でなんか処理する方法を考えるべきだと思うのだが……
そして問題はこの関数というのが1000行を超えるような巨大なものであること。
というわけで解析ツールでフローチャートを出力させて、
そのフローチャートを大まかにくくってプログラムのフローを示す図を作った。
goto文を使っていても、if文とelse文の間が数百行と離れていても、
大まかなフローとプログラムの行数との関連が付けば理解はしやすくなる。
ある程度のまとまりで関数としてくくり出されていればこんなことには……
それはswitch文の中のgoto文を見てもわかるけど、
あの例だったらこういう書き方でもいいんですよね。
switch(mode){
case MODE1:
exec1();
exec4();
break;
case MODE2:
exec2();
exec4();
break;
case MODE3:
exec3();
exec4();
break;
問題はexec4();にあたる部分が100行ぐらいあることであり。
今までの仕事で触っていたプログラムはちゃんと構造化プログラミングしてたなぁと。
ループで使われるbreak文、continue文、あるいはプログラム途中でのreturn文は厳密に構造化プログラミングではないのだけど。
ただ、これらはむしろ理解しやすくするものと一般的には理解されているのだし。