PHP Mentors (Posts tagged dci)

1.5M ratings
277k ratings

See, that’s what the app is perfect for.

Sounds perfect Wahhhh, I don’t wanna

DCI Tokyo 2 - Commonality / Variability Analysis: Practical MPD - が開催されました

2018年1月10日に開催された DCI Tokyo 1 に続き、2018年3月27日に DCI Tokyo 2 が開催されました。今回も James Coplien @jcoplien さんをお招きしてのトークセッションとなりました。会場は 株式会社ヴァル研究所 様に提供していただきました。

セッションは、前回同様 @remore さんと @ganchiku さんによる同時通訳とともに進められました。

今回のテーマはマルチパラダイムデザイン(Multi-Paradigm Design: MPD)の中核を成し、DCI / リーンアーキテクチャ(Lean Architecture)とも深く関係する 共通性/可変性分析 でした。

レポートは @smori1983 が担当させていただきます。

当日の様子は Coplien さんの許可を得て YouTube の DCI Tokyo 公式アカウントにて公開されています。Coplien さんのおもしろTシャツにも注目しながら、ぜひご覧ください。

全体の流れ

全体の流れとしては、1980年代以降のソフトウェア開発の歴史を、主にオブジェクト指向に対する考え方の変遷を通して振り返りつつ、MPD と DCI の関係性に迫るものとなりました。

オブジェクト指向(1988)

当時は C++ の他に、テレコム業界向け言語である CHILL や、軍需産業向け言語である Ada などの言語があり、それらの言語がオブジェクト指向なのかという議論がされていました。

最終的に C++ が残ることになりますが、C++ の作者である Stroustrup 氏は、そもそも C++ をオブジェクト指向言語ではなく、マルチパラダイム言語と定義していたことに注意する必要があります。

当時考えられていたオブジェクト指向の構成要素は次の3項目でした。

  1. inheritance / 継承
  2. polymorphism / ポリモーフィズム
  3. instantiation / インスタンス化

1994年に Coplien さんは David Weiss 氏と Robert Chi Tao Lai 氏とともに仕事をしますが、その時、共通性/可変性分析を通じて、ソフトウェアファミリの振る舞いなどを特徴づけ、当時取り組んでいたソフトウェアのための DSL(Domain Specific Language) を開発しました。開発言語は C でした。

この、共通性に注目してグルーピングしたものをファミリと定義する方法は、Weiss 氏らが考案したFAST(Family Abstraction Specification Technique) という手法に基づいています。

DSL は小さなドメインを対象にしていて、ドメインが変化しない限りはうまくいったそうですが、ドメインは変化していくものです。DSL は、デバッガなど周辺技術との密な関係性の中で成立するため、その変化に対しては脆いという問題がありました。

ただし、共通性/可変性分析自体は非常に有用で、ドメイン分析に対しては非常に多くの収穫があったそうです。

分析の結果得られる共通性/可変性テーブルの内容の埋め込み(注: 解決ドメインの抽象による実装)に関して、

  • DSL で埋め込むと、固くなりすぎ、脆くなる。
  • C で埋め込むと、言語特有の制約が多く、開発者の intentionality(意図)がわからなくなる。

という課題がありましたが、C++ を用いることで、よりうまくいくようになりました。

オブジェクト指向(1995)

Coplien さんは、C++ を

共通性/可変性分析のための、汎用言語キットである

と捉えます。

その視点で見たオブジェクト指向は、次の4項目の構成要素で理解されるようになりました。

  1. Common Signature
  2. Common Data
  3. Variation in Data
  4. Variation in Algorithm

また、polymorphism / instantiation は、共通性/可変性のバインディングタイム(注: 共通性/可変性が変数に束縛されるタイミング)の選択の問題として捉え直されることになりました。

オブジェクト指向(DCI 時代)

Coplien さんによれば、更にこの後、3段階の気付きを経て、現在のオブジェクト指向理解へとつながっているそうです。

1段階目: There  is no OO in C++

  • 2010年くらいまでは、オブジェクト指向という言葉を、抽象データ型( Abstract Data Type: ADT )という意味で使っていた。
  • C++ は実際には抽象データ型ではなく、具象データ型( Concrete Data Type: CDT )であるということ。

2段階目: There are no objects in source code

  • C++ プログラムはソースコードである。
  • そこにはオブジェクトは存在しない。ソースコードに記述しているのはクラスである。
  • 多くの人々が Java などの言語でやっているのはクラス指向プログラミングである。

3段階目: OO is mental model

  • Trygve Reenskaug 氏とオブジェクトとは何か、オブジェクトパラダイムとは何かについて話し合い、DCI が生まれた。
  • DCI は、抽象データ型の実装を意味したこれまでのオブジェクト指向とは別の視点を与える。
  • 実行時におけるオブジェクトのネットワークがどのように問題を解決するかに注目する( Alan Kay の定義)。
  • 我々はクラスとロールをごちゃまぜにして考えていた。
    • 抽象データ型について考えるのがクラス指向。
    • ロールについて考えるのがオブジェクト指向。
    • 抽象データ型+インスタンス化がロールを実行(play)。

MPD と DCI

MPD は、クラス指向のレイヤーにおいてドメイン分析を行う時、ソフトウェアファミリの共通性/可変性分析を通じてドメインモデルを構築する手法を提供します。

DCI は、我々のメンタルモデルに基づいて、オブジェクトどうしの相互作用を定義します。そこでは、抽象データ型としてのクラスに具体的なロールを注入されたオブジェクトが、特定のコンテキストのもとに配置され、相互作用するという、動的な側面を捉える方法を提供します。

以上を簡単にまとめると、次の表のようになるかと思います。

パラダイム設計対象領域手段視点
DCIオブジェクトとそれらの相互作用ロール / コンテキストユースケースTime
MPD抽象データ型、モデルの構造クラス / Abstract Base Class (ABC)共通性/可変性分析Space

歴史的には MPD が先行しました。それは後に、モデルの静的な構造の分析を行うための領域を担う設計方法論という捉え直しがされました。そして、オブジェクト指向に対する新しい視点を提供する DCI が、クラス指向では扱うことが難しかった、モデルの動的な関係性を捉えるための方法論として提起されています。

Time and Space と DCI における共通性/可変性

DCI は、オブジェクトどうしの相互作用に注目します。つまり基本的には時間の経過(シーケンス)が意識されます。しかし、あるロールを付与された複数のオブジェクトが配置されてこそ、相互作用が成立するものです。

コンテキストとは、このように、ある状況に参加する主体の空間的配置と時間的な相互作用の総体として捉えられるものと言えます。

ここで、日本人にとってはありふれた「間」という概念が取り上げられました。具体例としては次のようなものが話題にあがりました。

  • 歌舞伎( Coplien さん)
  • 龍安寺の石庭( @koriym さん)

また、アレグザンダーの考案したパターンランゲージに話が及びました。パターンランゲージは、建築の構造(空間的なパターン)だけではなく、その構造が生み出す人々の相互作用(時間的なパターン)も内包したものとして理解されるべきです。

pattern of behavior = pattern in space + pattern in time

セッションの終盤では、このように、DCI が担う領域における共通性/可変性概念の適用可能性が示唆されました。

さいごに

会場を提供していただきましたヴァル研究所様、DCI Tokyo スタッフの皆様、今回もすばらしいイベントとなりました。ありがとうございました。

次回は、ワークショップ形式での開催も視野に入れながら、DCI Tokyo 3 が開催されることと思います。MPD / DCI / Lean Architecture に興味のある方は、ぜひご参加ください。

design mpd multiparadigm.design dci leanarchitecture ddd practical.ddd

DCI Tokyo 1 - Lean Architecture by James Coplien - が開催されました(後編)

後半のセッションでは、「What the system is(共通性や可変性を分析する)」を説明しつつも、「What the system does」とは別のものとして切り分けることはできないという主張から、 DDD とリーンアーキテクチャとの比較、そしてパターンに関する議論が展開されました。 @remore が前半部分をまとめてくださっていまして、そこで出て来るいくつかの用語(Form, Structure, What the system is, What the system does)を前提として私のできる範囲で行いました。

なお当日のCoplien氏によるセッション内容は、許可を得た上でYouTubeにアップロードされていますので、より深くご覧になりたい方はこちらも併せてご参照下さい。

What system is に関して

Jim Coplien さんの言うアーキテクチャ

  • 共通性、可変性分析に関する What system is に関連している。
  • ほとんどのドメイン知識は、アルゴリズムで表される What system does よりも What system is の方にある。

ソフトウェアを開発する際に重要なもの

  • プログラム言語自体の知識ではなく、ドメインでありビジネスである。
  • ほとんどのドメイン知識は、構造(Structural)であって、アルゴリズムではない。

このミートアップでは、構造(Structure)の方を主に扱う。What system is の方である。

  • 実際のシステムを構築する際には、構造は安定されていることが多いので先に行うことが多く、それらが出来上がってから、まだ実装されていない機能を作り始める。
  • その機能が、人々が求めているユースケースになり、これがビジネスとなる。

What system is と What system does の関係性

しかし、What system is と What system does は別々に考えられるものではない。それは、構造と時間やアルゴリズムを全く別のものとして、分けることは不可能だからである。

リーンアーキテクチャ本では、 What system is も What system does も説明しており、夫婦で共著で書いている。 リーンアーキテクチャ本では分けることができると書いたが、実際は簡単に構造とユースケースを分けることができるわけではない。

それは、歴史が物語っている。

  • 1960年から1970年では、ノイマンコンピュータでは、データ構造は全く注目せずに、 What system does に注力され、 CPU が大事でこの性能を良くするということが重要だった。
  • 1970年後半にデータベースが登場し、ボトルネックはデータへのアクセス速度が重要となった。CPU のことは忘れ去れられ、What system is が注意の関心の的になり、データモデルが重要になった。
  • 1980年にコンピューターネットワーキングが登場し、オブジェクト同士のコミュニケーションが重要になった。こちらは What system does がまた注目された。

このように、歴史を見ると、 What system is と What system does のサイクルができている。

アーキテクチャ全般に言えることだが、例えば部屋の構造では、どこにライトがあって、どこドアがあって、それが何か(What system is)を説明することができるが、どうしてそこにある(What system does)のか、とか、部屋の中をどうやって歩くかなどのプロセスにも影響している。つまり、これらは簡単に分けることはできずに互いに関係しあっている。

リーンアーキテクチャと DDD の関係性

DDD

  • 分析に関しては扱っていない。
  • コードに関することを主に扱っている。
  • 形態(Form)の社会性は扱わない。
  • 構造(Structure)のみに注目しており、具体的な事柄から始める。

リーンアーキテクチャ

  • 人々について、そして人々のメンタルモデルを扱う。モデルに関する議論をすることを扱っているので、社会的な活動である。
  • 形態(Form)を扱う。
  • 形態(Form)からスタートして、抽象的な Commonality から Variation を適用させて正しい実装をしていくことである。

アレグザンダーは、アーキテクチャは、社会的な活動であると明記している。

対称性とパターンに関して

アレグザンダーは、空間からイベントのパターンを作成している。ソフトウェアはまだ歴史が浅いため、パターンはほぼ存在しない。建築は世紀を超えてパターンを作ってきた。ソフトウェアの世界で言われているパターンは、コンテキストの上でのロジックであって、それはパターンではない。唯一、例として上げてもらったパターンは、リーキーバケットに関するものであり、イベントのパターンを説明している。

リーキーバケット

共通性と可変性は対称性を持っているが、あるとき対称性が壊れることがある。プログラム言語では対称性を表そうとするが、実際の要件では、対称性のない複雑なものを作り上げる必要がある。リーンアーキテクチャは対称性を扱っており、負の可変性(Negative Variation)は扱っていない。ここが複雑な場所である。パターンは、What system is と what system does の両方を考える必要があり、また、空間と時間を考える必要がある。

パターンに関する議論に関しては、私(ganchiku)の中で消化しきれていない場所があり、まとめることができませんでしたので、今後の課題としてアレグザンダーや Jim Coplien さんの議論を追いかけて、自分の言葉で説明できるようになっていこうと思います。

会場を提供していただいた UUUM 株式会社、また、オーガナイズしていただきました DCI Tokyo メンバーの皆様ありがとうございました。

design ddd leanarchitecture dci mpd multiparadigm.design practical.ddd

DCI Tokyo 1 - Lean Architecture by James Coplien - が開催されました(前編)

1月10日に六本木ヒルズにて、James Coplien氏をお招きしてLean Architectureに関する勉強会を開催しました。UUUMさんに大変素敵な会場を提供頂き、スタッフ含めて40名前後の参加者が集まりました。

DCI Tokyo 1

このブログでは、当日の翻訳担当を務めた@remore@ganchikuが、当日の翻訳内容から抜け落ちた部分の捕捉を含めて、内容のアウトラインを簡単に振り返れればと思います。セッションの前半部分は@remoreが、後半は@ganchikuが解説を担当します。

なお当日のCoplien氏によるセッション内容は、許可を得た上でYouTubeにアップロードされていますので、より深くご覧になりたい方はこちらも併せてご参照下さい。


Form(形態)とLean Architecture

形態は機能に従う(Form Follows Function)という言葉があります。Googleで検索してみると、元々はアメリカの建築家ルイス・サリヴァンの言葉であると出てきます1。この言葉は建築やプロダクトデザインなど様々な領域で引用されている言葉ですが、ソフトウェア・エンジニアリングの世界でもしばしば引用されているようです。今回のセッション前半では、前回の内容を簡単におさらいしつつ、この「形態」に焦点を当てた内容が主に展開されました。

前半部分のセッションの内容について、独断と偏見で要点を書き出してみました。なるべく短く抜き出してみたつもりなのですが、各トピックの内容が濃いせいか、結果的にそれなりの長さになってしまいました:

1. Form(形態)とStructure(構造)の違い

  • Formは抽象的であり、Structureは具体的である
  • 複数のFormをよく見ていくと、パターン認識によって共通性と可変性という特徴を抽出することができる
  • ArchitectureはFormに関すること
    • Formは対称性を強調する
    • 人間の脳は共通性を見つけることに秀でている

2. 現代のプログラミング言語とOOPについて

  • 共通性と可変性のペアをパラダイムと呼ぶ
  • プログラミング言語はこれらのパラダイムのうちいくつか限られた数だけを言語の設計思想に取り入れているに過ぎない
    • 例えばOverloadingといったOOPのプログラミングテクニックはパラダイムの一つと言えるし、その他の言語が持つ機構や機能(例えばC++の場合#ifdef, Template、class, 継承といった言語が提供する機能)は共通性と可変性のペアをForm(形態)において表現しているだけとも言える
  • これを実際に実装していくとStructure(構造)の話となるが、設計において我々がどうそれを捉えているかというと、あくまでFormの形で捉えている
  • C++を作ったStroustrup氏は、C++をオブジェクト指向言語とは呼ばずマルチパラダイム言語だと呼んでいる
    • オブジェクト指向は一つのパラダイムに過ぎないのに、「オブジェクト指向分析」で全ての問題を解決しようと考えだすからおかしくなる
    • C++はオブジェクト指向以上のことが実現できる言語

3. マルチパラダイムデザインがどこで始まり、Lean Architectureがどこからきたのか

  • Lean Architectureでは2種類の視点でFormを捉えている
    • What the system is(共通性や可変性を分析する)
    • What the system does(振る舞いや"間"で見る)
  • 建築家は「形態は機能に従うか?(Does Form Follows Function?)」という点に関して各々の主張を持っている
    • 建築家アドルフ・ルースは「形態は機能に従う」と唱えた
    • ソフトウェアアーキテクトもこれを唱えがちだし、ユースケースを分析すれば最適なアーキテクチャも見つけられると考えがちであるが、これは完全に間違いである
  • 銀行口座送金の例を見ると、振る舞いを表現するために適切なものはオブジェクトIDでもクラス名でもなく、Roleであることが分かる
    • 機能の形態(Form of Function)はRoleの中にある
  • Roleという概念が一級市民として存在しており、オブジェクトがRoleを動的に演じることができるようなプログラミング言語を想像してみよう。機能の形態をコードで表現することができ、コードを読むだけでユースケースを理解できる。これがDCI。
    • ここでいう"機能の形態"は、オブジェクト間のやり取りや振る舞いといった"What the system does"の部分にあたるもののこと

前半サマリは以上となります。なお、動画の54:43頃から始まる対称性や幾何学の話を起点に展開される、現代のシステム開発におけるオブジェクト指向言語のあり方と利用のされ方に対する強い批判は、要約ではとても表現しきれる内容ではなくかつオススメの内容のため敢えて上記には含めていません。動画全てを見る時間が取れない方でも、英語と内容の両方が一部難解な部分もありますが2それでも、ぜひ一度ご覧頂くことをオススメします。

To Be Continued

更に興味がある方がいらっしゃいましたら、今回のセッションの参加ブログを書いて頂いた方もいらっしゃいますので、こちらも併せてご参照頂けると理解がより深まるかと思います。@ganchikuによるセッション後半の解説記事も追ってこちらのブログで配信予定となっております。また、Twitterでは #dcitokyo というタグで本勉強会についての過去のツイートを検索できますのでこちらもオススメです。

Jimは年内にまた来日予定があるとお話されていましたので、次回日程等決まりましたらまた告知などを行っていければと考えています。その際に、最近開設されたDCITokyoの公式Twitterアカウントから各種告知等されていく予定となっていますので、今後こちらもぜひご注目ください。


  1. 当日Jimはルイスと同年代の建築家アドルフ・ロースの言葉と紹介していたため、要出典確認 ↩︎

  2. 55:46頃にJimが解説しているオペレーショナルモデルについて当日私の方で通訳として十分に説明できなかった部分については、追ってTwitterで後追いする形で一部捕捉の説明を試みていますので、こちらもご参照ください ↩︎

design ddd multiparadigm.design leanarchitecture dci mpd practical.ddd

「Lean Architecture / DCI Evening」参加レポート

2017年10月18日、James Coplienさんとその奥様であるGertrud Bjørnvigさんをお招きして、「Lean Architecture / DCI Evening 」というイベントを開催しました。日本ではソフトウェアパターンやアジャイルのリーダーとして知られるJames Coplienさんは、『 マルチパラダイムデザイン 』(1998年)でドメインとドメイン間の関係を中心に据えた設計パラダイムを提唱していました。Coplienさんは2009年、MVCアーキテクチャの考案者である Trygve Reenskaug さんと共に「DCIアーキテクチャ」を発表しました。2010年、CoplienさんはGertrudさんとともに書籍『 Lean Architecture 』を上梓、トヨタ生産方式をソフトウェアアーキテクチャに適用するリーンアーキテクチャについて、DCIアーキテクチャをそのビルディングブロックと位置づけた具体的な方法を記述しました。今回、マルチパラダイムデザインの読書会を大阪で開催していた縁から、表題のイベントを開催することができました。この記事ではセッションの内容についてトークを再現する形でお伝えしたいと思います。

NOTE: (※)内は筆者によるコメントです。また、基本的に英語から修正せずに翻訳しているので、事実関係に関しては確認がとれていないものもあります。

CoplienさんによるDCIアーキテクチャの概説

Coplienさん(以下、C): 最初にクイズをします。みんな立って。DCIは、DCIと呼ぶ前はなんと呼んでいたか知っていますか?・・・DCA (Data-Collabolation-Algorithm)(※関連リンクを参照)です。知ってた人はそのまま立って。これを差し上げます。(※参加者の一人がサイン付きのLean Architectureをプレゼントされる)

C: (※DCIで開発するための言語基盤とIDEを提供する)Trygveのプログラムの拡張子は「.k」になっているのはなぜでしょう?これは哲学者のカントからとっている。

C: オブジェクト指向プログラミングができない、モダンな言語はなんでしょう?Rust?Java。OK、それだ。Javaはオブジェクト指向ではなく、クラス指向の言語だ。

C: DCIのWebサイトfulloo.infoにいろんなリソースがあります。サンプルコードとかビデオとかも入っている。

C: 今日はインフォーマルな会です。小さいDCIのチュートリアルをすることもできるし、私が最近どういう研究をやっているかを話すこともできる。DCIが実際にどうやってプログラマーの集中力を高めてエラーを取り除くことができるかということを話してもいいし、Trygve言語の実際の例もお見せすることができる。見た目はJavaみたいだけどね。そこには、コンテキストとロールも登場する。設計の話もしてもいいし、ドメイン分析の話をしてもいい。

参加者A: ドメイン分析とTrygve IDEも見たい。私たちは大阪で「マルチパラダイムデザイン」の読書会をしていました。MPDとDCIをどのように繋ぐかの話も聞きたい。

C: 今日は何人、大阪から来たの?Oh! DCIを殆ど知らないという人は?最初に小さいインタラクションをしましょうか。15分くらい。

C: マルチパラダイムデザインは、クラスベースの言語に基礎を置いて議論を展開している。DCIはリアルなビルディングブロック(構成要素)とオブジェクト群のネットワークです。そこが大きな違いです。ドメイン分析とかユースケース、ロールのアクターに関しては後ほど妻が説明してくれる。

C: まずはDCIの触り。簡単なデモを。銀行口座間の送金のケースを考えてみる。ソフトウェア上ではそれはどういう状態でしょうか。クラスを書いて、オブジェクト同士をインタラクションさせますね。クラスを書くときというのは、クラスの全てのメソッドを考慮して書く必要がある。

C: ATMの例を考えてみましょう。ATMが稼働していて、それを確認するために、コンソールにオブジェクトIDをプリントするプログラムを考えてみましょう。

(※メソッドが呼ばれるたびにオブジェクトIDがプリントされていくデモ)

C: これを見て何かパターンは見つかるでしょうか? 単に同じオブジェクトが延々と呼ばれていることがわかるだけですね!(※会場笑)呼ばれるオブジェクトの順番とかは全然重要ではなかったわけです。

C: 次にオブジェクトIDではなく、クラス名が表示されるようにしてみましょう。クラス名は重要ですよね。コンソールを見てパターンは見つかりますか?ないですね。これもまだ完璧ではないようです。クラスのうち、2つは口座に関するもの、最後の3つ目は通貨に関するものだということはわかります。しかし、アーキテクチャの振る舞いはクラスだけでは表現されていないようです。

C: さて、今度はメソッドが呼ばれる毎に、役割(※ロール)がプリントされるようにしてみますよ。・・・Ah ha!(※会場笑) 分かりましたね。お金を送金するときの構造はこういうふうになりますよね。構造は役割(※ロール)の中に入っています。

C: 1つの銀行に2つ以上の口座を持っている人はいますか?(※会場から手が)それでは、私に1つの自分の口座から他の口座に送金するユーザーストーリーについて説明して下さい。

参加者B: ある口座からある口座に送金します。妻にダイアモンドを買うために。貯蓄口座から普通口座へ。

C: もっと一般的に言うと?

参加者B: 振込元から振込先へ送金する。

C: 振込元口座を持っている人はいますか?それは、オブジェクトでしょうか?クラスでしょうか?なんでしょうかね、振込元口座みたいな役割とは?・・・人はこれを役割(ロール)として考えているわけですよね。クラスやオブジェクトとして役割(ロール)を考えてはいないですよね。

C: アラン・ケイの最初に考えていたオブジェクト指向というのはヒューマンメンタルモデルをコンピュータに適用させたものでした。そのメンタルモデルを今発見したのです!

参加者B: どうやって、口座の振込元とか振込先とかを判別するんでしょうか?

C: ピアジェという発達心理学者がいます。ピアジェに「オペレーショナルモデル理論」というのがあります。ビルディングブロック(※構成要素)を使って論理的に考える必要があります。オブジェクトやクラスではないものがあるのです。おそらく私たちは最初にオブジェクトを学び、その後にクラス・クラス化を学ぶようです。子どもが一番最初にオブジェクトを学ぶ時は、全てを自分自身として認識するようです、母親さえも自分だと思う。4-7ヶ月の間に区別をつけることができるようになっていく。小さな子どもは馬を見た時に、馬を指差して「犬!」と叫ぶ。(※あ〜!と会場共感の声)こういった認識は物事に共通性を見出していることによって成り立っています。私たちはプログラムを問題解決のために書いています。子どもは8ヶ月くらいの時に原因と結果という因果関係について学ぶ。その頃から机を押したり、上にあるものを叩いたりして何かが落ちたりするのを楽しむ。そうして最終的には、問題解決する能力を学習していく。それがオペレーショナルモデルと呼ばれるものです。

C: プログラムを通して問題解決するためにこの考えが必要ですね。プログラムを書くときにはどうやっているのでしょう?ここにこのようにオブジェクトが散らばっている。そしてオブジェクトをつなげるユースケースがある。これはまた別のユースケースですね。これはまた別のユースケース、また別のユースケース・・・(※会場笑)ユースケースはオブジェクトのシーケンスではないんですよね、こう見ると。順番があるわけではない。

C: では、ユースケースのコードをオブジェクトよりも上のレイヤーに取り出す、ということをやってみましょう。今、ユースケースのコードは、オブジェクトのクラスの中にあります。このユースケースの部分を、クラスの外側に出すのです。そうすると、オブジェクトの方は基本的なクラスのインスタンスのままなので、とても単純です。しかし、これらは実際のところユースケースの一部にはなりません。では、ユースケースの部品は何でしょうか? ロールですね。ユースケースの部品は、ロールの中にあります。開発者は、このようなロールの中からいくつかを選んでまとめます。このまとまりを、コンテキストと呼びます。 つまり、コンテキストがユースケースに相当します。そして、ユースケースを実行するには、サブジェクト(※ユースケースの入り口となるエンティティ、後述のサンプルコードのTransferMoneyContext.SOURCE_ACCOUNTに代入されるオブジェクトにあたる)にこれらのメソッド(※ロールのメソッド)をインジェクトします。このオブジェクト(※他のエンティティ、後述のサンプルコードのTransferMoneyContext.DESTINATION_ACCOUNTに代入されるオブジェクトにあたる)にもこれらのメソッドをインジェクトします。

C: オブジェクトは既にあるからユースケースを書きたい。例えばヘリコプターを飛ばすことについて考えてみましょう。オペレーショナルモデルで思考して、ロールを考えます。映画の「マトリックス」を見たことがあるますか?トリニティはヘリコプターを飛ばさなければいけなかったですよね。ヘリコプターを操縦するプログラムをダウンロードしていました。ロールのプログラムをダウンロードすることによってヘリコプターを飛ばすことができたわけです。ランタイムでオブジェクトに対してどのようにユースケース上で振る舞うのかを教えてあげる。ユースケースが終わると、オブジェクトは何をやるか、何をやったかを(※どのように振る舞うかを)忘れてしまうわけです。

参加者B: トリニティはオブジェクト(※エンティティ)、パイロットはロールということですね。

C: トリニティは、ヘリコプターを操縦するためのメソッドを持っている。手を動かしたり、足を動かしたりといった基本的なトリニティが持っているメソッドを使うことができる。コンテキストがヘリコプターを運転させる。トリニティは基本的な動作はできる。コンテキストとトリニティの間のメソッドの契約というものが必要で、トリニティは人なので人が持っているメソッドだけが使えるという契約がある。手を動かすとか。ヘリコプターを操縦するというのははコンテキストです。ロールはデータを持たない。純粋にメソッドを持っている。以上がDCIの基本的な話でした。

参加者C: ロールとクラスの違いがまだわからない。

C: クラスはデータを持ちます。あなた達はいわば人間クラスのインスタンスですよね?そこから初期化されている。(※会場笑)ヘリコプターパイロットはロールなので、ヘリコプターパイロットのインスタンスを作ることはできない。人間が必要なんです。あるいはロボット?もしくはゴリラ。

参加者C: Javaのインタフェースはロールに近い?

C: 似てるとは言えるけど、実装がないからロールを完全に表すかというと違うと思います。(※Java 8以降はインタフェースは実装を持てるようになりましたが)あと、Javaはインタフェースは静的に決まっていて、動的に決めることができないというところが異なります。クラスがインタフェースをextendするというのを全てコンパイル時に決めなければなりません。

参加者D: Swiftのプロトコルエクステンションを使うと近いことができますか?

C: Objective-Cでお腹いっぱいなので、Swiftの学習は避けているものでね(※会場笑)プロトコルエクステンションを使うと近いことができるようです。

参加者C: クラスとロールの違いについて。Java クラスとインタフェース、Scala クラスとトレイト、C++ 動的クラス、Virtualクラス これらはDCIの実装として使うことができるか。

C: Scalaのトレイトはかなり近いです。Scalaではトレイトのミックスインを使う。トレイトを使ってクラスに合成できる。その結果クラスは両方の機能のメソッドを持つことができる。C++ではどうするか?C++の場合はクラスの合成に継承を使ってしまうので難しい。どうするか?ロールをテンプレートを使って書くことができる。テンプレートを使えばDCIを実現することは可能です。ただそれはコンパイルのタイミングになってしまう。ダイナミックではない。fulloo.infoも見てくださいね。なんとなく、DCIのイメージが掴めましたか?

C: DCIのための言語として気に入ってるものの一つはRubyです。オブジェクトの拡張が簡単にできる。モジュールを使ってミックスインすることもできます。ただ、メソッドは自由に追加することはできるが、それを取り外すのが難しい。名前の衝突の問題もある。Matzはこの問題について理解してくれたが、一度インジェクトしたものを引き剥がす機能を入れることまでは、説得するには至らなかった。Rubyの仮想マシンを変更すればできるのですがね。

(※実は筆者はRubyistなのですが、refinementを使うとある程度は目的に適うのではないのかなーと思ってしまいました)

C: Trygveを使ってる人。(※一人だけ手が挙がる)オープンソースですよ!(※会場笑) お金の送金のサンプルを見せていきます。ここにクラスがあります。これはドメイン分析から抽出されたものです。 amount()(金額) などのメソッドを持っている。クラス指向のプログラミングならこんなものでしょうか?Trygveの紹介をしていきますね。

(※以下のコードはTrygve で書かれた送金のサンプル https://github.com/jcoplien/trygve/blob/master/examples/july_money_transfer.k より引用)

/*
* july_money_transfer.k
*/

class Currency
{
    public Currency(double amount) {
        amount_ = amount.clone
    }
    public Currency +(Currency amount) {
        assert(false)
        return this;
    }
    public Currency -(Currency amount) {
        assert(false)
        return this;
    }
    public Currency() {
    }
    public String name() const
    {
        assert(false);
        return ""
    }
    public String sign() const
    {
        assert(false);
        return ""
    }
    public double amountInEuro() const {
        assert(false);
        return 0.0
    }
    public double amount() const {
        return amount_
    }
    public String toString() const {
       return amountInEuro().toString()
    }
    public int compareTo(Currency other) {
       if (amount() > other.amount()) return 1
       else if (amount() < other.amount()) return -1;
       return 0
    }

    private double amount_
}

class Euro extends Currency {
    public Euro(double amount) {
        Currency(amount)
    }
    public Euro -(Currency amount) {
        return new Euro(amount() - amount.amountInEuro())
    }
    public Euro +(Currency amount) {
        return new Euro(amount() + amount.amountInEuro())
    }
    public String name() const
    {
        return "Euro";
    }
    public String sign() const
    {
        return "€";
    }
    public double amountInEuro() const
    {
        return amount()
    }
    public String toString() const {
        return amount().toString()
    }
}

class Account
{
    public Account(int acctno) {
       acct_ = acctno
    }
    public String accountID() const {
       return acct_.toString()
    }
    public Currency availableBalance() const { assert(false); return null }
    public void increaseBalance(Currency amount) { assert(false) }
    public void decreaseBalance(Currency amount) { assert(false) }
    public void updateLog(String message, Date dt, Currency amount) {
        assert(false)
    }

    private int acct_
}

class CheckingAccount extends Account {
    public CheckingAccount() {
            Account(1234);
            availableBalance_ = new Euro(100.00)
    }
    public Currency availableBalance() const
    {
        return availableBalance_
    }
    public void decreaseBalance(Currency c) {
        availableBalance_ = availableBalance_ -  c
    }
    public void updateLog(String message, Date t, Currency c) const {
        System.out.print("account: ").print(accountID())
                  .print(" CheckingAccount::updateLog(\"").print(message)
                  .print("\", ").print(t.toString()).print(", ")
                  .print(c.toString()).print(")")
                  .println()
    }
    public void increaseBalance(Currency c) {
        availableBalance_ = availableBalance_ + c
    }

    private Currency availableBalance_
}

class SavingsAccount extends Account {
    public SavingsAccount() {
            Account(1234);
            availableBalance_ = new Euro(0.00)
    }
    public Currency availableBalance() const {
        return availableBalance_
    }
    public void decreaseBalance(Currency c) {
        assert(c > availableBalance_);
        availableBalance_ = availableBalance_ - c
    }

    public void updateLog(String logMessage, Date timeOfTransaction,
                               Currency amountForTransaction) const {
        assert(logMessage.length() > 0);
        assert(logMessage.length() < MAX_BUFFER_SIZE);
        // assert(new Date() < timeOfTransaction);
        System.out.print("account: ").print(accountID())
                  .print(" SavingsAccount::updateLog(\"").print(logMessage)
                  .print("\", ").print(timeOfTransaction.toString())
                  .print(", ").print(amountForTransaction.toString())
                  .print(")").println()
    }
    public void increaseBalance(Currency c) {
        availableBalance_ = availableBalance_ + c
    }

    private Currency availableBalance_;
    private int  MAX_BUFFER_SIZE = 256
}

class InvestmentAccount extends Account
{
    public InvestmentAccount() {
            Account(1234);
            availableBalance_ = new Euro(0.00)
    }
    public Currency availableBalance() const {
        return availableBalance_
    }
    public void increaseBalance(Currency c) {
        availableBalance_ =  availableBalance_ + c
    }
    public void decreaseBalance(Currency c) {
        availableBalance_ = availableBalance_ - c;
    }
    public void updateLog(String s, Date t, Currency c) const {
       System.out.print("account: ").print(accountID())
                 .print(" InvestmentAccount::updateLog(\"")
                 .print(s).print("\", ").print(t.toString())
                 .print(", ").print(c.toString()).print(")")
                 .println()
    }

    private Currency availableBalance_;
}

class Creditor
{
    public Creditor(Account account) {
            account_ = account
    }
    public Account account() { return account_ }
    public Currency amountOwed() const { return new Currency(0.0) }

    private Account account_
}

class ElectricCompany extends Creditor
{
    public ElectricCompany() {
        Creditor(new CheckingAccount())
    }
    public Currency amountOwed() const {
        return new Euro(15.0)
    }
}

class GasCompany extends Creditor
{
    public GasCompany() {
        Creditor( new SavingsAccount());
        account().increaseBalance(new Euro(500.00))    // start off with a balance of 500
    }
    public Currency amountOwed() const {
        return new Euro(18.76)
    }
}

context TransferMoneyContext
{
    // Roles

    role AMOUNT {
        public Currency(double amount);
        public Currency +(Currency amount);
        public Currency -(Currency amount);
        public String name() const;
        public String sign() const;
        public double amountInEuro() const;
        public double amount() const;
        public String toString() const;
        public int compareTo(Currency other)
    } requires {
        Currency(double amount);
        Currency +(Currency amount);
        Currency -(Currency amount);
        String name() const;
        String sign() const;
        double amountInEuro() const;
        double amount() const;
        String toString() const;
        int compareTo(Currency other)
    }

    role GUI
    {
        public void displayScreen(int displayCode)
    } requires {
        void displayScreen(int displayCode)
    }

    role SOURCE_ACCOUNT {
        public void transferTo() {
            // This code is reviewable and meaningfully testable with stubs!
            int SUCCESS_DEPOSIT_SCREEN = 10;

            // beginTransaction();

            if (this.availableBalance() < AMOUNT) {
                // endTransaction();
                assert(false, "Unavailable balance")
            } else {
                this.decreaseBalance(AMOUNT);
                DESTINATION_ACCOUNT.increaseBalance(AMOUNT);
                this.updateLog("Transfer Out", new Date(), AMOUNT);
                DESTINATION_ACCOUNT.updateLog("Transfer In", new Date(), AMOUNT);
            }
            // GUI.displayScreen(SUCCESS_DEPOSIT_SCREEN);
            // endTransaction()
        }
    } requires {
        void decreaseBalance(Currency amount);
        Currency availableBalance() const;
        void updateLog(String msg, Date time, Currency amount)
    }

    role DESTINATION_ACCOUNT {
        public void transferFrom() {
            this.increaseBalance(AMOUNT);
            this.updateLog("Transfer in", new Date(), AMOUNT);
        }
        public void increaseBalance(Currency amount);
        public void updateLog(String msg, Date time, Currency amount)
    } requires {
        void increaseBalance(Currency amount);
        void updateLog(String msg, Date time, Currency amount)
    }

    public TransferMoneyContext(Currency amount, Account source, Account destination)
    {
        SOURCE_ACCOUNT = source;
        DESTINATION_ACCOUNT = destination;
        AMOUNT = amount
    }
    public TransferMoneyContext() {
        lookupBindings()
    }
    public void doit()
    {
        SOURCE_ACCOUNT.transferTo()
    }
    private void lookupBindings() {
        // These are somewhat arbitrary and for illustrative
        // purposes. The simulate a database lookup
        InvestmentAccount investmentAccount = new InvestmentAccount();
        investmentAccount.increaseBalance(new Euro(100.00)); // prime it with some money

        SOURCE_ACCOUNT = investmentAccount;
        DESTINATION_ACCOUNT = new SavingsAccount();
        DESTINATION_ACCOUNT.increaseBalance(new Euro(500.00)); // start it off with money
        AMOUNT = new Euro(30.00)
    }
}

context PayBillsContext
{
    public PayBillsContext() {
       lookupBindings
    }
    role [] CREDITORS {
    } requires {
       Currency amountOwed()
    }
    stageprop SOURCE_ACCOUNT {
        public String accountID() const;
        public Currency availableBalance() const;
        public void increaseBalance(Currency amount) unused;
        public void decreaseBalance(Currency amount) unused;
        public void updateLog(String message, Date dt, Currency amount) unused
    } requires {
        String accountID() const;
        Currency availableBalance() const;
        void increaseBalance(Currency amount);
        void decreaseBalance(Currency amount);
        void updateLog(String message, Date dt, Currency amount)
    }

    // Use case behaviours

    public void doit()  {
        for (Creditor credit : CREDITORS) {
            // Note that here we invoke another Use Case
            TransferMoneyContext xfer = new TransferMoneyContext(
                                                      credit.amountOwed(),
                                                      SOURCE_ACCOUNT,
                                                      credit.account());
            xfer.doit()
        }
    }

    private void lookupBindings() {
       // These are somewhat arbitrary and for illustrative
       // purposes. The simulate a database lookup
       InvestmentAccount investmentAccount = new InvestmentAccount();
       investmentAccount.increaseBalance(new Euro(100.00)); // prime it with some money
       SOURCE_ACCOUNT = investmentAccount;

       Creditor [] creditors = new Creditor [2];

       creditors[0] = new ElectricCompany();
       creditors[1] = new GasCompany();

       CREDITORS = creditors
    }
}


{
    // Main

    TransferMoneyContext  aNewUseCase = new TransferMoneyContext();
    aNewUseCase.doit();

    PayBillsContext anotherNewUseCase = new PayBillsContext();
    anotherNewUseCase.doit()
}

C: Account(口座) という別のクラスがあります。ドメイン分析から抽出されたものです。口座番号というデータを持っている。メソッドは increaseBalance()(残高増) と decreaseBalance()(残高減) 。口座番号を与えれば、このメソッドを使っていろんなことができる。いわば、ちょっと高級な Integer みたいなものです・・・。全くもって良くないですね!貯金用の口座とか、投資用の口座とかどんどん増えていってしまいます。会社の口座、お金の債権者… ここまでができの良くないクラス指向のプログラミングのやり方でした。

C: さて、それでは送金のユースケースについて見ていきましょう。Context がキーワードです。多くの点でクラスのように見えるのだけれど、クラスとは異なっていて、ほとんどの場合データを持たずにロールだけを持っている。 Amount(金額) と呼ばれるロールはとてもシンプルなロールです。これで資金移動をすることができる。

C: (※デモを見せて)これがロールのインタフェースのシグネチャです。オブジェクトがこれらのメソッド全てをサポートするように定義する必要があります。これを「必須の契約」(※the requires contract)と言います。オブジェクトがロールを表現する場合、これらのメソッドを全てサポートしなければならない。

参加者C: ここで言う契約とは、「契約による設計」の契約のことですか?

C: その通りです。バートランド・メイヤーという人が提唱した契約による設計。この人の言ってることは、だいたい合っているけど突き詰められていない。

参加者B: メイヤーの考えのどこが機能しないんでしょう?

C: クラスAがあり、そこにメソッド1があります。そこには事前条件、メソッド実行、事後条件があります。メソッド1は事前条件が真であることを要求し、事後条件が保証されていることを約束します。次にメソッド1の中からメソッド2を呼ぶケースを考えてみましょう。メソッド2にとっての事前条件を見た時、メソッド2にとってはメソッド1が状態を維持してくれているという想定をしているわけですが、メソッド1の実行中に自己隠蔽されている状態の中で条件がおかしくなった状態でメソッド2を呼ぶとメソッド2が結果保証している事後条件が壊れてしまうことがある。

C: クラス側で本体となる実装を持たないのは、ロールのプレイヤーの持っている実装を使うだけだからです。ロールを演じるオブジェクトの実装を使います。別のオブジェクトに由来するメソッドを使うわけです。これらのメソッドはパブリックインタフェースで、ロールが何をするかを知ることができます。

C: よりわかり易い例として SourceAccount(振込元口座) と呼ばれる別のロールも見ていきましょう。SourceAccounttransferTo()(〜に送金する) として呼ばれるメソッド、責務を持っています。今からこの箇所のコメントを外して動くようにしましょう。トランザクションを開始しますよ。もし振込元口座が利用可能な残高を持っていれば、コメントを外した箇所は何になるでしょうか?

参加者B: ロールを注入されたオブジェクト

C: そうです! SourceAccount ロールを適切に演じるオブジェクトです。利用可能な残高はどこでしょうか?気をつけてください。ここ(※ロール)にはありませんよ。残高は、ロールを演じる口座オブジェクトの側、ロールプレイヤーにあります。

C: クラスみたいだけれど、ランタイムで考えられるようにしてくれます。SourceAccount ロールを演じるオブジェクトについて考えてほしいです。ロールプレイヤーはマトリックス(※コンテキストが持つロールとオブジェクトのマッピング表)によってもたらされるものです。ロールがオブジェクトに注入されると、オブジェクトが機能を持つようになって、送金ユースケースを実現できるようになります。送金メソッドは振込元の Account オブジェクトに注入されます。

C: こちらは別のロール、DestinationAccount(振込先口座) です。DestinationAccountはメソッド transferFrom()(〜から送金される) を持ちます。transferFrom() メソッドは DestinationAccount の残高を更新します。

C: コンテキストのコンストラクタを見てみましょう。TransferMoneyContext(送金コンテキスト) です。コンテキストの中で何がオブジェクトかを伝える必要あります。送金の金額、振込元、振込先、口座の種類。

C: マトリックス(※コンテキストが持つロールとオブジェクトのマッピング表)を表すオブジェクトを持っていて、ロールのメソッドが振込元の Account オブジェクトに注入されます。要はこれ(※コンテキスト)はオブジェクトだと言うことです。Account オブジェクトに対して振込元口座にあるべきメソッド群を与えていきます。送金ユースケースのためにロールプレイヤーが演じるメソッド群です。そうすれば、ランタイムでオブジェクトを組み立てることができます。

C: 他のユースケース、お金を支払うについて見ていきましょう。特別なロールがあります。そして、AmountSavingsAccount(貯蓄口座) のようなオブジェクトが新たに一つここにあります。これらは、プラグアンドプレイをするために用意したアクターです。私が台本を書いたロールがあり、アクターは芝居においてロールを演じます。

C: これは舞台のようなもので、大道具(stage props)はアクターの持っている道具です。大道具だけでは、一切何もできません。ただデータを引き出すことはできます。

参加者B補足: 何かをすることはできないのだけど、データを引き出すことはできる。

C: このロールの全てのメソッドは const で、アクターの状態を変更することはできません。

参加者B: const というのはロールの型かなにかですか?

C: そうです。ロールの束縛、制約の種類です。

C: さて、stage props (大道具に由来する機構名) の使いどころはわかりますか?オブジェクトは2つのロールを一度に演じるかもしれません。同じオブジェクト、同じインスタンスで二役を一度にこなすことがあります。とても複雑な例として再帰実行になるケースもありえます。これが先程上がっていた問題の一つで、複数回呼び出したら事前条件が崩れることがあるという例です。このstage propsはデータを更新しないから、問題が起こらないようにすることができます。

C: ちょっと難し目の例を見てみましょう。Javaのグラフ描画のデータ構造です。ブロック崩しと言われるピンポンゲームです。変数の中をチェックするデバッガも入っています。700行くらいのプログラムでこれができます。

C: MPDからDCIへどう変遷していくかについて。よく知られているように、対象のドメインを理解することから始めます。殆どの場合、インタラクティブなプログラムはクラス指向のプログラムでできています。オブジェクト指向プログラミングとは、アラン・ケイが定義したように、オブジェクト同士の協調によるネットワークです。これをどうやって意識するかについて。

GertrudさんとCoplienさんによるユーザーストーリー、アジャイルとリーンアーキテクチャ

Gertrud さん(以下、G): 皆さんはユーザーストーリーは知っていますか?ユースケースは?アジャイルになるためにはユーザーストーリーをたくさん作りましょう。リーンになるためには、ユースケースが必要。

G: ユーザーストーリーの形式(フォーム)として、以下のようなフォーマットで考えるといいです。

As a <USER or ROLE> 例: 口座を持っている人として
I want <FEATURE>    例: ある口座から別の口座に移したいという行為
So I can <MOTIVATION>   そうすればなにができるか。例えば、ダイアモンドを買ってあげることができる。

ユーザーストーリーの構成要素はこのようになっています。これをブレインストーミングなどでたくさん作っていく。 上から、Who、What、Why に対応している。

G: ユーザーロールには、口座を持っている人、銀行の従業員、(※例えば電気代を支払おうとする)市民などがいます。

G: お金の送金は、今回の場合、全てのロールで行うケースがある。しかし、それぞれのロールでやりたい事は違う。ロールやモチベーションによってコンテキストが変わってくる。実装する前に、モチベーションがわかるので、実装することができる。これは実装する前に知る必要があるんです。

G: ユーザーストーリーの洗い出しは、ブレインストーミングであげていく。するとかなり数が増えていく。そこから一般的な(※抽象度の高い)状態に近づけていく。そうすれば、ユーザーストーリーからユースケースに落とし込むことができる。

G: ユーザーストーリーは具体的で、抽象的なのがユースケース。どこまで抽象的にするべきか?コードで理解できるレベルまでです。ユースケースをリーダブルコードに落とし込めるところまで持っていくのが目標になる。

G: ユーザーストーリーはインプットなので、なるべくたくさんの実例を出したほうがいい。銀行の業務であれば、昔に使っていた小切手の話をユーザーストーリーとして抽出するくらい。

G: ユースケースの段階で要点を厳密に抽出する。これがそのままプログラムに使われるから。

(ここで休憩に入りました)

G: ユースケースで大事なことはターミノロジー(※用語)。システム分析とシステム設計で同じボキャブラリー、ターミノロジーを使ってお互いの同意を取っていくことが大事。関係者の間で合意をとるのも大事になってくる。

G: 分析と設計で、同じボキャブラリー、同じターミノロジーを使って話をするので、相互に一貫性をもたせることができる。もちろん違うチームに行くと言葉も変わっていく。

G: 銀行だったら銀行員にも話を聞かないといけない。ドメインエキスパートだけではなく、銀行員もチームに入って、彼らの使っている用語をコードに落とし込んでいく。ユースケースレベルになると、コードのステップレベルまで落とし込めるようになる。

G: コアシナリオとサテライトシナリオというのがある。コアシナリオはシナリオの中でも本道で大事な物。サテライトシナリオは、例外的だったり拡張されていたり複雑だったりするもの。それでも、システムはこういうものもカバーしなければならない。

参加者: なぜ、「サテライト」という用語にしたか?

G: オリジナルの用語は、インクルードとエクスクルードだった。その使い分けはわかりにくいので、コアシナリオとサテライトシナリオにした。また、デビエイション(※deviation: 逸脱、脱線、偏向)と一時期は呼んでいた。コアシナリオじゃないなにか、そんな感じ。

G: ユースケースという用語は昔ながらの用語で古臭い感じがするので使わない方向でというのはある

G: 感覚的にはコアシナリオがスコープ全体の80%、残りはサテライトシナリオという割合になる。

G: サテライトシナリオのほうがアジャイルっぽいやり方で進めていける。そもそも複雑だったり、変わりやすかったりするので。

G: 具体的なユーザーストーリーをユースケースに落としていく時に、どこまで一般化するのか。例えば、銀行で資金移動する時に使う一般的な用語は送金するという用語とか、そういうことを考えてちょうどいい落とし所を見つけていく。

C: 銀行口座の残高がマイナスになった時に、自動的に所有者の他の口座から振り替えてマイナスを補填するような、システム起動のユーザーが関与しない形でのシステムオペレーションを考える。MVCはエンドユーザーから見た、それに基づいたプログラミングモデル。DCIはプログラマーサイドのユーザーモデルを表現している。先程の、ユーザーがでてこないシステムオペレーションのようなシステムオペレーションも自然に扱うことができる。

G: ターミノロジーがとても大事。ターミノロジーのデータベースを会社が持っていて、緻密に定義した辞書を持っている。そう、会社に辞書がある。プログラムの中の変数やファイル名なんかもそのデータベースを見て決める。 辞書に用語がなければ、辞書に追加したり変更したりする。追加するときにはちゃんと申請しなければいけない。

G: ソースコードの中にはコメントを書いてはいけない。さきほどの辞書を使って、ソースコードが、例えば英語からドイツ語などの自然言語にそのまま翻訳されるので。ソースコード中にコメントがあるとその内容をいちいち翻訳しなければならなくなる。

G: 最初に生まれた子供と同じくらいの注意深さで名前をつけよう。

C: ポリモーフィズムがないからコードが読みやすいのかもしれない。ポリモーフィズムがあると、メソッドがどこから呼び出されるかわからないからプログラムの見通しが悪くなる。ボキャブラリーも大事だが構造も大事。ある研究では、ユースケースのいろんな箇所にコードを分散させるとエラー率が70%増えるという調査結果がある。

C: リーンアーキテクチャの良いところとして、エラーを早く見つけることができることがある。テストをしているときではなく、コードを読んでいる時にエラーに気づくことができる。

C: フォードは組み立てを実際にして、テストは最後に確認している。トヨタの場合は、組立時にその場でテストをしている。リーンのアプローチの1つとして、問題をより早く見つけるという利点がある。

C: アジャイルとリーンについて。リーンはクラスの構造が重要。リーンにコストをかけて、アジャイルで実際にどう稼いで行くかを考える。アジャイルはオブジェクトのインタラクションが重要。

C: ドメイン分析とユースケース分析も同時並行的にやる。同時並行的にやると重なる場所がある。みんなそれぞれ同時に実行していく。

C: 野中先生がトヨタにいた時に気づいた。それぞれの分析がかさなりあって進んでいく。これを、刺し身モデルという。(※ラグビーの)スクラムも刺し身。挟まり合っているでしょ。ウォーターフォールとは違って皆が全てのことをやるからスクラム。

C: リーンは長い期間のプランニングが必要で大事になってくる。アジャイルはフィードバックに応じた再調整が肝要。リーンは、深いレベルの専門家が作る。アジャイルはなんでもありみたいなところがある。スクラムとDCIはそのふたつのことをやる。

C: パターンも同じように両方のものを含む。これは日本の禅から来ていると思います。パターン系のガイダンスに従って、門を通ってゴールに近づくことはできるのではないか?パターンもスクラムも、ある程度のレベルではDCIも、ほとんどは日本由来のもの。道教だよね、これ。

C: リーン側のほうは、日本のトヨタの思想。山田 ひろしと言う有名なエンジニアでホンダの人がトヨタに教えた。(※原典見つけられず)

C: アジャイルのほうは、日本でよく知られた比喩で伝えるのは難しいですが… 社会的構造が違うので。デンマークは、社会がとてもフラット。日本は階層的なところがある。よりシステム化されているので、色々なルールが存在する。日本は、もうちょっとアジャイルのほうをエクササイズするといいかもしれません。

C: 実際の開発の流れ。ドメイン分析 → 共通性可変性分析 → クラス → インタフェース、API設計。最初はインタフェースしか書かない。もしかしたらスタブくらいは書くかもしれない。実装はいつ書くか?ユースケースのタイミングで。Just In Time。ユースケースが必要とするインタフェースだけを実装する。(※インターフェイスをクラスで実装するのではなく、インターフェイス自体を実装する)インタフェースは抽象的な概念で、ロールは具体的なインタラクションやアルゴリズムなので、そのタイミングで。

C: この中でプラットフォームを開発している人。プラットフォーム開発はリーンではない。あらかじめ用意しておくもの。だからリーンではない。使わないものを作る時は会社が危なくなる。これは、無駄と言います。

C: DCIアーキテクチャを利用した開発の順番としては、まずアーキテクチャを作ってそれからそれぞれデータベースとかGUIのインタフェースを作る。会社としてなにを売るかというのはいろいろあるけど、売るのはユースケース。それぞれのクラスを売っているわけではない。ユースケースは、それぞれのデータベースとかサーバーとかクライアントのユーザーインタフェースなどを少しずつ重ねたもの。

C: 生産性が一番の無駄を生むと、トヨタの大野耐一さんがおっしゃっている。

C: こういう話を、「object-composition」というメーリングリストで話をしているので、入ってくださいね。

C: 1月にまた東京に来るので、またこういう会を開催しましょう!

最後に、GertrudさんとCoplienさんと参加メンバーで集合写真を撮りました。

image

NOTE: 当日は同時翻訳付きの英語トークでした。通訳の任を快諾してくださった、 @ganchiku さん、@remore さん、ありがとうございました。また、記事を書くにあたっては英語の下訳で @kuma_nana さんに貢献していただきました。こちらもありがとうございます。もちろん、当ポストの文責は執筆者である私にあります。

関連リンク

関連記事

design ddd multiparadigm.design practical.ddd mpd dci leanarchitecture

Beyond MVC

PHP Advent Calendar 2013 - 6日目

昨日は@fivestrさんのComposerを使った簡単Travis CI設定でした。

TL;DR

  • オブジェクト指向/MVCでうまく捉えきれていなかったものは何なのか?
  • MVCから続くソフトウェアアーキテクチャーの「その先」は何なのか?

Reenskaug博士を知っていますか?


image

WikipediaによればReenskaug博士は1930年生まれ。MVCという概念が世の中に送り出された論文『MODELS - VIEWS - CONTROLLERS (pdf)』は1979年ですから、49歳の時ということになります。1960年からソフトウェアを書き始め、1973年からオブジェクト指向でソフトウェアを開発しており、現在でも現役でソフトウェアの世界にいらっしゃいます(ex 2009年の講演)。「プログラマ歴42年 (* Clean Coderのイントロダクション、2012年のもの)」のUncle Bob(Robert C. Martin氏)よりも一回り上、50年もこの世界に身を置かれている方なのです。

そもそもMVCとは何だったのか

Reenskaug博士は前節の論文以降、MVCの世の中への浸透とソフトウェアの世界の進化に合わせて、次のような論文を出されています。

一度深呼吸してから、DCIの論文にある次の文を、声に出して、ゆっくりと、じっくりと、読んでみてください。


image



プログラマーである私たちは、あらゆることを「コードの実装の視点」で捉えがちです。コードを書く・実装することは最も重要なことですが、実装をうまくやるための視点や概念を整理するために、時には実装から離れて抽象的に思考しなければなりません。MVCは単にソースコードをM、V、Cに整理するためだけのテクニックではないのです (So it’s just a housekeeping technique, right? To think of it that way is to take a nerd’s perspective.)これは1979年の論文でも書かれていることですが、本来の意図を明確にするために2009年のDCIの論文では「MVC-U」という言葉で、「情報の表現」と「ユーザーとシステムのインタラクション」を分離するための考え方であることが改めて示されました。

  • ユーザーのメンタルモデルと一致するコンピュータ上のモデル
  • モデルの表現を行うビュー
  • ユーザーとのインタラクションを取り持つコントローラー

image

ここに示されているのは、実装の問題としてクラスの関係がどうだとか、どことどこに関連があるのが正しい/間違いだとかいうレベルのことではないのです。ソフトウェアがどういう構成になっていればよいのかを抽象的に示したものなのです。

私たちのMVCは間違っているのか?

これだけ世の中に広く浸透した考え方であるMVCは、意識的か無意識的かの違いはあるにせよ私たちの認識に深くなじんでいます。これがすべて間違いだと覆って「非MVC」のようなスタイルに変わることはありえません。みなさんは、MVCではない問題の分離方法で物事を考えられるでしょうか? 難しい、、というより不可能だと思います。実装だけを考えても、HTMLタグと何らかの集計処理とユーザー操作の分岐処理がごちゃ混ぜに書かれたPHPの世界(何か別の観点で整理されているかもしれませんが)を想像してみてください。MVCは、相当本質的な位置づけにある関心事の分離法ですから、これを否定して全く別の視点に立つことは難しいのです(私たちが一度獲得してしまったメンタルモデルを覆すことが困難なことにも関係します)。

また、MVCの捉え方を間違っているというプログラマーは滅多にいないと私は考えています。Reenskaug博士のオリジナルの論文やDCIの論文を読んでいなかったとしても、大枠としてのMVCの基本コンセプトは肌で理解しているのではないでしょうか。それほど基本的な考え方で、私たちの深層にまでしみ込んでいるものだと思います。

MVCについて何を議論しているのか?

ここまで、MVCの基本を振り返り、MVCそのものが覆ることはない、プログラマの認識も間違っていないという話をしました。ではMVCについての議論が絶えないのはなぜでしょうか? 以下の点は、議論の根底にある共通認識です。みなさんもきっと考えたことがあるはずです。

  • MVCという枠組みだけでは、ソフトウェアを作りきれない
  • 別の視点が必要

しかし、ここまで読まれたみなさんは、この2つの問題に対する解として、次のような考えを持ちますか?

  • そもそもMVCがダメ。全く新しい別の何かに置き換える必要がある

共通認識として挙げた2点について、素直に考えれば、MVCと排他的な関係ではないことは自明です。MVCを抽象的な第1のアーキテクチャースタイルとして基盤に持ちながら、そこにプラスして現代の問題の分析を支えるアーキテクチャーを採用すればよいのです。しかし、世にある様々なMVなんとかパターンは、あくまでMVCの実装上の亜種で、元々のMVCに対して実装環境を加味した具象パターンにすぎません。MVCにプラスするアーキテクチャーは、MVCとは異なる視点を我々に与えてくれるものでなくてはなりません。

MVCと併用するアーキテクチャーとは?

ソフトウェアの大枠の構成としてMVCに沿った上で、現実の問題に対処するためには、モデルをどううまくまとめあげるのかが焦点になります。Robert C. Martin氏の主催するGoogle Group「Clean Code Discussion」への彼自身のポストに、同じ方向性を持つアーキテクチャーとして以下が挙げられています(順序を変更し、やや実装より視点なHexagonalアーキテクチャーは除外しました)。

  • ドメイン駆動設計のスタイル
  • Cleanアーキテクチャー
  • DCIアーキテクチャー

ドメイン駆動設計のスタイル

ドメイン駆動設計はEric Evans氏により提唱されたもので、彼の著書が出版されたのは2003年8月30日です。彼の提唱するドメイン駆動設計とは単なるアーキテクチャーではなく、もっと広範囲を扱ったものですが、それを成し遂げるための一定のアーキテクチャーが想定されています。ドメイン駆動設計のスタイルでは、モデルの中核となる部分を「ドメインレイヤー」として独立させます。意図的にアプリケーションのレイヤーから切り離すことで、業務のモデル、さらには業務を行っている人のメンタルモデルと一致させやすくなります。ドメインレイヤーには業務で扱う情報だけでなく、業務上のルールやルールを適用する手順といった振る舞いを見いだし、集約させます。

モデルによってとらえられる知識は、「名詞を見つける」ことに留まらない。ビジネスの活動やルールも、ドメインに含まれるエンティティと同じように、ドメインにとって中心的なのだ。
– エリック・エヴァンス著 『エリック・エヴァンスのドメイン駆動設計』 翔泳社 2011年
第1章 知識をかみ砕く p.17

Cleanアーキテクチャー

CleanアーキテクチャーはRobert C. Martin氏により提唱されたものです。(The Clean Architecture | 8th Light (2012年8月13日)) Alistair Cockburn氏により提唱されたHexagonalアーキテクチャーBCEアーキテクチャー(バウンダリ、コントロール、エンティティ)から影響を受けています。

image

image

(cleancodersビデオシリーズ エピソード 7「Episode 7, Architecture, Use Cases, and High Level Design」中の図)

Cleanアーキテクチャーでは、エンティティなどに対する様々なユースケースを設計・実装において起点とします。アプリケーションレイヤーとエンティティのレイヤーの間にユースケースレイヤーを明示的に設けることでアプリケーションとエンティティの依存関係を切り離します。なお上の図におけるユースケースは「Application Specific Business Rules(アプリケーション依存のビジネスルール)」、エンティティは「Application Agnostic Business Rules(アプリケーション非依存のビジネスルール)」であると説明されていますから、ドメイン駆動設計の視点と共通していると言えます。

DCIアーキテクチャー

DCIアーキテクチャーはMVCの提唱者であるTrygve Reenskaug博士とJames O. Coplien氏により提唱されたものです。論文掲載は2009年3月20日です。

image

DCIの論文 Figure. 5

DCIアーキテクチャーでは、エンティティが状況によって異なる側面で扱われるという事実を、コンテキストとロールという概念で導入しています。モデルの振る舞いの側面はロールに切り出してまとめられ、コンテキストに応じてデータがロールを身にまといます。ドメイン駆動設計、Cleanアーキテクチャーと比較すると、DCIアーキテクチャーは旧来の素朴なオブジェクト指向よりも進んだパラダイムを前提としています。

これらのアーキテクチャーが捉えようとしているものは何か?

前節で取り上げた3つのアーキテクチャーに共通しているものは、何だと思いますか?

DCIの論文に次のような説明があります。

image

オブジェクト指向の世界で編み出されたMVCが、それだけではうまく整理しきれなかったものは、実は手続き・振る舞いなのです。MVCはアプリケーションの基本となる静的な構造の枠組みを表してはいますが、動的な部分をどうまとめあげるのかは開発者に任せられたままだったのです。先に取り上げたアーキテクチャーはいずれも、ソフトウェアの中に手続きや振る舞いがあることを再認識し、それをモデル化して明示的に取り扱うための枠組みを提供しようとしているのだと私は解釈しています。エンティティが中心にあってそこに振る舞いが付属しているというエンティティ中心主義ではなく、振る舞いをエンティティと同等に独立した要素として扱う(DDDではサービス、Cleanアーキテクチャーではユースケース、DCIではロール)ということです。

- To improve the readability of object-oriented code by giving system behavior first-class status;
(オブジェクト指向のコードの可読性を向上させるために、システムの振る舞いを第一級市民として扱う)
- To cleanly separate code for rapidly changing system behavior (what the system does) from code for slowly changing domain knowledge (what the system is), instead of combining both in one class interface;
(絶え間なく変更され続けていくシステムの振る舞い = what the system does のためのコードと、緩やかに変更されていくドメインの知識 = what the system is とをきれいに分離する。これらを1つのクラスにまとめるのではない)
Data, context and interaction - wikipedia

まとめ

  • MVCは、MVC-Uが本来の姿であった。モデルというのは人間の頭の中にあるもの
  • MVC自体を否定するのはナンセンスである。また、実装上のパターンの亜種は実装環境に依存して成り立っているのであり、どれがどう優れていると一列に並べて比較するものではない
  • MVCの上に乗るアーキテクチャーに関心を移そう

先駆者たちの思慮深い論文・書籍の翻訳に尽力されている方々、特にDCIの論文等をいち早く翻訳し公開された和智氏に深く感謝します。

PHP Advent Calendar 2013 - 6日目

明日は@brtriverさんです。

architecture mvc dci cleanarchitecture ddd design