2007-07-29

_ プログラミングにおける階層構造と前提知識について

この前会社で少し話していたことではあるが、 自分でもあまりしっくりこない部分でもあるので、 ここにまとめておく。

プログラミングにおいては、 適宜処理や定義を分離し、複雑度を局所的に低下させる必要性が生じる。 それによって、人間が理解しやすくなり、管理しやすくなるだけではなく、 コンピュータ的にも部分的な更新をしやすくなったり、 コードをコンパクトに収めることによって、キャッシュ効率が上がるというような効用も存在するかもしれない。 代表的なコンセプトは、ライブラリやモジュールだろう。 ある特定の機能に専念し、直接アプリケーションを構成するのではなく、 あくまでより上位の階層を支援するわけだ。

階層的な考え方としては、水平方向と垂直方向の両方が存在するわけであるが、 ここでは垂直方向について考えてみたい。 まず、根本的に下位層は上位層よりも情報量が少ない、ということを前提としてよいだろう。 例えば、 libcurl のようなネットワークプロトコル用のライブラリは、 その上位において、botが開発されるのか、 インタラクティブなブラウザが作成されるのか、 そういったことは分からないし、分かるべきではない。 十分に汎用的な設計を行う必要があるし、 それゆえに、どう使われるかを決め打ちすることはできないのである。

こうした性質はアプリケーションレベルまで到達しなくても、多々出現する。 ここで、一つ具体的な例を考えてみる。 言語は何でも同じ事なのだが、 仮にPythonを使うことにしてみる。

class Picture(object):
  def __init__(self, data=None):
    # Do something here.

画像を操作するようなモジュールを設計している。 画像の初期化のため、データを与えることができる。 さて、問題は、このdataがどこまでサポートするべきなのか、ということなのだ。

一つの考え方としては、バイナリデータさえ受け取ればいいので、 strしか受け付けないとすることだろう。 これは非常にシンプルで、それなりに使える考え方である。 あらゆるデータが最終的にstrに変換されるのであれば、 これでは機能的に足りない、という事態もありえない。

その一方で、もっとよしなに扱ってほしいという考え方もありうる。 例えば、fileオブジェクトも受け付けてほしい。 そうすれば、openして、そのまま渡すことができる。 あるいは、StringIOでも構わないではないか。

この考え方を押し進めていくと、 実は大変なことになりはじめる。 なぜかというと、他にも似たようなデータの表現手法はたくさん存在するからなのだ。 例えば、Pythonのarrayモジュールには、より効率的なバイナリデータを扱う手段として、array型が提供されている。 これもサポートした方がよいのではないか? あるいは、ユーザ定義のクラスだったら、どうなのか?

この問題に対する解決手段としては、いわゆるpolymorphismを利用して、 同じ名前のメソッドで、最終的にstrオブジェクトが取り出せるようにしておけば、 上のPictureクラスは具体的なことを何も知らなくて済む、ということが考えられるであろう。 しかし、それぞれのオブジェクトの振舞は必ずしも一致しているわけではなく、 それにはそれ相応の理由があったり、なかったりする。 実際、Pythonのfileオブジェクトに対して、strを実行しても、ファイルの中身が返ってくるわけではなく、 RubyのIOオブジェクトでもto_strは存在しなくて、readと同じになったりはしない。 結局、いくつかのパターンをPictureクラスに組み込まないことには、 さまざまなオブジェクトに対応することはできない。

私が、個人的にそうすることに抵抗があるのは、どう使われるべきかを下位に埋め込む結果となるからだ。 そういうことをやりすぎると、逆に上位が下位がどう実装されているのかを知らないと、 うまく実装できなくなってゆく。 例えば、どのようなメソッドを持っているかを調べて、 それによって処理を切り替えるとしよう。 すると、もしユーザが独自のクラスを作り、そのインスタンスを渡したくなったら、 そして、そのクラスが検査対象となるメソッドを複数持っていたら、 一体何が起こるのだろう。 検査の順番に依存したりしないだろうか。 仮に依存していたとしたら、どうやって解決するのか。 結局自前で渡す前にstrに変換せざるをえないのではないか。

私は、上位が下位を強く、内部実装を意識させられる状況がたまらなく嫌いである。 それでは隠蔽化が破綻しているわけであって、 まさにお節介というべき状況なのだ。 このような状況は業務アプリケーションでは頻繁に見られるのだが、 実際に何がなされるかがプログラム内だけを見ていては収まりがつかなくて、 結局その上位の人間の意思決定こそが重要な局面が増えてくる。 もしもプログラムが多くを前提として作成されていると、 究極的には人間がコンピュータにお伺いを立てて、 プログラムの都合によって、人間の方がやりかたを合わせてあげない等という、 本末転倒な事態に発展するのである。

その一方で、もっと適当にやってくれる方がいいじゃないか、という考え方も否定することが難しい。 例えば、ユーザ定義型は知ったことではないが、 せめて組込みのクラスぐらいは処理してほしいと思ったりするわけだ。 しかし、ここのところの線引きはひどく困難だ。 バージョンが上がるにしたがって、 組込みのクラスが増加したら、それも加えていくのか。 あるいは、組み込みではないが、頻繁に利用されているライブラリはどうなのか。 はっきりした、常に有効な方針を決定することは一筋縄ではいかない。

現時点での私の方針は、とにかく一つのことに専念させる、ということ。 確かに、もっと適当にやってくれたらいいと思うこともあるが、 そこはぐっと我慢する。 どうしてもそうしたければ、同じメソッドにつっこむより、 新しいメソッドを作る方がいい。

これでいいのかどうか、まだよく分からないのだが、 もっとうまい考えが浮かぶまでは当分この方針を採るしかないと思っている。

脱線すると、 同じ理由で私は自動プログラミングというものを全然信じていない。 プログラムに普通書かない部分までつっこむ必要が生じるわけで、 そんなことが本当に可能だとは思えないというか、 仮にできたとしても、ちっとも使いやすくならないという直観があるのだ。 人間が打ち込む情報を減らす工夫はまだまだできそうだとは思っているけれど。

本日のツッコミ(全1件) [ツッコミを入れる]
_ anon. (2007-07-30 12:29)

http://internet.watch.impress.co.jp/cda/news/2007/07/30/16483.html <br> <br>こういうの、どうなんでしょう。おそらく何の関係もないソフトだと思うのですが。

[]