- 前書き
これから書く事は、後から読み返して、ああ、この頃はこんな事を考えていたんだ、今とあまり変わらないな、としみじみするためのものです。
- 副作用のない関数
『プログラミング言語の基礎』を読み、デザインレシピというプログラミング手順を知りました。
Amazon.co.jp: プログラミングの基礎 (Computer Science Library): 浅井 健一: 本
この手法を知った事が、僕の転機となりました。
それまで自分はプログラムを組む時、明確に手順を意識していませんでした。IOから先に書くことで、データの入口と出口を決めておき、データの変換として内部の処理を書く手法は、デザインレシピを知る前に既に知っていました。
この手法は、ちょっと思い出せないのですが、確かITコンサルタントの書いた本もしくはサイトで見たと思います。思い出してしみじみするための文章なのに、今の時点で既に思い出せない。
それはさておき、この『プログラミングの基礎』により、自分は実装上の指針を得る事になり、以後プログラムを組むのが楽になりました。
デザインレシピでは、テストを書くことをプログラム作成の工程に組み込んでいます。テスト対象のモジュールに副作用がなければ、
If ( succ( 1 ) == 2 )
{
System.out.println("test 01 O.K.");
}
private int succ( int i )
{
return i + 1;
}
と書ける訳です。
これで、succメソッドが正しく動いている事が確認できると思います。
そういう、副作用のない関数のテストの書き方を知ったのも、この本だったと思います。
この手法を知った事により、プログラミングにかなり自信を持てるようになりました。手順が明確に決まっているので、その通りに進めれば、プログラムが書けてしまうのです。
自分はIT業界で働きながらプログラマを目指していたので、相当良かった訳です。
プログラミングの勉強をする中で、分割統治という手法を知りました。
これはアルゴリズムを学ぶと出てくるのですが、問題を小さな問題に分割し、小さな問題を解決していく事で、元の問題を解決するというやり方を指すようです。
プログラムにバグがある場合、これに似た手法で解決する事でできる事があります。
例えば、あるプログラムが実行途中で異常終了するとします。
その場合、自分は以下の方法を取ります。
1. 問題発生個所の特定
まず、異常終了が発生しそうにない、当然実行されていると思われるソースコード上の箇所に以下を書きます。
System.out.println("実行されていると僕は信じる。");
このデバッグ用メッセージが出力されている事が確認できれば、この箇所までは正常に動作しているであろうことが分かります。
さらに、異常終了のすぐそばと思われる所に似たデバッグコードを仕込みます。
System.out.println("実行されていると俺は信じるぜ。");
この時、最初に書いたコードと同じメッセージを出力してはいけません。
どっちが実行されたのか、区別できない事があるためです。
そうやって、デバッグメッセージを沢山出力していくと、ある所で、メッセージが出力されなくなります。その場合、その出力されないメッセージを仕込んだソースコード上の位置より前の時点で、プログラムが異常終了している事になります。
プログラムにメッセージ出力させることでデバッグする方法はprintfデバッグと呼ばれるようです。これはおそらく、C言語でメッセージを出力する時、「printf」という命令文を使う所から来ているのだろうと思います。
自分はJavaで書く事が多いので、さながら「System.out.println debug」、つまり「システム・アウト・プリントライン・デバッグ」といった所でしょうか。とはいえ、「システム・アウト・プリントライン・デバッグ」という言葉を実務で使った事はなく、個人の開発で使った事もありません。たった今、ブログを書いていて思い付いただけです。おそらく人が
「プリントエフ・デバッグ」
と言ったとき、
「プリントエフ・デバッグ? ああ、システム・アウト・プリントライン・デバッグのことね?」
と言ったら、とてもうざったい人だと思われるでしょう。
それはそれとして、なぜprintfデバッグが分割統治法と関係しているかと言えば、この方法を使う時、単に最初の方にメッセージ出力を仕込んで、徐々に異常発生個所に近付けるのではなく、大雑把にメッセージ出力を仕込み、出力されたり、されなかったりを繰り返しながら、異常の発生個所を絞り込んでいく事が多いからです。
これは、アルゴリズムでいう所の二分探索と同じ方法です。
プログラムが逐次実行される場合は、この方法が使えると思います。
今調べていて気がついたのですが、二分探索は分割こそしているかもしれませんが、別に結果をマージしたりしないので、統治はしていないかもしれません。つまり、分割統治法ではないのではないかと思い始めました。
しかし、バグを発見する事で、ローマ帝国が都市を治めたように、「話を収める」という点で、統治していると考えれば、大丈夫かもしれない。問題の解決が統治であるという訳です。
この分割統治、乃至二分探索的デバッグ手法は、プログラムに限らず、他の分野での問題調査にも使えます。
最近経験した所だと、ネットワークの障害で、相手先の機器に信号が届かない事がありました。
自分は次のようにしました。
① 現状の把握
HTTP通信が正常に動いている事は分かっていました。HTTPはTCPで動いているので、これでTCP通信が正常に動作している事も保証されます。
② 問題の調査
その通信はUDPを使っていました。UDP通信がうまくいっていないかもしれません。UDPの通信はうまくいっていて、通信先のプログラムが異常終了しているかもしれません。
ここは二分探索です。ざっくりと問題を切り分けるのです。
まずはUDP通信が動作している事を確認しました。
使用したツールは以下です。
Portqry.exe コマンド ライン ユーティリティの説明
このツールを使うと、UDP通信が出来ている事を確認できます。
通信は正常にできていました。相手先のアプリケーションにも届いているようです。
となると、ネットワークはO.K.です。アプリケーションの問題である可能性が大きいです。
② 推測
ここで思い当たるのは、このUDPはブロードキャストを使っている事です。
ブロードキャストに失敗しているかもしれません。
まずはユニキャストでパケットを送ってみて、うまく動いたら、いよいよブロードキャストが怪しい。
もしブロードキャストに失敗していて、ユニキャストで成功するなら、ユニキャストで相手先の機器にひとつひとつパケットを投げよう、と思いました。
ちょっとダサいですが、背に腹は代えられません。
② 更なる調査
実際の調査は自分はしていないのですが、調査用のプログラムを作成し、調査を依頼した所、ブロードキャストで失敗している事が分かりました。
③ 解決
自分も初めて知ったのですが、UDPのブロードキャストには、制限ブロードキャスト(255.255.255.255)とローカル・ブロードキャスト(192.168.X.255)があり、それぞれセグメント単位、ローカル・ネットワーク単位でブロードキャストするそうです。今回は制限ブロードキャストを使っていたので、同じローカル・ネットワーク上の相手に通信が届かなかったようです。ローカル・ブロードキャストに送り元プログラムを変更し、無事解決しました。
これも、現状を把握して失敗している可能性がある箇所を絞り込み、大まかにネットワークの調査から始めて、アプリケーションの問題だと分かり、障害箇所に辿り着いたわけです。
この方法は、おそらく機械の保守作業でも使えるのではないかと思っています。
2. 問題の解決
そうやって問題の発生個所が特定できたら、あとはその問題を解決します。
そのために必要な事は以下です。
「じっと見よ、されば間違いに気付かん」