第10回 プログラミングについて 『後藤ちゃんの悲哀』

『後藤ちゃんの悲哀』
むかしむかし、FORTRANの村に三郎ちゃんも遠藤ちゃんもいなかったころ、いつもお使いに行かされたのは後藤ちゃんという元気な子供でした。村人は後藤ちゃんをいつも気軽に小間使いにつかっていましたが、あまりにもみんなが使っているうちに村のおじさんやおばさんは後藤ちゃんがどこへお使いに行ったのかが分らなくなってしまい困るようになりました。やがて三郎ちゃんや遠藤ちゃんが産まれ後藤ちゃんの代わりに仕事をしてくれるようなりました。三郎ちゃんはしっかりと仕事をした後ちゃんと言いつけを守って帰ってきます。遠藤ちゃんは仕事を頼んだおじさんの畑をきちんと耕し、決して他人の畑まで荒らさない子でした。二人ともしっかりした良い子でしたので、だんだんと後藤ちゃんは村人から相手にされなくなりました。そしていつの日か後藤ちゃんはみんなに嫌われるようになってしまいました。
昔話はためになる。ということが今回のテーマではなく、この村の主人公の後藤ちゃんの話しです。多分想像ができたかと思いますが、後藤ちゃんは GOTO 文のことです。そして三郎ちゃんは SUBROUTINE で、遠藤ちゃんは ENDDO。
本当にFORTRANが誕生した最初の頃はサブルーチンも関数もELSE文もENDIFもなかったのです。IF文も算術IF文といって、
if(i)100,200,300
などと表現するもので、i の値が負だったら行番号100に、0だったら行番号200に、正だったら行番号300にジャンプするという文なのです。言ってしまえば IF~GOTO文みたいなものです。DO文も
DO 100 I=1,10
100 WRITE(6,200)I
200 FORMAT(1H,I)
などと行番号が欠かせないものでした。サブルーチンなどというものがないので、GOTO文でばんばんとあっちこっちに制御をとばしていたのです。こんな風にプログラムを作っているとやがては、スパゲッティがボールの中でぐちゃぐちゃに絡み合ったような不可解なものになってしまったのです。
FORTRANは規格の改定が進に従って、サブルーチンや関数、IF~THEN~
ELSEIF~ENDIFなどが加わり、なぜかENDDOはFORTRAN77には規格としては入らなかったが、どのコンパイラも拡張機能として採用していました。このような制御文が追加された背景には、プログラムを開発しやすくするために構造化を行なうというものがあったようです。
規格の改定がいくら進んでも構造化を乱す張本人のGOTO文は残り続けました。やはりGOTO文のもっている威力を捨て去るだけの理由がなかったのです。GOTO文の威力はなんといっても現在のルーチンの中のどこにでもぶっ飛んでいけることにあります。ただこれの使い方が問題なのです。
現在のほとんどの高級言語には構造化された表現ができるようにフロー制御の命令が採用されていますので、あまりGOTO文に頼ることはありませんが、GOTO文を使用するとスッキリと表現できることもあります。私はメインのルーチンを記述するとき
によく次の様にすることがあります。
main()
{
char fname[81];
if(!input_file_name(fname))goto end;
if(!load_data(fname))goto end;
if(!sort_data())goto end;
if(!store_data(fname))goto end;
:
:
end:
exit(1);
}
もしこれをGOTO文を使わずに記述すると、
main()
{
char fname[81];
if(input_file_name(fname)){
if(load_data(fname)){
if(sort_data()){
if(store_data(fname)){
:
:
}
}
}
}
end:
exit(1);
}
と、ものすごい入れ子になってしまいます。上の例はエラー処理のときに使用するGOTO文の例です。この例ではエラーが発生したらとにかくプログラムを終了させるだけでしたのが、エラーが発生したら何か処理をしなければならないときにGOTO文を使わないで記述すると、例えば上の load_data 関数を、
load_data(fname)
char *fname;
{
FILE *fp;
char data[81];
fp=fopen(fname,"r");
if(fp==NULL){
printf("--- Error in opening file ( %s ) ---\n",fname);
return 0;
}
while(1){
fgets(data,sizeof(data),fp);
if(feof(fp)){
printf("--- EOF detected in loading ( %s ) ---\n",fname);
fclose(fp);
return 0;
}
else if(ferror(fp)){
printf("--- Error in loading data ( %s ) ---\n",fname);
fclose(fp);
return 0;
}
:
:
if(end_data(data))break;
if(!check_data(data)){
fclose(fp);
return 0;
}
:
:
if(!put_data(data)){
fclose(fp);
return 0;
}
:
:
}
fclose(fp);
return 1;
}
といった感じになってしまいます。エラーの発生する度にエラーメッセージを表示してファイルをクローズし、リターンしなければならなくなります。エラーメッセージは仕方がないとしても、似たような処理を随所で行なわなければなりません。またこのようにすると、エラーの処理ばかりが目に入って本来の処理の内容が直感できなくなってしまいます。これをGOTO文を使ってみると、
load_data(fname)
char *fname;
{
FILE *fp;
char data[81];
fp=fopen(fname,"r");
if(fp==NULL){
printf("--- Error in opening file ( %s ) ---\n",fname);
return 0;
}
while(1){
fgets(data,sizeof(data),fp);
if(feof(fp)){
printf("--- EOF detected in loading ( %s ) ---\n",fname);
goto error;
}
else if(ferror(fp)){
printf("--- Error in loading data ( %s ) ---\n",fname);
goto error;
}
:
:
if(end_data(data))break;
if(!check_data(data))goto error;
:
:
if(!put_data(data))goto error;
:
:
}
fclose(fp);
return 1;
error:
fclose(fp);
return 0;
}
とだいぶすっきりします。ファイルの入出力に関するエラーメッセージを表示するルーチンを用意しておけばもっとすっきりするでしょう。エラーが発生するとループを抜け出すようにすれば、GOTO文を使わずにすっきりと表現ができます。
load_data(fname)
char *fname;
{
FILE *fp;
char data[81];
int error_status;
fp=fopen(fname,"r");
if(fp==NULL){
printf("--- Error in opening file ( %s ) ---\n",fname);
return 0;
}
error_status=1;
while(1){
fgets(data,sizeof(data),fp);
if(feof(fp)){
printf("--- EOF detected in loading ( %s ) ---\n",fname);
break;
}
else if(ferror(fp)){
printf("--- Error in loading data ( %s ) ---\n",fname);
break;
}
:
:
if(end_data(data)){
error_status=0;
break;
}
if(!check_data(data))break;
:
:
if(!put_data(data))break;
:
:
}
fclose(fp);
if(error_status)return 0;
else return 1;
}
ともっとすっきりします。この例ではこれで十分なのですが、 goto error と記述するのと break でループを抜けるのとでプログラムを読んだときに直感的に何をしたいのかを理解できるか否の差があります。
さて、この例の while ループの中に複数のループが入れ子になっていると break で対処するのがつらくなってきます。
:
:
error_status=0;
while(1){
:
:
while(1){
while(1){
if(!put_data(data)){
error_status=1;
break;
}
:
:
}
if(error_status)break;
}
if(error_status)break;
}
fclose(fp);
if(error_status)return 0;
else return 1;
}
となってループが終了する度に error_status の内容をチェックしなければなりません。このような深く入れ子になったループの外側に脱出するときには、GOTO文は重要になります。
GOTO文をどしどし使用すべきだとは決して言いませんが、GOTO文を嫌って変なプログラムにならないように注意しなければなりません。すっきりと明確に記述ができるのであれば、なんでも使おうといった柔軟な気持ちが大切なような気がします。あまり頑固になると後藤ちゃんが泣いてしまいます。
それではまた次回。