<< 豆知識 >>



 

エラーへの対処法


   プログラミングにはエラーがつき物です。どんなプログラマーだって、一度もエラーをしたことは絶対ないし、また全然エラーをしなくなることだってきっとないと思います。その数は少なくなったって、どうしたってエラーとはうまくお付き合いしていかなければなりません。
 エラーはプログラマのミスによって起こります。特にコンパイル時のエラーはそうです。実行時のエラーにはコンピュータが原因で起こるものがあるかもしれませんが、コンパイル時は文法の間違いで引き起こるものがほとんどです。全てがプログラマが原因のエラーであるわけではありませんけどね。例えばFortran77のコンパイラにFortran90の文法を持ち込めばエラーになってしまいますから、バージョンをしっかり意識していなかったプログラマが悪いといえば悪いのですが、コンパイラが古いのが問題です。ともかく、どうやったらエラーをなくしていけるでしょうか。あるいは少なくしていけるでしょうか。完璧な方法があれば当然いいのですが、どうしても最後はプログラマ次第になってしまいます。それでも少なくする心構え、ガイドラインは存在しますから、それを実践していきましょう。もちろんFortranの話ですが。
 まずどうやってソースコードを作成していったらいいかの話です。Fortranのサイトは数少ないながらもそれなりにあるので、それらを見て回ったところ、いくつか共通する見解がありましたので、それを紹介したいと思います。僕の経験も踏まえてお話します。
 まず、自由形式で書きましょう。Fortranには77以前の固定形式と、90以降の自由形式とにプログラミングスタイルが分かれますが、77の方法は忘れてください。固定形式は非常に使いづらいです(77のコンパイラを使用している人には無理な話ですが)。固定形式の特徴は、一行にかけるのが72欄までで、始めの5欄までは文番号用、6欄は文の折り返しを示す記号用として決められていて、実際に文を書き始めることができるのは7欄からです。実質66個しか文字を書くことができません。プログラムによっては問題ないですが、いささか少なすぎます。そして書ききれなかったら折り返すのですが、折り返すときには6欄にその目印の記号を書かなければなりません。タブを使ってインデントしている場合に、この処理はとても嫌なものに感じてきます。なぜなら、折り返す前のインデントの位置に継続文を移動させると、折り返し記号「+」との間に空白が生じてしまい、折り返す前の文との間に空白が入り込んでしまうからです。このように、「固定」形式というだけあって、その使い勝手はかなり制限の加えられたものであるということができます。
 自由形式にすれば、「欄」という概念がなくなり、1行132列まで書くことが可能になります。要は固定形式の2倍の文量を一行に書くことが可能となるわけです。さらにどの位置から書き始めることも可能になり、折り返し記号「&」もどの位置に書いても構わなくなったので、自由なソース作成が実現します。コメントも固定形式では1行丸まる使用しなければならなかったのが、命令文の右側に付けることができるようになるので、ソース文を邪魔することなく、横側に添える形で説明を加えることが可能になります。セミコロンを挟めば一行に何個でも命令文を圧縮することが可能なのも便利です。
 次に挙げておきたいのが、文番号の使用をやめようということです。文番号はGOTO文やFORMAT文で使用される概念ですが、これがエラーの元になることが多いようです。文番号を使用する場合は、今どの文番号まで使用していたのかを確認する作業が面倒くさく、もし間違えて既に番っている番号を使用してしまえばエラーになってしまい、間違った位置に番号をつけてしまえば実行エラーの原因探しに一苦労となってしまいます。FORMATぐらいでしたら、WRITE文やPRINT文の下にあるからいいでしょうが、GOTO文の場合はどこに向かって飛んでいるのかが分かりにくいですし、飛び先の番号の位置があっているのかが分かりにくい場合があります。また文番号として確認したうえで20番を書こうとしていたのに、勘違いで10番と書いてしまうこともありますので、文番号の使用はやめたほうがいいです。幸いFortranも文番号なしで十分プログラムがかけるぐらいに成長しています。以前は条件によりループを途中で抜け出したいときはGOTO文しかありませんでしたが、今はEXIT文があり、DO文にループ名をつけておけば、多重ループから一気に抜ける事だって可能です。CONTINUEも同様です。ENDDO文があるのですから、不要で面倒くさい文番号の必要なCONTINUE文はやめましょう。入れ子が多すぎで、このENDDO文はどのDO文と対をなしているのか分からないようであれば、コメントをつけておくとか、DO構文名を書いておけば大丈夫です。FORMAT文も出力文に省略して書くことで、文番号なしで書式付出力を行なうこともできるようになっているので、文番号がなくても十分プログラムは組めます。ダイクストラの頃から批判の多かった文番号は、使うのをやめましょう。
 Fortran特有の機能である暗黙の型宣言がありますね。これは使用すべきではありません。確かに便利な面はあります。例えばカウンタとして僕はiやjをよく使用しています。これらは一文字ですから、打ち間違えしないので、暗黙の型宣言の不利益を被ることなく、わざわざintegerで型宣言をすることなく使用できます。このスムーズなソース作成を支援するのが暗黙の型宣言です。しかし、暗黙の型宣言は変数名の打ち間違いを、新しい変数の作成と誤認してしまい、コンパイラがエラーを発見しません。最初「ia」と使っていて、次に「la」と書き間違えてしまっても、コンパイルが気付いてくれないので、明らかに意図しない実行結果を得ることになってしまいます。また「kazu」は整数型であるにもかかわらず、意識せず実数型の変数と用いてしまうかもしれません。僕のコンパイラは入力後は引用、宣言後は入力しないとコンパイルエラーに引っ掛かるのである程度はコンパイル時に気付くことができますが、なるべくこうしたつまらないエラーは起こさないようにするために、暗黙の型宣言は封印するべきです。封印の仕方は知っていますね。IMPLICIT文を用います。プログラム単位の先頭に「implicit none」と書けば、暗黙の型宣言は使用されず、常に明示的な型宣言が必要になります。変数を用意する際に必ずプログラム単位の先頭に戻って変数を宣言しなければならず、手間は増えてしまいますが、その代わりエラーに悩まされる数はかなり少なくなります。
 副プログラムを使用するのであれば、INTENT文を使用しましょう。Fortran90では副プログラムのエラー排除のための手段として、INTENT文が追加されました。この文を副プログラムの仮引数の宣言文の際に用いれば、副プログラムへの入力用なのか、出力用なのか、その両方なのかをハッキリとさせることが可能です。仮引数が一つだけで、入力兼出力なのであれば、この機能を利用したほうがいいと思います。INTENT文を使用すれば、コンパイラに対してその引数の役割を明確に伝えることができ、ソース文を読む人間にとっても引数の役割がわかりやすくなり、可読性の向上につながります。
 字下げもしっかり行ないましょう。やはり人間は視覚的要素を非常に重要視します。人のよさを見るときに、一番重要視するのは見た目ですよね。いい女かどうかを見分けるとき、大抵の人は顔を見ると思います。それと同じで、字下げをすることによって縦方向にそろっているプログラム文は、見た目がかっこよくなるだけでなく、上と下のつながりが非常に分かりやすくなります。全く字下げをしない文章とした文章をくらべてみれば、その差は歴然であると思います。字下げをする場所は、各プログラム単位のないよう部分、DO構文内、IF構文内などです。とくにDOとIFで字下げをしないと、大変なことになります。
 後は、コメントを付けておきましょう。コメントは「!」をつければ、ソース文の横に書くことが可能です。いくら構造化プログラミングを意識したり、字下げをしたとして分かりやすいプログラムを書いたとしても、プログラムはやはり分かりにくいものです。他人のであれば特にそうです。自分のためにも他人のためにも、必要最低限のコメントは付けておきましょう。簡単なもので構いません。この位置から何々が始まるとか、この副プログラムは何々をするものだ、ということを書くだけでも、見返したときにグンと理解しやすくなります。
 続いて、実際にプログラミングをしたときのどんなエラーが出て、どういう風に対処したらいいのかを紹介したいと思います。人によってエラーの数は違いますし、下のだけではありませんが、したのを意識するだけでもエラー処理がかなり楽になるのではないのかと思います。

  • スペルミス・・・これは結構多いと思います。命令文の書き間違いや、変数名の打ち間違いです。変数名に関しては前述の通り、暗黙の型宣言を封印することによって、簡単に間違いを発見することができます。定義されていない変数はエラーになりますから。命令文の書き間違いは、エディタの強調表示機能がいいと思います。このホームページで紹介しているfcpadには高機能な強調表示機能が付いていますし、最近のエディタにはプログラムの開発サポートとしても性質も持ち合わせているものが多いので、おそらくオプションの中に強調表示の欄があると思います(でもFortranだと無理かな? 秀丸エディタならその機能の追加ができたはず)。それを使えば、命令文の書き間違いをしてしまうと、太字+色付き表示されないので、書いてる時点で間違いを発見できて、非常に便利です。見やすくもなるのでお勧めです。
  • 72文字を超えてる・・・ここでは自由形式を進めましたが、コンパイラのせいで固定形式しか無理な人もいるかと思います。そういう人は、この点について注意してください。fcpadのような、73文字になると赤字になるという機能がないと、一行の限界の72文字を超えてしまっても気付かないことが多いです。そうするとそれ以降の部分はコンピュータには認識されず、そこが入力や分岐などその下の文にも影響を与える場合、72文字の制限がなければ起きるはずがないエラーが次々と起きてしまう(10か20は増えます)ので、エラーが出たら、まずは72文字を越えている箇所がないかを確認してみてください。これを直すだけでコンパイルエラーがなくなってしまうことすらあります。
  • IF構文のエラー・・・僕は結構このエラーをやってしまいます。このエラーで多いのは、ブロックIFの形なのにIFの横にTHENが書かれていないとか、IFの下にELSEやENDIFが書き忘れているとかです。とくにC言語をFortranと一緒に使用している人は、C言語ではTHENを使用しないでIF文が成立するので、勘違いして書き忘れてしまうことがあるのではないかと思います。僕はよく忘れます。また中括弧がないので、終わりを示すENDIFも書かなければなりませんし、例えELSEの下に命令文を書かなくてもブロックIFならELSEが必要なので、これらも忘れずに書かないといけません。これを忘れると、恐ろしいほどのエラーが出ます。びびらずに、IFの行がエラーになっているのなら、まずはこの点をチェックしてみてください。またそういうエラーをしてしまう人は、、IF文を書いたら、すぐにELSEとENDIFも書くように心がけてください。
  • IF文の演算子がおかしい・・・IF文の演算子とは、「==」の関係演算子や、「.and.」の論理演算子のことです。これを書き間違えてエラーにしてしまうことがあります。特に多いのが、「等しい」の意を表す「==」を、「=」と書いてしまう失敗です。これで大丈夫な言語もあるのですが、Fortranでは無理です。しっかり演算子がかけているかを確認しましょう。また括弧が正しくない場合もあります。左・右括弧が不足する事で論理条件式が正しく成立していないことでエラーが出ていることがあります。しっかり式が括弧で包まれているか、目を凝らしてよく見てください。
  • DO文のエラー・・・これもIFと同じエラーを結構やります。CONTINUEを使用していた頃は、CONTINUE文に付ける文番号を間違った文番号にしたり、そもそも文番号をつけるのを忘れたときもありました。またDO文は書いているけれども、ENDDO文は書いていなかった、ということもあります。IF文と同じように、DOを書いたらすぐにENDDOも書いてしまうようにしてください。
  • 配列の添え字間違い・・・これは配列に慣れていない頃はよくやるんじゃないでしょうか。配列宣言のときは「a(4)」と総数だけを書きますが、宣言以降配列要素全体を使用するときには「a(1:4)」とする必要があります。「a(4)」と書いてしまえば、配列aの4番目の要素という意味になってしまいます。宣言時と使用時の配列の指定の仕方は違うので、注意が必要です。
  • 引数の型違い・・・これはコンパイルエラーで引っ掛かると思うので、見つけるのは簡単だと思います。呼び出し側の実引数は整数型なのに、呼び出された仮引数は実数型になっている、ということはたまにあります。暗黙の型宣言が有効になっていたり、変数名を変えているとこうした間違いが起こる可能性があります。また呼び出し元は配列なのに、受ける側はスカラ変数(普通の変数)である場合もエラーになります。変数のところでエラーが出たら、どういう定義を変数にしたのかを確認すれば大丈夫です。
  • 引数の大きさ違い・・・僕はなぜかこれが多いです。大きさが違うというのは、実引数が大きさ40なのに、受ける仮引数が50になっている、ということです。複数の呼び出し元が同じ副プログラムを使用する場合や、何気ないミスで起きてしまうことがあります。これは別にエラーができるだけでプログラム自体は生成可能なのですが、やはりエラーは起きないほうがいいので、しっかり潰しておきましょう。対処方法は、単に配列の大きさをそろえればいいだけです。実引数が60なら、仮引数も60にすればいいだけです。
  • 未定義のシンボル・・・シンボルとは変数のことです。型宣言により定義していない変数を入力や出力に使用すると、こういうエラーが出ます。暗黙の型宣言を封じたゆえのエラーですが、これがあるので変数名の入力失敗を見つけることが容易になります。対処方法は、その変数が自分が使っている変数であれば先頭で型宣言し、そうでないなら間違いなので名前を修正します。また副プログラムでの変数未定義と言うこともあります。COMMONやMODULEにより変数の共有をしていなければ、使用する変数は各プログラム単位で型宣言しなければならないのですが、仮引数を実引数と同じにしてしまうと、副プログラムでの引数の型宣言を忘れてしまうことがあります。忘れると当然エラーになってしまいますから、未定義であれば定義しなおしてください。
  • オーバーフロー・・・高度な計算やとにかくでかい計算を行なうと、実数の扱える範囲を超えた値がはじき出されてしまうことがあります。円周率をモンテカルロ法を使って求める場合、簡単にその限界を超えて「実数での計算ができない」という、オーバーフローエラーが出てしまいます。これはプログラムが悪い・おかしいというわけではなく、計算の限界に達したというだけなので、安心してください。オーバーフローを起こしてしまった場合には、実数型の変数を倍精度実数型にすればいいだけです。こうすれば大抵の計算は何とかなります。これでもダメな場合は...さすがにそれは分かりません。
  • 値が未定義・・・その変数の値が未定義、つまり入力されていないということです。例えば、自分は変数A(1)に3を入力していたつもりが、実はA(0)に入力していて、A(1) を出力したらこのエラーが出た、という具合です。値が入っていないということは入力する変数の名前をどこかで間違えたということなので、注意深くソース文を辿っていけばきっとおかしな点が見つかります。配列を使用した場合に多いですね。サブルーチンの引数引渡しで間違えたとか。
  • 実行結果が正しくない・・・コンパイルエラーもない。実行エラーもない。でも実行結果が自分の用意した答えと違う。当然こういうことだって起こります。ある意味、このエラーが一番つらいです(オーバーフローに比べればましですが)。実行結果が違うということは、どこかで計算の仕方を間違えていたということなので、これも注意深くプログラムの内容を探っていくしかありません。紙に書いて順序を確かめたり、手順どおりに進めて値がどこで正しい答えと違ってくるのか。とにかく、地道な作業となりますDOカウンタの開始値や終了値が間違っていないかとか、IFの条件が「以上」じゃなくて「より大きい」だとか、可能性は無限にあります。がんばりましょう。
  • 引数の数が違う・・・たまーにありますね。主プログラムの実引数が4つ用意されているのに、サブルーチンの仮引数は3つしかないような状況です。どうしてこういうことが起こるのか。それはもちろんぼけている時だってあります。他には、引数の見直しをしたときですね。初めはとりあえず主プログラムで使用していて、おそらく副プログラムでも使うだろうという引数(例えば入力したデータやその個数)を引数として登録しておいたものの、実際には使用せず、引数として描いておくことが無駄になったので削除した。しかし、削除したのは副プログラムだけで、主プログラムのほうの修正はしていなかった。だから個数の不一致が起きてしまった。こんな感じです。個数が違うだけなのですから、直すのは簡単ですね。
  • いきなり値が消えた!・・・どういうことか。それは、副プログラムに値を引き渡して計算処理を行おうとしたものの、なぜかいきなり1行目で変数内の値が消えてしまう!という事態です。信じられないかもしれませんが、実際にありました。掃き出し法にて逆行列を求めようとしたところ、サブルーチンに送った元のデータ行列Aの値が、そのサブルーチンの1行目「T=0.0」で「0.0」に変化してしまいました。Tを0にするのに、Aも0になってしまいました。もちろん0にする処理はしていませんし、変な文もありません。とにかく見ためでは修正不可能な状態です。しょうがないので、その時は「T=0.0」を主プログラムで行なってしのぎました(これでなんで直るのかな?)。わけの分からないエラーです。こういうときは、もう何でもかんでも試してみてください。正直、分かりません。


 コンパイルエラーは大抵コンパイルが示すエラーの箇所を見れば理解できるのですが、実行時のエラーはなかなか分かりません。実行時のエラーとして、上に示したような「配列の大きさが違う」のような場合は何行目かが表示されるのでいいのですが、実行はされるけれども思い通りの実行がされていない(例えば、1+2の結果が10になってしまうとか)場合もあります。また大部分の実行はうまくいくけど、一部分についておかしな動きをする(例えば車が壁に突っ込んでもすり抜けてしまう)バグが起きることもあります。こうしたエラーはその原因の理由・原因箇所を非常に見つけるのが難しいです。そこで、デバッガを持っている人は、ぜひそれを使ってデバッグしてみてください。かなりそうしたエラーが見つけやすくなります。
 デバッガのよいところは大きく分けて2つあり、一つは任意のところで実行をとめることができることと、変数の値の変化が把握できるということです。全てのデバッガにそれが備わっているのかどうかは分かりませんが、僕が持っている「Fortran&C」を元にお話させていただきます。
 デバッガの利点の一つ目についてですが、任意のところで実行をとめることができるというのは非常に便利です。僕のデバッガは、実行の仕方に2種類あって、中断点というのを実行をとめたい行に打ち、そこまで一気に実行する方法と、一行ずつ実行していく方法とがあります。プログラムをそのまま実行してしまうと、最初から最後まで一気に進んでしまいます(READ文がなければ)。しかしデバッガを使用すれば、実行を見たいところまで勧め、見たいところから一行ずつゆっくり進めていくということが可能になります。例えばIF分岐を考えてみてください。明らかにプログラムの実行の仕方がおかしいが、その原因はIF文の分岐の仕方がおかしいからじゃないかと感じたとします。実行をIF文の前で止め、そこから一行ずつ実行していきます。そうすることで、自分の考えたとおりにIF文野下に進んでいるのか、それともELSE文に飛んでしまっているのかを確認することができます。DO構文でも同じ活用の仕方ができます。しっかり決めた回数分ループしているのか、EXITで条件通りにループを抜け出しているのかを、ゆっくり実行することで確認することができます。そこで変な動きをすれば、そうなる原因を考えればいいのです。デバッガを使用しないと、ソース文でプログラムの動きをイメージしなければならないのですが、デバッガがあれば、実際にプログラムの動きを見ることができ、便利です。
 もう一つの利点は、変数内の値の変化を見ることができる点です。Fortranは破壊的代入をしているので、同じ変数でもどんどん新しい値になって行きます。ループを使用した合計を求めるプログラムをイメージしてもらえれば分かりやすいと思います。プログラムを実行した結果、思い通りの値にならなかったのを解決する場合、上のように少しずつ実行してどういう動きをしているのかを見るほかに、変数内の値の動きを追うことでぐんとわかりやすくなります。例えば、EXITが作動するには基本的にIF文の条件式がうまく作動することが必須ですよね。aが3になったらループを抜ける場合、「if(a==3)exit」とかきます。そしてこれが正常に作動するには、ループが抜けてほしいときにaが3になってくれないと困りますね。デバッグをし、aの中にしまわれた変数の値を追ってみると、n回目のループのときにaが3になるはずだったのに、実際にはn+4になって初めてaが3になるということがよくあります。この原因はそれ以前のプログラムに原因があるからですが、これはaの、そのときの値が一体いくつなのかを把握できなければ、なかなか見つけることはできません。
 このように、デバッガは優れた機能を持っています。デバッガには他にも機能がありますが、これだけでも十分な威力を発揮してくれることが分かってくれると思います。もちろんデバッガがなくても想定外の動きをするエラーを潰すことはできますが、デバッガをうまく活用することで、より効率的にエラーの原因を見つけることが可能となります。デバッガをお持ちであれば、宝の持ち腐れにしないで、コンパイラ同様、使えるツールとして活用してください。
 そうは言っても、デバッガは実行の動きや変数内の値を示すだけで、何が原因で思い通りに動かないのかを示してくれるわけではありません。ですので、デバッガで変な箇所を見つけたら、ソース文とにらめっこする必要があります。「ここで変な動きをしていたんだけど、どうしてそうなってしまうんだろう」「ここを直せば、正確に動くのかな?」といった感じでね。結局、エラー潰しにはソース文をよく理解し、プログラムの動きを頭の中で正確にイメージする能力が大事になってきます。そして文法のしっかりとした理解と、自分の思い描く理想の実行順序による先入観の排除、そしてソース文をよく読みこむことが大切です。
 一番プログラム開発で根気が要るのがデバッグですが、ここさえクリアすれば完成です。僕もデバッグに苦しみ、何度もパソコンを叩き壊してやろうかと思いました。ですが今まで起きたエラーは全て解決することができました。どうしても無理だと感じても、寝て次の日に気持ちを変えてやってみるとできることもあります。諦めずがんばってください。諦めてしまうことは、この上なくもったいないことですから。


参考: ウィキペディア
ASCIIデジタル用語辞典
コマンドプロンプトを使ってみよう!
コマンドプロンプトで作業効率UP
FORTRANの歴史
Fortranを使おう



表紙へ戻る