プログラミング作法に関する世界観3A(制御フロー)です。Cの世界観2(制御フロー)も参照のこと。
ifやforなどの「条件分岐」と「繰り返し」は、その時その時の場合によってプログラムが何を実行し、何を実行しないか、どれだけ実行し続けるか、を条件的に記述します。
変数が何を持っているか、条件式が何を判断しているか、実行するか実行しないかの範囲と内容は何か、いつどのように実行されるか、いつどのように実行されないか、などを考えると良い。
紙にフローチャートを書いて練習すると良いでしょう。
アルゴリズムも参照のこと。
ここで重要なのは、「条件式」です。条件式に応じて、条件が満たされる間繰り返し処理を実行したり、分岐して別々のコードブロックを実行したり、変数に別の値を代入したりします。
当然のことながら、条件式が間違っていた場合は、プログラムは正しく動きません。
条件によって別の関数を実行する関数のように、「ラッパー的な関数」を作ることもあります。
また、ゲームであれば、ランダムな数値を変数に代入したり、敵が出現するかどうかの判定や、キャラクターの攻撃が当たるかどうかの判定、敵の生死の判定、ターンごとに攻撃に参加できるかどうかの判定などにも、条件式と変数の代入を使います。
データベースやファイルシステムであれば、データの一意の識別のためにキーとなるIDナンバーやiノードなどを代入して、条件ごとにコードブロックや関数を実行します。
また、代入されている変数の値に応じて、別々の処理や制御を行うこともあります。
先のゲームの判定で言えば、代入されている値に生存が代入されていればゲームを続け、死が代入されていればゲームを終了する、といった具合です。
条件式に応じたプログラミングは、C言語のコマンドラインプログラムなどに多くみられます。
構造化プログラミングも参照のこと。
条件式以外の制御フローとしては、イベント駆動などが考えられます。
たとえばゲームであれば、キャラクターがジャンプした時、どれくらいキャラクターが上に飛び上がるかとか、キャラクターが前方方向に進んだ場合に敵に当たらないか、ステージから落ちないか、画面をスクロールするかどうか、のようなことをイベント駆動で行います。
これはイベントループによって、何らかのイベントが生まれるまで待機し、何らかのプレイヤーの操作が行われたらそれに応じた処理や判定をして画面を再描画する、といった具合で実現できます。
イベント駆動によるプログラミングは、UNIXのカーネルや、GUIのプログラム、ネットワークサーバーなどに多くみられます。特にGUIではメッセージループとイベント駆動により、ツールキットフレームワークを使ったプログラム開発を行うため、CUIのプログラムよりも冗長で複雑かつ、プラットフォーム依存のプログラムになります。
イベント駆動も参照のこと。
また、プログラムを作る上で重要な考え方が、「抽象的な基盤システムの構築」です。
たとえば、ゲームを作るのであれば、ひとつひとつの対戦画面を作るのではなく、ひとつの「共通の対戦を行うための画面」を作って、そこに具体的なデータを流し込んで、戦闘画面を作ります。
そのため、ピカチュウとカイリューが戦うのであっても、ニドキングが戦うのであっても、作るべきなのは、ひとつの「対戦画面」です。
マップ画面やダンジョン画面も同じです。ひとつひとつのダンジョンを一から作るのではなく、「共通のマップ画面」を作り、そこにキャラクターの「ドットイメージ」をマスの中に表示していきます。
どんなゲームであっても、これは変わりません。カードゲームなら、盤面の画面をひとつ作ります。3D対戦ゲームなら、3D画面を作ります。また、シミレーションRPGであっても、マップを移動するそれぞれの「駒」を適切に表示するような画面を作ります。
ゲーム開発も参照のこと。
プログラム初心者が忘れがちなこととして、「繰り返しの回数も数である」というのがある。
たとえば、10という数字が与えられて、10回繰り返しの中の文を繰り返すプログラムというのがこれである。
プログラミングとは、「その数から何をするか」という「文の実行順序・実行回数」を書く作業である。このため、回数も数である、ということは大切である。
また、繰り返しには条件式を使ったり、あるいはbreak文が実行されるまで永遠に繰り返したりなど、「繰り返しを終える条件」というのも必要である。
たとえば、味方の全キャラクターが死ぬか、敵の全キャラクターが死ぬまで、互いにキャラクター一人ずつが互いに攻撃する、といったゲームがこうしたプログラムである。
また、繰り返しと良く似ているのが、正規表現を用いた文字列をパターンマッチングさせるプログラム。
たとえば、HTMLをTeX文書に変換するスクリプトなどがこれである。
HTMLをTeXにコンバートするためには、それぞれのタグをTeXのマクロに変換する必要がある。
たとえば、
1.コメント部分(<!--~-->)を削除する。(これだけでも、きちんと作るのは大変)
2.<body>から</body>までを切り出す。
3.切り出した内容について、<script>~</script>を削除する。
4.<h1>~</h1>などをsectionやsubsectionなどに変換する。
5.それぞれのタグ(<li>など)を適切にTeXのマクロに変換する。
6.必要のないタグ(<p>など)を削除する。
7.最後に、TeXの適切な記述をヘッダ部分とフッタ部分に追記する。
このようになる。実用性を考えるならば既にあるコンバートツールを使うか、手動でテキストエディタで検索・置換を繰り返した方が良いだろうが、プログラミングの勉強としては最適である。
正規表現も参照のこと。
また、関数は自動の実行処理です。ある引数に基づいて、どのような処理を行うかが、汎用的に記述され、汎用的に実行されます。
引数とreturnを上手く使いましょう。また、再帰を使うことも良くあります。再帰とは「その関数の中でその関数を呼び出すこと」です。
後日注記:言ってしまえば、定型の処理をあらかじめ記述しておいて、そこにどんな引数が来ても良いようにプログラムを記述し、さまざまな引数によって呼び出して実行し、その結果となる返り値を返す、これが関数の基本です。そのため、自動の実行処理と言えます。
関数も参照のこと。
また、サブルーチンはサブのルーチンだと思えば良く分かります。「メインのルーチン」があったとして、そこに「サブのルーチン」を作り、メインからサブを呼び出すのです。
ひとつの処理の流れがあったとして、関数やサブルーチンにはそのメイン処理とは別の処理の流れがあります。
メインルーチンからさまざまなサブルーチンに移動することでプログラムは動きます。つまり、「メインのプログラムとは別個にあるサブのプログラム」があり、「プログラムが別のプログラムを呼び出す」ような形で全体のプログラムを作ることで、構造化プログラミングは成り立ちます。
プログラムには、小規模プロジェクトと大規模プロジェクトがあります。
小規模プロジェクトは、たとえば簡単なUNIXコマンドや、掲示板スクリプトなどです。こうした小規模のプログラムは、ひとつのファイルで正しく制御フローを記述します。関数もいくらかしかありません。
これに比べて、大規模なプロジェクトは、LinuxカーネルやNetBSDカーネル、あるいはApache httpdサーバなどです。こうした大規模なプログラムは、ディレクトリを大きなサブディレクトリ階層構造に正しい規則で分類し、ソースファイルは関数だけではなくライブラリやコンポーネント機構を使ってモジュール化していきます。
また、小規模でも大規模でもない、普通ぐらいの規模のプログラムというものも存在します。これはIEコンポーネントを用いたタブブラウザや、テキストエディタなどです。
どんなプログラムを作るのであっても、まずは小さなプロトタイプの開発から始めます。データ型や汎用的な関数は開発の途中段階で開発します。また、リファクタリングとテストを行い、ひとつひとつソースファイルを整理していきます。同時に、保守がしづらくならないように、分類の方法、分化の方法を考えながら開発を行います。
もちろん、動くためのアルゴリズムやアーキテクチャも考えなければなりません。中心となるアルゴリズムを、どのような設計と実装で実行するように作るかが鍵となります。
また、プログラミングとは「手続き」の記述であると考えられます。
これは、ハードウェアに近い処理で言えます。特に顕著なのがネットワーク処理(ネットワーキング)です。ネットワークでは、ソケットインターフェースを使って、手続きそのもののプログラミングを行います。多くのコードが、形式的な手続きの記述になります。
また、現代的なコンピュータは、「テキストの動く機械」であると考えられます。
この「テキスト」とは、プログラムの書かれたソースコード、設定ファイルや設定データベース、そしてデータファイルやログファイルなどが言えます。
UNIXのプログラムは、プログラムのソースコードとバイナリファイル、設定ファイル、データファイルがセットになっています。プログラムが設定とデータを読み込んでプログラムの動作が決定されます。場合によっては、コマンドの実行オプションや引数、そしてプログラムをコンパイルする時のビルドオプションまでが、プログラムの動作を決定すると考えることができます。ほかにも、モジュールや拡張機能などで、プログラムをカスタマイズできます。ソースコードを直接編集しなくても、オブジェクト指向の機能やマクロ言語などの機能を用いて、プログラムをカスタマイズすることができることもあります。
この「テキストが動く機械」という発想は、Gentoo Linuxをやると良く分かります。Gentooでは、Portageと呼ばれるソースベースのパッケージ管理システムを使うことで、プログラムをコマンドに応じて自動でダウンロード・ビルド・インストールできます。まさに「Linuxがどのように形作られるのか」を、コンパイルやインストールなどの出力を「自分の目で見る」ことで知ることができます。また、Gentooでは設定を自分で設定ファイルに手動で書くことによって行いますが、これにより、「自分で設定した内容を自分で見て設定する」ことができます。インストーラもありませんが、ローリングリリースを採用しており、一度インストールしたら二度同じことをする必要はありません。
C言語では、ファイルのオープンの際、ファイルがきちんと存在し、読み書き権限が得られ、オープンに成功した時と、そうでない時を区別し、エラーが発生した時のためにエラー処理をしなければなりません。
これに対して、C++やJavaなどのオブジェクト指向言語では、「例外」を用いてエラー処理を行うことができます。例外が吐かれると、その時点でコードの実行をやめ、「キャッチブロック」と呼ばれるブロックにキャッチされて、そのエラーを処理することができます。
Java(2C.ガーベッジコレクションと例外)も参照のこと。
また、関数を作る時のコツとして、「呼び出し方」を考えながら、「共通の処理をひとつにまとめる」ように作ります。
関数の呼び出し方は、たとえば引数に2つのint型の数値を取って、int型の返り値を返す、といった具合です。
共通の処理をひとつにまとめるとは、たとえばお札の数を数える時に、ひとつひとつのお札の条件比較をする処理を、ひとつのルーチンにまとめる場合などが考えられます。
ここで、ポインタが活躍します。というのも、共通の処理をひとつにまとめる上で、どうしても引数を関数の中から参照だけではなく変更したい場合があるからです。
ポインタだけではなく、グローバル変数やオブジェクト指向のクラスとオブジェクトを使うことで、「たくさんの関数の中でどのようにデータを保持し、共有してアクセスするか」を考えなければいけないこともあります。関数の外側にある、関数を呼び出した元のローカル変数にアクセスしたい場合は、やはりポインタが活躍するでしょう。
関数について他には、Windows APIやVisual Basicのようなイベント駆動の考え方を用いて、「コールバック関数」のように使う方法があります。
これは、既にあるシステムから自分の作った関数を登録して「自動で適切に実行されるようにする」という考え方です。
JavaScriptでも、HTMLのタグの中でJavaScriptの関数を呼び出すことで、「クリックされたらこの関数を実行する」といった具合にすることができます。
また、Pythonなどでは、関数型プログラミングの考え方で、mapなどに関数を渡して、「関数そのものを関数の引数として渡す」といったことができます。C言語では、関数ポインタを使うことで同じことができます。
また、関数について他に言うと、オブジェクト指向ライクな考え方として、「特定のデータ構造を操作する一連の関数を用意して、その関数からデータ構造を操作する」という発想があります。オブジェクト指向では、こうした関数をメソッドと呼び、データ構造とメソッドが一体化したものをクラスと言い、オブジェクトを作成し特定のインターフェースから操作することをカプセル化と言います。
また、オブジェクト指向と似た考え方として、「関数のスコープと同じ場所にある変数に関数の内部からアクセスする」という方法があります。これを「クロージャ」と呼びます。クロージャにより、スクリプトライクに共通のデータをさまざまな関数から操作できます。
オブジェクト指向やクロージャ・無名関数・関数オブジェクトも参照のこと。