プログラミング作法に関する世界観4(テクニック)です。
自分の書いた「エリカの技術・芸術日記」2021/08/15より。
プログラミングの本質、それは「思ったよりも簡単である」ということだろう。たとえば、100個の返信を表示するには、1個の返信を表示するコードを書いて、それを100回繰り返すだけだ。ほとんどのプログラムは、簡単に書けば簡素に書ける。ブラウザなど、DOM解析器を作っただけにすぎない。
プログラミングをする上で、どんなプログラムを書いたらいいか分からないとか、アイデアが浮かばない時は、身近なハードウェアやOSの機能から考えてみましょう。
ハードウェアには、CPU、メモリ、ストレージデバイス、マウス、キーボード、モニター、ネットワークインターフェースなどが付属しています。
また、OSには、ファイルシステムやマルチタスク機能などがあります。
このようなハードウェアから考えると、演算、ジャンプ、記憶領域、IO処理、通信、ファイル処理などが挙がってくると思います。
また、数値情報から考えると、文字列の処理、数値の処理、マルチメディアデータの利用などが考えられるでしょう。
ほかにも、ミドルウェアをOSの一部と考えることもできるでしょうし、身近なネットワークサービスから考えてみてもいいでしょう。会社で行う経理や事務の業務効率化から考えてみてもいいでしょう。
ほかには、普段使うアプリケーション、たとえばワープロ、表計算、Webブラウザ、メール、あるいはネットワークインフラであるサーバーなどから考えてみてもいいでしょう。
このような中からアイデアを出してください。
僕が、初心者が練習するのに最適で、最初に取り組むべきだと考えるプログラムは、条件式に応じてフラグを設定し、動き方を変えるプログラムです。
このようなプログラムは、UNIXのコマンドプログラムとしては一般的であり、簡単であり、応用可能性や自分で機能を付け加えることができるという点で、おすすめです。
CUIのプログラムであれば、コマンドラインオプションを解析して、それに対してファイルや標準入出力などに処理を行います。
GUIのプログラムであれば、フォームに入力された情報から、さまざまな処理をファイルや画面上のコントロールに行います。
このスタイルで開発すると、プログラムは大きく二つの部分に分けられます。まず、オプションを解析してフラグを設定する部分、もうひとつは具体的な処理を行う部分です。
UNIXスタイルのオプションの解析をするには、自分で文字列処理を行うこともできますが、それが難しい場合はgetopt()やgetopt_long()といった専用のAPIがあります。コマンドオプションだけではなく、ファイル名の指定や与えられた標準入力を解析することなども重要です。
また、処理を行う部分では、FILEポインタを使ってstdioで処理を行ったり、あるいはソケットなどを使えばネットワーク処理も可能です。if文による条件分岐やfor文によるループ、変数へのデータやフラグの代入などを上手く使い、解析や操作を行うルーチンを書きましょう。
実際にプログラムを開発する場合は、デバッグやテストのために、あえてフラグやデータが格納される変数にコードの中で具体的な「テスト用の値」を代入し、フラグがどの値であってもきちんと意図した動作を行うように確認しながら開発するといいでしょう。たくさんのフラグと機能を付け足すことで、自分のプログラムがどんどん進歩していき、UNIXの標準ツールにはないさまざまなオリジナルの機能を付け足すことができて、「プログラミングを行う楽しさ」を感じることができると思います。
GUIで行う場合は、さまざまなコントロールを使うことで、高度なアプリケーションを開発できます。ダイアログボックスなどのOSのコモンコントロールを表示したり、タブコントロールの中に新しいコントロールを作成したりなど、可能性は無限です。
もし機能が増えてきた場合は、ひとつひとつの機能をサブルーチンや関数に分割します。同じデータを複数の処理で共有したい場合はクラスを使います。また、ほかのプログラムでも同じ機能を使いたいとか、一度作ったサブルーチンを再利用したいなどといった時は、ライブラリAPIとして分割します。
このような条件式によるプログラム以外にも、イベント駆動によるプログラムがあります。これはGUIやサーバーデーモンなどの開発に多く、どちらかというとバッチ処理というよりはマルチスレッドなどを用いた「なんらかの外部からの呼び出しに反応するプログラム」となります。この外部からの呼び出しを「イベント」と言います。イベントは、分かるとプログラミング中級者になれる、重要な概念です。条件式に加えてイベントを活用することができるようになると、一流のハッカーに近づいたと言えるでしょう。
以下のリソースは、Rails をはじめようの冒頭でリンクされている、プログラミング関係の厳選リソースです。これらを眺めるだけでも、大いにプログラミングの勉強になります。必ずしも書籍を買いあさる必要はありません。
プログラミングには、論理的に考える力が必要です。なぜなら、制御が大切だからです。
プログラミングをする上では、その場合その場合に応じて、どのような挙動をさせたいかを考え、その挙動にするためにはどのような記述をするのが正しいのかを考えなければなりません。
また、プログラミングにおいて重要なのは、「何を実行するか」だけではなく、「どのように実行内容を制御するか」も含まれます。
仕様を知る上で大切なのは、「どのような利用のされ方を想定して提供されているか」を知ることです。システムがどのように利用されるのかということをユースケースと言います。想定されたユースケース通りにAPIを使う場合と、そうでなく「想定外の方法」を使ってAPIを使う場合では、まったくやり方も難易度も違ってきます。
パソコンには、得意なことと不得意なことがありますが、プログラミングも同じで、簡単にできることと簡単にはできないことがあります。
このような側面があるため、プログラミングを行う上では、「論理的に考える知性」と「仕様や想定したユースケースを詳しく知っている知識」が求められます。
僕について呆れた事実として言えるのは、このようなプログラミングのページを作りながら、僕は人並みにプログラミングがちっともできていません。
その理由は、僕は「覚える」「計算する」「判断する」ということが苦手だからです。
ですが、僕は仕組みを知ればその原理の通りに作ることは得意ですし、実際のシステムを見て自分の頭で考えるのは得意です。
たとえば、Gentoo Linuxを見ていて、僕はプログラムがPortageによってコンパイルされるのを眺めたり、設定をテキストファイルで行うのを知って、「プログラムとデータの関係」が分かりました。
つまり、プログラムはプログラムだけでは成立しません。外部にあるデータを読み書きして、はじめてプログラムが成立します。これはプログラムだけではコードや値が変わらなくても、データを読み書きする中で変化する値を外部とやり取りすることができるということです。
なので、僕の教えていることを知って、人並みにプログラミングができることは期待しないでください。この文章は僕が分かったそうした「僕が独自に考えた事実を知ることができる文章」です。この文章を読んでも、ほかのプログラマと同じようなプログラマにはなれません。僕の分かったこととまったく同じことがただ分かるだけにすぎません。
プログラムというのは、データをどこかに保存して、そのデータに対して通信や処理を行うことで成り立ちます。Linuxであればプログラムが終了しても残るような設定やデータファイルをどこかに必ず残しておきます。WebであればWebサービスを通じてデータベースの中のデータとやり取りします。このように、プログラムはデータがあって始めて成り立ちます。
パソコンの基本は、僕は「自動処理」だと思います。
ファイル処理であっても、ネットワーク通信であっても、グラフィックス処理であっても、基本は自動処理です。
パソコンは、プログラムとデータを人間の手を介さずに自動で処理することのできる、「自動処理装置」です。
この自動処理ですが、変数を与えることで、自動処理の内容をさまざまに変えることができます。
if文やfor文を使うのであっても、さまざまなパラメータを与えることで、どんな場合でも対応できるようにし、さまざまな場合において違った処理をさせることができます。
これが、まさにプログラミングです。プログラミングとは、自動処理を記述し、どんな場合でも対応できるようにして、さまざまな場合において自動でパソコンに処理させることです。
プログラミングのコツは何か、それはずばり「プログラムを小分けにすること」です。
つまり、「完璧なひとつのプログラム」を作るのではなく、「複数のプログラムに小分けにして、その集合体としてシステムを設計する」のです。
たとえば、文字を検索するプログラムなら、検索アルゴリズムとして二分探索木による二分探索が必要になるかもしれません。
この場合、まず、二分探索だけを可能とするプログラムを作り、何度もテストしながら、二分探索だけが上手く動くようなサブルーチンを開発します。
そして、この二分探索をファイルオープンしたデータファイルに適用するようにし、そのために一単語ごとに二分探索をかける別のサブルーチンを作ります。
このように、ひとつひとつの機能を洗い出した上で、その機能ひとつひとつを「小さなプログラム」としていき、その集合体としてプログラムを開発するのです。
カーネルを作るのであっても、コンパイラを作るのであっても、あるいはウィンドウシステムやウィンドウマネージャを作るのであっても同じです。ひとつひとつの「もっとも小さなプログラム」を作り、それらの「プログラムの集合体」としてコードを書いていくのです。
ちなみに、これはC言語の話です。しかしながら、Javaでも同じです。ただし、Javaであれば、オブジェクト指向の機能が使えます。ここでも、クラスをひとつひとつ作っていきますが、最初は最低限の機能を持つ最小限のクラスを作り、そのクラスの集合体として全体のプログラムを作っていきます。
また、プログラミングする時に注意することとして、あくまで主体はハードウェアであり、ソフトウェアはそれを使うだけだ、ということが言えます。
たとえば、YouTubeを作る時に、主役はモニターであり、動画を再生するために必要なのはGPUです。ネットワーク通信はパケットでケーブルを使ってTCPやUDPで行うのであり、データをやり取りするためにメモリを使います。
その上で、総合的なことや専門的な応用を行うために、CPUとプログラミング言語を使ってプログラミングを行います。たとえば、ストリーミングといって、データをダウンロードしながら少しずつ再生します。
ある意味、YouTubeであれば、HTML5やWeb系の言語・フレームワークを使うことで、簡単にデータベースを参照・変更するだけでも作れるはずです。むしろ、プログラミングというよりもUIデザインに近いかもしれません。
プログラミングを行う際、CPUの命令を想定して、どのようにCPUを使えばいいか頭の中で考えると良いかもしれません。基本的に、C言語であろうとJavaやPythonであろうと、if文やfor文はCPUに対するジャンプ命令などに置き換わります。CPUをどのように分岐・反復しなければならないのかが分かったら、それをプログラミング言語のソースコードに直すことも簡単です。
if文やfor文という「具体的な文」を考えるよりも、「ここでテキスト処理をしないといけない」とか、「ここでネットワークからダウンロードを行う」とか、そういうブロックごとに「記述すべき処理」を想定し、それを上手く変数すなわちメモリ上のデータと、フローすなわちルーチンで実現するのは、難しくはありません。テキスト処理であれば、ヌル文字と比較しながらひとつひとつの文字を編集したり、繰り返し表示すればいいのであり、ネットワークであればソケットを使えば良いのであり、考えることは多くありません。それを、小さな機能ごとにコードに表現すれば動くものはできます。
しかしながら、あくまで主体はハードウェアです。「ハードウェアを上手く利用する手段」、それがソフトウェアであり、そのソフトウェアを目的を持って作ること、カーネルやシステムコールなどのOSの機能を用いて、ハードウェアをそのプログラムが動くように利用することがプログラミングであると言えます。
プログラムを見ていて、「とても複雑なことをやっている」と思われるかもしれませんが、複雑な機能を作る時に必要なのは、「単機能なものを作って組み合わせること」です。
たとえば、コメントを100とか200とか表示するツイッターのコメント表示機能は、実際は1つのコメントを表示する機能をつければ、あとは100回繰り返して表示するだけです。
実際には、100回という個数はプログラム内部には存在せず、その時その時の数に柔軟に対応した「汎用コメント一覧表示機能」から、「ひとつのコメントを表示する機能」を呼び出します。
このように、複雑な機能を持っているかのように見えるアプリケーションは、本当は単純な単機能を組み合わせることで動いています。
パソコンを単純化してしまうと、数と論理の計算を行っているだけです。
サブルーチンや関数、入出力などの機能も確かにありますが、それは関数に変数を送り出しているだけと考えると、残るのは純粋な「数の計算」と、if文やwhile文の条件式に書くような「論理の計算」(と論理式や比較を使った分岐と繰り返し、すなわちジャンプ)、そして「変数への値の格納と参照」であると言えます。
プログラムを作るということは、すなわちこうした数と論理の計算をパソコンにさせるということです。プログラマの仕事とは、パソコンを使って数と論理の計算をさせるようなプログラムを書け、ということです。
また、最後の変数すなわち「記憶」についていえば、メモリ上の記憶だけではなく、ハードウェアのストレージにあるファイルやディレクトリ、あるいはネットワーク通信上の情報やデータベースに格納された情報も記憶であると考えられます。このようなコンピュータにおいて利用可能なデータ資源のことを、コンピュータ用語では「リソース」と呼びます。プログラムは、数と論理の計算だけではなく、リソースの管理や取得・投稿や送受信も行います。
プログラミングについて、現在のOSでは、むしろデザインの方が重要かもしれません。
昔の情報端末のように、情報となる入力をプログラムに与えて、与えられた情報をプログラムに基づいてCPUが処理し、その結果となる情報を出力としてユーザーに提示する、という「コマンドラインプログラム」は、今でも、プログラミングの入門では最初に出てくる課題です。
これでも、OSとその言語のライブラリの機能を使えば、GUIに匹敵する、あるいはそれ以上にスマートなプログラムを書くことはできます。
ですが、今はGUIの時代です。GUIの時代では、このようなルーチンを記述するだけではなく、GUIのデザインが重要になってきます。
プログラムを使う上で、ユーザーはソースコードの中身など気にしていません。気にしているのは「どう使うのか」というデザインだけです。どう使うのかということがはっきりと分かるデザインで、期待する結果と処理内容さえ返ってくればユーザーは満足します。
また、GUIだけではなく、今はWebの時代です。Webエンジニアは、フロントエンドエンジニアとバックエンドエンジニアに分かれています。フロントエンドでは、HTML/CSS/JavaScriptを用いて、「ユーザーが目に触れる部分」を開発します。これに対してバックエンドエンジニアは、フロントエンドの裏側で機能するバッチ処理やデータベース処理といったプログラミングの領域を担当します。
もし、フロントエンドエンジニアになるのであれば、バックエンドエンジニアよりもデザイン能力に長けた人でなければならないでしょう。
僕は、プログラムの設計とは「仕掛け」を作ることではないかと思います。
たとえば、インタープリタを作る時、背後にプッシュ・ポップのできるスタックを置いたり、自動的に字句と構文を解析して、パースツリーを作り上げるような仕掛けを作ります。
この仕掛けが上手く働くように、関数やサブルーチン、データ構造を作り、「作ったものを使って作っていく」という過程を書きます。
システムを作るとは、簡単に言えば仕掛けを作るということです。どのような仕掛けを用意すればその技術が実現出来るのか、それを考えながら全体の設計を考えると良いでしょう。
Windowsなどの影響で、アプリケーションを使ったり、ファイルシステムやネットワークを使ったりする、「OSを使うことがパソコンだ」と思っている人が、最近は多いと思います。
ですが、パソコンは使うだけではありません。パソコンとは、「プログラミングをする機械」なのです。
プログラミングは、文字列ばかり記述して、動いても面白くないし、きちんと書いたつもりでも最初から動くものは作れないため、「プログラミングはつまらない」と思う人が多いでしょう。
ですが、言ってしまえば、「プログラミングをしなければ、パソコンを使っているとは言えない」のです。
たとえば、科学技術計算をしたり、ゲームを作ったり、Webサービスを構築したり、フリーソフトを作ったり、といった全てのパソコンの関連分野は、プログラミングによって成り立っています。
決して、プログラミングは楽しいだけの作業ではありませんが、プログラミングをやりたいのであれば、「実際のプログラミング言語で書いてみる」ことです。それは、「OSを使うということとは全く違う」ということを知っておきましょう。OSのような優雅でかっこいいソフトウェアは最初から作れません。最初はPythonでHello, Worldが表示されるだけであり、「なんでこんなものが楽しいのだろう」と今のパソコン世代は思うと思います。ですが、テキスト処理やファイル処理、ネットワーク処理や人工知能などを通して、「なるほど、こんな感じにすれば動くのだな」と思うことで、「自分でコードを発明できる楽しさ」が見えてきます。パソコンは、簡単な計算でもプログラミングすることで実現します。「フロントエンドからコアのバックエンドまで全てを作って、優雅に動く喜び」が分かったら、あなたはプログラマとして、そしてハッカーとして初めの扉を開けたことになるでしょう。
プログラミングをやる上で重要なのは、「焦らないこと」と「良く考えること」です。
たとえば、毎日継続してプログラミングを行う必要はありません。いったん動くコードを書いたら、そこで終わりにして、ひとまず休憩を入れて思索し、何か新しい発想をしたらその時に追記すれば良いのです。
継続して取り組むことより、ゆっくりと思いつきながら、深く考え広く調べながらやることです。
最初のプログラムを作って、次のプログラムを作るまでに、1か月かかっても良いのです。その間の期間は適当に過ごしましょう。考えていれば、何か新しいアイディアが思いつくでしょう。毎日思いつく必要はありません。自分のやりたいことだけを自分のペースでやっていけば良いのです。
Windowsプログラミングのつまらない点は、いつまで経っても文字列ばかりで、グラフィックスのかっこいいウィンドウシステムは作れそうにないことです。ですが、そうでもありません。Windowsと言っても、パソコンのプログラムにすぎず、つまり、「何万行の文字列」が動いているだけにすぎないからです。Windowsは、ファイル処理やウィンドウシステム、ツールキットのような部品が組み合わさって動いているように見えますが、実際に動いているのはプログラムにすぎません。Windowsも、ソースコードのレベルでは、ただの文字列にすぎないのです。これを「コード」と呼びます。
そう、全てのソフトウェアの裏側にはコードがあります。OSだけではなく、ファイルマネージャやウィンドウシステム、ウィンドウマネージャ、テキストエディタ、ツールキット、ブラウザ、サーバー側のWebサイトやHTTPサーバーまで、全ては裏側にコードがあり、それが実行されていることで、コンピュータ環境の全ては実現されています。「全てコードである」と思えば、理解できると思います。
ある意味、会社で何時間もプログラムを書き続けるのは、とてもハードで体力の居る作業ですが、オープンソースは片手間で、好きな時に作れば良く、またたくさんの人々が貢献しているため、みんなと協力すれば楽にプログラムが作れてしまいます。そのことが、Linuxのようなオープンソースの開発ソフトウェアを品質の高いものにしているのではないでしょうか。会社でデスマーチをするのは苦労が伴います。馬鹿なエンジニアの書いたコードを延々と直さなければいけません。オープンソースならば、自分だけで自由にでき、コミュニティでたくさんの人々の成果を自由に得られます。
オープンソースでコードを公開することで、全世界のインターネットユーザー全員にコードを公開することになりますが、この簡単な方法で、ソフトウェアの品質を高めることができます。ただコードを公開するだけで、人々はコードを読むことができます。それだけで爆発的な発展が起きる、それがオープンソース革命だと良く言われましたが、実際のところ、オープンソースはきちんと動くインフラ製品を提供するようになりました。ですが、オープンソースであることはとても良いことです。先に書いたように、プログラムの実行の裏側にはコードがあります。そのコードにアクセスできることが保証されることは、学習や教育のために良いことではないかと思います。
会社の社員だけがコードを見るのと、全世界の全員がコードを見るのとでは、雲泥の差があります。決してオープンソースが正しいわけではありませんが、できるだけ多くの人にコードを公開することで、品質が高くなる、ということはあると思います。
後日注記:僕はある意味、オープンソースやインターネットには、自然淘汰の原則が発生していると思う。馬鹿なサイトや馬鹿なソフトウェアは、誰にも使われず自然に消えていく。本当に正しい情報とソフトウェアだけが普及する。会社の書いたコードでは、馬鹿なコードでもそのソフトウェアに含まれていれば、使わざるを得ない。結果、代わりのものが開発されず、不安定でバグがいっぱいになる。ただし、最近のインターネットはおかしい。悪い噂やフェイクニュースなどが簡単に拡散してしまう。そうした「確からしさが失われた世界」では、きっとオープンソースなソフトウェアも全て、間違ったものになるのではないかと僕は思う。
オープンソース開発も参照のこと。
本当のことを言えば、Windowsはとてもつまらないシステムです。それは、多くが「Windowsというコアシステムに命令しているだけ」だからです。
Windowsには多くのコンポーネントがありますが、そのコンポーネントに対して命令しているだけで、多くの場合プログラミングができてしまいます。
これは単純かつ便利なシステムで、Adobe製品などを作る上では楽でしょうが、プログラマからしてみれば、「結局Windowsを使っているだけに過ぎない」ということになります。
これに対して、Linuxカーネルが面白いかというと、そうでもありません。
Linuxカーネルは、基本的にハードウェアの操作しかしません。Windowsと同様、Linuxカーネルに命令することでプログラミングを行います。
違うのは、Windowsと比べて、「記述が美しい」とか、「コンポーネントが分離・整理されている」ということであり、あまり変わりません。
では、面白いプログラミングとはどこにあるのか。それは、「低レベルと高レベル」という話になるでしょう。
ハードウェアに近くなりすぎても、ハードウェアの仕様を実装するばかりで意味はなく、ユーザーや数学的な記述に近くなりすぎても、機能を使うだけで意味はありません。
ビル・ゲイツやリーナス・トーバルズなら、「バックエンドもフロントエンドも全て設計・実装して分かる」ということができますが、それはとても根気の要る作業です。
結局のところ、どこかで折り合いをつけてバランスを保つしかありません。低レベルのハードウェアも高レベルのシステム操作もできるようになれば、それがプログラマという職業であると言えるでしょう。
システムレイヤーも参照のこと。
そう、結局のところ、パソコンとは「機械を使うこと」にすぎません。ですから、OSをただマウスやキーボードで操作していても、それをコードの中でOSや言語やライブラリを用いた記述をしていても、OSの内部でハードウェアをマシン語で操作していても、それら全ては機械を使うことに過ぎない、そしてその機械は最終的にはハードウェアの中の電気信号と論理回路であり、半導体であると、そうなります。それが、パソコンの正しい「使い方」でしょう。本当のことは、物理学や数学のレベルにならなければ、見えてきません。だから、数学は高水準であっても低水準であっても、プログラマにとっては必要であると言えるでしょう。
プログラミングとは、特に経験や知識の乏しい初心者にとっては、「バグとエラーとの永遠の戦い」です。
そもそも、Windowsなどでもバグがいくらでもあるように、プログラミングにおいて、「動くわけの無いコードを何とか動くものにする」ということが、プログラミングの、特に初心者にとっての大きな時間を占めます。
バグやエラーが起きても諦めず、根気強く間違いさがしを行うこと、それが初学者がプログラミングを挫折しない唯一の心構えです。
オープンソースは、無保証かつオープンに無償でソフトウェアを多くの人に使ってもらいながら、みんなでバグを直して貢献する、という、一種の「バグ直しの開発共同体」であると言えるでしょう。