第6報:努力と怠惰

 とうとう21世紀です。3千年紀です。誰が何といおうと今年から3千年紀です。百年紀(世紀)と千年紀が一緒に来ないはずがありません。

 でもドラえもんの22世紀まではまだまだです。とてもじゃないですが宇宙進出がいいとこの世紀です。もどりライトやうそつ機なんて道具はまだまだ実現できないでしょう(22世紀にも無理だとは思うが)。

 20世紀の間にも、20世紀には実現できんだろうと思ってた「青色レーザー」「ウェアラブルコンピューター」「電子ペーパー」「3Dディスプレー」なんてのが実現されてしまいました。これからは「空飛ぶ車」だの、「アンドロイド」だの、「タイムマシン」だの、「不老不死」だのを頑張って欲しいもんです。

 しかし、21世紀になった途端何かががらりと変わるわけはなく、プログラマの友第六報も21世紀だからといって特別なことは何もやりません。当然です。


 では、本題に入ります。今回はプログラムにおける努力と怠惰についてのお話です。

 みなさん、一生懸命作った関数が「既に用意されていた」とか「ある関数を使えばもっと簡単にできるのだった」という経験はありませんか? 誰でも一度は経験したことがあると思います(反語)。例えば sprintf を知らない初心者が strcpy と strcat で必至に文字列を作ったり、vsprintf を知らずに可変長引数を使った文字列作成に手間取ったりという具合です。

 そういう処理も自分で作るという制限を課してプログラムを組んでみているときや、標準関数にバグや不便なところがあるときは別ですが、普通は用意された関数を活用してやった方が煩雑にならず、楽にプログラムを組むことができるでしょう。自分でコーディングしなくてもいいところは、とことん怠けてしまいましょう。

 そのためには、ヘルプを大いに読むことが必要です。何か処理したいときは「こんな関数ないかな〜」と、とりあえずヘルプを見てみましょう。「怠けるための努力は無駄じゃないか?」と思うかもしれませんが、ここでいう怠惰とはプログラムが簡単になるかどうかを重要にしているので、こういう努力は惜しまないようにしましょう。努力と怠惰のバランス、使いどころが重要です。

 しかし、ヘルプをただ闇雲に探したところで、それは無駄な努力です。計画的にヘルプを見ることが大事です。計画的にヘルプを見る努力をすることで、ヘルプを見る労力はどんどん減っていくでしょう。ということで、VC++6.0のヘルプ「MSDN」に関していくつか目安をあげていきたいと思います。別のヘルプでも、ある程度傾向は一緒かもしれませんね。


 先ず2点をあげると、1つは「近い処理の関数を知っていれば、その関数から飛べることがある」、そして、2つ目は「カテゴリ別になっている場所を見つけだすと楽」です。

 例えば「文字列から整数への変換関数はないかな〜?」と考えます。先ず、文字列の操作関数なので、strlen でも strcpy でもいいのでヘルプを見ます。するとヘルプの下の方に関連リンクがはってあります。その中に「文字列操作ルーチン」というのがあるので、そこを見てみます。すると、文字列操作に関する関数が列挙され、簡単な解説も付いています。こういうカテゴリ別になっている場所は便利です。このページ内で「数」など、適当な文字で検索をかけてみましょう。

 しかし、この中には目的の関数はないようです。そこで、今度は数値を扱う関数を見てみましょう。サインを求める関数 sin や絶対値を求める関数 abs などのヘルプを見てみます。「sin という関数があるかどうかを知らない!」というときも、とりあえず sin という関数がないかどうか調べてみます。調べなければ分からないことをごちゃごちゃと考えるのは無駄な努力です。すると、sin には4つの項目があるようです。ここで、C言語の標準関数についてのヘルプは主に「Visual C++ プログラマーズガイド」にあることを覚えておきましょう。このことを覚えていなくても、何のヘルプなのかは大体ページのレイアウトや使っているフォントから分かるようになっています。主要なレイアウトはぼやーっと覚えておくといいでしょう。

 ということで、sin 関数のヘルプに辿り着きました。また下の方に行って関連リンクをざっと見ます。すると、「浮動小数点サポートルーチン」というのがあります。今回は整数に変換する関数が欲しいのですが、小数に変換する関数からリンクが張られている可能性もあります。早速行ってみましょう。すると、上の方に atof という「文字列を倍精度浮動小数点数に変換する」関数がすぐ見つかると思います。これは近そうです。atof が alphabet to float の略であると推測して、「alphabet to int の略の atoi が目的の関数になる!」と推測しても構いませんが、ここは atof のページを見てみることにします。

 すると、atof と一緒に何やら数個の関数が載っています。説明書きを見ると、「文字列を double (atof 関数)、int (atoi 関数、_atoi64 関数)、または long int (atol 関数) に変換します」と書いてあります。どうやら atoi が目的の関数のようです。これで調べ終わりました。あとは、実際にどういう動作をするかヘルプをよく読んで使うだけです。

 この時、動作確認の要らないほど簡単な関数ならいいのですが、気になる点がいくつかあることもあるでしょう。そういうときは実際使いたいプログラムとは別に、動作検証用の簡単な使い捨てプログラムを作ってみるとよいでしょう。それ用のプロジェクトを作っておくと楽です。ここで検証しておくと、実際に使いたいプログラムの方では洗練された、つまり「綺麗な/楽な」プログラムができるはずです。

 カテゴリ別のページを見つけたら、それをお気に入りに登録しておくのも1つの手です。HTMLヘルプには簡単なお気に入り機能が付いているので、活用してあげましょう。atoi のヘルプの下にはさらに「データ変換」「ロケールルーチン」というカテゴリがあります。これもついでにチェックしておきましょう。

 API関数では(英語ですが)「Win32 Functions by Category」や「Win32 Functions in Alpha Order」が便利です。目的の関数をザッと見つけて、日本語のヘルプがあるかどうかキーワードから探してみましょう。


 次に、「MFCを使わないプログラムでも、MFCのヘルプは役に立つ」ということをあげておきたいと思います。

 これはこの前の「カテゴリ別になっている場所を見つけだすと楽」ということにも通じます。ウィンドウ関係の関数は CWnd クラスに、ビットマップ関係の関数は CBitmap クラスにまとめられているので、簡単に目的の関数が見つけられます。どんなクラスがあるかは階層図から分かります。

 ここはまだMFCのメンバ関数のヘルプでしかありませんが、MFCは基本的にはAPI関数を扱いやすくしたクラスを集めたものなので、同じ機能を持った、そしてほとんどは同名のAPI関数が用意されていることがよくあります。そして、そこにはMFCのヘルプ内からリンクがはってあることもよくあります。なければキーワードで探します。ちょっと名前の変わっていることもあるので(例えば CDC::MoveTo はAPIでは MoveToEx になります)、周辺も探してみます。このとき、もっと便利な関数が見つかることもたまにあるでしょう。

 ただ、これでも見つからないことがあります。CWnd::CenterWindow がまさにそういう関数です。この関数はMFC固有の関数なので、MFCを使わないときは自前でルーチンを作る必要があります。なさそうな気配を感じたら、途中で探すのをうち切るのも選択の1つです。しかし、CFile::Seek のようなないと困るような関数はどこかに必ずあるはずです。これについては次の項目で話をします。

 例えば、多角形を書きたいと思います。描画関係のクラスは CDC なので、CDC のメンバ関数を見てみます。サブカテゴリとして「楕円および多角形関数」というのがあるので、そこを見てみます。すると、Polygon や PolyPolygon 、PolyLine といった多角形描画関数が見つかります。あとはこれらのヘルプにある関連リンクからAPI関数のヘルプに移り、それらを読み比べ、どの関数が適しているかを試してみるといいでしょう。

 「角の丸い四角形が書きたいが、さすがにそれはないだろう」と思ったときも、一応調べてみましょう。RoundRect という関数があることが分かります。調べる前から投げてしまうのはちょいと早計です。さすがに「ドラえもんを描いてくれる関数」とかはありませんが...。


 最後は、「名前を想像してキーワードで探してみる/検索してみる」です。

 所詮は同じ人間の作ったもの。そして、分かりにくい名前をわざわざ付ける人はいないという事実。このことから、名前を想像してヘルプで探してみるのも意外と有効なことがあります。さっき言った sin 関数がまさにそうです。sin 関数に sin 以外の名前を付ける人は先ずいないので、有効な手段になります。

 APIを探すときは、「何かを取得する関数は Get で始まる」とか、「ファイルを作成するから CreateFile だ」とかあたりを付けて探すと、意外と見つかるものです。

 どうしても見つからないときや、関数名が思いつかないときは、早めに検索に切り替えます。最初から検索でもいいのですが、検索はちょっと時間がかかるし、沢山検索に引っかかって見にくくなったりするので、一概に検索が一番いいとは言えません。

 例えばファイルのサイズを取得するAPI関数を探したいと思います。先ずはあたりをつけて GetFileSize とします。すると、目的の関数が見つかりました。へちょい(大したことない)もんです。(これは開いているファイルにしか使えません。ファイルを開かずにサイズを調べるには FindFirstFile を使う必要があります。)

 と調子に乗って、開いているファイルのファイルポインタ(次にファイルを操作するときの開始地点)を取得するAPI関数を探そうと考えます。しかし、GetFilePointer や GetNowFilePointer を調べてみるも不発です。このとき getFilePointer というのが引っかかりますが、先頭が小文字で、途中に大文字のある名前はCでは使わないので、別の言語のヘルプだと分かります。MSDNはC++だけのヘルプではないので、このようなことはざらです。

 ここで検索に切り替えます。検索キーワードは「ファイルポインタ」と「Get*」です。Get のあとのアスタリスク (*) はワイルドカードの1つです。アスタリスクは「0文字以上の何か」を表します。HTMLヘルプの検索は単語単位で行われるので、Get だけだと Get という単語にしか引っかかりません。あ、大文字小文字は無視されるので、get や GET も引っかかりますね。そこでアスタリスクを付けておくと、Get で始まる単語全てに引っかかるようになります。他には任意の一文字を表すクエスチョンマーク (?) もあります。あと、日本語は単語毎に検索とかは行いません(行えませんといった方がいいでしょうか)。

 で、検索結果が表示されます。このままでは見づらいのでソートします。関数名が Get で始まるとあたりを付けていたので、タイトルでソートしてみましょう。しかし、Get ではじまるAPI関数は見あたりません。API関数は大文字で始まるので、一目で分かります。つまりは、ファイルポインタを取得するAPI関数は Get で始まらないと予想されます。

 次は「ファイルポインタ」だけで検索してみます。今度は場所でソートします。関数名の手がかりが不明な場合は、場所でソートすると便利なことが多いです。一般的なAPI関数は「プラットフォームSDK」という場所にあることを知っておくとさらに便利です。

 ファイルポインタを取得できそうな関数は見あたりませんが、ファイルポインタを設定できそうな関数 SetFilePointer ならあります。下にある SetEndOfFile という関数は、ファイルポインタをファイルの最後に持っていってくれそうな関数名ですね。こういう別の便利そうな関数もついでにチェックしておくと、あとあと便利なことがよくあります。ここら辺は普通の辞書と同じですね。

 で、SetFilePointer のヘルプを見てみます。早速関連リンクを見てみますが、それらしき名前の関数はありません。どうやら、ファイルポインタを直接取得できる関数はないようです。

 しかし、そこで希望を捨ててはいけません。ファイルポインタを取得できないはずはないとは思いませんか? 絶対にできるはずです。ここで、唯一の手がかりである SetFilePointer のヘルプをよく見てみましょう。すると、戻り値の説明の所に「関数が成功すると、新しいファイルポインタの下位ダブルワードが返ります」と書いてあります。ということは、ファイルポインタを動かさない設定で SetFilePointer を呼べばファイルポインタが取得できることになります。

 結果として、SetFilePointer(hFile, 0, NULL, FILE_CURRENT) がファイルポインタを取得する処理だということが分かりました。GetFilePointer というインライン関数でもつくってやるといいでしょう。


 これでヘルプについては終わりにしますが、ここに書いてあること以外にも便利な方法を見つける努力は怠らないで下さい。勉強が苦手だ苦手だといっている人は、目先の努力に目を奪われていてこういった努力を忘れていることがほとんどです。無駄な努力より真の努力です。

 あ、どうしても分からなければ人に聞けるなら聞いてしまうのも1つの手です。ただ、忙しい人もいるので、相手の都合も考えてから聞きましょう。それと、あまり他人に頼りすぎていると、まわりに聞く人がいない状態に陥ったときに問題が解決できないなんて事になるかもしれません。いざというときに一人でも問題を解決できるよう、調べもののスキルは身につけておくようにした方がいいでしょう。


 次に、プログラムを組んでいく上での他の注意点について話します。それは、処理単位毎に関数を分けるということです。つまり、1つの関数を長くしないということです。

 処理単位毎に関数を分けておくと、処理の使い回しの効く状況で非常に楽になります。ちょっとした努力で、後々大きく怠惰できるわけです。ここで関数を作らずに強引にプログラムを作っていく、つまりコピー&ペーストで済ませてしまっていると、ちょっと処理を変えようというときにコピー&ペーストした箇所の全てを直さなくてはなりませんちょっとした怠惰で、後々大きく努力しなくてはならない事になります。

 また、関数を作るということは処理に名前を付けるということです。そして、多くの処理が1つの関数という小さなスペースで表示できるようになり、色々な意味でプログラムを読みやすくします。for 文や if 文の異常なコピー&ペーストをしたプログラムを見たことがありますが、一種独特の表面的な美しさを感じつつも、「こんなプログラム読んだり管理したりできるか!」という吐き気とめまいに襲われました。

 読みにくい処理、何が書いてあるか分からない処理は、極端に言えば何が起こるか分からない処理だと言えます。処理をまとめてコンパクトなプログラムを組むよう心がけておけば、バグはずいぶんと起こりにくくなるでしょう。(なくなるとは言いませんよ。あくまで減るだけです。)また、バグが起こったときも、原因を特定しやすくなるでしょう。

 プログラムを組む上で重要なのは無駄を無くすことです。一番無駄なのは無駄な努力の結晶です。「はいはい、頑張ったね〜。でもここをこうすると処理が20分の1になるよ〜」と言われたら、無駄な努力の結晶を作っていたということです。配列にしたら簡単なところを配列を使わずに処理したり、関数にまとめると楽なのにコピー&ペーストで済ませたり、そういう処理を行う関数があるということを知らずに自分で関数を作ったり...。そういう無駄な努力はやめにしましょう。

 無駄な努力をせず、できるだけすっきりとしたプログラムを組む。しかし、決していい加減なプログラムは組まない。これがプログラマのあるべき姿だ...と、自分は思います。(注:一個人の勝手な意見なんで、「これが正しい」と鵜呑みにすると危ないこともあるかもしれません。そこら辺は取捨選択して下さい。)


目次に戻る

トップページに戻る

Last update was done on 2001.1.7