|
漁師プログラマ
山崎敏 YAMAZAKI Satoshi
vol.07 |
|
|
●●ある日 |
|
|
筆者がまだ大学生のころ,MSXというパソコンが流行っていまして,そのMSX用のゲームソフトをアルバイトとして作ることになったのです.そのころのソフトウェア開発はひどいといいますか,とても「開発」と言えるようなレベルではなくて,とにかくコードの書ける人間をかき集めてきて,マシンを与える…それだけなのです.
今ではそんな状態でまともなプログラムが完成するとはとても思えないのですが,当時は「ソフトウェアの作り方」なんて存在しないようなものでしたから,ひたすら根性,根性,ど根性の世界でした.
開発手法という言葉を無理矢理に探すのなら「人海戦術」となるでしょう.それでも若さだけが武器であった筆者は徐々にゲームを形にしていったのです.夜中になると食料の調達ということで,コンビニに行き,夜食にカップラーメンという,お腹の脂肪が気になりはじめた今となっては選択外のメニューがパターンになっていました.
そんなある日,悲劇が起こります.なんと食べかけの「チリトマトヌードル」を開発マシン(MSX)にぶちまけてしまったのです.こぼすとか,たらすとか,そんななまやさしい状況ではなくて,とにかくまるごと全部キーボードの上にぶちまけてしまったのです(注1).「ぎょえー!」.キーボード上には麺が散乱し,汁はマシンの中を通って下まで垂れています.しかもプログラミングの最中でしたからマシンは停止.プログラムはお釈迦.
とりあえず急いで電源を切り,分解,掃除をしてみたもののマシンは動かず.納期は近いし,かえのマシンなんてないし,お先真っ暗,嗚呼無情,最悪な状態.とにかく「まだ拭ききれていないところがあるのだ…」と自分に言い聞かせ,最後の手段と基板の水洗いを強行!(注2)かなり時間をかけて乾かして,祈るように電源ON!…ピポ.「うごいたー,よかったー」.
その事件以来,筆者はマシンの近くではカップラーメンを食べないことにしています.え?ゲームは完成したのかって?も,もちろんですとも.あたりまえじゃないですかぁ.やだなぁ.はは.はははは…(汗).
その日から十数年経った今では,ソフトウェアの開発手法は人海戦術以外にも多くのパターンが提供されるようになりましたが,状況はあまり変わっていない気もします.
注1)一般にMSXは本体とキーボードが一体になっています.
注2)良い子はまねしちゃダメですよ.
|
|
|
●●パターンって何だろう |
|
|
●身近なパターン
プログラマという人のイメージは,というとたいていは「太っている」と思われているようです.マラソンの選手のイメージは,というと「痩せている」となるでしょうか.やはりその人の仕事や趣味によって生活パターンが決まり,そのパターンによって体型までもが左右されてしまうようです.ちなみに,筆者はプログラマですが標準よりも痩せているので,必ずしもパターンにあてはまるケースばかりではないということでしょう(注3).スレンダーな人にはスレンダーな人の生活パターンがあるだろうし,太っている人はそれなりの生活パターンがあると思うのです.学生のパターン.会社員のパターン.お金持ちのパターン.健康な人のパターン.ほかにもいろいろなパターンがあります.仕事だって,家事だって,料理だって,趣味だって,恋愛も,人生も,何にでもパターンがあると思うのです.
そのパターンが良いとか悪いとかではなくて,世の中に同じことの繰り返しは多いし,その繰り返しの中で学習をして,失敗するパターンを覚え,成功するパターンを嗅ぎ分けているわけです.パターンがあたりまえになると習慣になり,無意識の状態でそのパターンをこなせるようになります.泳ぎ方を覚えれば一生忘れないでしょうし,自転車に乗れるようになればこれも一生忘れないでしょう.職人や匠たくみと呼ばれる人たちも仕事のパターンを体で見極め,その腕に刻み込み,言葉には表せない習慣としてパターンを持っているわけです.
注3)職人気質のプログラマは痩せているという説もあります.
●ソフトウェア開発とパターン
ソフトウェア開発においてのデスマーチもパターンの1つと言って良いと思います.アンチパターンの1つといいますか,失敗パターンの1つですね.
逆に良いほうのパターンとして「KISS(Keep It Simple, Stupid.)」や「可読性を意識する」があると思います.筆者は可読性の重要さについて何度も述べているのですが,実際に可読性のメリットが発揮されるのは数ヵ月から数年先だったりするので,即効性のある薬ではないのです.あとからじわじわと効いてくる薬のようです.開発現場の最前線でひーひー言っている人たちや,現場から遠く離れたところでのんびりと進捗の管理をしている人たちには可読性のメリットはなかなか伝わらないのかもしれません.
とはいえ,たくさんの仕事にはたくさんのパターンがあり,つねに新しいパターンも生まれています.
言葉にできないパターンもたくさんあるでしょう.
しかし,そのパターンが言葉として伝わるのなら,共通のイメージとして伝わり,コミュニケーションがスムーズに行われ,生産性が上がるので,結果として良いことだと思うのです(注4).
各個人でもいくつかのパターンを持っていると思います.今回のテーマである「デザインパターン」についても「こんなパターンは前から使っているよ.
今さら教えてもらっても何のメリットもないよ」と言われる方もいると思います.しかし,デザインパターンのメリットは「新しいパターンを教える(教わる)」ところにあるのではなくて「すでに知られているパターンを共有する(同じ名前を付ける)」ところにあるわけです.同じ名前で同じ概念を共有するところに「デザインパターン」の真の目的があると思うのです.
しかし,デザインパターンであっても万能ではありません.むしろ,ソフトウェア開発において万能ツールは存在しないと言い切っても良いと思います.とくに筆者は「これをやったら必ず失敗する」というパターンは存在すると思いますが「これをやったら必ず成功する」というパターンは存在しないと思うのです.「適材適所」です.この言葉がピッタリはまると思います.どんな方法論も適応すべき所を間違えてはいけないということです.
注4)筆者は,どんな情報もマニュアル化し「マニュアルのとおりに実行すれば良い」などといった個人の考えの介入を抑止するようなことはすべきではないと思います.しかし,情報の共有化によるメリットは大きいと思います.
|
|
|
●●パターンを共有する |
|
|
●パターン共有のメリット
電話やFAXやメールなどのシステムを思い浮かべてください.これらの使い方は統一されて標準的な使い方のパターンが決まっていると思います.システムの設計者が決めた部分もありますが,そのインターフェースをどのように使うか,どのようなパターンにするか,は誰かが決めたというよりも自然に淘汰されて決まったと言えるでしょう.
ここで重要なのは,このパターンを「ほぼすべての人が共有している」ということです.共有しているからやりとりができるし,話がスムーズに進むのです.
たとえば
A:「じゃぁ,土曜の午後6時に新宿で決まり」
B:「で,新宿のどこ?」
A:「着いたらケータイに連絡入れるよ」
B:「…オレ,ケータイ持ってないよ…」
A:「…」
と,これはうまくいかないパターンでした.この場合,ケータイというツールのインターフェースとその使い方のパターンを共有しているので会話が成り立つのだし,お互いにメリットがあるわけです.これから紹介する「デザインパターン」もそういったインターフェースと使い方のパターンを共有することで,ソフトウェア開発でのメリットを引き出そうとしているのです.
●何のためのパターンか
ソフトウェア開発では「車輪の再発明をするな」という言葉をよく聞きます.すでに定着している概念やパターンがあるのにもかかわらず,同じ概念をオリジナルのコードで書き直すような無駄な行為はするなということでしょう.「同じことを2度書くな(注5)」や「コピー&ペーストをするな(注6)」という言葉の意味と似ているとも思います.
もう少し具体的に言うと「sort()関数が用意されていて,正しく動くことも確認されていて,誰もがその存在を知っているにもかかわらず,自前でソートするプログラムを書くという行為についての警告」になるでしょう.書くという労力も,そのコードをテストする労力も,そのコードを他人が読んで理解するための労力も,多くの労力が無駄になってしまうということです.共通のパターンとして定着したモノを無駄なく利用しようと言っているわけです.
また,「誰が書いても同じになるような普通なコードを書け」とも言われます.「誰もが知っている普通なパターンを使え」とも言えるでしょう.これは可読性を上げることにも関わるのですが,コミュニケーションの円滑化,情報の伝達速度の向上だと思うのです.コードが意図している意味を情報として伝えようとする,広い意味でのコミュニケーション手段だと思います.
前回紹介したSTLも「STLを使うという共通のパターン」によってメリットを引き出すわけですし,とくにSTLのイテレータのようなインターフェースを決め,そのインターフェースに対して共通のアプローチを決めるパターンは,これからのソフトウェア開発で主流になっていくと思うのです.
注5)「同じことを2度書くな」というのは,ソースコードとコメントとか,ソースコードとフローチャートとか媒体を超えても起こることで,どちらかを変更してももう一方の変更を忘れるために起こってしまう失敗に対する警告です.できるかぎり情報は集中管理したほうが良いということです.
注6)「コピー&ペーストするな」というのは,安易にコピーしてソースコード上に同じコードを複数書くことへの警告です. 同じ機能は関数などにまとめるべきですが,必要以上の機能をまとめると余計に混乱するので加減が難しいのです.プログラマの腕が問われるところです.
|
|
|
●●デザインパターン |
|
|
●オブジェクト指向とは別物
デザインパターンといえば,次の本が有名です.
・『Java言語で学ぶデザインパターン入門』(結城浩著/ソフトバンクパブリッシング/ISBN:4797316462)
・『オブジェクト指向における再利用のためのデザインパターン』(Erich Gammaほか著/本位田真一,吉田和樹監訳/ソフトバンクパブリッシング/ISBN:4797311126)
どちらもデザインパターンを知るのに欠かせない本だと思います.ただ,筆者には1つだけ引っかかる点があるのです.それは「オブジェクト指向」です.
デザインパターンはあくまでもパターンであって,オブジェクト指向とは直接は関係がないと思えたのです.
もう少し書くと,デザインパターンを具体的に実装する1つの方法としてオブジェクト指向を使うことはあるでしょう.しかし「デザインパターンはオブジェクト指向で実装しなければならない」と言ったらこれは嘘だと思うのです.デザインパターンはパターンであって,そのパターンをどのように実装するかは単なる手段の一例に過ぎないと思うのです.設計手法としてデザインパターンを適用したあとに,実装手段としてオブジェクト指向言語でないC言語を採用しても良いと思うわけです.なにもデザインパターンは実装手段をJavaやC++に限った設計手法ではないと思うのです.
たとえば,Facadeパターン(複雑な処理を簡単なインターフェースとして提供する)や,Builderパターン(生成過程を共通インターフェースとして提供する)などは,JavaやC++以外の言語でも十分扱えるパターンですし,デザインパターンとして知られる以前から(オブジェクト指向とは無関係な環境でも)使われていたパターンだと思います.
とはいえ,デザインパターンを否定するわけではありません.デザインパターンはその「既知のパターンをまとめて,共通の言葉(概念)として扱おうとした姿勢」にメリットがあるのですから.
●パターンはパターンである
デザインパターンの解説ではオブジェクトというよりもインターフェースの概念を用いた説明が多くなるので,オブジェクト指向で説明すると楽らくになるとは思います.しかし,オブジェクト指向を理解していない人にはわかりづらくなる傾向は避けられないと思います.Iteratorパターンは「配列のポインタのような振る舞い」ですし,Proxyパターンは「代理人の構造」です.オブジェクトというよりは「パターンはパターンである」としか言えないと思います.
とにかくパターンとオブジェクト指向は直接は関係がないのです.ただ,デザインパターンはオブジェクト指向を用いて解説している(もしくは,オブジェクト指向で解説できるパターンをデザインパターンとした)ということなのです(注7).
注7)本の題名には『オブジェクト指向における再利用のためのデザインパターン』と,おもいきりそのまま書いてありました.
|
|
|
●●パターン的な考え方 |
|
|
●インターフェースの考え方
デザインパターンが意識していると思われるところは,オブジェクト指向よりも,オブジェクト指向の一部であるインターフェースの考え方だと思います.インターフェースを決めて使い方を決めて,これをパターンとして命名するアプローチだと思うのです.たとえば,booというオブジェクトがあったとします.そのbooの中身をソートする機能を実装したいとした場合,次の2つの実装方法があると思います.
@ オブジェクト指向の考え方でメソッドを追加実装する.
→ boo.sort() ;
A インターフェースを実装し,ほかのsort()とパターンを共有する.
→ sort( boo ) ;
@の考え方では,すべてのオブジェクトにsortアルゴリズムを実装しなければなりませんが,Aの考え方であれば,sortに必要なインターフェースさえ実装してしまえばアルゴリズムは共有できるのです.パターン的な考え方とは,オブジェクトにメソッドを追加するだけの考え方から一歩踏み出して,メソッドを共通化することに新たなメリットを見つけた考え方だと思うのです.AはSTL的な発想でもあります.STLではイテレータという共通インターフェースの概念を持ち込むことで,データ構造とアルゴリズムというパターンを実装しています.
●デザインパターンの意味するところ
デザインパターンという言葉や概念が知られる前から,データ構造やアルゴリズムというパターンは知られていました.たとえば,データ構造では,木構造やリスト,キュー,スタック,そして配列など,STLでも多く取り入れられています.そしてアルゴリズムもソートやサーチ,そして繰り返し(for_each)など,こちらもSTLで多く取り入れられています.
しかし,デザインパターンはデータ構造やアルゴリズムのパターンではありません.繰り返し使う考え方とか,再利用できる経験などが近いと思います.
データ構造やアルゴリズムのパターンよりも,もっと小さい部品といいますか,部品にならない情報(鉋(かんな)の持ち方とか,金槌(かなづち)の使い方とか)といったイメージが近いと思います.
|
|
|
●●デザインパターンを試してみよう |
|
|
ここで,たくさんあるデザインパターンの中から1つ「Visitorパターン」を解説します(リスト1).
| リスト1 Visitorパターン(非継承版) |
#include <stdio.h>//puts()
//お店を実装
class AppleShop
{
public:
template< class _VisitorPtn >
void Accept( _VisitorPtn &in_visitor )
{
in_visitor.Visit( this ) ;
}
} ;
class BananaShop
{
public:
template< class _VisitorPtn >
void Accept( _VisitorPtn &in_visitor )
{
in_visitor.Visit( this ) ;
}
} ;
//お客さん(訪問者)を実装
class Hirosue
{
public:
void Visit( AppleShop * in_apple )
{
puts( "リンゴは1つくださいな" ) ;
}
public:
void Visit( BananaShop * in_banana )
{
puts( "バナナはいらないんです" ) ;
}
};
class Honjyo
{
public:
void Visit( AppleShop * in_apple )
{
puts( "リンゴを2つくれへん?" ) ;
}
public:
void Visit( BananaShop * in_banana )
{
puts( "バナナはあるだけ全部もらっとくわ" ) ;
}
} ;
//買い物する町を実装
int main()
{
//お店4つ テンプレートだと
//テーブルにできないのが悲しい
AppleShop shop1 ;
BananaShop shop2 ;
AppleShop shop3 ;
BananaShop shop4 ;
//2 人が順番に4 つの店に訪問する
Hirosue ryoko ;
Honjyo manami ;
//テーブルでないのでループにもできない
shop1.Accept( ryoko ) ;
shop1.Accept( manami ) ;
shop2.Accept( ryoko ) ;
shop2.Accept( manami ) ;
shop3.Accept( ryoko ) ;
shop3.Accept( manami ) ;
shop4.Accept( ryoko ) ;
shop4.Accept( manami ) ;
return 0 ;
} |
Visitorパターンを選んだ理由は,実用度が高いと思われるのと,比較的複雑な部類に入るパターンだと思ったからです.ここでは,オブジェクト指向の継承は使わずに説明します(注8).なお,結果は次のようになります.
リンゴは1つくださいな
リンゴを2つくれへん?
バナナはいらないんです
バナナはあるだけ全部もらっとくわ
リンゴは1つくださいな
リンゴを2つくれへん?
バナナはいらないんです
バナナはあるだけ全部もらっとくわ
Visitorパターンは訪問する側と訪問される側のインターフェースを決めます.その後「訪問する側のインスタンスが訪問される側のインスタンスを訪問する」というパターンになります.ちょっと表現がややこしいですね.訪問する側のインスタンスを「客」,訪問される側のインスタンスを「店」として説明します.「客が店を訪問する」というパターンになります.簡単ですね.
客のインターフェースはVisit()です.Visit()をお店の種類分用意します.店のインターフェースはAccept()です.Visit()インターフェースを持つ客を受け入れます.インターフェースとしてはこれだけです.
実装時には複数の客と複数の店があり,それぞれの客がそれぞれの店を訪問します.このとき,店の並びは一列に並んでいても良いですし,木構造に並んでいても問題はないのです.Visitorパターンのメリットは店の並びとは無関係に店と客を実装できるというところです.ディレクトリとファイルの構造のように,ディレクトリやファイルという店がどう並ぶのかわからないといったデータ構造に効果を発揮します.詳しくはリストを見てください.
注8)筆者は継承のデメリットが嫌いなのと,C++のテンプレートを使えば同じ構造を実現できるので
|
|
|
●●デザインパターンへの近道 |
|
|
筆者が考えるデザインパターン(23パターン)を簡単に理解するための順番は次のようになります.
それぞれ表1〜5に対応しています.
@ ラッパー的な考え方(表1)
A テンプレート的な考え方(表2)
B インスタンスのあつかい(表3)
C 比較的簡単な振る舞い(表4)
D 少し複雑な振る舞い(表5)
@(表1)は基本的なラッパーの考え方で,既存のクラスのインターフェースを統一するためにラップするパターンです.それぞれラップする目的が違いますが,アプローチが同じなので一度に理解できると思います.
A(表2)はテンプレートな考え方です.これは一部の機能を未実装にしたり複数の実装と入れ替える考え方で,外側のテンプレートを用意しておき中身を取り替えるようなものです.
B(表3)はインスタンスの扱いに関する考え方です.このあたりまではとくに難しい考え方は出てこないと思います.
C(表4)の比較的簡単な振る舞いとD(表5)の少し複雑な振る舞いは,とくに共通する考え方はないと思います.1つ1つ別々な考え方だと思って良いでしょう.
筆者が実践でもよく使うと思うパターンには「★」を付けておきました.デザインパターンの理解に挫折した人は,これらの情報を元にもう一度挑んでみると良いでしょう.
|
|
|
●●最後に |
|
|
オブジェクト指向のメリットを理解することも重要ですが,デザインパターンのメリットを理解することも重要だと思います.それぞれ違った方向性を持っていると思います.
また「デザインパターンの本にある23パターンを記憶すればそれでおしまい」という考え方ではなくて,そのパターンをどこに使うべきかとか,共通のパターンがあることによるコミュニケーションやコードの可読性向上のメリットも理解してほしいと思います.デスマーチというパターンがあることの意味や,夜中にカップラーメンを食べるというパターンの意味も,同時に考えてみてほしいとも思うわけです.
次回は「職人気質」について書いてみたいと思います.お楽しみに.
|
|
|
●コラム C++で読むデザインパターン |
|
|
デザインパターンにはメリットがあります.ただし,本を読んで理解しただけでは足りないと思います.実際に体感して自分のモノにするべきと思うのです.何を隠そう,筆者はデザインパターンの本を読んだだけでは理解できませんでした.がんばって読むのですが,眠くなってしまうこともしばしばありました.漁師プログラマとしては「文章を読むよりも実際のコードを読んだほうが容易に理解できるのに…」といった思いが強くなり,ネット上を検索してみたものの,お目当てのコードを見つけることはできませんでした(注A).
そんな経緯もありまして「なければ自ら作る」というプログラマ魂がふつふつと沸きあがり,筆者のWebページ(http://www.01-tec.com/,図A)の中の「C++で読むデザインパターン」というドキュメントで公開するにまでに至りました.「デザパタ本を読んでみたけど,よくわからなかったよ」とか「コードで見せてくれよ(筆者のようなタイプ)」と思った人は一度ご覧ください.
注A)正確には見つけたのですが削除されてしまって,今では存在しません.
|
|
|
●コラム ランダムアクセッサ |
|
|
STLの話に戻ってしまうのですが,STLのイテレータの種類の中に「ランダムアクセスイテレータ(Randome
Access Iterator)」という,イレテータでありながらランダムにアクセスもできるというゴージャスな仕様のイテレータがあります.
個人的には,このランダムにアクセスできるという配列のような振る舞いをするパターンはイテレータとは別のパターンとして用意すべきではないかと思うのです.
具体的には,at()またはoperator[]と,size()インターフェースを実装するオブジェクト(vector<>など)が「ランダムアクセッサパターン」と呼べると思いますが,いかがなものでしょうか.
|
|
|
●コラム データ構造とアルゴリズムとIterator |
|
|
デザインパターンのIteratorパターンも,STLのIteratorも,データ構造とアルゴリズムの橋渡し的な位置づけにあると思います(図B).

|
|
|
●コラム 入力と出力は比例する |
|
|
●これもパターン
筆者は日常の生活において「入力と出力は比例する」というパターンが重要だと思っています.簡単に言うと,今使っているパターンはいつかどこかで覚えたパターンなんです.どこかで入力したから今出力できているといった比例関係にあると思うのです.その昔,転んで泣いた経験から今はちゃんと歩けるのだし,車の運転を覚えたから今でも運転できるのです.
考え方もそうです.どこかで読んだり見たり聞いたり,その蓄えがそのまま今の自分の考え方となって出てきているわけです.種を蒔(ま)いたから収穫できるわけです.エネルギー保存の法則ではないのですが,何もないところから突然何かが出てくるということはないのです.過去の入力があったからこそ現在の出力があると思うのです.
●何を入力するか
では,何を入力すれば良いのでしょうか.勉強?そういう考え方もあるかもしれませんが,筆者の体験では勉強だけでは足りない気がするのです.本を読んだり,映画を観たり,散歩でも良いでしょう.とにかく何か行動して,その行動から何かを受け取る姿勢になることが重要だと思うのです.コンピュータ関係の専門書を読んで,コンピュータの知識を深めることも必要ですが,それ以上に専門外の知識や体験を増やすべきと思います.
●頭が切れる→多趣味?
筆者は思うのです.頭の切れる人は多趣味であると.
これはおそらく「頭が切れるから多趣味になった」ではなくて「多趣味だから頭が切れるようになった」といいますか,二重螺旋構造だと思うのです.お互いがお互いを刺激しながら螺旋階段のように登っていくイメージです.
1つの趣味からではなく,多くの趣味からおもしろさを感じ取れる人というのは,それだけ入力のアンテナが広いことだと思うのです.よって,自然に出力も多くなり,頭が切れるという話になる.頭が切れると,よりアンテナが広くなり,また趣味が多くなっていく,この繰り返しです.ポジティブなループです.ループというよりやはり二重螺旋(スパイラル)です.
なかには日々「おもしろくないなー」と思っている人もいるかと思います.それはやはり主観だと思うのです.世の中のことをどう感じるかは,自分のアンテナの性能の問題だと思うのです.世の中にはいろいろなことが起こっているし,いろいろな体験をしている人がたくさんいます.それを感じ取れないというのは,ちょっぴり損をしているかもしれません.まずは少しずつアンテナを広げて行動しましょう.
●自分を変える方法
もし,自分を変えたいと思う人がいたのなら,まずは入力を変えると良いと思います.読む本を変えて入力する言葉や考え方を変え,見るものも変え,行動も変えて,自分の周りを置き換えていくわけです.すぐに効果は現れないかもしれませんが,徐々に自分が変わるのがわかると思います.
●自己とはブレンド?
「入力と出力が同じなら自己はないのか?」という話にまでなってしまうかもしれませんが,そうはなりません.なぜなら,入力したものがブレンドされ,時間をかけて熟成されるからです.この何をどうブレンドし熟成させるかが「自己」であると筆者は思うのです.
とにかく本を読むことと,そこで得た知識を体感に変えるために実際に行動をしてみることが重要なのです.
●漁師プログラマの原点
筆者が「漁師プログラマ(注B)」を名乗る原点はここにあります.本に限らないのですが,情報というものは,大筋はあっていても細かいところが伝わらなかったり,抜けてたり,古くなっていたりすることが多いのです.
ですから情報を鵜呑みにする前に実際に行動して確かめてみることが大切なのです.たとえば,リンゴの皮の剥き方を身振り手振りで教わったとしても実際に剥けるようになるかどうかはやはり体感で覚えるしかないと思うのです.やってみて初めて理解できることは多いですし,考えるよりもやってみたほうが簡単だったということはよくあります.何事もやってみなければわからないということです.
もう一度強調しますが「種を手に入れても蒔(ま)かなければ収穫はない」ということです.
注B)理論ではなく実践を重視する(コードは動いてナンボと思っている)プログラマのこと.筆者の造語.
|
|