はじめに
過去に作成したコードを眺めると、プログラミングを始めた頃には気付かなかった欠陥(必ずしもバグとは言えないもの)が見えてきます。リサイクルと同様、あるソフトウェアを設計する際に、後の再利用を考慮した場合とそうでない場合では、メンテナンスに伴うコストが激変します。同じ教訓は「西暦二千年問題」からも学べます。
今回は、先の連載で紹介した3つのゲームから共通する部分をフレームワークとして抽出し、再考します。
対象読者
こんな症状を抱えているなら……。
- コーディングよりデバッグ/メンテナンス作業に時間を割かれる
- 仕様の変更に柔軟かつ迅速に対処できない
前にも同じことが
先の連載で紹介した15パズル/ライフゲームには、重複する部分があります。これらのコードを作成しているときに、デジャビュにも似た「何度も同じようなコードを書いた」感覚がしませんでしたか。実際にオセロゲームは、15パズルゲームの改良という形で作成したといっても良いでしょう。
それは利用者の立場からも明らかです。マス目の数に違いはあるものの、どちらも似たようなボードを使ったゲームです。盤面をクリックすると、すぐに結果を確認できます。マス目の次数を増やすと、パズルゲームの難度は上がります。マス目の数を減らすと、ライフゲームは単純な機構なのが分かります。先の連載では、同じボードを再利用すると、(オセロゲームなどのように)他のゲームを容易に作成できることを確認しました。
ハリウッドの原則
アカデミー賞の授与式の季節になると、世界中の注目を集めるのがハリウッドです。業界の違いはあっても、同じソフトを作成する立場に身を置くと、観客としてではなく、制作者の立場から見入ってしまいます。その華やかな旧き良き時代のハリウッドの舞台裏には、ある掟(暗黙の了解)があったようです。古典的な「ハリウッドの原則 Hollywood Principle」のメタファーから学べることは、少なくありません。
我を呼ぶなかれ、我が汝を呼ぶのだ
既存のフレームワークと新規のコードを共存させるには「ハリウッドの原則」を理解するのが早道です。プロデューサー/俳優の関係は、これらの関係を彷彿とさせます。
俳優たちは、プロデューサーから通知されるまで、自分が出演者として選ばれたかどうか分かりません。また、それを自分から打診することはタブーとされていました。彼らは作品の配役が発表されたときに初めて、自分が採用されなかったことを知るのです。どのような名優でも、この原則の前では平等でした。
皆さんは、あるメソッドを定義して、それを呼び出すコードを記述するでしょう。しかし、フレームワークを利用する場合、自分が記述したコードを自分で呼び出すことはありません。それを実行したときに初めて、自分のコードが採用されたことを知ります。フレームワークの背景にある暗黙の了解を「ハリウッドの原則」が象徴しています。
オブジェクトの文字列表現
皆さんは既に、無意識のうちにフレームワークを利用しています。
class Tile(Shape): def __str__(self): return "%r(%d,%d)"%(`self.value`, self.x, self.y)
先の連載では、クラスTile
において、メソッド__str__
を定義しました。しかし、このメソッドを利用するコードはどこにもありません。利用しないメソッドが、なぜ必要なのでしょう。
print ">>>", tile
これを実行すると、次のような文字列が出力されます。
>>> '5'(0,2)
なぜこの文字列が出力されたのでしょう。print
文は、式を評価して得られるオブジェクトに対しては、組み込み関数str
によって得られる文字列を出力します。つまり、このメソッドが暗黙のうちに利用され、次を実行したことと等価です。
print ">>>", str(tile) print ">>>", Tile.__str__(tile)
インスタンスに固有の文字列表現として__str__
を再定義することは、Pythonの作法です。つまり「利用するためではなく、利用してもらうためにコードを記述する」のが、フレームワークを習得するための第一歩です。先の連載で、オブジェクト指向の本質は「メッセージの送受信」と紹介したように、オブジェクト間の双方向通信の必要性を示唆します。
printOn:
/storeOn:
で規定して、printString
/storeString
で利用する」という暗黙の了解があります。その場合、printString を再定義するのは(可能ですが)作法に反します。同様に、str() を再定義するのは(可能ですが)この原則に反する行為です。つまり、できるけどやってはいけないことを知る「大人の対応」が求められるのです。Java
のtoString()
がこれに相当します。