Think Stitch
PRINCIPIA  最近の更新


設計は試行錯誤

設計は必然的に試行錯誤を伴うものだと思います。 これには2つ理由があります。 1つは、欲しいものというのは、それが何であるかを表現してみて初めて明確になるからです。 もう1つは、設計の中に含まれている分割という作業が、一発では決まらないからです。 これがすべてというわけではありませんが、ここではこの2つについて書きたいと思います。

欲しいものは表現して初めて明確になる

日常生活の中や仕事の中で、何か困難や不都合を感じたとき、「こういうものがあれば便利なのに/楽なのに」と思うことがあります。 あるいは何か新しい価値を生み出すアイデアを思いついたときにも、「こういうものがあれば、こんなことができるようになるな」と思うことがあります。 こちらはそれほど頻繁には出会うことができませんが。 いずれの場合にもすでに問題が認識されています。 実はこれはかなり進んだ状態で、問題を認識するということ自体、そこに問題があると気づくこと自体が結構難しいことです。 ましてやこんなふうに問題が認識されていて、さらに答えのイメージを持っている状態は、もうスタートを切っている状態です。 こんなときは、ものを作る人間として、自分の思いつきにワクワクしますよね。

いきなり一般的な話になってしまいましたが、ここではコンピュータのプログラムの話をしています。 プログラムの問題、プログラムの新しいアイデア、プログラムで解決できる問題、あるいは新しい価値を生み出すプログラムということです。 今は昔、今よりもずっと脳細胞が多かった頃は、何か思いつくとすぐにエディタに向かってコードを書き始めたものでした(エディタと呼べるものがないころから)。 小さなプログラムならばすぐにうまくいくかどうかがわかりますし、少しの投資で大きな結果が得られると、それはうれしいものでした。 しかしちょっと規模が大きくなると、とたんにうまくいかなくなります。

まず第一に、やらなければいけないことが次から次へと出てきて、収集がつかなくなってきます。 ここで「出てくる」というのは、作業をしていると、直前にやったことがきっかけで「気がつく」ということです。 たくさん出てきたことを若くてたくさんある脳細胞で保持して処理できたとすると、今度は次から次へとうまく動かない例が出てきます。 これも動かしてみた結果として気がつくということです。 そして直しては動かし、また直しては動かしと繰り返して泥沼になります。 どの段階でも「あ、そっか」、「あっ、そうか、あたりまえだ」、「あ、そうか、気づいてなかった」と独り言の言い訳を繰り返します。 ひどいときには「この段階でこれに気づくなんて、おれってすごい」なんて勘違いもあったりして、愚かなものです。 私だけでしょうか。いえ、独り言をいいながらプログラムを書く人は友達にもいます。

なんども痛い目に会ったあと、脳細胞も減ってしまった今になってわかることは、最初にアイデアを思いついたときに頭の中にあるイメージは、かなり不完全なイメージに過ぎないということです。 アイデアそのものはすばらしいものかもしれませんが、よく言われるようにそれは原石です。 磨く必要があるし、磨いている途中で欠けてしまうこともあります。 アイデアを思いつくだけならば、かなり多くの人ができて、差がつくのはそれを実現するところまでやりきれるかどうかだ、とまで言われます。 そして実現するところまでやりきるには技術が必要です。

おおまかなイメージを問題の解となるところまで磨き上げるには、何よりまず問題そのものも明確にしなければなりません。 「問題は何か」ということを記述するということです。 問題が何かということは、問題の無い状態、解決された状態は何かということでもあります。 問題の解決された状態とは、問題の解が実現する状態です。 したがってそれは解の満たすべき条件ということです。 「問題の解は、これこれこうでなければならない」というわけです。 ご承知の通り、プログラムの世界では、これは要求と呼ばれています。 要求は問題の記述であると同時に、解が満たすべき条件でもあります。

ここでは要求そのものをどのように抽出するかということは扱いません。 これに関する成書はたくさんあると思いますので。 ここでは問題が明確になったと仮定します。整理された要求が手元にあるということです。 ここから解を考えて行きます。これは本質的に創造的な活動です。 具体的な要求の内容によっては、解、あるいは解の候補を出すためのパターン、ヒューリスティクス、あるいは経験などがあるかもしれませんが、基本的に新しい問題に対しては、常に問題を念頭に置いておいて、何か思いつくのを待つしかないでしょう。 発想法のようなものを調べると、積極的な方法として、例えば類推によるものなど、いくつか手法があるようですが。

イメージのスケッチ

最初はおおまかに考えていきます。 いきなり細かいところから精密にはじめても、あとで重要な要求の条件を満たしていないことがわかればやり直しになってしまうからです。 そのために、要求には重要度、優先度をつけておくとよいといわれています。重要なものは頭に入れておく必要があるでしょう。

おおまかに考えているときにも、考えていることを表現する必要があります。 理由は2つあります。 1つは考えていることを固定して、条件を満たしているかどうか確認をするためです。 固定をしないと、確認することを忘れてしまったり、確認の最中に考えていることを都合良く変えてしまったりするかもしれないからです。 もう1つは、表現をするとそこからフィードバックがかかり、いろいろなことを思いつく可能性を高めることができるからです。 他にやらなければいけないこととか、別のアイデアとか、すでに表現されているものとの関係などです。 何かを作るとき、作ったものが十分であるかどうかを完璧に確認する方法はありません。 どれだけ可能性を思いついて検討したかということが重要になります。 この点はちょっと将棋などのゲームに似ているかもしれません。

すると、この段階で使う表現形式がどのようなものであればよいかが見えてきます。

  1. おおまかなスケッチがかけること。いい方を変えると、抽象的な表現ができるということです。
  2. フィードバックがかかりやすいこと。刺激があること。これは視覚的なものが優れています。
  3. 条件を満たしているかどうか確認できるだけの要素や属性を、必要な程度の正確さで表現できること。

このような表現形式があったとしても、要求に含まれているすべての条件を満たす解を1回でかけるわけがありません。 経験やパターンの再利用は別として、まず重要な条件を満たす解の候補を思いついたら、これを表現します。 部分だけでもよいです。 むしろ部分から始める方がよいでしょう。 手戻りが少なくて済むからです。 表現した部分が次に重要な条件を満たしているかどうか確認します。 もし満たしていれば、さらに続けます。 満たしていない場合は、何が悪いのか、どう満たしていないのか、どうすればいいのか、などを考えます。 修正すれば2つの条件を満たすことができるのであれば修正します。 複数の可能性が思いついたらメモしておきましょう。 すっかりやり直しになることもあります。 最終的にすべての条件を満たしていることが確認できたら、解の候補は解に昇格します。 私たちはこれを仕様といいます。 まさに磨いているという感じでしょうか。 あるいは洞窟探検のようなものです。 (行き詰まってしまう候補の方が多いのです。)

このように、設計の一部である解の表現は必然的に試行錯誤になります。 解の探索だということです。 ここでは表現だけでなく、確認がとても重要だということがわかります。 解を作る過程で、何度も同じ条件を確認する必要があるからです。

このような開発の途中段階での確認の重要性は、ユニットテスト、テストフレームワーク、TDD、BDD などの広まりとともに認識され理解されていると思われます。 しかしこれらはもっぱらコードのレベルの話で、設計の初期段階での確認が行われているかどうかは少し疑問です。 もし設計段階でコードのテスト並みに質の高いテストができたならば、開発の効率をずっと上げることができるでしょう。

この点で、一般によく使われている図式による設計表現形式は上記の 3 が弱いように思っていました。 設計段階では解の候補を描くだけで満足してしまい、問題の気づきはコーディングまかせ、確認はシステムテストまかせという印象でした。

分割と合成

作るものが明確になったら、次はどのように作るかを考える段階に入ります。 いわゆる分割という作業です。 「分割統治せよ」というわけです。

ここで素朴な疑問がたくさんあります。 1つはなぜ分割するのかということです。 これには、そもそも分割するとは何をどうすることなのか、という疑問も含まれています。 それから、どのように分割するのかという問題です。 これはよく語られる問題ですね。 これに対する話はここではほとんどしません。 もしそれ期待していた方がいたらごめんなさい。 最後は分割して、それからどうするの?、ということです。 統治するわけではないでしょう。意味がわかりません。 あたりまえのような、あたりまえでないような話です。

なぜ分割するのか

プログラムの最終形はコードなので、コードを構成するプログラミング言語要素の1つ1つを見れば、要素に分割されていることは明らかです。 プログラムというのはコンピュータの動きを表したものです。コードになる前の段階では、望む動きをコードではない何かで表現しているはずです。これには脳内イメージも含まれます。 一方、コンピュータを動かすためには、最終的にはプログラミング言語要素のどれかを使って表さなければなりません。要素1つ1つはコンピュータのプリミティブな動作やそれを組み合わせる糊の役目をするものに対応しています。これ以外のものは作れませんし、ここに含まれていないことはできません。 つまりコーディングとは、難易度の高い、創造性を必要とする積み木、あるいはパズルのようなものです。 望む動きを分割するというのは、無限に供給される固定された種類の積み木やピースを組み合わせることです。 あらかじめ用意されたものだけを使って、望むものを組み立てなさいというゲームです。これを分割と呼んでいるわけです。 使えるピースの種類がはじめから決められているのですから、この意味での分割はコーディングそのものです。 試行錯誤が必要なことは明らかでしょう。

せっかちな方はすでにいらいらしているかもしれません。 分割とはそれのことではないだろうと。 前にも書きましたように、小さなプログラムだったらいきなりコーディングということもあるかもしれません。 しかしある程度以上の規模の開発では、そんなことはしません。 でも、それはなぜでしょうか。

その理由の1つは、仕様のときと同じで、全体の把握や確認作業が困難になるからです。 何万行もあるコードを頭に入れることは不可能ですから、なんらかのかたまりでの認識、あるいは抽象をすることなしに、大局的に把握することは難しいでしょう。 そのような状態で次のコードをどのように書けばよいのか想像がつきません。 「ここでこうするといままでのあそことあそこと... 132,758 箇所と抵触するから...」 なんてできる人はきっといないと思います。 確認もたいへんです。要求にある条件を1つ調べるために数千行を調べて、手直しも数百行する、なんてことを何回も繰り返すことになります。

その代わりに、ソフトウェアシステム全体(1つ)とコード(例えば数万行)の中間にある規模の、設計上の概念的な存在を考えます。 ここではこれをコンポーネントと呼びます。 システムをいきなりプログラミング言語要素に分割する代わりに、コンポーネントに分割します。 コンポーネントの集合体をシステムだと考える、ということです。 つぎにコンポーネントをプログラミング言語要素に分割します。 システムがより複雑な場合には、多段回の分割をします。 システムをコンポーネントに分割し、そのコンポーネントをさらにコンポーネントに分割し、最後のものをプログラミング言語要素に分割します。

このようにコンポーネントという設計概念を使って分割を多段回に行う理由はいくつかあります。 1つはシステムの構造や動作が把握しやすくなるので、要求にある条件の確認がしやすくなるからです。 問題の領域、つまりシステムが利用される領域にある概念や物理的な存在が、そのままコンポーネントに対応づけられるということもよくあります。 これは分割のすぐれた戦略として有名です。 この方法はシステムの理解をさらに容易にします。

もう1つの理由は解くべき問題を簡単にできるからです。 たとえば複数ある要求の一部を、分割したコンポーネントに排他的に割り当てることができる場合があります。 すると、コンポーネント1つあたりが実現しなければならない条件の数が減りますから、問題としては簡単になります。分割統治という言葉は、この点について使われているのだと思います。

3つ目の理由はあるコンポーネントの概念や存在が、1つのシステム開発を超えて再利用できる場合があるからです。 既存のコンポーネントを再利用する設計では、そのコンポーネントをプログラミング言語要素のように性質の決まっている部品と考えることができます。

コンポーネントへの分割とプログラミング言語要素への分割が異なる点は、後者では対象が何か、それらをどのように組み合わせることができるのかという点についてあらかじめ与えられているのに対し、前者では対象が何であるかということも、それらの組み合わせ方も決めなければならないということです。 これは相対的な問題というか、立場の違い、視点のレベルの違いというような問題で、例えばプログラミング言語を設計している人にとっては機械語の命令が与えられた要素で、プログラミング言語要素はコンポーネントと同じく決めるべき対象になります。 さらに CPU のアーキテクチャを設計している人にとっては、半導体、ロジック要素、あるいはマイクロコード、あるいは最近ですと各種 HDL などが基本要素で、機械語命令自体が設計の対象になります。 設計の分割とはこういうことです。 還元だということです。

分割するとは何をどうすることか

コンポーネントに分割するとは、それぞれのコンポーネントが何であるか、ということと、それぞれのコンポーネントの間にはどのような関係があるのか、ということを決めることです。 プログラムの場合は動きが主要な関心です。 それ以外の品質特性はちょっと置いておくと、コンポーネントへの分割とは、各コンポーネントの個としての動作を決めることと、それらの間の相互作用、つまり同期とか通信などを決めることです。 動作については状態遷移モデルや抽象度の高い疑似コードなどで表現することができます。 相互作用は、同じく状態遷移モデルや相互作用図、プロトコル記述言語などで表現することができます。 通常、相互作用はコンポーネントの動作の一部になるので、相互作用を別枠で表現すると1つのものごとに対して複数の表現ができることになります。 これはふつうのことで、何に関心があるかで、それに適した表現形式を選ぶということです。

どのように分割するのか

前置きした通り、これについては少しだけにしておきます。 有効な方法は、デザインパターン、過去の事例、ヒューリスティクス、そして経験の応用でしょうか。 具体的な戦略としては、これも先に述べましたが問題領域にある概念や物理的存在をコンポーネントとする方法があります。再利用性やメンテナンス性を高めるための分割方法としては、要求や仕様の変化の速度が大きく異なる部分にはさみを入れるという考え方があります。 こうすると、変化の速い部分だけが集中するので、そこだけ修正したり入れ替えたりすればよいからです。

分割した後はどうするのか

コンポーネントへの分割は試行錯誤になります。 動作に限定して話をすると、望む動作はシステムに対して、仕様として定められています。 これに対して設計者は、システムを構成する各コンポーネントの動作と相互作用を決めます。 これを決めると、これらを合成してできるシステムの動作は原理的に決まってしまいます。 このことはコンポーネントのコードを書くと、システム全体の動きが決まるのと同じことです。 したがって、各コンポーネントの動作と相互作用は、合成したときに仕様を満たすように狙って決めなければなりません。 しかしいきなり正解が出せるわけがありません。 とりあえず一通りコンポーネントを仮決めして合成したとすると、仕様に抵触する部分が出てくるでしょう。 そうしたらコンポーネントのどれかを直すわけです。 システムを直すわけではありません。 あたりまえのことです(すみません)。 すると、抵触していた部分は直るかもしれません。 しかし今度は別のところが問題を起こすかもしれません。 まるで料理の味付けのようなものです。 経験豊かなプロのシェフは、コンポーネントである調味料の量を適切に決めて、一発で味を完成させるかもしれません。 しかしソフトウェアははるかに複雑で、そうはいきません(シェフの方がいたらごめんさない)。 デザインパターン、ヒューリスティクス、経験などを駆使しても、ある程度の試行錯誤は避けられないでしょう。

したがって設計は分割しただけでは終わりではありません。 合成によって得られるシステムが仕様を満たしていることを確認しなければなりません。 これは状態遷移図や相互作用図の上で、手動で行うこともできますが、コンポーネント修正のたびに確認が必要なので、自動化が望まれるところです。 システムテストまかせでは効率が悪いことはいうまでもありません。

試行錯誤を軽いフットワークで繰り返す

結局のところ、プログラムの開発では、仕様化にしても分割設計にしても試行錯誤は避けられません。 いつでもどこからでも、手戻りは起こりえます。 であれば、できるだけ試行錯誤を効率よく行うことを考えた方がよいでしょう。 そのためには次のような環境が有効であると思います。

  • 抽象的な表現ができること。注目する側面に集中できて、余計な作業を削減できます。
  • フィードバックが得られる表現形式。できれば視覚的なもの。
  • 動作の確認ができるほどに詳細な記述ができること。しかし詳細過ぎないこと。
  • コンポーネントの動作から、合成したシステムの動作が得られること。
  • 確認が自動でできること。
  • 問題が発生した場合の、分析の支援があること。

SyncStitch はこのような試行錯誤を支援するために開発しました。 プログラムを開発するときに、いつでもまず開くプロの道具として使っていただけるといいなと思っています。 (E×CEL じゃなくて。UML ツールの次くらいに。)

余談

  • 分割すればいつでも簡単になるとは限らないのだと思います。 すぐに例が挙げられるほどではないのですが。 切り方が悪ければ、状態も相互作用も増えるばかり、なんてことがありそうです。 大量の通信が発生して、それを処理するために状態が増えて、本来の仕事が埋もれているというような。 そういうことを評価する基準と、実際に計算して表示してくれるような仕組みがあると面白いかもしれません。 いわゆる凝集度や結合度のように(いわゆるとかいって、あまりよくわかってないんですが)。
  • 分割したときに、コンポーネントが直接担う機能と、コンポーネント間の協調によって実現される機能とがあるのだと思います。どういう場合にどうすればいいのかというような設計手法としてまとめることができるとよいと思います。
  • 将棋やチェスなどのゲームで、いつでも盤面を紙に書いて検討してもよいとしたらどうなるのか、想像するとちょっと楽しいです。コンピュータの支援もありありだとどうなるでしょう。持ち込み可の試験みたいですが。
  • いくら分割によって問題が簡単になるといっても、 ある複雑さの問題を解決するには、その複雑さを同時に頭に入れる必要はあるのでしょう。
2013/08/05
© 2013,2014,2015 PRINCIPIA Limited