[文法以前の超入門]プログラミングの本質は抽象化にある

良いプログラムは抽象化の先にある プログラミング
良いプログラムは抽象化の先にある
スポンサーリンク

対象読者

  • プログラマの思考法を知りたい方
  • プログラミングに興味がある非プログラマの方
  • プログラミング初心者の方

結論

プログラムは実体がなく、イメージしづらい。しかも、横文字の用語も沢山あって、初心者の方にはかなり取っ付きにくい。

おまけに、プログラムの捉え方(パラダイム)は主に3種類ある。

  • 手続き型
  • オブジェクト指向
  • 関数型

なおかつ、コードを綺麗に書くための工夫やら規則やらもある。

それらのプログラミング技術は、「抽象」というキーワードから一貫して説明できる。その理由を本記事で押さえれば、より効率的に学習を進めることができるはずだ。

文法はプログラミングの手段に過ぎず、その文法が生まれた背景や考え方を理解することが良いプログラムを書く上で必須となる。その根幹にあるのが抽象だ。

これが理解できれば、「〇〇は△△すべし」みたいなプログラムテクニックへの理解が深まるはずだ。

以下では、

  • 人間の認識=抽象化
  • プログラミング言語はそもそも抽象化の産物
  • うまく詳細を捨て、抽象化することが良いプログラミングへの方針

ということを示す。

プログラミングとは抽象化・モデリングである

詳細を捨てて、有意味な単位にまとめることを「抽象化」、その単位を「抽象」という。

何が意味のある単位なのか、という見方を「モデル」といい、モデルを見出すことを「モデリング」という。

モデリングをどう捉えるか、つまりモデリング自体のモデルを「パラダイム」という。

唐突になんだ?って感じだが、実は、プログラミングとは抽象化・モデリングに他ならない。まずプログラミングを学習すると、文法がどうだとか、関数やらクラスやら、いろいろ覚えることがある。しかし、そんなことは手段でしかない。コンピュータ上で問題をどう捉えて、その解決をどう表現するかこれがプログラミングだ

プログラムのモデリングやパラダイムの数だけ、プログラミング言語が存在する。例えば、

  • 簡単にWebアプリを作りたい、という目的で作られたのがPHP
  • 誰もがソースコードを綺麗に書くようにしたのがPython
  • 計算を数式のように簡潔に扱えるようにした最初の言語がFortran
  • オブジェクト指向というパラダイムによって設計された言語がJava

である。

まず、問題を捉えることの出発点を説明する。

人間の認識能力の限界が抽象を必要とする

ヒトは現象をそのまま理解することはできない。認識の第一段階は感覚器官への入力だ。絶え間なく同時に入ってくる5種類の時系列データが人間の思考の土台だ。

視覚の入力だけでも膨大な情報量だ。時間と平面という3次元の連続的な情報は、膨大どころか無限といっても差し支えないだろう。

無限の情報量は人間の認識を超えている。例えば、ヒトは風景画を写真のようにそのまま描くことはできない。絵の描写は以下のような手順で行われる。

  1. まず、目立つ物体を把握して書く
  2. 目立つ物体の周りに次に目立つものを書き加えていく
  3. (2)の繰り返し

このように、ヒトは大枠を把握(抽象化)し、その上で徐々に詳細化していくことしかできない。逆に言えば、そもそも人間がモノを把握するためには、適切に詳細を省く必要がある

史観というモデル

認識が抽象を必要としたのと同じ理由で、人間に歴史を扱うことはできない。なぜなら、歴史とは本来、無数の人による無限のイベントの時系列のことだからだ。

だから、学校で習う世界史や日本史は正確な歴史を扱えていない。有力な人物の行動や、当時の主な世論、風俗など、特筆すべき点を抽出してまとめただけだ。無数の出来事の集合から意味を見出すには取捨選択が必要となる。

この抽出の仕方がモデルであり、「史観」と呼ばれる。

例えば、自虐史観というのは、「日本は第二次世界大戦において、非道なことをした悪い国である」というモデルありきで、そのモデルに合うように出来事を取捨選択し、解釈する。

だから、歴史は英語で「his – (s)tory」であり、物語なのだ。

プログラミングにおける抽象と具象

コンピュータにおいて、データはオンとオフの2種類の状態の羅列として表現される。オン・オフの羅列を「機械語」という

昔、プログラム(データの処理方法)とは、ケーブルの配線を意味していた。それが電気の扱いや磁器メモリが発達したことにより、プログラムも機械語として表現できるようになった。これを「プログラム内蔵式」または「ノイマン式」という。ノイマン式を経て、コンピュータ上の処理の全ては機械語で表現できるようになった

しかし、機械語という膨大で無味乾燥した情報は、人間の認識能力を超えている。そこで、機械語を抽象化し、人間にとって扱いやすくしたものが「高級プログラミング言語」である。一般にイメージされたり、仕事で使われるプログラミング言語はほとんどが高級である、と思って差し支えない。

プログラミングにおいて、より抽象的であることを「高級」より具体的であることを「低級」という。この「低級」という言葉に侮蔑の意味は含まれていないことに注意だ。

非予約語による抽象化こそがプログラミングの肝

良いプログラムは抽象化の先にある

良いプログラムは抽象化の先にある

プログラミングの中でも、具体的にコードを書くことをコーディングという。コーディングとは、予約語と非予約語を文法に従って配列する作業のことだ。

予約語とは、「プログラミング言語が提供する既存の語彙」のことだ。予約語以外の語彙、つまりプログラマが自作できる語彙を「非予約語」と呼ぶことにする。

非予約語とは、自作した抽象に名前を付けたものだ。非予約語は文法に従って、予約語や非予約語を組み合わせて作られる。これを積み重ねていき、より高級な非予約語の集合を練り上げることで、人間にも読みやすく、機能追加や修正もしやすいコードは作られる

文法を知っていて、動くコードが書けることはプログラマとしての最低レベルだ。非予約語を駆使して良いモデリングを行う能力こそが良いプログラマの条件だ

文法と抽象化の両輪

ほとんどの高級言語で共通する代表的な非予約語に以下のような種類がある。

  • 変数
  • 関数
  • クラス

文法を学ぶと、これらの非予約語は一通り覚えることになる。しかし、プログラミング初心者がこれらを適切に使いこなすことは難しい。非予約語を用いて抽象を練り上げずとも、動くコードは書けてしまうからだ。

同様に、ある程度の実務経験のあるプログラマですら、抽象を使いこなせなかったりする。豊富な経験からコーディングは難なくこなせる。だが、分かりやすく、保守しやすいコードが書ける、つまり抽象能力は経験だけでは培われない。

良いコードを書くためには、なぜ抽象化するか、という文法の背後にある意味を押さえなければならない。

それでは、これら3つの代表的な非予約語のそれぞれについて、

  • 何を抽象化したものか
  • その抽象化から道を踏み外した悪い例(アンチパターン)

を説明する。

変数という抽象

変数とは、データ(機械語)を抽象化して保存するための非予約語だ。数学の変数とは別物であることに注意だ。

データの格納場所のことを「アドレス」という。勿論、アドレスも機械語として表現される。本来、データを保持する際、

  • アドレスはどこにするか
  • どれだけのメモリを確保すべきか

等を指定しなければならないはずだ。

変数はそれらの機械語を意識せずに、直観的にデータを扱うことを可能にしてくれる

データ型という抽象

データは全て機械語として扱われる。データには、文字列や整数、実数などいろんな種類があり、機械語の並びの意味が異なる。

例えば、文字”a”の機械語の表現は「01000001」である(ASCII)。しかし、これを正の整数として解釈するなら、つまり2進数として読めば、\(1 \times 2^6 + 1 \times 2^0 = 65\)となる。

やはり、機械語だけ見ても意味不明だ。そこで、機械語を意識せず、高級に扱うための仕組みが「データ型」である。データ型は機械語の解釈の仕方を予め定義することによって、機械語を意識せず、直観的にデータを操作できるようにしてくれる。

  • どういう機械語の並びなのか
  • 2つのデータの演算は機械語でどうやって行うか

という低級なことを考えずに済み、人間はより高級な、創造的なことに頭を使うことができる。変数とは、データ型とアドレスの隠蔽を併せたものだ。

リテラルという抽象的な具象

機械語を使わず、データを直接書き下したものを「リテラル」という。ベタ書きされているため、リテラルは変更不可能な固定された値となる。

リテラルを変数へ格納する例をPythonで示す。

#文字列リテラル
characters = "abcd"

#数値(整数)リテラル
integer = 1234

#数値(実数)リテラル
real = 12.34

機械語が隠蔽されている、という意味でリテラルは抽象的だ。しかし、値が剥き出しになっていて、具体的過ぎる。具体的なリテラルはコードを分かりづらくすることを次に示す。

マジックナンバーというアンチパターン

以下は何かしらの計算を行っているコードだ。

value = 10 * 20 / 2

なんのこっちゃ。では、リテラルではなく、変数を使ってみよう。

length = 10
height = 20
area   = length * height / 2

三角形の面積を計算していることが分かるだろう。

変数によってリテラルに適切に名前が与えられたことで、コードの意図を表現できる。抽象化によってコードが改善できるのだ。

数値リテラルのことを特に「マジックナンバー」といい、コーディング作法の本には悪い例(アンチパターン)として載っている。

これらのコード例は短く、そこまで不都合を感じないかもしれない。しかし、業務レベルのコードはもっと巨大であり、マジックナンバーが積み重ったコードはかなり読みにくいことは想像に難くないだろう。

関数という抽象

(自作)関数とは、データの処理をまとめあげるための非予約語だ。具体的にどんな処理をしているのかは分からないが、入力を与えれば、それをなんか処理してれる。つまり、関数はデータ処理の抽象のことだ。

因みに、データの操作に関する予約語のことを特に「組み込み関数」という。関数を自作する素になるという意味で、変数に対するリテラルのようなものだ。

例えば、三角形の面積の計算は関数化できる。入力は底辺と高さで、出力が面積だ。

function calcTriangleArea(length, height){
    return length * height / 2
}

area1 = calcTriangleArea(10, 20)
area2 = calcTriangleArea(20, 30)

文法が分からなくても、何となく意味は通じるだろう。予約語と非予約語による抽象の賜物だ。

コピペというアンチパターン

関数化(処理を抽象化)したことによって、面積の計算、という処理が再利用できるようになった。しかし、変数とリテラルしか使わない場合は、以下のように同じ記述を繰り返すことになる。

length1 = 10
height1 = 20
area1   = length1 * height1 / 2

length2 = 20
height2 = 30
area2   = length2 * height2 / 2

初心者の方はコピペして、ちょっと修正することで似た処理を書いていがちだ。

マジックナンバーの例と同様に、コードが短すぎて、関数化して何が嬉しいかは伝わりにくいと思う。だが、より複雑で長いコードで想像して欲しい。

同じ処理であることは全文を読まない限り分からない。その上、そのブロックの記述にマジックナンバーが使われていたり、別の処理が追記されていた場合、処理の重複を見つける難易度は高くなる。

コードは短いほど分かりやすく、加筆・修正もしやすい。故に、関数を使った再利用は重要だ。処理の抽象(関数)が見いだせているのなら、コピペによるいたずらなコード膨張を防ぐことができる。

処理ブロックにコメントをつけるアンチパターン

コメントとは、プログラムの動作に影響しない注釈のことだ。初心者の方はコメントは書くべきだと習う。だから、いたずらにコメントを書き連ねがちだ。

上記のコードにコメント付けてみよう。

# 三角形1
length1 = 10
height1 = 20
area1   = length1 * height1 / 2

# 三角形2
length2 = 20
height2 = 30
area2   = length2 * height2 / 2

何か分かりやすくなっただろうか?

本来、コメントとは、コードで表現できないような情報を書くべきなのだ。例えば、

  • 他の書き方があったにも関わらず、なぜこの実装を選んだのか
  • このコードの弱点
  • 改善案

等だ。決して、コードを読めば分かるようなことにコメントを使うべきではない。

コメントには、

  • 読むべき文章量を増やしたり、
  • コード変更に際してのコメント更新忘れ

    等のデメリットが存在する。

    適切に抽象化できているのなら、関数名によって、コードの概要は説明できるはずで、わざわざコメントを書く必要はないはずだ。処理内容を関数名だけで説明できない場合は、本来まとめてはならないような複数の処理を無理やり関数化してしまった可能性がある。

    クラスという抽象

    クラスとは、オブジェクト指向と呼ばれるパラダイムの基本的な抽象だ。

    変数と関数は完全に無関係な訳ではない。特定の変数に対応する関数があるはずだ。ならば、関連する変数と関数をまとめちゃおうよ。これがクラスだ。

    変数と関数という抽象をまとめたクラスは、より高級な抽象と言える。

    関数には入力を与えなければならなかった。ところが、クラスが持つ関数(メソッドという)は、入力せずとも、クラスが持つ変数(プロパティという)を処理に使うことができる。これにより、関数の入力の数を減らすことができ、よりスマートに問題を表現することができる。

    クラスの文法はちょっと複雑で、本記事の範囲を超える。なので、クラスの定義の部分は省いたコードを示す。

    # クラスの作成
    tri = Triangle(三角形の情報)
    
    # 面積の計算
    area = tri.calcArea()
    #  重心の計算
    center = tri.calcCenter()

    文法を知らなくても、何となく伝わるだろうか。クラスも変数として扱うことができる点に注意だ。

    面積や重心は三角形が持つデータだけで計算できるはずだ。底辺と高さ、という情報だけでは無理かもしれないが、その場合は必要な情報を三角形クラスに追加すればよい。

    このように、クラスを使うことで、どの変数とどの関数を組み合わせればいいか、というような詳細を気にせずに、より高級に問題を扱うことが可能となり、コードを簡潔に表すことができる。

    神クラスというアンチパターン

    クラスを使えば、関数への入力を省略できる。また、他にもクラスには継承と呼ばれる便利な機能があったりする。

    なので、初心者の方は1つのクラスに色んな変数や関数を詰め込んでしまいがちだ。そういう多機能すぎるクラスのことを「神クラス」という。「全知全能である」、または「そんな複雑なクラスを理解できるのは神だけ」という意味が込められている。

    三角形の面積などを計算するクラスが例えば、ユーザーのメールアドレスなどを持っていたら問題だ。そのようなクラスははたしてどんな抽象なのだろうか。

    関係のないものをゴチャ混ぜにした抽象を理解するためには、具体的にその中身を確認しなければならない。それでは抽象化の意味がない。理解するのが困難なコードになる。

    まとめ

    以上、基本的な非予約語について、

    • 何を抽象化したものか
    • その抽象化の意味に合わないと、良くないコードが出来上がる

    ことを確認した。

    抽象についての理解が、プログラム全般をスマートに理解する鍵だ。

    参考

    1. レガシーコードからの脱却[David Scott Bernstein (著), 吉羽 龍太郎 (翻訳), 永瀬 美穂]
    2. 論理学 考える技術の初歩[エティエンヌ・ボノ・ド・コンディヤック]
    3. 不勉強が身にしみる 学力・思考力・社会力とは何か[長山 靖生]
    4. ソフトウェアの20世紀―ヒトとコンピュータの対話の歴史[長谷川 裕行]

    コメント

    タイトルとURLをコピーしました