プログラム言語の歴史について
「いいね」ありがとうございました。
もしも「最も得意なことは何か?」と聞かれたら、私は「プログラミング」だと答えます。
たぶん、日本語の文章を書くよりもプログラムを書く方が得意です。
私はこれまで、趣味やら仕事やらでいくつものプログラム言語に触れる機会がありました。
その経験から、プログラム言語に関して少し語ってみたいと思います。
さて、プログラム言語の話の前に、コンピュータの歴史について少し触れたいと思います。
世界初のコンピュータとしてよく名前が上がるのがENIACです。
しかし、第二次世界大戦中に開発されていたコンピュータは軍事機密とされ、開発はされたけれども発表されなかったものも多かったそうです。
それに、ENIAC自体も今のようなプログラムによって汎用的に使用できるものではありませんでした。
Q「世界初の非ノイマン型コンピュータは何か?」
A「ENIAC!」
と言う冗談も聞いたことがあります。
そもそも、コンピュータとは何か? と言えば、計算をする機械のことでしょう。
ENIACは真空管を使用していたそうですが、リレーを使用したコンピュータも開発されていたそうです。
そして、電子計算機より前には、機械式の計算機も存在しました。ハンドルをぐるぐる回して掛け算とかができる機械です。
そろばんは計算自体は人の頭を使って行うからコンピュータには含まれないと思いますが、計算尺ならば入力に対して答えを返すコンピュータの一種とみなせるのではないでしょうか。
機械式の計算機まで含めると、コンピュータの範囲は広く、歴史は古くなります。
「アンティキティラ島の機械」と言うものをご存じでしょうか?
紀元前の古代ギリシャで作られた機械で、歯車によって天体の運行を計算する機械だったと考えられています。
江戸時代の日本でも万年時計(万年自鳴鐘)などと言うものが作られました。
当時は昼夜の長さに応じて、つまり季節によって一刻の時間が変わるのですが、機械仕掛けで計算して一刻長さの違いに対応しているのです。
機械式の計算機は精密に設計されて作られていますが、基本的に同じ内容の計算しか行いません。
複数の計算を行えたり、四則演算のような用途を限定しない計算を行えるものもありますが、最初に設計した計算しか行うことはできません。
言ってみれば、歯車の組み合わせがプログラムです。物理的に固定されたハードウエアとしてのプログラムです。
計算機が機械式から電気を利用したものに替わっても、最初の頃は同じことでした。
電子回路の特性を利用して計算を行うアナログコンピュータだけでなく、リレーや真空管を使用したデジタルコンピュータでも、計算の内容はどのような回路を組むかによって物理的に決まります。
ENIACでも別の計算を行うためには配線を切り替えて回路を組み替える必要がありました。
つまり、回路を構成する配線こそがプログラムなのです。
その証拠に、今でもプログラムのことを「コード」と呼びます。
(嘘です。紐を表す「コード」はcord。プログラムの「コード」はcodeで、記号、符丁、暗号の意味です。)
ENIACの開発当時からメモリ上にプログラムを搭載する方式は検討されていたそうですが、それを実現した後継機のEDVACの開発が難航している間に、SSEMとかEDSACと言ったコンピュータが登場しました。
これ以降、メモリ上のデータとプログラムを区別なく格納するノイマン型コンピュータが発展して行くことになります。
ノイマン型のコンピュータの登場によって、プログラムは歯車や配線といった物理的な組み合わせから、メモリ上のデータとして記述されることになりました。
ソフトウエアの誕生です。
そして、プログラムを記述するためのプログラム言語も誕生しました。
もっとも原始的なプログラム言語は、機械語(マシン語)です。
機械語は言語と言うよりはコンピュータに対する命令セットです。
よく、コンピュータの内部は「0」と「1」の二進数で動いている、と言われますが、これはちょっとだけ違います。
デジタル回路ではスイッチがオンかオフか、電圧が高いか低いか、と言った二つの状態を信号として扱います。
微妙な電圧の差とかは無視するのでノイズに強く、誤動作し難いことが特徴です。
二つの状態しか表せない信号線の一本、メモリの一個といった情報の最小単をビット(bit)と呼びます。
1bitで表せるのは二つの状態だけですが、二本の信号線や二個のメモリセルをまとめて扱えば四つの状態を表すことができます。
表すことのできる状態の数は、束ねるbitの数を増やすほどに多くなります。
1bitで2個。
2bitsで4個。
3bitsで8個。
4bitsで16個。
5bitsで32個。
6bitsで64個。
7bitsで128個。
8bitsで256個。
1bitの二つの状態に「0」「1」と言う数を当てはめ、全てのbitを「0」「1」で表示すれば、二進数の数として数値を表現することができます。
しかし、これらの情報は数値を表す以外の使い方ができないわけではありません。
1bitの状態に「0」と「1」を当てはめたように、別のものを割り当てることもできます。
例えば文字。7bitsもあればアルファベットの大文字小文字に数字と英記号まで割り当ててお釣りぎ来ます。16bitsあれば日本語で使用する多くの漢字も割り当てることができます。
そして、コンピュータに対する命令も同じように割り振ることができます。
メモリ上に配置したコンピュータに対する命令の羅列が機械語によるプログラムになります。
コンピュータのメモリ上では、数値も文字も機械語も全く同じデータとして扱われます。
メモリや信号線の取り得る状態の中には文字や命令が割り当てられていないものもありますが、二進数の数値としてならばあらゆる状態に対して対応する数値が存在します。
だから、コンピュータ内部のデータは全て二進法の数値としてみなすことができます。
そのため、機械語のプログラムも数字の羅列として表現されます。その方が便利だからです。
コンピュータの内部が「0」「1」の二進数で動いているのではありません。コンピュータのメモリの状態は二進数を使えばすべて表現できるというだけです。
二進数ではさすがに桁数が大きくなりすぎて分かり難いので、4bits集めた16進数や、初期の頃は3bits集めた8進数もよく使われたそうです。
さて、機械語はコンピュータが直接理解でき、そのコンピュータにできる全てのことを指示できる万能な言語なのですが、人が記述するのは少々面倒です。
まず、メモリ上に設定する数値と、コンピュータに対する命令との対応関係を憶えるのが面倒です。
まあ、命令の数は限られているので慣れるとある程度は憶えてしまうのですが、コンピュータの進歩とともに命令セットが増えたり、機種が変わると命令と数値の対応が全く変わってしまいます。
そして、一番面倒なのがアドレス計算です。
コンピュータが扱うデータもプログラムも全てメモリ上に置かれます。
メモリにはアドレス(番地)が割り振られていて、何処からデータを持ってくるか、次にプログラムのどの部分を実行するかなど、全てアドレスで指定します。
プログラムの途中に処理を追加したり削除したりすると、プログラムのそれ以降の部分の配置されるアドレスが変わります。プログラムの後のデータ領域を設定していると、そのアドレスも変わります。
データ用の領域を追加削除、あるいは大きさの変更を行った場合も、同様にそれ以降のアドレスが変化します。
プログラムは順番に実行するだけでなく、条件によって一部の処理を飛ばしたり、同じ処理を何度も繰り返すこともあります。
プログラムの制御を移す先のアドレスを間違えると全然関係の無い処理を実行してしまいますし、データの格納アドレスを間違えると関係ないデータを持って来たり別のデータを破壊してしまうこともあります。
プログラムを少し修正する度にアドレスを指定している箇所をすべて見直さなければならないのです。これを怠るとプログラムはまともに動かなくなります。
プログラムが大きく複雑になるにつれ、この作業は加速度的に膨大になり、やっていられなくなります。
そこで、機械語の面倒な部分を解消するために作られたのが、アセンブリ言語です。
アセンブリ言語では、コンピュータに対する命令を数値ではなくニーモニックと呼ばれる単語で表記します。
ニーモニックは英単語かそれを省略したような文字列で表記するため、数値よりも憶えやすくなっています。
また、プログラムの任意の箇所や、データ用に確保した場所にラベルと呼ばれる名前を付けられるようになっています。
アドレスを直接指定するのではなく、ラベルを指定することでアドレスの変更をいちいち気にする必要が無くなります。
アセンブリ言語で書かれたプログラムはアセンブラーと呼ばれるプログラムによって機械語に変換されます。
この際、ラベルの位置に対応するアドレスの計算はアセンブラー(つまりコンピュータ)が行ってくれるので手間が省けて間違いがありません。
アセンブリ言語は機械語と一対一に対応するため、機械語のプログラムと言った場合、アセンブリ言語で書かれたものを指すことも多いです。
アセンブリ言語によってプログラムの作成はかなり楽になりましたが、それでもまだまだ大変な部分は多くあります。
機械語はコンピュータを動かすために必要最低限の命令セットしかないことも多いです。
単純な計算式を解く場合でも、数式を計算を行う順番に並べ直して、途中の結果を一時的に保管するメモリの領域を確保して、といった具合にかなり面倒なロジックを書かなければなりません。
紙に手書きで計算する場合を考えてみてください。
×や÷は+-よりも先に計算し、括弧で括られた中はそれよりも先に計算する。
必要に応じて式を変形しながら、先に計算した部分を計算結果の数値に置き換えて行き、最終的な答えを出します。
そのプロセスをコンピュータの命令セットで実現できるロジックに置き換えてプログラムを作らなければなりません。
でも、本当に作りたいロジックは、数式をコンピュータに計算させるプログラムではなく、その計算結果を利用して目的とする結果を導き出すことです。
また、プログラムを構成する定番の処理――条件分岐、ループ、サブルーチン、データ入出力、文字列の扱い等々、アセンブリ言語で書いても長くて複雑になります。
それに、何と言ってもアセンブリ言語や機械語では長くなるほどに、人が見たら何をやりたいのかが分かり難くなって行きます。
そこで、もう少し人が見て分かり易い書き方でロジックを記述できる言語が作られました。
コンピュータを直接操作するアセンブリ言語を低水準言語と呼ぶのに対して、人が読みやすいように工夫されたプログラム言語を高水準言語と呼びます。
プログラム言語と言うと、多くの場合高水準言語のことを指します。
一般に、人の読みやすい形式のプログラムをソースコード、機械語に変換した後の数値の羅列をバイナリーコードと呼びます。
高水準言語の登場により、コンピュータのプログラミングに一つの変化が起こりました。
機械語のプログラムはコンピュータのアーキテクチャが変われば使えなくなります。
アセンブリ言語はニーモニックが同じならば多少流用はできますが、それでもかなりの手直しが必要になります。
つまり、別の会社が開発したコンピュータや、画期的なアイデアを搭載した新製品などでは新たにプログラムを作り直す必要があります。
それに対して、高水準言語ならば、その言語を機械語に翻訳するプログラムさえ作れば、同じプログラムを別のコンピュータで使用することができるようになります。
ここからソフトウエアとハードウエアの分離が始まったと考えてよいでしょう。
高水準言語は一つ作って終わりではありませんでした。
目的に応じて様々なプログラム言語が作られ、改良されてきました。
例えば、科学技術計算などに向いたFORTRAN、事務処理向けのシステム用のCOBOLなどがあります。
科学技術の分野では、ロケットで人工衛星や宇宙探査機を飛ばすような大きな世界から、原子や分子といった小さな世界まで幅広い数値を扱います。
また、有効桁数と言う概念があり、あまり細かすぎる数値は誤差に埋もれてあてにならないと言うものです。1億に1を加えても、だいたい1億です。
なので、科学技術の計算では浮動小数点という形式で数値を表現します。一定の精度で、天文学的な大きな数から素粒子物理の小さな数まで扱える数値形式です。
FORTRANではこの浮動小数点の計算が普通に行え、数学の数式と同じような形式で計算式を表すことができ、また複素数も扱うことができました。
一方、事務処理のシステムではそこまで広範囲の数値は扱いません。国家予算でも天文学的数値には及びませんし、何万分の一のような値が出て来ることもまずありません。
その代りに、誤差は許されません。
総額が数百億円になるとしても、一円単位で正確な値が出てこなければ銀行のシステムには使えません。
COBOLでは、十桁や二十桁の整数でも誤差なく正確に計算し、表示できるようになっています。
FORTRANやCOBOLはかなり初期の頃に作られた言語なのですが、バージョンアップを繰り返しながら長く使い続けられています。
たぶん今でもどこかでFORTRANやCOBOLで記述されたプログラムが動き続けているでしょう。
FORTRANには科学技術計算で使用された大量のプログラムの資産があります。また、プログラムの最適化や並列処理への対応がやり易いらしく、スーパーコンピュータでも使用されているそうです。
COBOLで作成された事務システムは重要なものが多いです。最新の機器を導入してシステムを刷新する場合でも「以前のシステムと完全に同じ結果が出ること」が必須になることも多々あります。そこで古いシステムを新しいコンピュータに移植することになり、COBOLを使い続けるのです。
変化の激しいコンピュータ業界で、この二つの言語は驚異的に長寿です。
さて、高水準言語が作られ、コンピュータが様々な用途に使われるようになって、ソフトウエア産業が一気に花開いたかと言うと、そうではありません。
ソフトウエアの重要性が認識されるまでには少し時間がかかります。
初期のコンピュータにとって、ソフトウエアは「おまけ」でした。
最初の最初は配線を切り替えずに別の計算ができて楽になった程度の認識だったのでしょう。
昔のコンピュータは大きくて高価でした。
今では「大型コンピュータ」と呼ばれる機械は、一台(一基と呼ぶべきか)でそれなりに大きなコンピュータ室の一部屋を占有します。
電力も食うから電源工事も必要だったり、発する熱を冷ますために空調も必須、場合によっては床の補強工事や建屋そのものを新しく作ることもあったでしょう。
価格は軽く億円単位。
購入してもそれで終わりではなく、高価なコンピュータを使い続けるために保守契約を結びます。
本体が高価な分、保守費用もまた高価で、その保守費用でシステムエンジニア(SE)がおまけに付いてきます。
コンピュータで何か計算したいと思ったら、SEに「こんな計算をしたい」と要望を出します。
SEはちょろちょろっとプログラムを書いて渡し、使い方を説明します。
なお、言われた使い方やマニュアルの説明に飽き足らず、できることをガンガン試して今で言う「裏技」を見つけ出し、終いには自分で好き勝手にプログラムを作り出すような人が後のハッカーです。
極端なマニアは別として、プログラムを作るのはコンピュータの開発側の人間に限られていました。
一般の人は、高価なコンピュータの「おまけ」として付いてくるソフトウエアを使うだけでした。
状況を変えたのは、コンピュータの小型化がきっかけでした。
真空管やリレーを使用した巨大なコンピュータからトランジスタを利用した小型高性能なものへ、集積回路(IC)の東條によってさらに小型化されました。
大型コンピュータが一部屋占有するサイズなので、冷蔵庫サイズくらいまで小型化した物がミニコンピュータと呼ばれました。
そして、コンピュータの心臓部である中央処理ユニット(CPU)がワンチップ化されると卓上サイズのマイクロコンピュータが登場します。
コンピュータの小型化に伴い、価格も下がって行きました。
個人向けに発売されたマイクロコンピュータをパーソナルコンピュータと呼びました。パソコンの誕生です。
しかし、初期のパソコンはマニア向けの玩具でした。ゲームソフトなども発売されましたが、市場は限られたものでした。
状況が決定的に変わったのは、パソコンが仕事用に職場に導入されてからでしょう。
大型コンピュータと比べてパソコンは安価です。当時高くても数十万円で一式揃いました。
数十台まとめて導入しても、大型コンピュータの価格より桁が少ないのです。
電気代や保守費など、ランニングコストもずっと安くなるでしょう。
しかし、コンピュータ本体の価格が下がった分、おまけでサービスできる内容は減ります。
少なくとも、必要なソフトウエアを全て無償提供などと言う無茶はできません。
ハードウエアの価格が下がった分、相対的にソフトウエアの価格が上がったのです。
さらに、コンピュータの性能が上がった分パソコンでもできることが増え、プログラムも高度で複雑なものになって行きました。その分ソフトウエアの価値がさらに上がります。
コンピュータのメーカーがソフトウエアの無償提供を行わないのならば、プログラムを自作するか別の誰かに作ってもらう必要があります。
全ての企業がプログラムを開発する人材を確保しているわけではないので、ソフトウエアを開発する商売が成立するようになります。
それ以前にもプログラム開発を行う人はいたでしょうが、ハードウエアのメーカーが抱え込んでいたり、ニッチな市場で細々と行っていたのだと思います。
しかし、ビジネス向けのソフトウエア市場が台頭してきたことで、ソフトウエア産業が一気に大きくなったのでしょう。
余談ですが、こんな話を聞いたことがあります。
システム開発の商談があったのでお客様に話を聞きに行ったら、変わった条件が付いていたそうです。
・全てのプログラム言語は一種類に統一すること。
・プログラムのソースコードを全て提供すること。
結構規模の大きいシステムの話らしいのですが、そのお客様自らプログラムを弄り回す気満々です。
何でも、そのお客様の以前のシステムはメーカーの人と二人三脚で自分たちでシステムを作り上げたという自負があったのだそうです。
だから、今回も自分たちが主体となってシステムを構築すると。
この話を聞いた時、私は絶対に失敗すると思いました。
詳しくは聞いていませんが、前のシステムと言うのは大型コンピュータを使用したものだったのだと思います。
コンピュータを納入したメーカーの技術者が親身になってお客様の要望を拾い上げ、システムを構築して行ったのでしょう。
しかし、この商談のあった時点で時代はすでにオープンシステムが主流になっていました。
一社でシステムの全てを構築するのではなく、様々なベンダーの製品やサービス、技術を組み合わせて全体としてシステムを作り上げるのです。
プログラム言語を統一したところで全てを理解することは困難ですし、「ソースコードを提供する」という条件は自社で権利を持っていないプログラムを使うことができないという非常に厳しい縛りになります。
さらに、顧客側で勝手にプログラムを修正されると、開発元としても管理やメンテナンスが非常に困難になりますし、それで問題が発生した場合にだれも責任が取れません。
これは、ソフトウエアがハードウエアのおまけだった頃と、高度に専門的な技術で作られた製品となった時代との感覚の差ではないかと思います。
このシステム開発が最終的に上手くいったのか、失敗したのか、結果は聞いていません。
ただ、成功させるためにはソフトウエアの技術的な能力ではなく、どうやってお客様を納得させるかと言う落としどころが一番の課題になるでしょう。
さて、コンピュータの性能が向上し、周辺機器やネットワークの発達に伴いコンピュータにできることがどんどん増えて行きました。
また、コンピュータを利用する人の幅も広がり、コンピュータに関わりの無い一般人も使用するようになりました。
このため、プログラムに対する要求も高いものになりました。素人でも分かり易く、操作を間違えても致命的な問題になり難いことが求められます。
結果として、プログラムは大きく複雑になって行きます。
このため、プログラム開発には二つの大きな課題が立ちはだかるようになりました。
一つは、生産性の向上です。
プログラム開発において最も大きなコストは、人件費です。
複雑で巨大なプログラムをなるべく短時間で作ることでコストが削減できます。
凄いプログラムの開発を始めたけれど、何年経っても完成しない、では困ってしまいます。
もう一つは、保守性の向上です。
一度作成したプログラムでも、不具合が見つかったら修正もするし、状況の変化で機能を修正したり拡張したりすることもあります。
プログラムをよく書く人は経験があると思うのですが、昔書いたプログラムを読み直すと「何でこんな処理を書いたんだっけ?」と思うことは珍しくありません。
特に他人の書いたプログラムを修正することになったけれど、何処をどう直せばいいのか理解するために物凄く苦労した、などと言うこともよくあります。
この、一度書いたプログラムを後から直しやすいようにすることが保守性の向上です。
生産性の向上と保守性の向上は、対立する部分があります。
生産性を向上するためには、書く量を減らすことが手っ取り早いです。
例えば、よく使う変数名に10文字の名前を付けるよりも、1文字で済ませた方が短く書けます。
10文字の変数名を10回書くよりも、1文字を10回の方が楽でしょう。
しかし、1文字だけの変数名と言うものは、後から見ると「これ、何のための変数だっけ?」となることが多いです。
すごく長い計算式を分割して記述するために一時的に途中の値を格納するとか、ループカウンタとしてしか使っていないその場限りの変数ならばさほど問題はありません。
しかし、何か重要な計算に使われている1文字の変数がプログラムのあちこちに出現するとわけが分からなくなってきます。
重要な変数には、多少長くなっても読めば何のための変数であるのか想像できる名前にした方が後から見た時に理解が速くなります。
また、ほとんどのプログラム言語では、コメントを書くことができるようになっています。
コメントは、プログラムの実行には何の関係もない文章です。
プログラムの動作には影響しないのだから、書く必要はありません。生産性で考えれば単なる無駄です。
しかし、適切にコメントを付けることで、「この処理は何のために行っているか」「この変数は何に使用するためのものか」「後から機能を追加する場合はここに処理を入れれば良い」など、プログラムの理解を助け保守性をよくすることができます。
生産性と保守性の対立は、現在では保守性を優先する考えが主流です。
なぜならば、如何に早くプログラムを書き終えたとしても、まともに動かなければ意味が無いからです。
プログラムが巨大で複雑になるほど、一発で完璧に動作することは稀です。
必ず不具合は存在しています。
テストを行ってバグを発見し、そのバグを修正するデバッグ作業を行う。
それを繰り返して一定の品質を確保したところでようやくプログラムは完成したと言えるのです。
保守性の悪い、分かり難いプログラムではデバッグ作業に手間取ります。
また、不具合を発見する手法の一つに、ソースコードのレビューと言うものがあります。
プログラムを書いた本人とは別の人がソースコードを読んで間違いがないか確認するものですが、保守性の悪いプログラムではやってられません。
一定の品質を確保しなければ完成とはみなさないと言う立場からすれば、保守性を高めることで最終的な生産性も高まると考えるのです。
この生産性と保守性の問題は、実のところはソフトウエアが一大産業として花開く以前から存在していました。
高価なコンピュータを導入して行う事業はそれだけ重要なものです。
アポロ計画などはアメリカの威信をかけた国家プロジェクトとして期限付きで行われました。プログラムが間に合わなくて遅れた、は許されません。
それに、プログラムのミスでロケットが明後日の方向に飛んで行ったりしたら目も当てられません。
事務処理用のコンピュータでも似たようなものです。高い金を払って購入したコンピュータがいつまで経っても使えないのでは困ります。
お金の絡む処理が間違った答えを出したら莫大な賠償金が発生するような大問題になります。不具合は即座に修正しなければなりません。
この保守性を確保しながら生産性を高めるために、昔から様々なプログラミング技法の開発やプログラミング言語自体の改良が行われました。
例えば、「構造化プログラミング」という考えが提唱されました。
発端は、プログラムが複雑になって訳が分からなくなって行ったことにあります。
俗に「スパゲッティプログラム」と呼ばれます。
プログラムの流れを追っていくと、あっちへ飛び、こっちに飛び、何処と何処がどう関係しているのかさっぱり分からない、見通しの悪いプログラムのことを言います。
一度作ったプログラムに場当たり的に改造を加えて行くと、絡まったパスタのようにこんがらがったプログラムが出来上がることがありました。
この訳の分からないプログラムを作らないようにと考えられたのが「構造化プログラミング」です。
当時のことを知っている人は、「goto文を使ってはいけない」と憶えているかもしれません。
「goto文」はプログラムの制御を強制的に別の箇所に移す命令で、乱用するとぐちゃぐちゃなプログラムになります。
最近は「goto文」の存在自体を知らない人もいるかもしれませんが、「構造化プログラミング」で悪者扱いされた結果、排斥された歴史があります。
実は、C言語やBASIC(C#やVB含む)では今でも「goto文」自体は存在します。
「構造化プログラミング」のポイントは、プログラムの制御構造を三種類に絞ったことにあります。
つまり、「順次」「選択」「反復」です。
「順次」は一つの処理が終わると次の処理に順々に進んで行く流れです。
「選択」は条件によってある処理を行うか行わないか、あるいはどの処理を行うかが決まると言うものです。
「反復」は条件を満たしている間同じ処理を繰り返し実行することです。
この三つだけであらゆるプログラムを作ることができる、と言うと不思議に思うかもしれませんが、それぞれの構造内の「処理」の部分にも入れ子の様に「順次」「選択」「反復」の構造を作り込めるのでいくらでも複雑な処理を表現できます。
実際、現在作られているプログラムの多くは基本的にこの三種類の構造の組み合わせでできています。
まあ、ループ制御(breakやcontinue)、複数分岐(switch-case、実はgotoが隠れています)など微妙に三種類の構造から外れるものもありますが。
それでも、数多くの大規模で複雑なプログラムが、これらの制御構造を意識して作られています。
どれだけ複雑なプログラムになったとしても、この三種類の構造を押さえておけば処理が何処へ飛ぶか分からない「スパゲッティ」にはなりません。
このプログラムの構造を表現するために使用されたのが「インデント」です。
多くのプログラム言語では行の先頭に空白を入れ、命令などを書き始める位置をずらしても動作には影響しません。(影響するプログラム言語もある)
行頭に空白を詰めて書き始める位置をずらすことをインデント(字下げ)と呼びます。
このインデントを利用して、制御構造の中で「順次」実行される処理は行頭を同じ位置から始める(先頭に詰める空白の数を同じにする)。
制御構造の「選択」で条件次第で実行される処理や、「反復」で繰り返し実行される処理はインデントを増やして書き始める位置を右にずらす。
入れ子上の階層構造に対しては、階層が深くなるほどインデントも増やして右側にずれて行きます。
そういったルールでプログラムを記述することで、プログラムの制御構造や階層構造が一目でわかるようになります。
これは、言語の仕様としてのルールではありません。
インデントが不適切でもプログラムはちゃんと動くし、コンピュータに怒られることはありません。
ただ、そのソースコードを読んだ他の人に怒られます。
構造化プログラミングで処理の流れが明確になったら、次に来るのは変数の局所化とカプセル化です。
昔のプログラム言語の中には、変数がプログラム全体で共通になっているものもありました。
これ、大きなプログラムを作る時にはすごく不便なのです。
例えば、「反復」の処理で何回目の繰り返しになるかを数える変数には伝統的に「i」と言う変数名を付けることが多いです。これはFORTRAN時代に始まった伝統です。
処理が一回行われる毎に変数「i」の値を1増やして、一定の値になったら反復を終了するのです。
この時、「反復」の中で行われる処理として、サブルーチン(言語によっては関数やメソッド)を呼び出したとします。
もしも、サブルーチン側でも同じ変数「i」を使って反復処理を行っていた場合、サブルーチンから戻った時点で元の処理の変数「i」の値も書き換わってしまい、「反復」の回数が狂ってしまうことになります。
同様に、一時的な変数を下手に使い回すと、何処で値を書き換えられてしまうか分かりません。
大きなプログラムになると同じ処理をあちこちで行うことも多く、そうした同じ処理を一ヵ所にまとめてサブルーチンにします。
サブルーチンで変数に値を書き込んだ結果、呼び出し元の処理にも影響を与えてしまうとなると、サブルーチンを作る場合に非常に注意しなければなりません。
どの変数が何処で書き換わるか見当もつかない、となるとそれはもうデータのスパゲッティー化です。
データのスパゲッティー化を防ぐための方法は二つです。
一つは、変数名の管理を徹底して、関係ない処理で変数を使い回さない。
一時的な変数でも使い回さないことを徹底すれば、予想外に書き換えられてデータが破壊されることを防げます。
しかし、それは全ての変数を一元管理しなければならない手間と、重要性の全くない一時的な変数にも長い名前を付けなければならない面倒があります。
他に手段の無かった昔のプログラマーの苦労がしのばれます。
もう一つは、局所変数を扱えるプログラム言語を使用することです。
局所変数は限られた範囲内だけで利用できる変数です。
サブルーチンの中だけで有効な変数のみを使っていれば、呼び出し元のメインルーチンに影響を及ぼす心配はありません。
メインルーチンの中だけで有効な変数を使えば、サブルーチンの処理で値を書き換えられる恐れはありません。
大規模なプログラムが作られることを想定したプログラム言語では確実に局所変数をサポートしています。もしくはサポートするようになりました。
元々は、変数には名前とデータ型さえあれば良かったのですが、局所変数が導入されるとスコープ(その変数が見える範囲)とライフサイクル(変数が作られるタイミングと破棄されるタイミング)を意識する必要が出てきました。
その代り、変数名の管理は格段に楽になりました。スコープの外の変数名は気にする必要がありません。
一時的な値を格納する変数名に同じ名前を使い回しても、スコープの外の変数と干渉することはありません。
注意すべきは複数の処理で共通に使用するスコープの広い変数のみ。
変数のスコープをなるべく必要最低限に絞るようにすれば、一度に意識する変数の数が少なくて済みます。
変数を局所化してサブルーチンの中だけで完結させると、そのサブルーチンの独立性が高まります。
独立性が高まるということは、メインルーチン側はサブルーチンの中身を気にする必要が無くなるということです。
プログラムの一部、一定の機能を実現する部分をひとまとめにして、その処理内容や内部のデータを隠蔽することをカプセル化と言います。
カプセル化されたプログラムを使う場合は、その機能と入出力の仕様さえ知っていればプログラムの内部に関しては気にせず利用することができます。
また、カプセル化したプログラムを作る場合は、何処でどう使われるかを気にせずに、ただ仕様を満たしたプログラムを作ればそれで良いのです。
このことは、その後のプログラム開発において非常に有用に働きました。
まず、カプセル化されたプログラムの部分は、仕様さえ満たしていればだれがどう作っても問題ないのです。
この結果、プログラム開発の分業が容易になります。
本来、一つのプログラムを複数の人が手分けして同時に作ることは非常に困難です。他の人が重要な変数の値を勝手に変更したり、共通のサブルーチンの動作を変えて別の処理の動作が変わってしまったりといったことが頻繁に起こります。
しかし、カプセル化された内部をいじるだけならば他に影響はありません。
カプセル化された機能を単位として割り振り、複数のプログラマーが共同で一つのプログラムを作成する。今でも使われている開発の基本です。
また、カプセル化されたプログラムは、その機能を必要とする別のプログラムでもそのまま使用することができます。
つまり、プログラムの使い回しが可能になるのです。
生産性と保守性を両方上げることのできる手法が、過去に作ったプログラムをそのまま流用することです。
既にあるプログラムをそのまま使用するのですから、その分新たに作成するプログラムの量は減り、生産性が上がります。
カプセル化されたプログラムは仕様が決まっており、使用する箇所ではその機能を利用することが明確で、カプセルの中まで見る必要はありません。
プログラムの流れを追うにしても、その範囲が狭くて済むので保守性が上がります。
頻繁に再利用されるプログラムはきっちり動いた実績があるので信頼性も高まります。
プログラムを書いていると、「また同じような処理を書いている」と思うことがよくあります。
そうしたよく使う同じ処理をどんどん再利用して行けば、大きなプログラムも楽に作れるようになるでしょう。
最初から様々なプログラムで再利用することを前提で、カプセル化して作成されたプログラムを「モジュール」とか「共通部品」とか呼びます。
そのような多くのプログラムでよく使われる処理をたくさん集め、そこから必要な処理を選んで使用できるようにしたものをライブラリと呼びます。
このライブラリを積極的に利用したのがC言語でしょう。
C言語の言語仕様はとてもシンプルです。
プログラム言語としてサポートしているのは、基本的な制御構文と数値演算、データの取り扱いの部分のみです。
画面に文字を表示したり、ファイルにデータを入出力する方法も言語としては決められていません。
それらはすべてライブラリに丸投げしています。
C言語を学んだ人は、こんなことを言われたのではないでしょうか。
「プログラムの最初におまじないとしてこの一文を入れておけ。」
#include <stdio.h>
これは、標準入出力ライブラリを使用するという宣言です。
ほとんどのプログラムで使用するライブラリなので「おまじない」として必ず書いておけという説明になるのです。
C言語では全ての処理は関数の形で書かれますが、ライブラリの中身も関数として実装されたプログラムであり、ユーザーの作成した関数と標準で提供されるライブラリとに差はありません。
ライブラリとして提供することで、言語仕様として作り込むには複雑な、けれどもプログラムとしてよく使うような処理を提供できるようになりました。
このライブラリという仕組みはとても強力です。
コンピュータに接続する新たな機器が登場した時。
画像や音声など、新しいデータフォーマットが現れた時。
CGやGUI、セキュリティーや暗号処理などそれまで存在しなかった概念のデータ処理が必要になった時。
言語仕様を変えることなく、ライブラリを追加することでそれら新しいものに対応できるようになるのです。
今ではC言語以外のプログラム言語も多くがライブラリをサポートし、後付けで機能を拡張できるようになっています。
よく使う処理をどんどん共通部品にして行ってライブラリを拡充すれば、部品を組み合わせるだけで大きくて複雑なプログラムも簡単に作れるようになる。
そう思われていた時代もありました。
実際にやってみると、これが結構難しいのです。
まず、似たような処理でもちょっとずつ違ったりします。
あらゆるプログラムに対応しようと汎用的に作ると、共通部品の中が複雑になり、利用する際のパラメータ指定がややこしくなります。あるいは共通部品の外側で行う処理が増えて、ライブラリを使うメリットが減る場合もあります。
また、共通部品化することが難しい処理も存在します。
例えば、データを順番に並べる「ソート」と言う処理があります。
アルゴリズムを勉強する際の題材にもされる「ソート」ですが、実際のプログラムでもよく使用されます。
数あるソートの手法の中でも単純なバブルソートは、「隣り合う二つのデータを比較して、並べたい順番と逆になっていたらその二つを入れ替える」という手順を入れ替えが起こらなくなるまで何度も繰り返し行うというものです。
説明を聞けばプログラムを作ることは難しくありません。しかし、汎用的に作ることは困難です。
何故かと言うと、並べ替えるデータの型が異なると、プログラムが別物になって来るからです。
数値を表すデータでも、整数データと浮動小数点データとではメモリ上でのデータの形式もデータが占有するメモリの量も異なります。値を計算したり比較したりする方法も異なります。
プログラム言語で記述したコードは変数の型以外はほとんど同じでも、機械語に変換すると全く異なるコードになります。
これが、数値以外のデータですと、比較するロジック自体がデータに応じて変わってきます。
整数データをソートするプログラムや浮動小数点データをソートするプログラムなどデータ型を決め打ちにするならば簡単に共通部品として作ることはできますが、ちょっとでも違う形式のデータを並べ替える場合は、また似た様なプログラムを書かなければならないのです。
実はソートに関してはどのような形式のデータでも並べ替えられるプログラムが、C言語の標準ライブラリには用意されています。
ソートのアルゴリズムの中でも最も早いクイックソートを実行する「qsort」という関数なのですが、これがなかなかに癖があります。
qsort関数には引数が4個あります。
・対象となるデータ配列のアドレス(汎用ポインタ)
・配列の要素数
・配列データ1個分のメモリ上の大きさ
・データの大小比較を行う関数へのポインタ
汎用ポインタとはどんなデータ型でも無関係に指し示すことのできるポインタです。
この汎用ポインタによってあらゆる種類のデータ配列に対応できるのですが、その代り汎用ポインタが指示したデータの内容については言語的には一切保証しません。
指定した大きさのデータが指定した要素数だけ存在する配列でなければ、データの内容を無茶苦茶に破壊することになります。
そこは言語ではなくプログラマーの責任になります。C言語はそういう言語です。
最後の「データの大小比較を行う関数へのポインタ」はデータの種類によって変わる比較処理をライブラリから外出しにしたものです。
どのような形式のデータをどういう基準で大小関係を決めるかというロジックを関数として作成して、その作成した関数を引き渡すものです。
C言語を学んだ人が引っかかる二大要素の一つであるポインタ(もう一つは構造体)の中でも、C言語でプログラムを作ったことのある人でも滅多に使わないであろう関数へのポインタです。
プログラム言語によっては使えない手法であり、データの型が言語的にチェックされなかったり、引数で渡された関数を呼び出すなど処理の流れが分かり難くなりやすいです。
この手の手法を乱用すると、確実に保守性が落ちます。
ちょっとだけ違うために共通化できない処理とか、扱うデータが異なるために書き直す必要のあるロジックに対しても、どうにか再利用や省力化ができないかと様々なことが考えられてきました。
例えば、スケルトンと呼ばれる手法があります。
スケルトンとは骸骨のことです。
プログラムの基本的な骨組みだけを抜き出して提供し、そこに個々のプログラムで異なる部分を追加することで肉付けします。
最初に提供する雛形的な基本の骨組みとなるソースコードをスケルトンと呼びます。
基本構造は最初から提供されているから、個別の部分だけ作り込めばよく、生産性は上がります。
スケルトン部分に手を加えなければ、追加部分だけを確認すればよいので、保守性も良くなります。
しかし、スケルトン部分も容易に手を加えることが可能で、気を付けないとうっかり共通処理を変更してしまったということも起こり得ます。
また、スケルトン部分の処理に改良を加えたものを、既に肉付けも終わって完成したプログラムに適用するのが難しいという問題もあります。
様々な手法が考えられた中で、主流になったのが「オブジェクト指向」でした。
物事を表現するデータ構造とそのデータを操作するロジックをひとまとめにしたものをオブジェクトと呼び、オブジェクト間でメッセージをやり取りすることで処理を実行する。
概念的には色々とありますが、プログラマーにとって重要なことは、それで生産性や保守性は上がるのかと言う点だけです。
オブジェクト指向を取り入れたプログラム言語は、そのあたりちゃんと考えられています。
そもそも後発のプログラム言語なので、構造化プログラミングへの対応とか、変数の局所化とか、ライブラリによる機能拡張とか一通りサポートとしています。
その上で、オブジェクト指向はプログラミングにも役に立ちました。
まず、カプセル化との相性が物凄く良いのです。
オブジェクトがそのままカプセルになります。
オブジェクト内部でどのようなデータを抱えているかを外部から知る必要はなく、ただオブジェクトを操作するメソッドを理解していれば十分です。
一方オブジェクトを設計する場合も、外部から使用されるメソッドの仕様だけ押さえておけば、データの構造やロジックは好きに作れます。
オブジェクト指向でプログラミングすると、割と自然にカプセル化が行われるのです。
また、オブジェクトを定義するクラスを作る際に、既存の別のクラスを「継承」することができます。
「継承」によって、元のクラスの持つデータやメソッドを全て引き継いだうえで、追加のデータやメソッドを持ったり、一部のメソッドを上書きしたりすることができます。
この「継承」を利用することで、「だいたい同じだけれど細部がちょっとだけ違う」プログラムに簡単に対応することができます。
元となるクラスを継承して、「ちょっとだけ違う」部分のメソッドだけを上書きしてやればよいのです。
これはかなり画期的なことでした。
オブジェクト指向以前は、同じ部分も違う部分も含めてプログラムを全部コピーし、必要な部分だけ変更するということを行っていました。
変更点が少なくても全部コピーしなければならないので無駄ですし、共通で使用しているロジックに問題があった場合、コピーしたすべてのプログラムを修正する必要があります。
また、コピーして変更と言う作業を繰り返していると、変更した個所に書いてあったコメントを修正し忘れて、後から読むとコメントの説明と実際のロジックが食い違うことがあります。
これ、結構重要な問題で、保守性を著しく低下させる恐れがあります。
しかし、クラスを継承する場合は、元のクラスはそのままで、変更箇所だけを新しいクラスに記述すればよいのです。
変更部分しか記述しないので、保守する場合も元のプログラムからどこを変更したのかと探す手間もありません。
共通で使用されているロジックを修正する場合は、元のクラスを修正すれば継承しているすべてのクラスに反映されます。
スケルトンプログラムも同様に継承で実現できます。
骨格となるクラスを継承して肉付け部分の個別のロジックを実装すればよいのです。
スケルトン部分のプログラムから何処に個別の処理を入れれば良いか探す必要もないので楽ですし、保守性も上がります。
(スケルトンプログラムで実現していたことと同じことを継承で実現できるというだけで、これはもうスケルトンプログラムではない)
また、異なる種類のデータに対して同じような処理を行う共通のプログラムも容易に実現できます。
オブジェクト指向を取り入れたプログラム言語の中でも、JavaやC#ではインターフェース(interface)と呼ばれる機能を持っています。
インターフェースは、そのクラスが必ず持っているメソッドを指定するもので、一つのクラスに複数指定することができます。
このインターフェースを利用すると、異なる種類のオブジェクトをまるで同じもののように扱うことができます。
例えば、「一定距離を走るのにかかった時間を取得するメソッド」を持つインターフェースを作ったとします。
このインターフェースを設定したクラスは、必ず「一定距離を走るのにかかった時間を取得するメソッド」を作っておく必要があります。
このインターフェースのメソッドを利用して、速い順に上位何件かを抽出するようなプログラムを作ったとします。
このプログラムは、百メートル走の記録を整理して上位入賞者を抽出するといった目的で使うことができます。
しかし、それだけではありません。
百メートル走だけでなく、自転車レース、自動車レース、スキー、スケート、水泳等々。
スピードを競う競技全般に、プログラムを変更することなく利用できます。
継承だけなら同じクラスを継承した同じ種類のデータに対してしか使えませんが、インターフェースならば同じ概念のメソッドを共有するあらゆるオブジェクトに対して同じ処理が可能となります。
(C++の場合インターフェースは使えませんが、多重継承によって同じようなことができます)
C言語のqsortの例のようにメモリ空間を直接操作するのではなく、データ構造をオブジェクトとしてしっかり管理しているので安全で、保守性も良くなります。
末端の細かい処理を部品として共有するだけでなく、全体的な大きな処理の流れを使い回す。
そのような目的で作られたものが、フレームワーク(framework)です。
フレームワークは「枠組み」とか「骨組み」とかいう意味です。
スケルトンとも似ていますが、骨格となるプログラムに書き足して肉付けするスケルトンと異なり、フレームワークの場合は個別の処理を外付けで作り込みます。
独立性が高いので、フレームワークのソースコードが無くても、フレームワーク上で動作するシステムを作ることができます。
有名なのは、Webアプリケーションでしょう。
実は、WWWと言うシステム、HTTPと言うプロトコルは、元々ブラウザ上で動作するアプリケーションを想定してはいませんでした。
一回のリクエストに対して一つのレスポンスを返す。つまり、リンクやボタンをクリックすると対応する画面を表示する。
その一回の操作で完結しているのがWebのシステムです。
誰がどういった操作を行って来たかと言うことにWebサーバーは関与しません。
(一応ユーザー認証を行ってファイルを表示するか否かを判定する仕組みくらいはあります。)
数多くのリクエストの中から同じブラウザから行われた一連の操作を認識して、アプリケーション独自のユーザ認証も行って、HTTPで受け渡されるデータの解析を行う。
そうした様々な面倒な処理を行わなければアプリケーションとして動作しません。
この手の面倒な処理は、どのようなアプリケーションでも大差ないのですが、全体的な処理の流れにかかわるのでライブラリによる共通部品だけでは対応しきれません。
スケルトンプログラムでも規模が大きくなると扱い難くなります。Webアプリケーション毎にWebサーバを構築するようなもので、開発の効率も悪くなります。
そこで、HTTPのリクエストを受け付けてそれぞれ対応するアプリケーションに振り分けるWebコンテナと言う仕組みが作られました。
Webコンテナを利用することにより、Webアプリケーションの開発は格段に楽になりました。
Webアプリケーション共通の重要で煩雑な処理はWebコンテやがやってくれるので、アプリケーションの部分だけ作れば良くなったのです。
また、Webコンテナ以外にも大小様々なフレームワークが作られました。
全体の流れが似たような処理はたくさんあります。
ソート処理などもフレームワークの一種で、オブジェクトのどのデータをどう比較するかのロジックを外付けすればどんなデータでも並び変えることができるようになります。
また、Webアプリケーションの台頭によってMVCと言う考え方も広まりました。
MVCのMはモデル(Model)。
入力したデータをファイルやデータベースに保存したり、表示するためのデータを用意したりと、その機能で本当にやりたいことを行う処理です。
MVCのVはビュー(View)。
画面表示を行う部分です。画面デザインとモデルから取得したデータをどのように表示するかのロジックを実装します。
MVCのCはコントローラー(Controller)。
入力内容に応じて実行するモデルを切り替えたり、処理結果に応じて次に表示する画面を選択したりします。
このように、役割毎に分けて作ると、開発や保守がとてもやり易くなります。
画面表示がおかしければビューを修正すればよいし、処理の結果が正しくない場合はモデルを重点的に調べます。
そして、このMVCの考え方はフレームワークとも相性が良いのです。
MVCのそれぞれをフレームワークに組み込む部品として作るだけです。
そうしてできたアプリケーションに対して、ビューの部分だけ入れ替えると、中身は同じでも見た目だけ全く異なるアプリケーションが出来上がります。
外見は全く違うのに操作が変わらないようなシステムを見たことはありませんか?
操作性を統一するためにわざと似せている場合もありますが、一皮剥けば中身は一緒の場合もあります。
また、同じようなネットショップでもポイントや送料の計算が異なったり、同じ店でも計算方法が改正される場合がありますが、そんな場合でも該当するモデル部分を入れ替えれば他の処理はそのまま利用できたりします。
モデルの部分は特に独立性を高く作ることができるので、フレームワークに関係なく、例えばサーバ上で動くWebアプリケーションと同じものをパソコン上にインストールするアプリケーションに組み込むようなこともできます。
こうして、再利用しやすいプログラム開発が主流になったことに加えて、オーブンソースの流れで汎用的なフレームワークや共通部品が誰でも自由に使えるようになりました。
今では、利用できるフレームワークやライブラリを探して組み合わせることがプログラム開発の重要な仕事になっています。
そうした、既にある共通部品を再利用することは単に楽をする以上の意味があります。
それは、プログラムを作る際に詳細な専門の知識を必要としなくなることです。
例えば、Webコンテナとその上で動作するフレームワークを利用することでHTTPやWebシステムに対する詳細を知らなくてもWebアプリケーションを作ることができます。
本来ならば、HTTPという通信プロトコルが何をやっていて、どうやってデータを受け渡しているのか詳細に知らないとプログラムなんて作れません。
しかし、その面倒な部分をWebコンテナやフレームワークがやってくれるので、アプリケーション部分に集中して作ることができるのです。
同様に、画像や音声、あるいはCSVやXMLと言ったよく使用されるデータファイルに関しては、必ずそれを取り扱うライブラリがどこかにあります。
詳細なファイルのフォーマットを知らなくてもデータを読み込んだり書き込んだりできるようになっているのです。
特に近年複雑かつ重要になっているセキュリティに関しては、フレームワークならフレームワーク側で、共通部品なら共通部品側でしっかりと対策したものを使用すれば、アプリケーションの開発者は自分の作成する部分のセキュリティだけに集中することができます。
専門性の高い部分、難解な部分は専門にやっている人に任せて、それらを組み合わせて作りたい機能を実現する。
一度作ったアプリケーションを部分的に修正して似たような別のアプリケーションを作成する。
こうした、アプリケーションを作りやすい環境を構築してきたから、現在のように世界中で数多くのアプリケーションが作られ、稼働しているのです。
今後も新しいプログラム言語や開発技法が現れることでしょう。
その方向性は生産性と保守性の向上、つまり一度書いて動作実績るあるコードを二度書かないための工夫が重要になるでしょう。
コンピュータの性能が上がるにつれてアプリケーションはどんどん大規模で複雑なものになって行きました。
その一方で、変化の速いIT業界ではなるべく早くシステムを開発して公開する必要があります。
そしてもちろんシステムの不具合は大問題となり得るので見つけたら即座に直さなければなりません。
こうした状況が続く限りは、生産性と保守性を向上し、さらに使用実績があって信用できるプログラムを流用する傾向は続くでしょう。
・おまけ
最近は、プログラマーでなくてもマクロを組むなどで簡単なプログラムを作る人も多いでしょう。
仕事の関係でプログラムを学ぶ人の中にはとても真面目で、教わったプログラムを丁寧にノートに書き写す人もいます。
ただ、私見ですが、こういう人はプログラミングに向きません。
プログラミングに必要なことは、ソースコードを憶えることではなく理解することです。
もちろん、最低限憶えなければならないこともありますが、今の時代なら忘れていても使う時に調べればそれで済みます。
弁護士は、六法全書を全て丸暗記しているわけではないそうです。ただ、必要な時にどこを調べればよいかを理解している。
それと同じように、プログラマーはプログラムのソースコードを丸暗記しているわけではありません。
必要になったらその場でソースコードを組み立て、分からないことはどこをどうやって調べればよいか理解しているだけです。
丸暗記の勉強方法では応用が利きませんし、そもそもあらゆるプログラムを憶えることなどできません。
メモしたソースコードを後から見直して、きちんと理解できれば良いのですが。
ついでなので、私が個人的に、プログラミングが苦手な人と自分が異なっていると感じている部分を書いて見ます。
私は、プログラムを作る際にメモリ空間をイメージすることができます。
実際にコンピュータのメモリ空間に割り当てられた厳密なものではありませんが、メモリ上にどのようなデータが入っていて、それをどう処理して結果をどこに格納するか。
そんな感じで、プログラムの処理をメモリ上のデータの移動という感じのイメージして把握しています。
他のプログラマーはどう認識しているかは知りませんが、メモリ空間上のデータをイメージできるととても便利です。
どんなロジックも最終的にはメモリ上のデータの加工や移動に還元できます。こっちのメモリからデータを取って来て、こんな加工をして、こっちのメモリに格納する。突き詰めればそれだけです。
どんなプログラム言語でも、メモリ上のデータの操作のイメージまで落とし込めばみんな一緒です。些末な表現の違いでしかありません。
趣味や仕事でいくつものプログラム言語を利用することがありましたが、言語の習得に苦労しなかったのはメモリ空間上でロジックを考えていたからです。
私は趣味でプログラミングを始めた初期の頃に機械語(アセンブリ言語)に触れる機会があったので、それでメモリ空間をイメージしやすくなったのだと思います。
プログラマーを目指す人は、一度は機械語でプログラムを作ってみると良いと思います。




