見出し画像

シアトルで実践する「プログラミング的思考」[13] 抽象化(2)パターン認識による抽象化

抽象化(1)分解による抽象化 では「分解の文脈」でどのように抽象化が行われるかを解説しました。ここでは「パターン認識の文脈」で抽象化をみていきます。パターン認識は「プログラミング的思考」の派生元とされる「コンピューテーショナルシンキング」の4つの主要概念のひとつです。他の3つは分解、アルゴリズム設計、そして抽象化です。

パターン認識(pattern recognition)とは解決すべき問題の具体例の中から規則性を見出すことです。パターン認識の結果「共通する部分」と「異なる部分」が見えてきます。規則性は「プロセス・処理(動作)」と「データ・情報(状態)」のどちらの特徴においても見出すことが出来るでしょう。ここではプロセス・処理における規則性をどのように設計に反映させていくかを見ていきます。

一般化による抽象化

パターン認識のひとつのゴールは「一般化」だと言えます。一般化とは、具体的な個々の問題を抽象化してより多くのケースを解決するアプローチです。一般化の手順としては、まずいつくもの似通った問題を分析して、解決のパターンを発見します。そしてその解決パターンをフォーミュラ化して、一般化された解決を開発します。その解決を利用して、今後起こる同様の問題のほとんどすべてを解決できるようにするのです。

「処理の一般化」としては「サブプログラムの入力をパラメータ化」して実行時にケース(事例)ごとに異なる値を指定できるようにする方法があります。実行時に入力を受け取ることでサブプログラムがより多くのケースで利用できるようになります。そのようなサブプログラムを設計するには、まずパターン認識を通じて「共通点(共通部分)」と「相違点(相違部分)」に整理します。

共通点は多くの場合アルゴリズムが占めることになります。手順はほとんど同じだけれど、こういう場合にはこうなる、このような状況ではこのように手順を少し変える、などと言った条件を含む「共通のアルゴリズム(処理の流れ)」になるでしょう。この部分はみんながよく知っているプログラミングに相当します。

共通のアルゴリズムのほかに「共通の値」を発見することもあるでしょう、例えば円周率などを使う処理なら、円周率は共通点なのでサブプログラム内部に定数として実装できます。常に同じ値なので外部からわざわざ渡す必要はありません。処理の完了やエラーを現す文言なども殆ど同じであればサブプログラム内部で共通点として記述します。「セーブしました」や「パスワードが違います」といったメッセージはユーザが違っても同じものを使います。

相違点については、アルゴリズムの中の条件文や、評価式の未知の値として扱い、実行時にサブプログラムの入力として外部から指定できるようにします。オンラインで商品を買う時にクーポンを使う場合と使わない場合の処理は、ほんの少しの違いしかありません。この場合、1つのアルゴリズムにクーポンがあった場合の条件文を組み込めばサブプログラムはひとつで済みます。共通点と相違点をうまくマネージして実装することで、サブプログラムを可能な限り「一般化」するのです。

処理の一般化による抽象化は「プログラミングそのもの」と言っても良いぐらいたくさん例があります。およそ殆どの、何らかの目的を持つプログラムは「一般化された解決」のことです。逆を言うと「一般化されていないプログラムも存在します。この一般化の度合いが低いものはプログラムとしての利用価値が低いと言っても良いでしょう。下に例を示します。

あるクラスの担任教師が自分のクラス用に「テストの平均点を算出するプログラム」を開発したとします。そのプログラムでは、生徒数は自分のクラスの生徒数(例えば38人)と固定しているとしましょう。具体的には配列の要素数を38個に固定している、もしくは入力される配列の要素数が38の前提でプログラミングされているといった設計です。

するとこのプログラムは「生徒数が38人ではないクラス」の平均値を出すためには利用できません。他のクラスで利用しようとしても、生徒数が38人のクラス以外では使い物にならないのです。またこの教師のクラスだったとしても、誰かが欠席してテストを受けなかったとしたら生徒数が変わってしまうので、その場合もこのプログラムは使えません。

この平均点を出すプログラムをより一般化するには、異なる生徒数でも処理できるようにしなくてはなりません。具体的には、プログラムの入力として「任意の数のテスト結果」が指定できるように、入力パラメーターの配列の要素数を限定せず、何人分でも渡せるようにすればよいわけです。この場合38と言う定数をプログラムから取り除くことになります。具体的な数値が減っていく時、一般化が進んでいると理解できます。

ところで最初から何をパラメータ化すればいいか、その最適解は分からない場合があります。後になってパラメータ化したりすることはよくあります。その場合、契約が変わるのでバージョンをマネージしなくてはなりませんが、少しずつ最適化していくことはごく一般的です。アプリケーション成熟度モデル (application maturity model)と呼ばれます。パターン認識とそれを反映する作業は長期的に進められます。

パラメータで一般化されたサブプログラムの利点

サブプログラムに限らず、プログラムや関数と呼ばれる「ひとまとまりの処理」について言えることですが、パラメーターによる抽象化は処理の質や安定性にも寄与します。

例えば、ある関数が処理しようとする入力データに「意味をなさない値」が含まれることがあるかもしれません。100満点のテストの平均を出す関数が、入力として493729という有り得ないテストの点を含んだ配列を受け取ったとします。

コンピュータから見ると、この数値はなんら問題ありません。ただの数値です。しかしこの関数は「100点満点のテスト」という抽象を扱っているので、その抽象的な概念では493729という値は無意味なのです。「100点で満点になる」と言うルールはコンピューターは知りません。実際、200点満点や500点満点のテストも世の中にはあるかもしれません。

この抽象化を強制的にするために、無意味な値を検知して処理を拒否するようなコードを記述することが出来るのです。0から100の間の数値以外は無効として扱います。さらに「それを検出した時どのように対応するか」も人間がルールとして決めます。クラスの平均であればエラーとして扱い処理を中止するでしょう。しかし統計処理などある程度の誤差が許容される処理ならば計算に含まず、しかし処理そのものは続行することもできるでしょう。

ところで、なぜすぐに処理を中止することが良い場合があるかというと、プログラム内で何らかの問題(バグ)が存在する場合、その問題が検知されたら出来るだけその問題箇所に近いところでエラー状態にした方が良いからです。エラーが検知されず、意味の無い値(例えば100点のテストの平均値が320.21点)が幾層ものサブプログラムに処理されてしまうと、問題が表出した時点で「根の問題」への距離が遠くなってしまい、根の問題の理解に時間がかかってしまうからです。

一般化を意識しながらプログラミングする

ここまで見てきたように、漠然とプログラミングという活動をするのではなく、「出来るだけ一般化する」という目的意識を持ちながら取り組むことが抽象化を最大化するのに大切になります。特に入力されるパラメータの値が抽象化に従って有効な範囲内にあることを保証出来るサブプログラムは扱いやすく、生産性や性能の向上につながります。

情報を抽象化する

ここまで主に「処理(アルゴリズム)」がどのように「パターン認識」の文脈で抽象化されるのかを見てきました。一方「情報(データ)」も抽象化することでコンピュータで扱いやすいようにできます。2つの抽象化は独立しているわけではなく、2つを合わせることで人間にとって理解しやすい抽象レベルを実現します。

「情報(データ)」のトピックに関しては シアトルで実践する「プログラミング的思考」[9] ~データを表現する(2) データを抽象化する で詳しく解説しているのでそちらを参照してください。

この記事が気に入ったらサポートをしてみませんか?