1. Home
  2. /
  3. プログラミング
  4. /
  5. プログラムが一向に動かない!ハマった!のトラブルを解決する羅針盤

プログラムが一向に動かない!ハマった!のトラブルを解決する羅針盤

ハマった プログラミング

#プログラム #問題 #動かない #トラブル #解決 #難しい #ハマった

本記事中のプログラム例を示すところでは、主としてpythonで記述します。
pythonは現在人気の言語であるということも理由ですが、小生は他の言語との類似性やAIなどにも活用できるという長所など、総合的に他の言語に比べてpythonを推奨しているためです。
また、Windows上でShift-JISコードで利用することを前提にしています。
スポンサーリンク

どのような問題が考えられるか

3つの道筋を活用してプログラムの問題から早く開放されましょう!“で説明した手順を踏まれたでしょうか?

その上でなお、このページを訪問されているということは、かなり頭を悩まされているかと思います。
早くダンジョンから抜け出したい! という思いにかられているかと思います。
良くわかります。
小生もかなり、様々なケースで頭を抱えさせられてきました。長いときは、3週間かかってなんとか解決したケースもありました。

しかし、落ち着いて頭をリセットして問題に向き合おうではありませんか
こういう難しいケースにもある程度のパターンがあり、それらを知った上で対処する経験を積むことで、その後はやりきった満足感と推測可能な未来が待っています

本記事では小生が20年以上の開発の中で遭遇した頭の痛かったケースを説明をします。
これらのケースそのものであったり、ケースは異なっても、考え方が参考になるかと思いますので、ぜひ御覧ください!

1.深い呼び出し階層が邪魔をしているケース
2.別のスレッドが邪魔をしているケース
3.並行実行プロセスが邪魔をしているケース
4.再帰処理で邪魔されているケース
5.複雑なオブジェクト構造が邪魔するケース

これでもパターンとして結構ありますが、上記が複合しているパターンもありますし、実際にはもっと細かなレベルで問題が発生することもあるでしょう。
しかし、上記のパターンをまずは基本ケースとして1つ1つ該当しているか、いないかを確認して着実に進めると、解決のいとぐちが見えてくると確信します!

本記事では、UMLのアクティビティ図を使って解説していますが、わかりやすさを優先し、アクティビティ図本来の記法とは異なる記法で図解しています

深い呼び出し階層が邪魔をしているケース

最も良く目にするケースです。
関数の呼び出し関係があまりに深く複雑になっており、関数内の奥深くにある処理(図中の関係処理)が行った処理が影響するケースです。
奥深くの関数が、ある変数やオブジェクト(*1)の値・状態を変え、それを参照している処理に影響を与えてしまいます。
下図のでは、関数X→関数A→関数B止まりですが、現実にはもっと深いことでしょう。
また、処理と次の処理の間に別の関数呼び出しがいくつもあり、複雑なソースコードになっているでしょう。

*1 オブジェクトとは、変数の集合体と考えて差し支えありません。

解決手順

1.変数・オブジェクトメンバ変数の特定

問題が発生した処理が参照・更新している変数やオブジェクトのメンバ変数(*2)を特定
します。

*2 メンバ変数とは *1 で説明したオブジェクトを構成する変数のことを指します。

2.変数・オブジェクト名の検索

変数名やメンバ変数が属するオブジェクトの名前をgrepコマンドなどで検索し、
呼び出している関数に渡していないか確認します。
渡していなかった場合は、本ケースには該当しません。

3.呼び出している関数の伝播経路を追いかけて問題箇所を特定

2.の変数・オブジェクトの名前は、呼び出している関数側では、引数に記載され
たものに変わってしまいます。
通常は別の名前になるので、今度は、引数の名前で検索を行います。
以降、3.を繰り返して伝搬経路を調査して、問題箇所を特定します。

調査に際して

小生の場合、このケースの調査では
– grepなどの検索結果を書き出す
– 呼び出し関係も分かるように整理する
– 確認が終わった所に目印を入れる
というようにしていきます。

こうすることで、確認した内容と確認した事実を証拠として残しつつ、確認していないところがどれだけ残っているかが記録として残ります。
途中で調査を打ち切って、後から再開するときも、再開しやすくなります

別のスレッドが邪魔をしているケース

スレッド(*3)処理を行っている場合で、別のスレッドと、同じ変数やオブジェクトを参照・更新しているケースです。

*3 スレッドとは、プロセスよりも小さな単位です。
プロセスもスレッドもWindowsやLinux, MacOS, iOS, AndroidなどのOSが管理します。
スレッドは、スレッド同士で軽快に情報交換ができますが、プロセス同士の情報交換は壁が厚く処理が大変になります。
また、プロセスは、Windowsのタスクマネージャ、Linuxのpsコマンドで見ることや、終了させるなどの操作ができますが、スレッドにはこのような操作ができません。
そして、プロセスや別のスレッドが終了すると、一緒に終了してしまいます。

また、マルチスレッドやマルチプロセス特有の反応が帰ってこなくなる問題(俗にいうダンマリ、フリーズ)が発生することがあります。その原因は、互いに互いの処理完了を待ち合うデッドロックと呼ばれる現象です。下図がその状況です。
互いの処理完了を待ち合わせるために、処理の状態を覚える変数またはオブジェクトが2つあり、それらを互いに交差して参照・更新することで発生します。

解決手順

1.キーワードを検索

スレッド処理を意味するキーワードでgrepコマンド等で検索を行います。
Pythonではthreadingやconcurrent.futuresモジュールを用いて実装するため、これらが検索キーワードとなります。
見つからなかった場合は本ケースではありません。

2.変数・オブジェクトメンバ変数を調査して問題箇所を特定

問題発生箇所で参照・更新している変数やオブジェクトのメンバ変数にアクセスする別のスレッドがないか確認します。

前出の図のような可視化を行うことが解決の早道となります。

同じ変数、オブジェクトのメンバ変数を参照・更新している場合は、そこが問題箇所であり、スレッドA,Bのどちらかの処理が直接の問題箇所となります。

ダンマリやフリーズが発生していてデッドロックを疑う場合は、同じ変数、オブジェクトのメンバ変数を参照・更新している箇所の順序も確認し、前出の図のように交差して参照・更新していないかを確認します。

並行実行プロセスが邪魔をしているケース

別のスレッドが邪魔をしているケース“がスレッドであるのに対し、こちらは、それより大きい単位であるプロセスであった場合です。
この場合、1つのコマンドやアプリの中でプロセスが複数起動する場合だけでなく、別々のコマンドやアプリ同士で問題を起こすこともありえます。
また、スレッドの場合と同様にデッドロックの問題が発生することもあります。

スレッド=影、プロセス=ロボットと考えるといい例えになるかもしれません。
スレッドは片方が異常などで終了すると、プロセスごと終了するため、もう一方も消えてしまいます。そのため、影のような存在です。
ロボットは互いが独立しているため、スレッドのようにはなりません。

解決手順

1.キーワードの抽出と検索

プロセス同士で連絡を取りながら行う処理は、下記の3つがあります。
これらの特定を行います。

・ ファイル入出力
・ ネットワーク通信
・ メモリアクセス

Pythonでは、ファイル入出力を行っている場合は、open, read, write, closeなどのキーワード、ネットワークアクセスを行っている場合は、connect, accept, send, recv, read, write, closeなどで検索を行います。メモリについては多数存在するため、説明を割愛します。

キーワードは、何を使っているかによって、プログラミング言語のマニュアルを調べて、入出力とその前後の前処理(open,connectなど)と後処理(closeなど)の関数をキーワードに選定します。

2.処理内容を調査して問題箇所を特定

プロセス同士で行っている処理内容を調査します。

前出の図のような可視化を行うことが解決の早道となります。

前処理、入出力処理、後処理と、入出力しているデータの中身を調べて問題箇所を特定します。

再帰処理で邪魔されているケース

いつかは遭遇し、悩まされる再帰処理に関係する問題です。
何しろ、頭の中が混乱してくるので、ソースコードとじっくりにらめっこしていただけでは到底、解決のいとぐちが見えてこないかと思います。
再帰処理とは、呼び出し元の関数Xが呼び出し先でも呼び出されるというものです。
関数X→関数X→・・・ というケースがよくありますが、下図のように一度別の関数を呼び出し、再び自身が呼び出されるというケースもあります。

関数X→関数X→・・・ というケースは、ディレクトリの中身を調べる処理などが代表的です。ディレクトリの中にはファイルもあればディレクトリもあるため、また同じ処理(ディレクトリの下に行く)を行うためです。
上図のケースは、プログラムなどの文法解析処理が代表的です。文法では、同じ文法がいくつか先に現れるためです。
例えば、 if (A=B) { X = 10; if (B=C) { X = 20; … }}では if ( )の文法解析処理の後、{ } 内の解析があり、また、if ( )の解析が必要です。

解決手順

このケースでは、いとぐちとなる決まったキーワードがありませんが、以下を当たってみてください。

関数の外にある変数やオブジェクトを見つけて処理内容を点検する

関数の外にあるグローバルな変数やオブジェクトは、何度も呼び出される再帰関数が常に参照・更新する可能性があるため、その処理内容を点検します。

再帰関数として作り出したときの修正箇所を点検する

こういった処理の作り方として、

・ 一通り動くものを作った後、何度も呼び出される部分を関数化する
・ もとからあった関数をさらに進化させて再帰関数として作り出す

といったことが多いと思います。
そのため、一度動いたにも関わらず、再帰関数にしたときに行った修正箇所に問題がある確率が高いと考えます。
小生もよくやらかしました。。。

図示して可視化し怪しい箇所を点検する

ここまでの点検事項を当たってみて、なお分からないようであれば、図示して可視化し、処理を見直すようにしたほうが、面倒で手間がかかりますが、『急がばまわれ』の教訓どおり、進展に繋がります。

複雑なオブジェクト構造が邪魔するケース

何らかの言語的な構文を解析するような処理や、実行スケジュールを計画する処理では、多数のオブジェクトが複雑にリンクして作成されます。
このようなケースで、オブジェクトの中に記憶させた情報に誤りがあった場合、問題箇所を特定することが非常に難しくなります。
小生もこのケースにだけは大量の時間を使って苦労したことが多々あります。

解決手順

オブジェクトの情報を全て表示させる処理を作るしか有効な方法を思いつきません。
目に見えて問題が発生しようと、しなかろうと、作り出したオブジェクトの情報が正しいかどうかを、いずれはテストで確認しなくてはなりません。
そのため、テストを始める前から作ってしまうのが早道であると考えます。

まとめ

いかがでしたでしょうか?
ダンジョンをさまよっていた状況から抜け出せそうになっていたら、嬉しい限りです。

なお、今回のようなケースが発生するころには、コーディング量が相当多くなってきているかと思います。
初めから多いコード量の保守を行う場合はともかく、少ないコード量から始めて作成していく場合には、こまめなテストを行いながらコーディングを行うと良いです。
都度テストを行なうため、コーディングスピードは落ちるものの、今回のような大ハマリするケースにほとんど遭遇することなく、コーディングができるようになります。

では、以上の知識を使って、プログラミングライフをエンジョイしていきましょう!

今回のケースは代表例であり、具体的解決法が完全に記載できているものではありません。本記事で問題が解決しない場合、小生のホームページからメールでご連絡いただきますと、ご相談に乗ります。お気軽にご連絡ください。

コメント

タイトルとURLをコピーしました