プログラミング作法に関する世界観2(入門)です。
数式とプログラムの違いとは、数式は「正しさや論理を表現する」記法であり、プログラムは「計算し関数を実行する手順」の記法である、ということです。
数式では、x=4+3ならばx-3=4であるといったように、その数と記号の書かれた意味から成り立つ論理の正しさを「表現」していきます。定理の証明を行う時でも、「表現」を中心に論理的に考えます。
ですが、プログラムは表現ではなく、計算の手順です。ですので、たとえば、
int x; int y; int z; x = 3; y = 4; z = x + y; z = z + 1; printf("%d", z);
のように、まず使う変数を宣言して、それ以降は「どのように代入し、その代入をどのように変更し、いつどのように処理するかを逐次記述していく」という作業になります。
ですので、
x = x + 1;
のような、数式ではありえないような式もプログラムでは記述できます。逆に、4=1+3のような数式は、プログラミングでは書く意味がありません。
OSのやっていることは、このようなプログラムを管理し、実行するための機能を提供することと、プログラムに対してどのような機能を提供するか、たとえば入出力やメモリ管理、ネットワークやファイルシステムのような機能をプログラム側にどう提供するか、ということになります。これらはシステムコールとしてカーネルが提供しますが、一部の機能はカーネルではなく、ライブラリ関数やXサーバーとの通信プロトコル・ライブラリなどの、カーネルよりも上にあるプログラムが提供します。また、多くの場合プログラミング言語で書かれたプログラムの実行には、プログラムを機械語に変換するコンパイラや、逐次的に読みながらプログラムを実行するインタプリタを使います。
プログラムの開発は、創造的な仕事に見えて、本当はとても細かい仕事です。
コンピュータは、指示・命令されたことを忠実に、少ない「命令されて出来ること」だけをやることしかできません。
プログラマは、コンピュータにも理解出来るぐらい、細かく、詳しく、正しく、全部をきちんと書くことしかできないのです。
プログラムの基本は制御です。変数と制御で、いつどこに流れが行くかを考えることで、プログラムは作られます。
知識を誇ることも大事ですが、制御とアルゴリズムもきちんと考えましょう。
おそらく、シングルタスクでは、制御がパソコンの基本ですが、Golangのような並列処理言語では、go構文を行うことで簡単にマルチタスクができます。先進的な非同期のシステムはそのようになるでしょう。
現在のノイマン型コンピュータでは、「命令を逐次的に実行していく」ことしか出来ません。Lispのような関数型プログラミングや、Javaのようなオブジェクト指向プログラミングが出来ないわけではありませんが、現状のノイマン型コンピュータでは、全てのことを「命令の羅列」として実行・計算します。
そのため、C++であろうと、PHPであろうと、やることは同じです。プログラムから見れば、さまざまな記法を使いながら、計算の手順を具体的に書いていき、OSや言語・ライブラリの用意する関数を使いながら、「自分で設計するセンス」を持った上でプログラミングを行います。機械工学技術者とデザイナーの中間のような仕事がプログラマです。
ノイマン型コンピュータでは、どこまで言っても、プログラミングは「命令と記憶のやり取り」にすぎません。さまざまな計算をさせた内容を、記憶として保持し、関数に渡したり関数から受け取ったりしながら、プログラミング言語の記法(ifやfor)でアルゴリズムを書いていくだけです。そこで行われる全ての機能は、用意された仕組みと提供されたインターフェースを使います。
また、コンピュータはどこまで行ってもブラックボックスです。PHPの命令を記述していても、PHPの関数や記法を「使う」だけに終始し、「仕組みを知る」ことがたとえオープンソースだから出来たとしても、「自分で作る」のは現実的ではありません。
ですが、if文やfor/while文のような基本的な要素を上手く使うことで、「関数や命令をもっと高度に利用する」ということはできます。エラー処理を行ったり、関数を呼び出したり、フレームワークを作って使ったりすることができます。現代的な言語では、クロージャを使ったり、デザインパターンのような設計をすることができます。
また、オブジェクト指向のプログラミングは、あるひとつの「個性」のあるプログラミングが行なえます。たとえば、ゲームを作るのであれば、キャラクターそれぞれに必要なデータ属性はインスタンスとして持っておいて、制御はそれぞれのイベントに対するメソッドやプロパティとして実装し、場面場面でそのキャラクターを作成・起動して実行することができます。オブジェクト指向はとても便利で作りやすい考え方です。
Cだけではなく、VB.NETなどで開発する際も、ポインタや参照型を上手く使うことで、「関数としてこの部分の処理を抜き出したい」という時に役に立ちます。たとえば、ある変数を「変更可能」なままで関数に渡したいときなどに、Cならばポインタ、VBならばByRefなどの方式で渡すことで、簡単にコードブロックを関数にすることができます。
Lispのような関数型プログラミングは、プログラミングでは特殊な方に分類されるかもしれません。純粋に関数としてプログラムを実行することで、全てを式として扱い、ラムダ式・無名関数や再帰のような形でプログラミングが行なえます。ある意味、数式の展開を書いていく感じになります。
コンピュータ科学も参照のこと。
main()関数からコードを書き始めて、関数を分けていくだけでも、プログラミングは出来ます。
最小限の処理をきちんと行う関数を作って、それの組み合わせとして行くだけで、きちんとしたプログラムは書けます。
ただ単にブロックを分けるだけではなく、繰り返しの中を関数にしたり、条件分岐を関数にしたりすることで、プログラミングはスマートに出来るはずです。
ポインタは、引数の中から外側の世界を操作するような、参照型として使いましょう。
最初のうちは、動的なメモリ管理なんか、必要ありません。
コンピュータは電子頭脳です。プログラムを書く、ということは、人間が考えるのと同じようにコードを書く、ということに他なりません。
人間が考えるのと、同じように書いてください。変数や構造化プログラミングを使いながら、人間が考えるのと同じようにプログラムを作っていきましょう。
変数はメモリの中の値の格納場所です。計算したり、関数を呼び出したりした結果の値を格納します。プログラミングでは、変数と関数の呼び出しを組み合わせて使います。
まずは、変数への値の格納と関数の呼び出しを、四則演算や配列のような発想でコードを書いてはいかがでしょうか。
if文(条件分岐)やfor文(繰り返し)は、ある意味「サブルーチン」の一種だと考えられます。
関数を作る時は、引数と返り値を良く考えて作ることで、「さまざまな利用目的によって呼び出される、汎用的な処理」を作ることができます。この時、引数にポインタを使うことで、関数の内部から外部の情報を操作できます。これは、サブルーチンを繰り返し実行するように処理を書く上で、とても有効で、便利です。たとえば、お札の枚数を数えるプログラムであれば、カウント途中の残りの金額を関数の内部から書き換える必要があります。こういう時に、ポインタを使いましょう。これを「値渡しではなく、参照渡しをする」と言います。
C言語では、関数と関数外とのやり取りの中で、構造体のポインタを使って共通のデータにアクセスすることが多いですが、これは一種の「データ構造の共有」だと考えられます。
ある種の、関数の内部と外部のやり取りを行うための、「インターフェース」の一種であると考えることもできます。
これは、オブジェクト指向のクラスとインスタンス変数や、JavaScriptのクロージャやスコープチェインと同様の考え方です。それぞれの関数の内部から、共通のデータ構造を参照することができます。
C/C++では、関数のインターフェースAPIを提供するためにプロトタイプ宣言とヘッダファイルのインクルードを行いますが、これは「コンパイル時間が無駄に長くなる」として一部では批判されています。このような場合、C2やD言語のような「Cの後継を目指している言語」で改善が図られています。
旧来のプログラミング設計では、まずデータ構造を設計し、その上でアルゴリズムたる「コード設計」を行います。Windowsプログラミングを行う上でも、どんな機能が必要になるかを明確に分析して、共通のデータや情報が必要なところでは、共通のデータ構造にそれぞれのモジュールが参照できるようにしましょう。
僕は、プログラミングとしてコードを書くコツは、「どの関数からでも処理を実行出来る」というところにあるのではないかと思う。
たとえば、ファイルシステムへのアクセスやネットワークの接続は、main()関数に流れるように書くこともできるが、多くの場合、それぞれの関数に処理を分けて、さまざまな関数を実行した時に実行する。
そのようにすることで、関数を「汎用的な処理」として使うだけではなく、「さまざまな部分に処理を分割(分散)させる」ということができる。
要するに、言ってしまえば「それぞれの機能を単純に関数として作って、引数を渡しながらそれらの関数を操作する」だけで、プログラミングは出来るのである。
ただし、僕が言いたいのは、単に関数を分割できる、ということではない。プログラムが順番通り動いて文字列を出力しているように見えて、実際はありえない関数のさまざまな場所から文字列を出力している。それは、ファイルシステムやネットワークでも同じで、あらゆるブロックからプログラムは関数を呼び出して操作する、ということを言いたいのである。それが、プログラミングのコツではないかと思う。
プログラミングの機能的な基本として言えるのは、昔のコマンドラインのプログラムは、ほとんどがテキストと文字列の編集や操作の処理であり、たまに条件分岐や繰り返し処理をしながら、計算したりすることがあるだけです。ですが、それを使って高度な(たとえばgitのような)システムを作ります。
GUIが標準的になった現代のプログラミングでは、ウィンドウの操作を中心に行います。ここで行われるのは、イベント駆動のプログラムです。どんなイベントに応じてどのような操作や処理を行うか、ウィンドウアプリケーションではそれがほとんどのプログラミングになります。
それ以外に存在するのは、ファイルの読み書きや削除・名前変更・一覧の取得などの「ファイルシステム処理」と、テキストを正規表現でマッチングさせる「パターンマッチング」や、あるいは、ネットワーク通信の処理です。ですが、ネットワークについて言えば、わざわざソケットを作ってクライアントとサーバーが通信する処理を書くことは少なく、多くはCGIのようにApacheのようなサーバーと連携させながら、HTMLを吐き出す処理を行います。他にあるのは、データベースの処理などがあるでしょう。それくらいで、あとは変数の格納や配列など、基本的な「一時的な記憶」を行う処理が多いでしょう。文法を学んだ後で、そうした基本的なライブラリをマニュアルの記述を参考に参照してください。それで、誰でも、JavaやRubyで何でもできるようになるでしょう。何を作ったら良いか分からない人は、Rubyのgemsのような汎用的なライブラリを、自分で工夫して開発すると良いでしょう。ライブラリが大量に多いのはそういう理由だと思います。
文字列の操作をプログラムから行う上で、日本人が直面する問題は、文字コードの問題でしょう。C/C++だけではなく、Apache/PHP/MySQLなどを使う上で、「文字コードとどのようにつき合うか」は日本のLinuxハッカーの永遠の課題です。ですが、最近はほとんどの場合、UTF-8を使えばなんとかなります。C/C++でマルチバイト文字を扱うのがとても不安だという方は、Java/.NETやRubyのような言語を使うと何とかなるでしょう。逆に、Pythonのように「きちんと文字コードを指定しないと日本語が使えない」プログラミング言語もあります。永遠の日本人の「躊躇と戸惑い」が文字コードだと思います。日本人をテキスト・文字列処理ハッカーの道から遠ざけている原因ではないかと思います。
プログラミングにおいて、関数やコードブロックを構築していく際に考えられるポイントがいくつかあります。それは、「実行し、保管し、渡し、得る」ということです。
1.どのように実行するか。関数を呼び出したり、クラスをインスタンスにしてメソッドを実行する。
2.どのようにデータを保管するか。変数に記録したり、計算したりする。
3.どのように渡し、どのように得るか。関数の呼び出しの時に変数を渡したり、逆に関数の呼び出しから変数を取得する。
そして、この上で、関数全体を設計します。その時必要なのは、
4.どのように実行されることを意図しているか。関数がいつ、どのように実行され、どんな結果を返すように関数を記述するか。
5.どんな機能を目的にそれぞれの関数に役割を与えるか。ファイルシステムなら、書き込み、読み込み、エラー処理などを、どのように作り上げるのか。どのようにそれぞれの関数がプログラムとして作られるのか。
そうした結果できるのが、巨大な関数たちの「関数の集団」です。これをどのように実行するのか、もっと言えばどのように実行されることを意図して作るのか、それによって何ができるのか、そもそもそれは何をしているのか、を考えます。
つまり、「関数で何をするのか」と、「どこでどのようにその関数を使うのか」を考えれば、もう、プログラミングはできたも同然です。あとは、必要なことを、ただ記述していけば、それが単純にプログラムになるでしょう。
言ってしまえば、パソコンというのは「ハードウェアを操作する演算計算機」に過ぎない。
ここで、「演算計算機」とは、「演算の順次実行と、記憶と、いくらかのジャンプや分岐を行う計算機」ということである。
カーネルも、X11も、ハードウェアと演算を繰り返しているだけにすぎない。仮想CPUや仮想メモリは、スケジューラや仮想アドレス空間やページによって行うが、これは「演算をただしやすくしているだけ」に過ぎない。
UNIXだけではなくWindowsについても同じで、ハードウェアの処理と演算を交互に繰り返しているだけに過ぎない。OSはそんなに大したプログラムではない。
プログラミング能力の向上に必要なのは、「課題を作ること」です。
僕はデザイナー見習いとしてデザインの勉強と仕事をしていますが、毎日のようにIllustratorに触れながら、実際のデザインの仕事をすることで、Adobe製品の使い方を知り、どのようにすればできるのかを学びました。
この「実際に取り組むこと」は、プログラミングにおいても変わりません。実際に、「課題を作ること」で、プログラミング能力は向上します。
どんなに本を読んでも、どんなに勉強しても、実際に課題を作り、その課題をこなして実現させること以上に、近道は存在しません。
課題となるプログラムを1つ決め、それを作るために全ての情報を処理して頑張ってください。プログラマーになる一番の正しい道は、課題を作ることです。
プログラムの基本は、データ処理と表示です。
データを読み書きし、出力を表示し、場合によってコマンドやフォームから入力を得ることで、プログラムは成り立ちます。
たとえば、CGIのゲームであれば、まずキャラクターのデータを読み込み、戦闘相手である敵とキャラクターとのバトルを繰り返し表示し、勝利か敗北かの出力を表示し、キャラクターのデータを保存します。
バトル画面だけではなく、アイテムの購入や装備品の変更画面でも、まず金銭状況や既に持っている武器の情報をデータから読み込み、プレイヤーの選択をフォームでやり取りし、購入確認画面へと進んで、所持品や金銭状況のデータを保存します。
ゲームだけではなく、チャットや掲示板のCGIプログラムでも同じです。CGIだけではなく、コマンドプログラムやGUIのアプリケーションでも、基本は「データの読み書きと入出力」です。
基本はそれに尽きると思いますが、さまざまな媒体やプラットフォームによって違いが生じます。以下はその一覧です。
媒体とプラットフォーム | 入出力の特性 |
---|---|
コマンド | コマンドラインオプションなどで引数を取得し、 printf(), fgets()などで文字列の入出力を行う。 |
ファイルシステム | open(), close(), read(), write()でデータを読み書きする。 |
スクリーンエディタ(viなど) | 端末のスクリーン全体に文字を表示し、 マルチラインで文字列を操作し、コマンドで保存する。 |
HTML/CGI | HTMLタグで出力を表示し、 GET(URLパラメータ)とPOST(テキストボックスやボタンなどのフォーム)で ユーザーとデータをやり取りする。 Perlなどの構文でファイルを読み書きする。 |
SQL | SELECT, INSERT, UPDATE, DELETEなどのSQL文でデータを抽出・挿入する。 |
GUI(Windows APIなど) | GUI画面でラベルやテキストボックスにデータを表示し、 メニューやボタンでユーザーとデータをやり取りする。 編集中のデータはコントロール操作としてインタラクティブに操作できる。 |
MFC(ドキュメント・ビュー) | DocumentクラスとViewクラスで読み書きと表示を行う。 |
MVCフレームワーク | モデル・ビュー・コントロールでデータの表示とデータベースでの保管を行う。 |
ある意味、Bashのようなシェル記法や、JavaやPerlのようなプログラミング言語も、こうした「データの読み書きと入出力」の形態の1つと言えるでしょう。
ただし、本当はMozillaのようなネットワーク処理やsedのようなテキストフォーマットの処理、gccのようなコンパイラやgitのようなバージョン管理システムなど、高度なデータ処理・文字列変換システムもたくさんあるので、あらゆるプログラムがこれだけに収まるわけではありません。
ですが、プログラミング初心者のように「何から書いていいか分からない」場合は、データの読み込み、出力の表示、書き込み、そしてフォームの表示によってプログラムが簡単に書ける、というのは、分かっていると有益だと思います。
何も分からない人には、Ruby on Railsをやるのをおすすめします。モデルとビューとコントロールを作ることで、誰でもこうした「データ処理と入出力」がどんなものであるか、ありありと分かるからです。(ただし、Railsだけで終わらないこと。Railsだけで終わってしまうと、Railsしかできない人間になると良く言われています。)
そもそも、コンピュータを「計算機」と呼ぶものの、内部でやっていることの多くは、変数や入出力などの「データ処理」と、関数や制御構造を用いた「手続き」です。データ処理を行い、さまざまなジャンプや変数渡し、条件比較のような「手順通りの実行」を行うこと、そのためのアルゴリズムとデータ構造をきちんと成り立たせることで、プログラムは開発されます。
プログラミングを行う上で、知っておくべきなのはライブラリの設計とAPIです。たとえば、GTK+のAPIを覚えれば、GTK+ウィジェットを使ったLinuxでのGUIプログラミングができます。
これは、たとえば、MS-Officeの使い方を覚えるのと同じです。どんな機能が提供されていて、どのように記述すればどのようなことができるのか、ということをただ覚えれば良いのです。
C/C++だけではなく、Java, PHP, Python, Rubyなど、さまざまな言語やクラスライブラリがあります。コンパイラや言語処理系のコアな振る舞いや原理を分かることも功を奏するでしょう。
そして、本を読むのであれば、初心者向けの本をいくらか読んだ先は、プログラミングPerlのような言語の設計者が書いた本を読めば、何でもできるようになります。「一冊完読すれば一生モノの知識」になるでしょう。僕は最初に知るべきはPerlをおすすめします。その後にJava, Python, Ruby, C#などを学び、最後にC/C++を習得するのが良いでしょう。
ですが、その際に、言語的な記述法については、他の言語と似通った部分はとばして構いません。ライブラリや関数・クラスのAPIのような「違う部分」だけを参照すれば、すぐに全ての言語をマスターできるでしょう。
言ってしまえば、プログラミングはサルに教える芸と同じです。「さあ、これをやれ。その次にこれをやれ。そしたら、こういう時はこうしろ。そら!できた。」と、そういう風にプログラム芸を馬鹿な猿コンピュータに仕込むこと、それがプログラミングです。
OSはプログラムを実行するプログラムで、リソースを割り当てるプログラムです。これを猿に喩えると、仮想猿集団に芸を仕込む曲芸師である、と言えるでしょう。そしてこれも猿です。この猿の他の猿と違う点を言えば、「猿に芸を仕込むという内容の芸をしている猿」であると言えるでしょう。
OSには仮想CPUや仮想メモリ機能があって、仮想猿集団はたくさんの猿が居るように見えて、本当はひとつのOSの統括する猿頭脳(ハードウェア)の中で実行されているのです。OSは芸を仕込むためのリソースが必要になると、仮想猿を1つ作ります。この仮想猿が芸をしますが、本当に頑張っているのはOSという猿の頭脳(本当のCPUとメモリ)です。
AIが人間並みの知能を持ったと良く言われますが、これは人間を作り出せたというわけではなく、人間と同じことができるまで猿の芸が進歩したというだけです。
これらの猿は賢いので、人間よりも賢い芸を巧みにこなしますが、芸を考えるのは人間(プログラマ)です。
プログラマがその時その時教えなくても良いように、芸は専用の言語で書かれます。この専用の言語を猿でも分かるような単純な猿語に変換するのがコンパイラです。インタープリタを使うと、猿に直接人間の言葉で話しながら、その時その時猿語に翻訳してくれます。
猿はとても優秀で、なんでもこなすことができ、それは芸だけではなく仕事や芸術、科学技術のようなことも猿が可能とします。だが、唯一、芸(プログラム)を考える猿は今のところ出来ていません 。人間の仕事は、猿に教えるための芸を考えることです。
ログインとログアウトは猿芸を見るためのチケットの確認と入退場です。ネットワークは、もっと大きなボス猿に共同作業を頼むことです。入出力は猿への声援、SSHは猿芸のテレビ中継でしょう。ファイルシステムやデータベースは、猿が芸をする時に必要な芸の子道具や絵(データ)を人間に頼んで用意してもらうことです。
猿と言うわけではなく、ピアノにも喩えられます。テープレコーダーに喩えることもできるでしょう。プログラムは音楽であり、プログラム言語は楽譜であり、ハードウェアはピアノであり、プログラマはピアニストであり、演奏は仕事です。
また、良くプログラマのことを「IT土方」と言う人が居ます。これは、プログラミングの作業というものが、公共インフラである道路やトンネル、水道管やダムなどの開発に似ているからです。どんどん開発し、変数の中を情報が通り抜けるように関数や制御構造を当然のように組んでいく作業は、IT土方そのものです。ですが、本当は、コードを書いている時間よりも、設計やアルゴリズムを考えている時間の方が長く、設計をする際もウォーターフォールなどの開発規則を使って賢くスマートに組んでいきます。ですので、工事現場の作業員というよりは、開発作業を行うお役人のようなものに近いと言えるでしょう。
ある意味、フロントエンドエンジニアは猿の曲芸師で、バックエンドエンジニアはIT土方のインフラ開発作業員であると言えます。
はるか昔の話ですが、昔のコンピュータは、プログラムを紙テープあるいは配線盤から供給していました。紙テープでは動作が遅く、配線盤は作り直す手間があったため、プログラムはデータとして扱われるようになりました。
そう、昔は、プログラムとデータは別々だったのです。
今のコンピュータの中央処理装置と主記憶装置という関係は、プログラムがデータになったごろに確立しました。1945年ごろの話です。
プログラムがデータになったおかげで、テキストとして機械のロジックを扱うことができるようになりました。高級なプログラミング言語を用いて、コンパイラでソースコードから機械語を生成するようになったのは1960~1965年ぐらいの話ですが、このプログラムをデータとして扱う、というところから、今のUNIXのモデルが誕生したと言えるでしょう。OSが一般的になってきたのは1965~1970年ぐらいの話です。そのごろから、マルチプログラミングや仮想記憶などの仕組みを用いるようになりました。
今のOSでは、プログラムは完全にデータです。この、データとして機械のソースコードを扱う、ということは、オープンソースととても相性が良く、UNIXシステムの真価を発揮させています。Gentoo LinuxでPortageを使うと良く分かりますが、Linuxにおいて全ての構成要素はテキストやデータから生まれるのです。
注意:UNIXは1969年から1970年代にかけて開発されましたが、一般的に使われるように普及したのは少し後の話です(1980年代)。また、ミニコンと呼ばれる1人が所有するコンピュータや、パソコンと呼ばれている個人向けのコンピュータの歴史もあります。
古いコンピュータ(1.メインフレーム・ミニコン)も参照のこと。
プログラムとしてのソースコードは、静的に同じで、変わることがありません。ですが、変数の内容(変数にどんなデータが代入されるのか)や分岐した上で条件などによってどのようにコードが実行されるか(条件によってAの道に行くのか、それともBの道に行くのか)、あるいは条件式を用いた繰り返し(いつまで繰り返しを実行し続けるのか)など、ということは、プログラムが実行するたびに変わります。
プログラマは、ソースコードとしての「静的に変わらない部分」や、データ処理や分岐条件としての「動的に変わる部分」を比較し、上手くバランスを取りながらプログラムを組みます。
たとえば、同じプログラムでも、「Hello, World!」しか出力できないプログラムと、引数として与えた$xを表示するプログラムでは、ソースコードが全く違うものになります。
プログラミングとは、そうした静的かつ動的な場合の変更を支配しながら、どれくらいの場合に対応できるプログラムを書くか、ということなのです。
また、コンパイル時に決まることと、実行時に決まることは、違う場合があります。たとえば、ソースコードをさまざまなプラットフォーム向けに#ifdefでマクロ的に処理するのはコンパイル時に決まりますが、ファイルを読み込んだりコマンドラインオプションを与えたりするのは実行時に決まります。拡張性豊かなソフトウェアを作りたい場合は、設定ファイルの読み書きやプラグイン機構も考えなければなりません。逆に、そうした動的処理にしないことで、単純な動作を可能にすることもできます。
関数を上手く使うことで、汎用的な自動実行処理を記述することもできます。たとえば、2つの引数を取ってその足した和をreturnで返す関数などを作ることができますし、変数にファイル名などを取って、汎用的なファイル処理を行う「バッチ関数」を作ることもできます。
ブロックや無名関数が使える場合、あるいは抽象クラスの抽象メソッドを継承したり、インターフェースを実装したりする場合などは、「後で誰かが勝手に動作を変えられるようなプログラム」を書くこともできるでしょう。動的なメモリ管理も、こうした分野に含まれます。特に、どれくらいの大きさの配列を用意すれば良いか分からない場合は、malloc()を使ってヒープ領域に配列のデータを確保します。リストのようなデータ構造を使って、配列ではない形でコンテナを作ったり、イテレータでコンテナを処理したりすることもできます。
これらは全て、「静的に変わらないものと動的に変わるもの」という考え方であると言えるでしょう。そして、プログラミング言語の提供する多くのことは、静的・動的な動作を「どれくらいプログラマブルにするか」ということに尽きるでしょう。ライブラリAPIや、テンプレートやジェネリックを使うことで、もっと動的に、もっと変化可能で汎用的なプログラムを作れるでしょう。
プログラミングはアプリケーションの設計ですが、プラクティスばかりに気をとられるのではなく、「基本はあくまでも計算」であると言うことを忘れないようにしましょう。
たとえば、ソートやリストのような設計についてそれが言えます。
プログラミングとは、「計算していく手順を書く」ものです。よって、計算が表現できれば、それできちんとアプリケーションの設計が出来ます。
この時、関数やクラスの設計も、同様に「計算」であると考えましょう。C/C++を使っていると、ありえないほど覚えることがたくさんありますが、基本は計算です。クラスは計算するための「要素を記述」することです。クラスにおいてメンバ変数とメンバ関数を上手く設計するために必要なことは、「どのように計算すればプログラムとして成り立つか」ということに他ならないのです。
オブジェクト指向でプログラミングを行う上で優れた点が、「メンバ変数の共有とメソッドの継承」です。これによって、複雑なプログラムであってもC++やJavaで動くコードに直すことができます。あるいは、インターフェースの実装などに見られるように、「別の人が作ったクラスでも、他の誰かが既に作ったものと同じように操作出来る」ということは功を為すことがあります。C++やJavaはあくまで計算を助けてくれるだけです。実際に計算手順を書くのは、プログラマという人間が行ないます。
また、関数やメソッドの設計は、「与えられた情報からどんな処理を行うかを決める」という点で、「情報」であると言えます。これは、プログラミングの全てに言えることで、「どこにデータを保管し、どこにデータを(整形などして)表示するか」ということが、CUIでもGUIでも言えます。この時、どのようにオブジェクト同士のやりとりを行うのか、どのようにオブジェクトを作成・破棄・追加・削除するのか、ということがプログラミングの課題になります。プログラムの中では、データを常にどこかに保持しながら、たくさんのオブジェクトを作成・破棄するのです。そして、そのオブジェクトが関係性を持って、オブジェクトを別のオブジェクトが操作し、オブジェクト指向プログラミングは成り立つのです。
フロントエンド的な関係性だけではなく、目的を持ってバックエンドも作りましょう。ここで言えることは、「どれくらいのレベルでのコンポーネントを目指すのか」です。多機能を目指すのか、高速・軽量を目指すのかで、仕様と実装は変わってきます。良い例として、2ちゃんねる専用ブラウザのOpenJane Doeがあります。これはあえてIEコンポーネントを使わず独自の軽量なビューを実装することで、高速かつ軽量な2ちゃんねるブラウザを実現しました。そのように、「どれくらいの機能レベルを目指すのか」が、バックエンドの開発の基本となるでしょう。
あとは、概念と方法を考えます。UNIXのファイルシステムの実装として必要なのは何か。それから、インターネットからファイルをダウンロードして表示させるために、必要な概念は何か。概念が分かったら、どのようにすればそれを実現させることができるか。方法として何が考えられるか。そして、どれくらいのレベルでそれを実装するか。そこまで考えて、書けばおそらく、目的のプログラムが見えてくるでしょう。
計算のクラス化は難しいように見えるかもしれませんが、やるべきことは単純です。必要な情報をデータとして保持するようにデータ化し、必要な処理をメソッドにし、場合やイベントに応じて実行すれば良いのです。FTPクライアントなど、決まった処理を実行するプログラムであれば、それだけで大部分は作れるでしょう。難しいのは、高度な計算が必要なデータベースやコンパイラですが、これも、1つ1つの必要な機能を細分化し、部品化し、その部品と部品を繋ぐように作っていけば、きっと作れるはずです。
プログラミングの基本は、「必要なたくさんの処理を、簡単にできるようにすること」です。たとえば、文字列の解析を行うのであれば、文字列の解析をする部分であるparse()関数を作って、解析を行いますが、この中で必要な処理の全てを書いて、parse(bytecode, in)と記述したら、簡単にinをbytecodeにパースできるようにします。これが、プログラミングの基本です。たくさんの処理を1つの関数にして、その関数を使うことで何度でも簡単に同じ処理を行えるようにする。あとは、ただそうした関数群を作ります。たとえば、bytecodeをアセンブリ言語に変換するconvert()関数を作り、convert(out, bytecode)などとします。ここで、parse()関数とconvert()関数の中で、変数定義をmvに変換するvar_convert()関数、if文やfor文や関数定義を機械語に変換するif_convert(), for_convert(), func_convert()などを作りましょう。実際のコンパイラの開発では、トークンの解析にはlexやyaccを使い、三番地コードと呼ばれる形式のコードに変換していきます。この中で、きちんと定義する必要があるのは、中間コードであるbytecodeオブジェクトをどのような形式のデータ構造にするか、といったことになるでしょう。
自分は、経験も無いのに適当なことを言ったかもしれませんが、このように作っていくのをプログラミングの作業だと思えば良いでしょう。データベースやUNIXのファイルシステムでは、「排他制御」を行う必要があるでしょう。これは、あるデータ構造を持ったデータが操作中の時、その間別の人間から操作できないようにすることです。そして、ファイルシステムであれば、ファイルを識別するiノードやデータ自体を格納するブロックをどのように保管するかが重要になります。この場合は、関数をただ作るというよりも、設計をどのようにして、どんなアーキテクチャで実現していくか、ということが重要になるでしょう。ディスクにファイルアロケーションテーブル(FAT)とセクタごとのデータを書かなければなりません。
やっぱり、プログラミングの基本はテキスト処理だと思います。グラフィックやネットワークに比べて面白くないかもしれませんが、テキスト処理を極めるところから、プログラム開発を始めましょう。
また、科学技術計算とゲームは、馬鹿にしてはいけません。それらが本来のプログラミングだからです。
テキスト処理を極めてください。そして、科学技術計算とゲームを作っていれば、それで正しいプログラマです。
オブジェクト指向をやる上で、良く分からないのが「クラスをどのように記述し、どのように利用するか」です。
ですが、言ってしまえば、クラスをnew Class()して作成するのは、アプリケーションの起動や実行と良く似ています。
たとえば、ブラウザクラスであるBrowserクラスを作って、ブラウザオブジェクトをブラウザクラスからnew Browser()してインスタンスを作成するのは、ブラウザアイコンをダブルクリックしてブラウザが開くのと良く似ています。
クラスの方で、格納するための変数やその変数を変え、プロパティを取得・設定するためのプロパティやメソッドを作るのは、アプリケーションであるブラウザに設定画面から設定するのと同じです。
そのように考えれば、オブジェクト指向でクラスを設計する、ということが、ブラウザのような巨大アプリケーションの設計と同じである、ということが分かります。つまり、プログラムの集合体として、全体の巨大なプログラムを開発する、ということです。クラスとはプログラムの部品です。インスタンスの作成は、プロセスの作成と全く同じなのです。
そういうわけで、クラスの設計は専門別のアプリケーションを作るという風に考えてすればできる。だが、デザインパターンのようなものもあるし、設計にはさまざまな技術的要素があるので、一概には言えない。たとえば、PHPであればデータベースの管理を行うクラスを作ったり、ビューを管理したり、index.phpから全てのPHPプログラムに橋渡しをしたり、と言った具合である。
オブジェクト指向も参照のこと。
言語とライブラリの基本が分かったら、クラス図を書きましょう。
クラス図とは、クラスとクラスの関係を考える、モデリング(図)の一種です。
それぞれのクラスの属性(メンバとなる変数と関数)とクラス間の関係をここに書きます。
クラス図が書けるようになったら、あなたはもうれっきとしたプログラマの仲間入りです。
実際にプログラムを開発してみましょう。コンパイラでもブラウザでもなんでも構いません。きっと良いプログラムができると思います。
たとえば、ブラウザを作るのであれば、それぞれのキーワードに応じたクラスを作って、そのクラスのメソッドを使うようにインスタンスを作ることになるでしょう。この時、共通の親クラスやインターフェースを作ると、Javaらしいプログラムになるでしょう。
また、JavaScriptからDOMで操作することになるのであれば、簡単なコンパイラも作る必要があります。これも、HTMLと同じように、キーワードごとにクラスを作れば、きっと作れるはずです。
また、ブラウザの制御として、ブラウザのGUIクラスも作りましょう。このGUIクラスには、ウィンドウにHTMLファイルを読み込んだり、ナビゲートしたり、通信を行ったりする機能が必要となるでしょう。
あとは、HTMLとCSSとJavaScriptを解析する「パーサー」が必要です。そこまで分かったら、もう、できたようなものです。あとは、それぞれのクラスで、グラフィックスをいかに描画するか、という処理を抽象的・汎用的に書くことになるでしょう。
唯一面倒くさい点は、OSによって異なる整形されたグラフィックスの表示の仕方です。そのために、ひとつバッファを描画するクラスを作りましょう。そして、このクラスを用いてグラフィックスを表示します。リアルタイムな表示にするのは難しいかもしれません。CSSの表示もこの中でCSS専用のクラスを作って使いましょう。これは「レンダリングエンジン」に相当します。
要するに、オブジェクトツリーを決められた通り表示するレンダリングエンジンを作る必要があります。このツリーをDOMと同じにすれば、JavaScriptからも操作しやすいでしょう。また、タグごとにクラスを作ると言いましたが、最低限のクラスだけを作って、あとはリソースファイルなどで対応することもできます。あるいは、線と文字と背景とレイアウトを表示するそれぞれのクラスを作った上で、点線や色のついた文字や線や背景などは派生クラスで対応しても良いでしょう。そのようにすれば、きっと簡単なブラウザを作ることができます。
タグのクラスとして必要なのは、全ての親になるGeneralTagと、SpanTagとDivTagが必要になります。ここから、PTagのようなものを作ります。そして、TagTreeクラスを作って、HtmlParserクラスがTagTreeを生成し、DomクラスがTagTreeをDOMから制御できるようにします。あとは、CssPerserクラスでCSSを解読し、ここにLayoutクラスを入れて線や文字・背景の色やレイアウトを行います。ViewerクラスでTagTreeオブジェクトをLayoutクラスとともに表示できるようにしたらビューは完成です。BrowserWindowクラスを作って、ブラウザからビューを操作できるようにしましょう。
ここで、それぞれのタグクラスが何をするのかが問題ですが、単純に、サイズや色や文字情報などを保管します。そして、CssParserで解析した「どんなオブジェクトは何色」という情報に基づいて、Layoutクラスが一気に表示するようにします。
良く考えると、オブジェクトを識別するためのオブジェクトIDが必要です。また、一気にメモリに確保するとメモリを大量に食うため、今作業しているそのそれだけに集中できるようなメモリアロケーション機能が必要になるでしょう。
初心者がプログラミングでやりがちなこととして、「最初から優れた仕様を作りたがる」というのがあります。
最初からアイデアばかりが先行して、自分の技術がそれに見合わず、結果巨大な仕様をいきなり実装しようとして、バグだらけで動かないものになって、そこで終わりになるのです。
MulticsやHurdのような経験から言って、最初から巨大な仕様や賢い仕様を求めて作ろうとすると、結果プログラムは動かないものになります。
では、どうすれば良いか。まず、最初はシンプルなものを目指しましょう。ただ動けばそれで良く、動くことだけを重視して、システム全体をシンプルにし、相互依存や強い結合をせず、疎結合のシステムを作りましょう。
そして、動いたらそれに満足して、とりあえずそこで終わりにします。あとの改良点は、少しずつ、全体のバランスを崩さないように、慎重に行います。ですが、プログラムが巨大になってきたら、分割して、それぞれの部分でテストを行ったり、少しずつ関数を増やすことで機能を増やしていきます。
シンプルなシステムほど優れたシステムはありません。UNIXが古びているのに今も使われるのは、シンプルできちんと動くからです。それ以上の理由はありません。
リーナス・トーバルズが好まれる理由もそこにあって、最初は単なるターミナルエミュレータを作っただけだったものの、Minixを改良するうちに、改良したコードだけで動くようになりました。最初はとてもシンプルなカーネルだったものを、みんなで改良しました。ですが、みんなで改良しただけでは、Linuxカーネルは生まれなかったでしょう。リーナスが何を作るべきで何を取り入れるべきか、きちんと考えて少しずつ改良した結果がLinuxです。そこが良かったから、みんなリーナスが好きです。IBMが嫌われるのは、最初からドンと大きなものを提示するからです。Microsoftも同じで、Appleも同じです。最初から大きなものを作っても、支持は得られません。Googleはそういうところがやり方がうまいのでしょう。GNOME3やKDEも、同じ理由で全く使われません。最初はXfceのような小さなソフトから作らなければいけないのです。
僕は、プログラミングにおける大部分とは、どこまでを静的にするのか、どこまでを動的にするのか、そしてそれがいつ決まるのか、ということだと思います。
たとえば、
・プログラムに直接書く
・変数に代入する
・ポインタとmalloc()で記憶領域を動的に作成する
・引数に渡す
・returnで戻り値に返す
・ポインタで共通の構造体データを操作する
・オブジェクト指向を使う
・コンパイルオプションで変わるマクロを記述する
・実行オプションを渡す
・設定ファイルを読み込む
・関数を別のプログラムからライブラリ関数・APIとして呼び出すように設計する
・CORBAのような分散コンポーネント技術を使う
・LispやPythonやXULのようなプラグインや拡張によって、プログラム自体を拡張する
・ソースコードそのものを書き換える
・継承によって、元のプログラムはそのまま残したまま、新しい機能を追加する
など、さまざまな方法で、どこまでを静的にするのか、どこまでを動的にするのか、それらがいつ決まるのか、ということがプログラム設計の巨大な柱となります。
それに、プログラムを記述する上で、どこまでを意図して汎用的にするのかは、関数の汎用性を高めます。
また、これと良く似た意味で、「移植性」が存在します。どこまでをそのプラットフォーム固有のものにするのか、それともさまざまなシステムに広く対応できるようにするのかは、UNIXやJavaなどにおける大きな課題でありました。
基本的に、動的にすればするほど汎用性は上がり、使うのも作るのも楽になりますが、バグの混入する恐れが強くなります。逆に、静的にするとシンプルにはなりますが、逆に動かないことやメモリリークが発生することも多くなり、「細かな管理技法」が必要になります。
C言語で使われるポインタは、変数のアドレスを使ったアクセスの手法です。
ポインタを使わずに変数を宣言した場合、宣言するごとに別の領域に変数ができます。
これに対して、ポインタを使うことで、変数は「アドレス」を指定してアクセスすることができます。
xとyとzを普通に宣言すると、それぞれに別の領域ができますが、yやzという名前でもxと同じアドレスを参照したい場合に、ポインタを使うことができます。
C言語(配列とポインタ)も参照のこと。
2017-10-15に関連する内容があります。
もし、あなたがアルゴリズムを研究したいのであれば、フローチャートを描くことが効果的だと思います。
フローチャートとは、処理の流れを分岐構造や反復構造などの制御フローによって表現した図で、条件と変数の演算の流れを大まかに捉えられます。
アルゴリズムを描く場合は、コードを書く前に、フローチャートにすることも、プログラムの設計の上で効果的だと思います。
また、プログラムとは何であるかを一言で言ってしまうと、「ルーチンを用意し、そこにデータを渡すこと」ではないかと思います。
たとえばウィンドウシステムであれば、ウィンドウの中にフォームやダイアログを表示する機能が必要ですが、これを作るためには、「どんなフォームの画面データが来ても、適切な大きさでそれを表示する」ことです。
この時、具体的にどんなフォームであるかなどは考えません。することは、どんなフォームであっても対応できるように、あらゆるフォームを表示することのできるルーチンを書くことです。
サイズやタイトルなどのフォームに属するデータも同様です。どんなサイズであっても、あらゆるサイズでウィンドウを表示できるようにルーチンを用意し、そのルーチンに対してサイズの情報を与えれば、どんな場合でも動くようにするのです。
メッセージなども同様で、どんなメッセージでもフォームとやりとりができるようなイベント通信機能を作ります。具体的なメッセージについては、このメッセージ通達ルーチンを「作る時」ではなく「呼び出す時」に、さまざまなメッセージを「具体的に送る」ことができるようにします。そして、今度はこのルーチンを使って、さまざまなメッセージを具体的にイベントの通達やフォームの返答に対して対応付けし、最終的にサイズを変えられるようにしたり、フォームを再描画したり、クリックやキーボード入力などのイベント機構を作ります。
要するに、「どんなフォームの画面でも、どんなサイズやタイトルでも、どんなイベントメッセージでも対応できるような、正しいルーチンを書く」ことができれば、それだけでプログラミングはできるのです。
プログラムの設計とは、このように、「どんな場合でも対応できるようなルーチンを書く」ことです。実際に具体的なデータを操作することはなく、ただ単にあらゆるデータを操作できるようなルーチンを書けば、それでプログラムは完成です。
後日注記:このように「汎用的などんな場合にも対応するルーチンを作る」のは、ウィンドウシステムに限ったことではありません。たとえば、GTKやQtのツールキットならば、イベントを回収する「イベントを回収するコアルーチン」や、ツールキット全てに適用する「グラフィックスの適切な配置と表示のコアルーチン」などは、特にオブジェクト指向であればクラスの継承階層を用いて、「ひとつの汎用ルーチン」を書くことで実装できます。ボタンであれ、ラベルであれ、配置に必要なルーチンやマウスクリックなどのイベントに対応するためのルーチンは同じ「コアルーチン」を作れば作れます。よって、フォームへの配置は「どんなウィジェットでも配置して表示できるルーチン」を作り、マウスなどのイベントは「フォーム上のマウスクリックやキーボード入力をそれぞれのウィジェットに適切に配送するルーチン」などを作れば作ることができます。その上で、これらのルーチンをオブジェクト指向のAPIで、「ウィジェットの具体的な作成とイベントに対するコールバック関数の登録をプログラマが行うことのできるフレームワーク」を作る必要があります。
後日注記:また、印刷のためのルーチンでも、どんなページでも印刷できるようなプリンターへグラフィックスを送るルーチンを作れば作れますし、インタープリタやコンパイラなら、一行の命令をどのように解釈するかを適切に判断する「文を解読するルーチン」を作れば作れるでしょう。実際には、ウィンドウシステムやツールキット、また印刷やインタープリタ・コンパイラには適切な「開発セオリー」があるため、「このように作ることが絶対に正しい」というものではありません。
後日注記:ウィンドウを表示する場合において、X座標とY座標によるひとつの点を示す構造体が2つあれば、そこから四角形を表示できます。また、3次元のコンピュータグラフィックスにおいて、「画家のアルゴリズム」と呼ばれるもっとも単純な陰面消去のアルゴリズムがあります。これは複数の画像を重ね合わせる時に、どこが見えていて、どこが隠れているかを処理するためのアルゴリズムです。