『システムの科学』を読み解く (1) – 人工物の本質はインターフェースである

『システムの科学』という本をご存知だろうか?

ノーベル経済学賞の受賞者、ハーバート・A・サイモン氏によるシステム論の古典的名著、ということでその筋では有名な本であるらしい。初版が出版されたのが1967年というパーソナルコンピューティングの黎明期に当たる時代で、筆者が所持しているのは、改訂が重ねられて1996年に出版された第3版の邦訳本である。

システムの科学
システムの科学

原著のタイトルは『The Sciences of the Artificial』といい、『システムの科学』というよりも『人工物の科学』といった方がより原題に近いように思われるが、邦訳で前者を選択した経緯については訳者あとがきで説明されている。

「システムの科学」と言っても、何の話なのかぴんとこない人もいるかもしれない。「システム」とは、ある特定の目的を達成するように、複数の要素(部品)が組み合わさって出来たものだ。つまり「システムの科学」とは、今日「デザイン」と呼ばれているものの仕組みを、あらゆる学問分野の知見を総動員して論じたものだと言えば分かりやすいだろうか。

邦訳本のカバーには以下のような紹介文が書かれている。

「人工物の科学はいかに可能であるか」
本書は必然性ではなく、環境依存性 —「いかにあるか」ではなく「いかにあるべきか」— に関与するデザインの諸科学、すなわち人工物の科学(The Sciences of the Artificial)の本質を明らかにし、その可能性を問うものである。

筆者は10年以上前に、上の紹介文に惹かれてこの本を購入してみたはよいものの、難しくて最後まで読み通す事が出来なかった。何度繰り返し読んでもぼんやりとした感覚的な理解(分かったつもり)以上のものは得られなかったのである。最近、この本の存在を思い出して久しぶりに読んでみたら、相変わらず難しいとは感じるものの、当時よりも格段に理解出来るようになっていた。更に言えば、いくつかの事柄については腑に落ちるようになっていることに気づいた。

『システムの科学』で議論されていることの多くは、ソフトウェア開発者、あるいはプログラマが日常的に遭遇する問題とほぼ重なっている。ただちょっと異なるのは、その辺の技術書と比較して少しフレンドリーさに欠けているというぐらいである。というわけで、この連載では数回に分けて、この難解な本をソフトウェア開発の現場で役に立つような形で読み解いて行きたいと思う。

人工物とは何か?

そもそも人工物とは何か? 人が作ったものである、以上。

で終わってしまいそうであるが、それだけだと何の役にも立たない。辞書を引いてみると、対義語は「自然物」とある。人工物と自然物の違いは何だろうか?

『システムの科学』は自然科学の話から始まる。自然科学とは、この世に存在する事物を「分析」して、一見複雑に見える現象の背後にある、単純な法則を見つけ出そうとする学問分野だ。分析によって見つけた法則は一体どこから来たのだろうか? 例えば、万有引力の法則は、そのようなものが存在する事は説明してくれるが、それが何故存在するかについては説明してくれない。自然科学においてはそれらの法則がただそこにあると認識するだけである。

一方、人工物には存在理由がある。つまり「目的」が存在するのが人工物の特徴だ。

自然界にある法則を組み合わせて目的を達成しようとする。その時に生み出されるものが人工物である。サイモン氏は、このような人間の営みに科学的な解析を施して背後にある法則を探ろうと試みる。これが「人工物の科学」である。

人工物の本質はインターフェース

あらゆる人工物に共通する特徴は何だろうか? サイモン氏によれば、目的の達成というプロセスは、以下の三つの要素の関係によって成り立つという。

  1. 目的(ゴール)
  2. 人工物の特性
  3. 人工物が機能する環境

この三つの要素の関係を踏まえて人工物を表現したのが以下の図である。

outer-inner

とても単純な図である。人工物とそれが機能する環境は「インターフェース」という境界で区切られる。人工物の内部を「内部環境」と呼び、外側を「外部環境」と呼ぶ。

外部環境と内部環境がうまく噛み合って人工物が期待通りの振る舞いをするとき、その人工物は目的を達成することになる。ここで重要なのは、人工物が期待通りの振る舞いをするとき、そこに関わっている条件は外部環境と内部環境のほんの一部分に過ぎないという事だ。その目的を達成するために必要な、最低限の条件を表すのが「インターフェース」という両環境の接面であり、ここに人工物の本質が現れることになる。

interface

例えば、移動手段としての車について考えてみよう。この場合、外部環境で目的の遂行に関わって来そうなのは、移動範囲の地面の状態、という条件がまず思い浮かぶ。そして、内部環境として車輪の存在を前提にすれば、地面の状態にあった車輪の素材と形状、そしてそれを楽に駆動させる何らかの仕組みがあれば、移動という目的には事足りる。このように考えると、移動手段として車を捉えた場合、我々が一般に思い浮かべる車のほとんどの要素はその目的とは関係のない事が分かる。

外部環境が決まれば、そこに適応しようとする人工物の目的から鑑みて、内部環境の知識はほとんどなくともその人工物の振る舞いを予測することが出来る。逆に言えば、この合目的な振る舞いを維持する限り、内部環境はどのようなものでも良いということになる。例えば、飛行機と鳥は、同じ環境において「空を飛ぶ」という共通の目的を持ちながら内部環境は全く異なる。

今、「鳥」という例を出したが、実は人工物でなくても、何らかの状況に適応していると見なし得るすべてのものについて上の図式が当てはまることに注意したい。その意味で、『システムの科学』で展開されている内容は「何らかの状況に適応していると見なし得るすべてのもの」についての考察であって、人工物に限った話ではない。それが邦訳者が「システム」という言葉を選択した理由だろうと思う。

さて、ここまで読んで頂けたプログラマの方々には、このような人工物の捉え方に比較的親近感を感じる方が多いのではないだろうか? というのも、この人工物の捉え方はオブジェクト指向のモデルとほぼ一致するからである。ということは、オブジェクト指向の「オブジェクト」というのは、人工物あるいは「何らかの状況に適応していると見なし得るすべてのもの」のメタファーだと言って差し支えないように思える。

プログラマは、ミクロのレベルで、インターフェースのデザインという問題に日常的に向き合っている。この試行錯誤によって身についたノウハウはマクロのレベルにも応用出来るはずである。プログラムだけでなく、プロダクトや組織のデザインもサイモン氏のモデルによれば本質的には同じ構造になっている。注意しなければならないのは「目的」を誤らないということだけである。

コンピュータは最も純粋な人工物

『システムの科学』には、機械のように物理的な人工物だけでなく、組織や社会といった実体のないものまで、ありとあらゆる人工物が登場する。その中でもコンピュータは「記号システム」という特殊な存在として紹介されている。

記号システムとは、記号(文字やマークなどの物理的パターン)を処理するシステムで、記号を保存したり、変更したり、出力することが出来る。一般的には「情報処理システム」と呼ばれるものである。コンピュータだけでなく、人間の脳も記号システムだと言える。

記号システムは、記号が表現出来る範囲において、どのような外部環境にも適応する事の出来るアメーバのような究極の「適応システム」である。先ほど紹介した人工物のモデルで言えば、インターフェースをどのようにも変更出来るシステムだということになる。まさに人工物のモデルをそのまま体現したかのような純粋な人工物だ。

昨今のITの世界に目を向ければ、あらゆるものがソフトウェア化していく時代である。一昔前はハードウェアの領域だったものの多くが、今ではソフトウェアとして表現され、操作されるようになっている。つまり、世の中の多くのものが記号で表現・操作出来るようになってきた。思えば、ガラケーからスマホへの移行も、インターフェースのアメーバ化として捉えられる歴史的な出来事だった。そう考えると、『システムの科学』のような、人工物に関する抽象的な議論の重要性というのは、かつて無いほどに高まってきていると言えるのではないだろうか。

『システムの科学』を読み解く (2) – 経済学ってそもそも何なのか問題

Elixir試飲録 (7) – Erlangの軽量プロセスはどのように実現されているのか?

Elixir/Erlang の最重要コンセプトは「並行指向プログラミング」である、というのは既に書いた通りなのだが、この並行指向プログラミングを可能にする、いわゆる「軽量プロセス」がどのように実現されているのかという情報については、Erlang VM のソースコード以外にまとまった情報はなかなか無いようである。

The Erlang Runtime System』という書籍が来年発売予定らしいので、そこで体系的に解説される事になると思うが、とりあえず今回は筆者がWebで見つけた情報を簡単にまとめておきたいと思う。それぞれのコンセプトには分かる範囲で対応するソースコードへのリンクも貼っておいたので、興味のある方はそちらから詳細を追って頂ければと思う。

ちなみにこの情報の主な元ネタはredditに投稿された以下のコメントである。

 

Everything is a term

Erlangでは、全ての値が Term と呼ばれる固定長データ(32あるいは64bitの整数)の組み合わせで表現されている。

ソースコード上で見ると、Term は単にC言語の unsigned long 型へのエイリアスであることが分かる。

typedef unsigned long Eterm;

 
Term にはタグと呼ばれるメタ情報が含まれていて、そこを見れば Term に含まれるデータの種類が分かるようになっている。

term

ヒープやスタックといったデータ構造も内部的には Term の配列として実現されている。

 

A process is a C structure

軽量プロセスの正体は、process という名前の C の構造体である。

この構造体は以下のようなデータで構成されている。

  • ヒープ領域
  • スタック(Term の配列としてヒープ領域の末尾に配置されている
  • レジスタ(関数の引数)
  • インストラクション・ポインタ
  • メッセージ・キュー(受信したメッセージ本体はヒープ領域内にコピーされ、キューには本体へのポインタが格納される)
  • 親プロセスのPID

Erlangでプロセスを生成するとき、内部で行われているのは、メモリ領域を確保してこの構造体を初期化することと、その構造体へのポインタをスケジューリングのための実行キュー(Run Queue)に登録することだけである。そして、この時に必要なメモリ領域は、たったの 309 ワード(32ビット環境では1ワード4バイト)である。これが「Elixir/Erlangで一つのプロセスを作るのは一つのオブジェクトを作るのと同じぐらいの感覚」と言われる所以だ。

プロセスを生成するのに必要なメモリ 309 ワードの内、初期のヒープとして確保されている領域は 233 ワードである。このようにヒープの初期サイズを小さくしている理由は「Erlangのシステムが何十万、何百万というプロセス数をサポートをするために、極めて保守的に設定されているから」だと説明されている。

erlang-process-memory

 

Scheduler

Erlang が他の言語環境と比較して本当にユニークなのは、並行タスクを処理するプリエンプティブなスケジューリングシステムを、OSに頼るのではなくて、言語環境内に独自に持っているところである。

ここで言うスケジューリングとは、コンピュータ資源を複数のプロセスに割り当てる仕組みのことを言う。スケジューリングは OS などでマルチタスクを実現する際のコアとなる部品で、以下の二種類に分類される事が多い。

  1. プリエンプティブ(Preemptive)
    • スケジューラの側で資源の割り当てをコントロールする。制御を強制的に横取りするので「プリエンプション (preemption, 横取り) 」と呼ばれる。
  2. 協調的(Cooperative)
    • 各プロセスが資源の管理に責任を持つ。スケジューラの実装は楽だが、割り当ての公平性を保つのが難しく、素行の悪いプロセスがシステム全体を道連れにする恐れあり。

Erlang は、その出自上、高度なリアルタイム性と並行性が要求されていたので、より堅牢なマルチタスクを実現出来るプリエンプティブなスケジューリングを選択している。

Erlang におけるスケジューラは、内部的には一つのCPUコアに割り当てられた OS のスレッドである。そのスレッドの中でループ処理が走り、予め設定されたルールに基づいてプロセスを実行する。

scheduler

上図のように、スケジューラはキューからプロセスを一つ取り出し、インストラクション・ポインタが指している箇所から処理を再開させる。

プリエンプティブなスケジューリングで重要なのは、限られた資源をいかに公平に分配するかということである。Erlang では、計算コストに reduction という仮想の単位を導入してこの割り当てを行っている。例えば、一つの関数を呼び出すのは大体 1 reduction ぐらいのコストになる。プロセスには一度に消費出来る reduction 数の上限が設定されていて(バージョン R12B では 2000)、この上限に達する度に、スケジューラは実行するプロセスを切り替えて行く。

R11B より前の Erlang だと SMP(マルチコア)サポートがないために、Erlangランタイム全体で一つのスケジューラ(メインスレッド)しかなかったのだが、R11B 以降では、複数のスレッドを別々のCPUコアに割り当てることによって、複数のスケジューラが同時に動くようになった。

R11B から R13B にかけて、スケジューリングの仕組みは以下のように進化したようである。

1) 全体で一つのスケジューラ

scheduler

2) 複数のスケジューラが同じ実行キューを参照

scheduler2

3) 複数のスケジューラがそれぞれの実行キューを持つ

scheduler3

2) の方式だと一つの実行キューがボトルネックになってしまうということで、スケジューラごとにキューを持たせたのが 3) であるが、この方式でも複数のキューの間でサイズや負荷の偏りが出たらどうするのかという問題がある。今の Erlang ではこの偏りを均すために Migration Logic という仕組みを導入している。

一つ一つのスケジューラは、他のスケジューラを定期的にチェックして、自分より大きなサイズのキューを抱えているようだったら、そこからいくつか実行待ちのプロセスを自分のキューに移動してしまう。これが Migration Logic の仕組みである。

 

Message Passing

Erlang並行処理の要であるプロセス間のメッセージ送信はとてもシンプルな仕組みで実現されている。

  1. 名前あるいはPIDを指定して、送信先のプロセス構造体をレジストリから取得する
  2. プロセス構造体にあるメッセージ・キュー(mailboxと呼ばれる)をロックする
  3. 送信元プロセスのヒープ領域にあるメッセージ(Term)を送信先プロセスのヒープ領域にコピーする
  4. コピーしたメッセージへのポインタをメッセージ・キューに追加する
  5. メッセージ・キューのロックを解除する

もし、あるプロセスがメッセージ待ち(receive)状態になっているときは、新しいメッセージを受け取るまで実行キューから除外される。現実には多くのプロセスがこのメッセージ待ちの状態であることがほとんどである。つまり、この仕組みによって、何百万のプロセスを同時に立ち上げてもそれほどCPUを消費せずに済ませる事が出来るというわけだ。


[2016/09/29追記]

Erlang VM の仕組みについて、以下のような素晴らしいサイトが立ち上がっていた。


[2016/10/31追記]

Elixir/Erlang プロセスについての解説。コンパクトにまとまっていて分かりやすい。

ユーザー視点凝集をめぐる冒険 (2) – 継続的なリリースという罠

前回は「どのようにシステムを切るか?」ということについて考えた。

ところで、何故システムを「切る」必要があるのだろうか? それはシステムを作る際に、切り分けたシステムを別々の開発者に割り当てて分業したり、スケジュールに割り当てて完成までのロードマップを作るためである。そしてこのとき、切り分けられたパーツが「ユーザー視点凝集」を満たしていれば、パーツ単体でユーザーの反応を確かめる事が出来る。

タスクを、それぞれ何らかのユーザー価値を生み出す「スモールバッチ(small batch)」に分ける事によって、ウォーターフォール開発のようないわゆる「ラージバッチ(large batch)」なアプローチよりも遥かに早い段階から継続的なフィードバックを得る、というのがアジャイル開発の勘所だった。

インクリメンタル開発(漸増型)とイテレーティブ開発(反復型)

ユーザー視点凝集ごとにシステムを区切る事を「vertical slice(縦方向の分割)」と呼ぶ、というのが前回の話だったが、vertical slice ごとにシステムを作り上げて行く手法は「インクリメンタル(incremental)開発」と呼ばれている。

これはアジャイル以前に開発プロセスの標準的な存在だった RUP(Rational Unified Process)や UP(Unified Process)に含まれていた手法である。これらのプロセスモデルが提唱された90年代後半は、ウォーターフォールを捨ててアジャイルを生み出すまでのちょうど過渡期に当たり、ソフトウェア開発に一発勝負のウォーターフォールは馴染まないという反省から、RUP や UP と言った「繰り返し型」開発プロセスが提唱されるようになった。

インクリメンタル開発は、新規の増分(開発部分)を積み上げていく方法です。この方法は、繰り返しの単位となるN回目、N+1回目で対象となるソフトウェア構造がまったく異なるものや、依存関係のないものに適しており、繰り返しの単位の独立性が保てるので非常に分かりやすいというメリットがあります。もし、N回目、N+1回目の中に、共通するソフトウェア構造が含まれていた場合は、共通部分を別々に開発してしまうことになり、ソフトウェアの保守性に問題が出ます。

この記事の中で紹介されているように、インクリメンタル開発は繰り返し型開発の中の一手法に過ぎない。繰り返し型開発で重要なもう一つの手法は「イテレーティブ(iterative)開発」である。

イテレーティブ開発は、ソフトウェアの全体、あるいは部分について、最初は薄く作り、少しずつ肉付けしていく方法です。この方法は、非常に重要かつ複雑なソフトウェアの個所について、徐々に確認しながら肉付けし、中身を濃くしていけるというメリットがあります。

つまり、イテレーティブ開発は、システムを区切るのではなく、同じユーザー視点凝集(のセット)の完成度を段階的に高めて行く手法だ。

RUP や UP には「ユースケース・ドリブン」と「アーキテクチャ・セントリック」という二大ポリシーがあり、それぞれのポリシーがインクリメンタル開発とイテレーティブ開発に対応している。ユースケース・ドリブンでは、ユースケースという vertical slice ごとに開発を進め、アーキテクチャ・セントリックでは、段階を踏んで全体のアーキテクチャを洗練して行く。この二つのポリシーを組み合わせて、インクリメンタルとイテレーティブのバランスを巧く取りながら開発を進めて行くというのが RUP や UP の要諦である。

考え方としては、今改めて検討しても合理的に思える部分も多いが、CI(Continuous Integration)がまだ一般的でなかった当時、繰り返しのたびにテストをやり直さなければならないという問題や、その他自動化が十分でないためにかかるオーバーヘッドなどを考えると、まだ現実的と言える手法ではなかったのではないかと想像出来る。さらに最も重大だと思われるのは、vertical slice ごとのリリースという考え方がないためにアジャイルのようなフィードバックループを得られず、ユーザー(顧客)視点からはウォーターフォールと何ら変わりのない手法に見えることだ。

「イテレーティブ」の再発見

一つ一つの vertical slice を順番にリリースして、早い段階から「本番」を経験させるのがアジャイルの勘所であると説明したが、このような繰り返しをアジャイルでは「イテレーション」と呼ぶ事が多い。しかし、実際にこの繰り返しが意図するところは「インクリメンタル」な開発である。

アジャイルがインクリメンタル開発にフォーカスしていることを典型的に示すのが、スクラム手法で頻繁に参照される「雪だるまモデル(Snowman model)」だ。

scrum-snowman-model

このように、一つのイテレーション(スクラムでは「スプリント」と呼ぶ)が終わる度に「出荷可能(shippable)」な vertical slice が積み上がって行くイメージである。ユーザーは早い段階で製品に触れて開発者にフィードバックを送る事が可能になり、管理者にとっては、開発のペースが安定して進捗も可視化されるなど、良いこと尽くめのように思える。

しかし、2007年にロンドンで行われた XPDay において、「インクリメンタル開発へのこだわりには大きな落とし穴がある」そう指摘したのが Jeff Patton 氏だった。

Patton氏はインクリメンタルにモナ・リザを描くとしたらどうなるかという例を挙げて、仮にインクリメンタルだけで開発を進めようとすれば、結局は最初の段階で全体像を決めておかなければならず、また全てのパーツを完成させない限り全体像の実物を検証する事も出来ないため、結果的にはウォーターフォールと同じ事になると警告した。

Jeff Patton氏のサイトより
Jeff Patton氏のサイトより

インクリメンタルなプロセスとは対照的に、普通に絵を描く時は以下のようなイテレーティブなプロセスを辿る。

Jeff Patton氏のサイトより
Jeff Patton氏のサイトより

全体像をぼんやりと決めてから徐々に細部を詰めて行く。細部を詰めて行く過程で全体像を調整して行くので、最初に完璧な構想を立てる必要はない。

アジャイル時代になって、インクリメンタル開発がイテレーションと呼ばれるようになり、それまでイテレーションと呼ばれていたコンセプトが抜け落ちてしまったというのが Patton氏の指摘だ。

出荷可能(shippable)とは何か?

インクリメンタル開発の落とし穴は、先ほどの雪だるまモデルで登場した「出荷可能(shippable)」という考え方である。

仮にシステムを vertical slice に分けて開発し、イテレーションの度にリリース出来たとして、それらのパーツの集積が本当に出荷可能だと言えるだろうか?

ほとんどの場合、答えは「ノー」である。

出荷可能の本当の意味は、顧客にとって意味のあるプロダクトである、という当たり前の事実だ。そのプロダクトを使って実際の業務を遂行出来る、あるいはサービスや売り物として成立するなど。そのように考えると、多くのケースで vertical slice はこの水準に相当しないだろう。筆者も何度か経験しているが、 vertical slice に分けて開発したとしても、ある程度全体像が完成してこない限り、顧客、あるいはそれに準ずる役割の人たちは、作りかけの製品には興味を示さなかった。

何故このようなズレが生じるのかと言えば、それはおそらく vertical slice の粒度が開発者視点で決定されているからではないだろうか。顧客に決めてもらうとは言っても、そのフレームワークはそもそも開発者側が考えたものだ。つまり、このときの vertical slice はユーザー視点凝集ではない、ということになる。

元々、開発者視点でシステムを区切る「horizontal slice(横方向の分割)」は好ましくないということで、ユーザー視点であるはずの「vertical slice(縦方向の分割)」を持ち込んだ訳だが、そこでも開発者視点を払拭する事は出来なかったということになる。

インクリメンタルとイテレーティブをミックスする

インクリメンタル開発の落とし穴を避けるためには、アジャイル以前の RUP や UP で提案されていたのと同じように、インクリメンタルとイテレーティブを巧く組み合わせようという話になる(実際には意識せずともそうならざるを得ないが)。そのための手法として、ユーザーストーリーをフィーチャーごとに書くだけでなく、一つのフィーチャーをイテレーティブに発展させるためにストーリーを三枚のカードに分けて書くという方法が提案されている。

この方法によって、一度の開発でフィーチャーを完璧(出荷可能)に仕上げなければならないという、インクリメンタル開発のプレッシャーから解放される。

ユーザー視点凝集をそれぞれ同じ完成度で比較すると粒度がまちまちになってしまい、アジャイルが目指す、ペースを維持した開発を実現することが難しくなってしまう。そこで、ユーザー視点凝集の抽象度(曖昧さ)を調節して、一回のイテレーションに収まるサイズに縮めるというのが、イテレーティブ手法が果たす役割である。

スコープ

アジャイルにおけるインクリメンタル開発の落とし穴と、それを回避するためのインクリメンタル/イテレーティブの組み合わせ、なかなかに説得力のある話ではあるが、一つ重大な点が見過ごされているような気もしなくはない。それはスコープと呼ばれる、全体像の問題である。

イテレーティブの導入によって、詳細な全体像を用意する必要は無くなったかもしれないが、それでも依然として全体像は必要になる。アジャイルやリーンの考え方として出来るだけ多くの意思決定を先送りにするとしても、全体像はある程度決定しておかなければならない。しかし、この全体像がちょっと油断するとチームをウォーターフォールへと引きずり込むアリ地獄のような存在になってしまう。

果たしてこの全体像というのは、どのように決定すれば良いのだろうか?

(続く)

受託開発でアジャイルというのはほとんど語義矛盾ではないだろうか

ここ最近、立て続けにアジャイル絡みの事を書きながらこぼれ落ちた考えをここに書き留めておく。

筆者にとってアジャイルというのは、かなりの程度、個人的な問題だということはまず認めておかなければならない。

個人的だと思う背景には、(自分にとっての)アジャイルというものを、それを知らない人に伝達する事の難しさをつくづく痛感しているからである。

アジャイルはそもそも道具ではない。すでに書いたように、アジャイルというのは価値観であるし、もっと言えば文化だと言っても差し支えないだろう。

人間の集団の中で理屈抜きで共有出来る何らかの価値観があれば、それを文化と呼ぶ。

例えば、一つの組織の中で、ある事が組織の利益になるから同意するというのは、基本的に契約の問題であって文化ではない。つまり、組織にとって利益になるからアジャイルを導入しようという(ベタな)言明はそもそも成り立たない。

アジャイルが結果的に組織にとって利益になるという事は当然あり得るが、利益になるからやるという順番だとそれはもはやアジャイルではない(それが「道具ではない」ということの意味だ)。このようなジレンマが文化というものには必ず付いて回る。

文化を築くのには気が遠くなるほどの時間と幸運が必要になるが壊れるのは一瞬である。特に営利組織の中で働いていて、その利益以外の事で価値観を共有するというのは基本的に至難の業である。

アジャイルはもう終わったという話が度々出てくるが、そのほとんどはハイプ・サイクル絡みの話だった。そもそも無理筋なことをやろうとしていた、というだけのことではないだろうか。

アジャイルはそもそも「無理」から出発した方が良いのだろう。

アジャイルはソフトウェア開発にクリエイションを持ち込もうとしたが、ほとんどの現場ではそんなものを求めていなかった。

日本ではソフトウェア開発の多くが受託開発だと言われている。

受託開発というものがそもそも「アイデアを他者によって実現してもらう」という考えで成り立っている以上、開発側にクリエイション(アイデアをいかに発見するか)が入り込む余地がない。

文化という側面で言えば、人に頼まれて何かをやるという仕事の方式そのものが減点ベースの評価にならざるを得ず、アジャイルとそもそも相容れないと言う事も出来る。

これは受託開発だったらウォーターフォールという選択の問題ではない。人に頼まれて何かをやるときは「交渉」が最重要事案になるというだけだ。

自分達で発見したものを世に問うという仕事に就きたいというのは完全に価値観の問題であって、多くの人にとっては「人に頼まれて何かをやる」のがそもそも仕事の定義であり、その上で生活が成り立っている。社会全体としては価値を生み出していなくても「交渉」によってお金を回す事は出来る(それが持続可能なのかは別にして)。

アジャイル最大の難点は、当たり前だが、そこで価値の発見が保証されているわけではないというところだろうか。以前、スティーヴン・キング氏の自伝「On Writing: A Memoir of the Craft」を紹介したが、それを読めば誰もが小説が書けるわけではないのと同じように。

何が見つかるか分からない状況で旅に出ることをこの上ない喜びと感じるか、そんな博打を打っていたら生活が成り立たないと嘆くか、そこが文化の分かれ道かもしれない。

アジャイルはプログラマーに万能を求める。

アジャイルの問題意識がそもそも役割の分断にあるので、必然的に分断された役割を小さな範囲に集約していくことになる。そこを突き詰めると、それは単にフルスタックエンジニアになるというレベルを超えて、各人が自律したマーケターになることも要求されるようになる。

興味深い事に、これはマーケティング分野の側でも同じような事が言われているようだ。

アジャイルにおいて創造のエンジンを担っているのはプログラマーの側である。その外側にいてアイデアや舵取りの責任を持つ人間の存在はアジャイルにとっては単なる非効率に過ぎない。技術がイノベーションの源泉になっている、あるいはそのような組織を目指しているのなら尚更、外側の人間がアジャイルのエンジンに噛み合って仕事ができるという幻想を捨てなければならない。

しかし、冷静に考えてみよう。こんなことが可能な環境や組織が果たしてどのぐらい存在するだろうか? あるいはそれこそがクリエーションの希少性と呼べるものなのかもしれないが。

ユーザー視点凝集をめぐる冒険 (1) – どのようにシステムを切るか?

凝集性というのは、どのような尺度でプログラムを分けるかという問題だ。凝集度は高い方が良いと言っても、この高い・低いという判断は一筋縄で行くものではない。というのは、どのような尺度を利用するかによって、ある視点からは凝集度が高くなるように見えても、別の視点からは凝集度が低く見えてしまうからである。

以前、連載記事『TDD再考』の中で、凝集性の分類として「論理的凝集」と「機能的凝集」というものがあるということを紹介した。論理的凝集は実装上の特徴でまとまりを作り、機能的凝集は(プログラムの利用者から見た)機能の単位でまとまりを作る。つまり、これらの尺度の違いは、それぞれのまとまりの受益者が異なる事に由来する。前者は開発者の視点であり、後者はユーザーの視点だ。

わざわざ「(プログラムの利用者から見た)機能」と前置きのあるところから分かるように、「機能」という概念がそもそも視点を前提としていることに注意して欲しい。機能というのは受益者がいて初めて成り立つ概念である。機能的凝集がユーザーから見た機能に対応する一方、論理的凝集は開発者から見た機能に対応するという意味では、いずれも「機能的凝集」と呼んでも差し支えないはずなのだ。分けるという行為を実際に根拠づけるのは視点であり、そのように考えると、凝集性の分類でより適切なのは視点による分類ではないだろうかと思う。というわけで、ここでは機能的凝集を「ユーザー視点凝集」、論理的凝集を「開発者視点凝集」と呼んでみる事にしたい。

coherence

プログラムデザインの手法としてレイヤーアーキテクチャが人気なのは、レイヤーが開発者視点凝集、つまりプログラマーから見た凝集性を実現しているからだと言っても良いのかもしれない。データアクセスの処理や、ユーザーからのリクエストを各々のロジックに振り分ける処理など、ユーザーにとってはブラックボックスの中身の話であるが、プログラマーにとっては日常である。

『TDD再考』では、Ruby on Rails 開発者の David Heinemeier Hansson 氏が——先ほど導入した言葉を使って説明すれば——開発者視点凝集よりもユーザー視点凝集を重視すべきだという主張を展開していることを紹介した。TDDのような手法を使ってプログラムをデザインすると、ミクロなレベルではどうしても開発者視点に引っ張られて、結果的にユーザー視点凝集よりも開発者視点凝集を優先してしまうというのがDHH氏によるTDD批判の要点である。

21世紀に入ってからユーザー視点凝集はプログラムデザインおいて極めて重要な地位を占めている。ひょっとしたら最も重要な概念だと言っても間違いではないのかもしれない。それはユーザー視点凝集がアジャイル開発を進める際の単位として想定されているからである。

アジャイル界隈では、ユーザー視点凝集を「vertical slice(縦方向の分割)」と呼んでいる。

この vertical slice についての分かりやすい説明が、最近話題の書籍『SOFT SKILLS』の著者、John Sonmez 氏のサイト「Simple Programmer」にあった。

Sonmez氏は vertical slice を「家を建てる」という例で説明している。

普通に家を建てる時の手順を想像してみよう。まず最初に作らなければならないのは土台である。基礎工事で土台を作り、そこに骨組みを乗せる。その後、骨組みに沿って壁を作り、最後に屋根を乗せる。この最後のステップが完了して初めて実際の家の中身を確認する事が出来るようになる。

このように普通の家屋建築で現れる手順は「horizontal slice(横方向の分割)」に基づいて作業が分かれている。

house-horizontal

この horizontal slice に対して、vertical slice で家を建てるというのは、まず一つの部屋を利用出来るような形で完成させ、その次にまた別の部屋を作るという感じで、人間が実際に利用出来る最小単位ごとに建物を作っていくことに相当する。

house-vertical

部屋ごとに家を建てるのは、建築においてはいささか非現実的なアプローチに見えるが、ソフトウェア開発においては重要な考え方になりつつある。

そもそもソフトウェア開発でも、この家屋建築の例と同じように horizontal slice に基づいた開発が主流だった。それは作業を縦に分割するよりも横に分割する方が遥かに簡単だからである。しかし、horizontal slice は一つ一つのスライスがシステム全体に渡るため、最初に全体の設計図を書かなければならない。更には、一つ一つのスライス(レイヤー)を合体して完成品とする最終段階で想定と異なる事実が判明しても、そこから大幅な変更をするためには大きなコストがかかってしまう。変化の速いソフトウェアの世界では horizontal slice による開発ではなく、vertical slice によって、出来るだけ早い段階で機能の検証を行うべきだというのがアジャイル開発の考え方であった。

A vertical slice from the components of a project
A vertical slice from the components of a project

しかし、この「家を建てる」例での説明は、若干ミスリードな部分もあるかもしれないなと思う。というのは、家の場合は例外なくまず先に設計書があって、それに従って現場の大工さんが家を建てる流れになるが、ソフトウェア開発、あるいはプログラミングは、大工さんの作業というよりも、設計書を作る方に相当するのではないかと思うからである。その意味で、家屋やその他の建築物の設計書を作るプロセスはソフトウェア開発と変わりない試行錯誤があるはずである。

ユーザー視点凝集をめぐる冒険 (2) – 継続的なリリースという罠

知識はツリーではない: 実験的知的生産プラットフォーム Oinker.me の紹介

今回はちょっと趣向を変えて、このゆびてくを運営しているたまチームの活動について紹介したいと思います。

たまチームでは、仕事の合間時間を利用して Oinker.me というWebサービスを開発しています。ちょうどこのサイトのドメインが ubiteku.oinker.me になっているので気づかれた方もいるかもしれません。

Oinker · Chat, Connect and Grow Your Ideas.
Oinker · Chat, Connect and Grow Your Ideas.

このWebサービスは、元々たまチーム内の情報共有のために気軽に使えるものをということで立ち上げたものですが、それと同時にそれまでの情報管理や知的生産のあり方に対する問題意識を出発点に「新しい知的生産のあり方を模索してみよう」という目的を持つ、実験的な色合いの濃いプロジェクトでもあります。

しばらくチーム内で使ってみて感触を試した後、これはなかなか良さそうだという事で公開してから既に一年以上が経過しました。今のところ利用者の数はそれほど多くないですが、たまチームにとっては欠かせない道具になりつつあります。

まず、Oinkerがどういうものかを知ってもらうためには以下の動画を見て頂くのが早いと思います。サービス公開当初に作った動画なので現状の見た目とは若干異なる部分もありますが、基本的なコンセプトは変わっていません。

よくあるチャットアプリのように、部屋を作ってチャットをする、そしてその発言をドラッグ&ドロップで保存したり繋げたりする。たったこれだけのシステムです。

connect-oink

今時のコミュニケーションにとってチャットは欠かせないツールになりました。組織やチームでの知的生産においても重要な役割を占めています。Oinkerではそのチャットの発言を切り取って保存したり、発言同士をつなげたりする仕組みを提供します。

チャットで議論していても発言はどんどん流れて行ってしまうし、重要な論点はなんだったかなと過去ログを遡るのもノイズが多くて面倒なものです。そう考えると、チャット上の重要な発言を保存しておきたいというニーズは普通にありそうです。でも、Oinkerにとって本当に重要なのは発言同士をつなげるという行為です。チャットのような気軽なディスカッションの内容を材料にして大きな情報を組み立てていく、これが Oinker の醍醐味です。

世の中の多くの情報管理ツール、あるいは情報共有の方法はトップダウンが基本です。予め決められたテーマや枠組みに沿って情報を整理して行く。蓄積した情報を後々探しやすくするために「整理」するというのが、このような情報管理の目的です。Oinkerではそこを逆転して、日常のあらゆる場面で発生するコミュニケーションや、個人的なメモ・つぶやきを材料にして、自然発生的に必要な情報やアイデアを立ち上げて行くというボトムアップのアプローチを取ります。

トップダウンの情報管理が「整理」に主眼を置くとすれば、ボトムアップの主眼は「発見」ということになるでしょうか。

もちろん、どちらかを選択したらどちらかを捨てるというような単純な二者択一ではありません。あくまでどちらに重点を置くのかということになりますが、たまチームでは若干の秩序を犠牲にして発見にフォーカスしたら情報管理はどのようになるだろうという興味を持ってOinkerというWebサービスを開発しています。

ボトムアップで組み上げた情報は、トップダウンの時のような綺麗なツリー構造にはなりません。これが表題の「知識はツリーではない」の意味です。このスローガンは有名な建築家であるクリストファー・アレグザンダー氏の論文「都市はツリーではない (A City is not a Tree)」からヒントを得ました。

Oinkerでは以下のような感じで、一つの情報を複数の情報の下に置く事ができます。

multi-parent

アレグザンダー氏の論文では、人間の脳には物事をツリー構造で捉えようとするバイアスがある、という研究が紹介されています。脳の負荷を減らすために、視点や文脈を一つに絞り、単純化して物事を把握しやすくする。それ自体はとても合理的な認知機能ですが、反面、視点を固定する事で発想の幅は狭まります。単一視点のツリーのような分かりやすさを若干犠牲にして、複数の視点から物事を眺める、複雑な構造をある程度複雑なままアウトプットに反映する。自然発生的に生まれた都市が、計画的に作られた都市と違って生き生きとしているのはそのような違いがあるからなのだとアレグザンダー氏は言います。

建築・都市計画におけるこのような考え方を知的生産の分野に応用したらどうなるのかという取り組みの一例が Oinker だと言っても良いのかもしれません。

さて、たまチーム内では完全に定着したこの Oinker ですが、まだ最初のアイデアを形にしただけの小さなシステムです。色んな方々に使って頂いて「ここをこうしたらもっと良くなるのに」とか「こんな使い方もできるよ」といったような意見や感想を是非是非聞いてみたいと思っています。面白そうだなと思った方は是非、https://oinker.me をお試し頂いて、このブログのコメントやTwitterのハッシュタグ #oinkerme でつぶやいて頂けたらとても嬉しいです。

Oinkerにまつわる知的生産の話は「知識はツリーではない」シリーズとして随時ポストして行く予定ですので、Oinkerに興味を持って頂けた方は是非こちらもフォローしてみて下さい。

#noprojects: もう「プロジェクト」というアプローチでは価値を生み出せない

ちょっと前に InfoQ から「#noprojects」という電子書籍が出ていた。

cover-noprojects-emag

この書籍には、それぞれ別の論者によって書かれた6つの記事が掲載されている。「No projects」というそのタイトルの通り、これまで当たり前のように採用されて来た「プロジェクト」というアプローチは既に有効ではない、というのがこれらの記事に共通する主張である。

一見、過激で論争を呼びそうな主張であるが、内容を仔細に検討してみると、アジャイルというものを追求して行けば自然にこの形に到達するだろうなと腑に落ちる内容だった。アイデアとしては、アジャイルがより多くの領域に援用されるきっかけとなった、リーン、あるいはリーン・スタートアップや、このゆびてくでも紹介したインパクトマッピングなどのアイデアをミックスしてより一般的な形で展開させたものである。

そもそも「プロジェクト」とは何だろうか?

プロジェクトには始まりと終わりがある。プロジェクトはあるタイミングで開始されて、そして終了するタイミングは予め決定されている(実際にその通り終了するかどうかは別にして)。プロジェクトの最大の目的は「プロジェクトを終了させる」ことである。プロジェクトには固定の期間と人員、そしてそれに合わせた固定の予算、そして固定のアウトプットが想定されている。つまり、プロジェクトとは、予算を持ってそれを計画する人の都合に合わせる事を目的にしたアプローチなのである。言って見れば、予算と青写真を入れてボタンを押せば、決まった期日にアウトプットが出てくる魔法の箱である。

そんな魔法の箱は存在しない、そして、たとえ青写真通りのアウトプットが出てきたとしても、その時にはすっかり価値がなくなっている、というのが #noprojects の主張である。

プロジェクトというのは、ウォータフォール型開発の最後の遺産だと言えるかもしれない。これまでその存在があまりにも自明過ぎたためにアジャイルの時代になっても生き延びてきた。しかし、アジャイルでは既に、どの段階でもプロダクトをリリース出来るという継続的デリバリー(Continuous Delivery)が普及しつつあり、そのような現場においてプロジェクトというアプローチは形骸化しつつある。

最初に計画を行い、そしてその通りに物事が進んで、予定の期日にものが出来上がってくるという想定は間違っている、必ず想定外の事象が発生して変更を迫られることになるのだから、予め変更に強い体制を整えておかなければならない、というのがアジャイルの考え方であり、自然に #noprojects に繋がるものであるが、実際には多くの管理者がガントチャートを作りたがるし、物事が想定通り進まないのはどこかに原因があって、それを個別に対処して行けばプロジェクトを元のレールに乗せる事が出来ると信じている。頭の中では変化を想定しなければと思う反面、行動としてはどうしてもウォータフォールの影を引きずってしまう。プロジェクトという形態を取る限り、失敗の原因を計画に求める他なく、故により多くの事を予め想定しておこうという考えから脱する事ができない。

あるいは「変化に対応しよう」というアジャイルのスローガンに問題があるのかもしれない。変化があるかないかで言えば、変化がなく想定通りに進むプロジェクトだってあるだろうと主張する人も出てくる。よって、アジャイルかウォータフォールなのかは適材適所なのだという主張も成り立つ。しかし、本当の問題はそこにはない。ソフトウェア開発が開発の中途で想定外の変化を遂げるのは、その中でより価値のあるものを「発見」しようとするからである。ほら、想定通り物事が進んだじゃないか、そういうことだってあるんだ、と言っても、そのようにして出てきたものには既に価値はない、というのがソフトウェアの世界である。よって、スローガンとしては「発見を主眼におかないソフトウェア開発にはもはや価値を産み出せない」と言った方が良いのかもしれない。

継続性

プロジェクトに代わって、新たなモデルとして提案されているのが「継続的な変更の流れ(Continuous stream of change)」あるいは「流れ作業生産(Flow production)」と呼ばれるものだ。「流れ作業」というと、アジャイルが目の敵にしていたテイラリズムを想像してしまうが、ここでのフォーカスは顧客価値に基づく小さな変更を継続的に行うことにある。リーンの「かんばん」などはこのモデルに近い。

そして、ここでのキーワードは「継続性」である。プロジェクトは基本的に large-batch なアプローチであり、リスクも高いしオーバーヘッドコストも馬鹿にならない。これまではプロダクト(アウトプット)ごとにプロジェクトを組むのが一般的であったが、この方法だと新しいプロダクトを計画する度にチームを編成してプロジェクトを立ち上げなければならない。同じプロダクトでもバージョンアップや改修があるときに別のプロジェクトを立ち上げるのはよく見かける光景だ。#noprojects が提案するのは、アウトプットではなく「成果(Outcome)」にコミットするチームを編成し、その成果を実現するために継続的な変更をリリースしていくモデルである。成果とは、組織にとってどのぐらいの「価値(Value)」があるかという尺度で評価される何らかの「変化(Change)」のことだ。

このようなモデルにおいて、プロジェクトに代わって重要な存在になるのは「チーム」である。チームは結果的な価値にコミットするため、同じチームが複数のプロダクトに関わることは自然なことであり、そこに継続性が生まれる。このようなチームを「価値提供チーム(Value-delivery team)」と呼ぶ。これまでのプロジェクトモデルの問題は、プロジェクトごとにチームが編成・解散されることが多く、せっかく蓄積した知識やノウハウの連続性がそこで失われる事であった。

これまでのプロダクトにフォーカスするプロジェクトベースの開発では、開発者、営業、マーケティング、サポートといった、組織内の機能ごとにチームを分けるのが一般的だった。そして、このように役割で組織が分断されるという問題が、このゆびてくでも繰り返し取り上げてきた、アジャイルやDevOps、インパクトマッピングといった考え方が繰り返し指摘する、現代のソフトウェア開発における最大の障害なのである。チームが価値にコミットするためには、そのために必要な全ての機能をチームの中に備えておかなければならない。これを「機能横断型チーム(Cross-functional team)」と呼び、たとえれば、チームがミニチュアなベンチャー企業となるようなイメージである。実際に、#noprojects の記事の中では、ベンチャーキャピタルの組織内バージョンだと指摘されている。

このような機能横断型チームが、小さなサイクルで実験と学習を繰り返しながら価値を発見しようと継続的な努力を続ける、これが #noprojects が提案するソフトウェア開発のあり方である。

#noprojectsの重要なコンセプト

#noprojects では、その手法を構成する重要な要素として、以下の5つのコンセプトを挙げている:

  • 価値(Value)
  • 成果(Outcome)
  • アクティビティ(Activity)
  • ルール(Principle)
  • 継続的デリバリー(Continuous delivery)

チームは「成果」を生み出すために「ルール」に基づいて「アクティビティ」に従事し、その「成果」は「価値」によって測定される。そして、その継続性とサイクルの短さは「継続的デリバリー」によって担保される。

価値(Value)

価値というのはなかなかに厄介な概念である。ほとんど「主観」と同義だと言っていいぐらいに不確かなものであるし、多くの賢人が指摘しているように、数字に置き換えて理解しようとすると途端に落とし穴にハマる危険性が高くなる。#noprojects では、その不確かさはそもそもビジネスをする際には付き物であり、そこが正確に予測出来るのであればマーケットも簡単に予測可能になるはずだが実際にはそうなっていないと、価値問題に深入りする事は避けている。しかしながら、組織が成功するかどうかのファクターのほとんどはこの価値に関わっていて、仮に #noprojects を実現したとしても、それは単にスタート地点に立っただけに過ぎない。

この価値の問題については、以前「我々は何のためにソフトウェアを開発するのか?」という記事の中でも取り上げた。その中で、Ron Jeffries氏による価値の定義「Value is what you want(価値とはあなたが欲しいもの)」を紹介した。価値を突き詰めれば、経済の問題だと考える人も多いだろうし、あるいは文学の問題だと思う人もいるかもしれない。

#noprojects によれば、一度成果として実現された価値というものは、変更を継続しなければ、時間が経つにつれて減衰していくと言う。これが、プロジェクトモデルに代えて継続的手法を取るべきだという主張の直接的な根拠になっている。

成果(Outcome)

成果とは、具体的なプロダクトやアウトプットではなく、価値によって計測可能な変化の集積である。このように表現するとなかなかイメージを掴みづらいが、記事の中では「アクティブユーザーの獲得」や「スタッフ満足度の向上」などが例として挙げられている。

Agile India 2016 - Leadership Day Summary より
Agile India 2016 – Leadership Day Summary より

この「成果」は、以前ゆびてくで紹介したインパクトマッピングの「ゴール(Why?)」に相当する。つまり、インパクトマッピングを利用すれば、成果を実現するためにはどのようなアクティビティを行えば良いかを考える際の助けになるし、かつチーム内での情報共有においても効果的である。

map

im_example

アクティビティ(Activity)

アクティビティとは、成果を生むための必要な個別のタスクのことである。#noprojects では、個々のアクティビティを事前に評価するための手法として「アクティビティ・キャンバス(Activity canvas)」というものを提案している。以下のように、アクティビティにかかる手間をX軸、実現できる価値をY軸とするキャンバスを用意し、そこに個々のアクティビティをマッピングして行く。

Agile India 2016 - Leadership Day Summary より
Agile India 2016 – Leadership Day Summary より

上のようにアクティビティをマッピングして行くと、どのような順番でアクティビティを実施したら良いかという優先順位が自然に現れてくる。#noprojects に限らず、アジャイルで重要なのは、タスクを「高」「中」「低」のような役に立たない優先順位に分ける事ではなく、実施の順序を決めることである(次に何をやればよいか?)。タスクを順序付けることによって同時並行で処理するタスクの数を減らせば、チーム全体のスループットを向上させることができる。

ルール(Principle)

アクティビティの実施方法は完全に自由というわけではなく、多少をコストをかけてでも若干のルールを作っておくことによって、アクティビティとプロセス全体の親和性、あるいはアウトプットの品質を保つ。アジャイルで言えばプラクティスに相当するが、プログラミング、コミュニケーション、セキュリティ、ブランディングなど、共有するルールはプラクティスよりも多岐に渡る。ルールでチームを縛るというよりも、最低限守らなければならない作法を明確にしておくことによって、その他はメンバーの裁量に任せるという意味合いが強い。

ルールの導入にはコストがかかるため、「MoSCoW」という優先度割当の方法が紹介されている。MoSCoWではルールごとに以下の4つのカテゴリーから選んで優先順位を割り当てる。

  • M (Must have) – 必ず従わなければならない
  • S (Should have) – 正当な理由がない限りは従う
  • C (Could have) – 個人の裁量に任せる
  • W (Won’t have) – 出来るだけ避けるべき

継続的デリバリー(Continuous delivery)

#noprojects のような考え方が提唱されたり、あるいは現実的になってきたのは、インフラ技術の進歩によって継続的なデリバリーが実現出来るようになってきたからだと言っても過言ではないだろう。継続的デリバリーを、アジャイル以後のDevOps時代に生み出された最も重要な技術的達成だと位置付ける人も多く、より具体的で分かりやすい継続的デリバリーという考え方をアジャイルの後継とすべきだという主張も見かけた。

まとめ

以上、#noprojects の考え方を概観してみた。

プロジェクトは計画の別名だと言ってもよく、計画を軸にビジネスを進めると本当に重要な価値の追求よりも計画の履行を優先してしまうことになる。#noprojects の問題意識をひと言で言えば、そんな感じになるだろうか。計画・施行のモデルは、同じようなプロダクトを作り続ける事に意味があった20世紀型のビジネスモデルであり、21世紀の今では発見的な手法に移行しなければ新たな価値を生み出す事は出来ない。その意味で、本来は技術者よりも、経営者やマネジメントがアジャイルや #noprojects のような手法を理解しなければならないはずである。しかしながら、マネジメントの利害を考えると #noprojects が世の中の大勢を占めるトップダウン型の企業に浸透するとは到底思えない。これまでプロジェクトマネジャーが担当していた責任をチームに委任することになり、マネジメントの役割は大きく変わる事になるからである。

プロダクト開発とアイデア信仰

先週の記事で紹介したスティーブ・ジョブズ氏のインタビュー。その中で彼は、パーソナルコンピューティングやオブジェクト指向開発だけでなく、ジョブズ哲学を総括するかのように実に様々な話題について自説を展開している。その中でも特に興味深いのが、プロダクト開発における「アイデア」の位置付けについての話だ。

この中で、インタビュアーはジョブズ氏に対して「プロダクト開発において重要なことは何か?」と聞く。ジョブズ氏はしばらく考えた後、そのインタビューの10年前、1985年に彼をアップルから追い出したジョン・スカリー(John Sculley)氏の話をする。

ジョブズ氏によれば、彼がアップルを去った後、スカリー氏は深刻な病に冒されていた。その病とは「素晴らしいアイデアを手に入れたら、それで90%の仕事が完了したと思い込む」ことだと言う。

しかし実際には、素晴らしいアイデアと素晴らしいプロダクトの間には気が遠くなるような craftmanship(職人芸)の集積がある。そのような集積を経て実現したプロダクトは、スタート地点のアイデアとはかなり異なるものになっている。その変化(発見)を可能とする数限りない試行錯誤、トレードオフにまつわる決断、このようなプロセスこそがプロダクト開発の魔法なのだとジョブズ氏は言う。

この気づきの有無は、この話の前に出てくる、marketing (or sales) people と product people の話題にも関係している。ジョン・スカリー氏はペプシコーラのプロモーションで大成功を納めた人物だ。この分野の人物が craftmanship やプロダクト開発の魔法を理解するのはなかなか難しいのではないかと想像できる。これら職能間の断絶は深く、2016年の今でも、プロダクト開発におけるアイデアの重要性について疑う人はそれほど多くないように見える。

ジョブズ氏のインタビューから15年以上経った2012年、似たようなことを主張している記事を見かけた。アメリカを拠点とするコンピュータ科学分野の国際学会 ACM(Association for Computing Machinery)の機関誌「Communications of the ACM」に掲載された「The idea idea」という記事である。

著者は、高名なコンピュータ科学者であるピーター・J・デニング(Peter J. Denning)氏。

デニング氏の問題意識は、現代の我々はアイデアの重要性を信じて努力を継続しているため、アイデアの獲得には苦労していないが、それを実際のイノベーションに繋げる段階になると極端に成功率が下がるのは何故なのか? ということであった。実際の成功率は4%ぐらいに過ぎないのだと言う。

彼は、イーサネットを発明したロバート・メトカーフ(Bob Metcalfe)氏が、コンセプトの発明にかけた労力よりも、それを普及させるのに費やした労力の方がはるかに大きかった経験を「花と雑草」にたとえた話を紹介し、重要なのはアイデアよりも実践の方なのではないかという仮説を展開している。

アイデアよりも前に、まず誰かによる実践があり、その実践に効果があると見た他の人たちがそれを真似する。しばらくするとその実践をより容易にするようなツールが開発され、その実践は更なる広がりを見せる。このようなプロセスを前提に考えると、「アイデアとは既に起こったイノベーションを説明するための後付けの理由」に過ぎなくなる。

デニング氏は記事の中で「氷山の一角」というアナロジーを紹介している。プロジェクト全体の中でアイデアというのは氷山の一角に過ぎず、海中に沈んでいる多くの部分がイノベーションにとって重要な実践の部分に当たる。そして、実践を継続することでアイデアの部分が氷山として浮き続けることが出来る。一方、盲目的にアイデアの重要性を信じてアイデアの獲得にコストを費やすと、このバランスが崩れてイノベーションの実現は難しくなる。

この記事が出てきた文脈には、ソフトウェア開発の世界で第一段階目の成熟を迎えつつあったアジャイルの影響があったと思われる。スティーブ・ジョブズ氏は、1995年の段階でその核心に到達していた数少ないビジネス界の人物だったと言えるのかもしれない。

このような考え方は、さらにクリエイティブな領域に行くとそれほど珍しい話ではなくなる。世界的な小説家であるスティーヴン・キング(Stephen King)氏は、彼の作家人生を記した自伝的著書「On Writing: A Memoir of the Craft」の中で、自身の興味深い小説技法について説明している。

on-writing

彼は「プロット」を信じていないという。プロットは小説における設計書のようなものだ。

I distrust plot for two reasons: first, because our lives are largely plotless, even when you add in all our reasonable precautions and careful planning; and second, because I believe plotting and the spontaneity of real creation aren’t compatible … I want you to understand that my basic belief about the making of stories is that they pretty much make themselves. The job of the writer is to give them a place to grow (and to transcribe them, of course). (p.159)

我々の人生にはそもそもプロットがないこと、そして真のクリエイションに備わっている spontaneity(自発性)とプロット(計画)はそもそも相性が悪いこと。そして、キング氏によれば、ストーリーは、適切な場を与えさえすれば自然発生的に育つものだと言う。

I told the interviewer (Mark Singer) that I believed stories are found things, like fossils in the ground, he said that he didn’t believe me. I replied that that was fine, as long as he believed that I believe it. And I do. … Stories are relics, part of an undiscovered preexisting world. The writer’s job is to use the tools in his or her toolbox to get as much of each one out of the ground intact as possible. (p.160)

キング氏は、ストーリーは発見するものだと考えている。地中に埋まっている化石のように。それは遠い昔に存在した、未だ発見されてない世界の遺品のようなものである。そして作家の仕事は、自分が持てる道具を駆使してそれらの遺品を出来るだけ無傷で掘り起こすことだと言う。

オブジェクト指向とは何だったのか?

このブログでも何度か取り上げているように、プログラミングにおけるここ数年間のトレンドで最も大きなものは、関数型プログラミングの隆盛だと言って良いだろう。そのトレンドと共に相対的に存在感を失いつつあるのが、それまで長らく主流を占めていた「オブジェクト指向」という考え方である。関数型プログラミングの観点から眺めると、オブジェクト指向プログラミングではシステムの状態を暗黙に扱う(情報隠蔽)ために実行時の挙動が予測しづらくなり、高度な並行性が求められる現在の環境では、信頼性を確保する際の大きな障害となるように見える。

しかし、筆者も含めて、オブジェクト指向という考え方にあれだけ熱狂した立場から考えると、昨今のオブジェクト指向に対する評価に対して若干の違和感を感じざるを得ないのも事実である。というわけで今回は、オブジェクト指向の発案者による発言を参照しながら、改めて「そもそもオブジェクト指向とは何だったのか?」ということについて考えてみたい。

まず始めに「オブジェクト指向」という言葉の意味については、どこかに正式な定義が存在する、というわけではないようだ。これは「関数型」についても同様のようである。なので、このブログで書かれていることはある種の解釈や立場表明をしているに過ぎないということに注意して頂きたい。

メッセージング

「オブジェクト指向(object-oriented)」という言葉を最初に提唱したのは、Smalltalkというオブジェクト指向環境を発明したアラン・ケイ氏だと言われている(これは本人も認めている)。

When and where was the term “object-oriented” used first?

At Utah sometime after Nov 66 when, influenced by Sketchpad, Simula, the design for the ARPAnet, the Burroughs B5000, and my background in Biology and Mathematics, I thought of an architecture for programming. It was probably in 1967 when someone asked me what I was doing, and I said: “It’s object-oriented programming“. – Dr. Alan Kay on the Meaning of “Object-Oriented Programming” (強調は筆者)

そのアラン・ケイ氏によれば、オブジェクト指向で最も重要なのは「メッセージング」なのだと言う。

Smalltalk is not only NOT its syntax or the class library, it is not even about classes. I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea.

The big idea is “messaging” – that is what the kernal of Smalltalk/Squeak is all about – Alan Kay On Messaging (強調は筆者)

上記引用にもあるように、ケイ氏は「オブジェクト(指向)」という言葉を持ち出したことに後悔さえしていると言う。メッセージングの観点から言えば、クラスやオブジェクトさえも重要ではないということから、本来は「メッセージ指向」と呼ぶべきだったのかもしれない。

さて、このメッセージングであるが、オブジェクト指向の発案者によってその重要性が強調されているのにも関わらず、職業プログラミングの主流を占めていた C++ や Java でオブジェクト指向を実践してきた多くのプログラマにとっては、むしろ馴染みの薄いものではないだろうか。

ケイ氏は、オブジェクトを、ネットワークを形成してメッセージを送り合うコンピュータのメタファーとして捉えており、インターネット上のサーバーのように、リクエストをメッセージとして受け取り、そのメッセージをサーバー側で解釈して何らかの処理を行うというモデルを想定していた。オブジェクトへのリクエストという意味では、C++ や Java のメソッド呼び出しがメッセージングに相当するように思われるかもしれないが厳密には異なる。ケイ氏の開発した Smalltalk では、このメッセージを非同期にやり取りするという、今まさに広がりを見せつつある並行処理のモデルを70年代の段階で実現していた(後にアクターモデルと呼ばれる)。しかし、その Smalltalk においても、ある段階から現在主流になっている(メソッド呼び出しと同等な)同期的な呼び出しに移行し、その後も「メッセージ」のような用語が維持されたためにメッセージングにまつわる誤解と混乱が生じた、という経緯があったようだ(参考: Alan Kays Definition Of Object Oriented)。


[2016/05/09追記]
この部分に事実誤認があるとの指摘をコメント欄にて頂いた

「ケイ氏の開発した Smalltalk では、このメッセージを非同期にやり取り」については、

Versions of Smalltalk before Smalltalk-80 were still largely based on the (asynchronous, unidirectional) ActorsModel of computation, but with Smalltalk-80, the developers of SmalltalkLanguage switched entirely to the (synchronous, bidirectional) procedural model, while misleadingly retaining the ActorsModel terminology (such as “messages” for what essentially are procedure calls rather than one-way notifications). – Alan Kays Definition Of Object Oriented

この言及だけを参考にして書いてしまっていたのだが、

アラン・ケイらの Smalltalk-72 が大きな影響を与えてはいますが(https://www.cypherpunks.to/erights/history/actors/actor-induction.pdf の Acknowledgements 等)、アクターモデル自体はカール・ヒューイットの考案であり、Smalltalk や ケイのメッセージングとはまた別物です。念のため、ケイの関わった Smalltalk(-72、-74、-76、-78)は非同期のメッセージングベースで実装されたことはありません。 – コメント欄(sumimさん)より

とのこと。ご指摘感謝!

[2016/05/10追記]

さらに別の方からコメントを頂く。

Actor Model自体がCarl Hewittの考案であり、Kayのメッセージングとは「別物」ということですが、歴史はしばしばもうちょっとややこしいものです。私が見聞きしたか限りにおいては、KayがMITに行ってSmalltalk-72に関する講演を行った時に、実際の実装とは別に元々のアイディアとして「ネットワークで接続されたオブジェクト群がメッセージを交換しながら計算を行う」という話をし、その時に聴衆にいたHewittがActor Modelの着想を得た、という経緯もあります。Actorの当初の論文には謝辞としてAlan Kayの名前があるもの、それ以上のクレジットがなかったために、Carlが「コミュニティー」からもうちょっとアイディアの出典を正直に述べなくてはならないという批判を受けることになったという話もあります。 – コメント欄(よしきさん)より

Smalltalk に興味がわいた方は、sumim氏による Smalltalk-72 入門も是非ご参考あれ。


messaging

このメッセージングに関係して、最近タイムリーなブログ記事を見かけた。メッセージングを実現している環境として、実は Erlang/Elixir が最もアラン・ケイ氏のオブジェクト指向を体現している言語環境なのではないかという考察である。ちなみに、この二つの言語は一般的には関数型プログラミング言語として認識されているはずである。

この記事の中で紹介されているように、Erlang の開発者である Joe Armstrong 氏が「Erlang が最もオブジェクト指向に近い言語である」という趣旨の発言をしているようだ。

しかし、何故メッセージングが最も重要なのだろうか? それは、メッセージングという仕組みの内に、ケイ氏が考えるオブジェクト指向の要点が全て含まれているからである。ケイ氏は、メッセージングの位置づけを日本語の「間」という言葉で説明しているが、氏によれば、システムの「間」つまりコミュニケーションに着目することによって、システムにとって重大な目的(ゴール)だけを表現し、その他の詳細については二次的に決定すれば良い、そしてそのような決定の先送りがシステムの寿命を延ばす鍵になるのだと言う。メッセージがどのように解釈されるのか、クラスから作られたオブジェクトによって処理されるのか、そのクラスは継承によって構造を共有しているのか、あるいはプロトタイプベースなのかという問題は全て二次的な問題である。

出来るだけ多くのことを「work in progress」として扱うことによって進化してきた Smalltalk は、ある段階からあらゆることを決定し固定化したがる人たちによって不毛な議論が繰り返され、その結果、進歩が停滞しているとケイ氏は苛立ちを見せていた。この「work in progress」の考え方は、21世紀になってから普及したアジャイルの考え方そのものである。アジャイルはオブジェクト指向のコミュニティが生み出したものであるが、その考え方の源流はオブジェクト指向の始まりから既に埋め込まれていた。

抽象データ型

ここまでの話で、オブジェクト指向の発案者であるアラン・ケイ氏の、メッセージングという真意が明らかになった。しかし、これだけでは職業プログラマの多くが実践してきたオブジェクト指向らしきものは一体何だったのかという疑問が残る。

One of the things I should have mentioned is that there were two main paths that were catalysed by Simula. The early one (just by accident) was the bio/net non-data-procedure route that I took. The other one, which came a little later as an object of study was abstract data types, and this got much more play. – Dr. Alan Kay on the Meaning of “Object-Oriented Programming”

上記引用のように、ケイ氏は、オブジェクト指向の起源となった Simula という言語からは、ケイ氏の主張する「the bio/net non-data-procedure route」の他に、「抽象データ型」を中心に据える流れも起こり、こちらの方がむしろその後主流になったと説明している。抽象データ型の流れは、C++ や Java に引き継がれ、最近では Scala など、いわゆる静的型付け(static typing)として職業プログラマの間では主流になった。

ケイ氏の考えとしては、抽象データ型系列の C++ はオブジェクト指向としては認識していないようであるが(「”I invented the term object-oriented, and I can tell you that C++ wasn’t what I had in mind”」)、型システムそのものについて否定的に見ているわけではないようである。

I’m not against types, but I don’t know of any type systems that aren’t a complete pain, so I still like dynamic typing. – Dr. Alan Kay on the Meaning of “Object-Oriented Programming”

クリエイティビティ

さて、結果的に抽象データ型が主流になったのあれば、それがいわゆる多くの人が認識するところのオブジェクト指向なのだから、今更「アラン・ケイ氏のオブジェクト指向」についてわざわざ考慮する必要があるのかと考える人もいるかもしれない。しかし、筆者にとってこの二つの立場の違いは、単にプログラミング言語の差異に留まらず、コンピューティングというものに対する立場を二分する大きな断絶を象徴する分岐であるように思えるのである。

抽象データ型というのは、単にプログラミング言語の問題である。あるいは、システム開発者側の問題だと言っても良いかもしれない。例えば、開発側の観点から仕事上のトラブルを少なくするといった、よりプラグマティックな理由から型システムという仕組みを評価できるだろう。職業プログラマの間で型システムが普及した理由にはこのような側面もあったのではないだろうか。

しかし、アラン・ケイ氏による1993年の論文、オブジェクト指向が生まれる過程が仔細に書かれた「The Early History Of Smalltalk」を読むと、オブジェクト指向は単にプログラミングだけの問題ではなく、もっと壮大な理想を追求するために考案されたのだということが分かる。

オブジェクト指向が生まれる直前の60年代という時代は、コンピュータが、予め計画された計算を行うだけというイメージの、一つの部屋を専有する巨大な機械から、もっと人間の力を高める形でインタラクションを提供する新しいメディアへと生まれ変わろうとする、いわゆるパーソナルコンピューティングの黎明期に当たる。そして、そのような運動の中心にあったのが、ARPA(Advanced Research Projects Agency)というアメリカ国防総省管轄の研究機関である。ARPA はインターネットの原型となった ARPANET を開発したことでも知られる。

このパーソナルコンピューティングを模索する流れの中で、アラン・ケイ氏が立ち上げたのが Dynabook 構想であり、この構想には今日では当たり前になっているパーソナルコンピュータやネットワーク、GUI(ウィンドウシステム)など、パーソナルコンピューティングにとって重要なあらゆる要素が詰め込まれていた。

パーソナルコンピューティングの主題は、コンピュータを使っていかに人間の能力を高めるか(「Amplify human reach」)ということであったが、これは言い換えれば「クリエイティビティ(創造性)」の追求である。ケイ氏が最も熱心に取り組んでいたのはコンピュータを使った子供の教育であった。オブジェクト指向プログラミングを子供に教えることによって、ソフトウェアを自身の問題に合わせて自在に変更出来るようにし、より高い問題解決能力を獲得させることを目指した。

この教育プロジェクトの試行錯誤によって得られた知見には大変興味深いものが多い。例えば、Smalltalk がクリエイティビティに寄与する一つの根拠として、より少ない原則で多くを表現できるというものがある。有名な「Everything is an object」というやつである。後のケイ氏であれば「Everything is a message」と言ったかもしれない(そのようなプログラミング言語が実際に存在する)。この全てがオブジェクトであるという原則と、大きなシステム(オブジェクト)は小さなシステム(オブジェクト)の組み合わせで作られるという「再帰的デザイン」によって、どんな複雑なシステムをデザインするときでも、覚えなくてはならない原則は少なくて済むようになる。さらに Smalltalk において重要なのは、システムを使うという行為と作るという行為が全く同じになるということである。システムを使うときと全く同じ操作で、その延長線上でシステム自体を変更することができる。このように、導入において覚えることが少なくて済む、そしてその少ない道具立ててであらゆることが表現できるという枠組みが、子供の教育にとって重要であることは想像に難くない。しかし、実際に試してみて分かったことは、あるオブジェクトにメッセージを送るという1ステップの変更については小さな子供でも理解することが出来るが、複数ステップの変更を組み合わせないと解決できないような問題になると、それがほんの2、3ステップだったとしても極めて難しくなってしまうということだった。これは子供だけでなく、プログラミング経験のない大人でも似たような現象が見られたようである。初歩の問題は容易にクリアできるが、問題が複雑化すると、それがプログラマから見たら些細だと思われる問題でも全く歯が立たなくなる。これは今で言うプログラムデザインの問題である。このデザインの教育に対応するためにケイ氏の同僚である Adele Goldberg 氏が「design templates」という仕組みを考案した。これはぼんやりとしたデザインのアイデアとプログラムによる実例の中間に位置する道具立ててで、今で言うところのデザインパターンやフレームワークに相当するものである。

ケイ氏は、このような大きなビジョンを掲げることの重要性について、そして出来るだけ多くを「work in progress」にすることの重要性について繰り返し語っているが、それはいかに多くの技術者や専門家が手段に埋没し、そして宗教論争に明け暮れているかということに対しての警鐘にもなっている。

スティーブ・ジョブズ

「The Early History Of Smalltalk」の一つのハイライトは、1979年、ケイ氏の勤めるパロアルト研究所(PARC)に、あのスティーブ・ジョブズ氏が訪れる場面である。当時のジョブズ氏は77年にアップルコンピュータを設立したばかりで、次世代のパーソナルコンピュータを生み出そうと Lisa プロジェクトを立ち上げていたが、決定的なアイデアがなく模索中の段階であった。ケイ氏は、PARCに訪れたアップルの面々に対して、ALTOというパーソナルコンピュータ試作機のデモを行う。ALTO には Smalltalk による OS が搭載されており、その上ではウィンドウベースのGUIが動いていた。

Xerox Alto Computer
Xerox Alto Computer

そのデモの最中、ジョブズ氏は、試作機で動いていたウィンドウシステムのスクロールをもっと滑らかな方式に変更出来ないかと指摘し、開発者の一人である Dan Ingalls 氏がその場で修正して訪問者を驚かせたという場面が紹介されている。Smalltalk 環境の強力さが垣間見えるエピソードである。

この邂逅の後の歴史は多くの人の知るところなのでここでは深追いしないが、1995年に行われたインタビューにおいて、ジョブズ氏がパロアルト研究所で見たデモについて、そしてその当時は理解出来なかったというオブジェクト指向の重要性について語っているので、その動画を紹介しておきたい。

この映像は放送後に一度紛失したと思われたマスターテープがジョブズ氏の死後に偶然見つかったということで、95年の放送時はカットされた部分も含めて全編放送されたものであるが、その内容にはただひたすら圧倒されるのみである。

このインタビューの中で、1995年当時、今後10年間で最も重要だと思われるテクノロジーとして、ジョブズ氏はオブジェクト(指向)と Web の二つを挙げている。

そして現在

オブジェクト指向についてその起源から検討してみたが、この歴史を前提に改めて考えてみると、昨今言われる「オブジェクト指向から関数型へ」という話が大分狭い領域の話であることに気づかされる。モジュールシステムの進化という観点から言えば、関数型プログラミングが有効である場面が増えているのは疑いようがない。しかし、より大きなスコープで考えるとオブジェクト指向の考え方は依然として重要であり、将来的には、以前紹介した「Functional in the small, OO in the large」という形で適材適所に住み分けることになるのではないだろうか。

そして、アラン・ケイ氏のオブジェクト指向という観点からより視野を広げて考えると、その枠組みにおいてオブジェクトはプログラミング言語とは切り離されたメタファーあるいはコンセプトに過ぎず、メッセージや遅延束縛(late binding)と言ったコンセプトは形を変えてあらゆる場面で見かけるようになった。その代表的なものの一つが、アジャイルという考え方である。オブジェクト指向はその出自から考えても分かるように、より上流の考えを重視する目的指向のパラダイムである。アジャイルはその後、経営やデザインなど、ソフトウェア開発の枠組みを超えて、クリエイション一般に適用出来るような普遍的な考え方として普及しつつある。

プロダクト開発とアイデア信仰 | ゆびてく

関数型つまみ食い: 関数型とはプログラミング言語ではなく、プログラムデザインの問題であることに気づく

関数型プログラミングに精通していないプログラマにとって、「関数型」という言葉でまず思い浮かべるのは、Haskell や Erlang,Scala のような関数型言語や、Immutableなデータ構造、高階関数、ラムダ式、モナドと言った関数型プログラミングの道具立てだったりすることが多いのではないだろうか?

前回登場したゲームプログラマの James Hague 氏も、筆者と同じく、キャリア半ばで関数型の世界に足を踏み入れた越境者の一人だったが、その彼が、関数型言語 Erlang でのゲーム開発を経験して気がついたことがあると言う。それは、アプリケーション開発に純粋な関数(副作用のない関数)というコンセプトを導入することは、いわゆる関数型言語や関数型プログラミングとは全く関係ないということであった。

関数型というコンセプトの要は、ビジネスロジックを「データの変換」と捉えることにある。

functional1

関数というのは要するに「変換のルール」のことであるが、この関数が文字通り変換のルールのみを定義し(副作用がなく)、そして変換対象のデータを直接編集せずに、常に変換後のデータに丸ごと置き換えて行けば、システムの状態変化は常にアトミックに行われることになる。

functional2

このような形で、システムの状態遷移をシンプルにすることによって得られるメリットの大きさに、筆者の場合、関数型言語というよりもむしろ、Redux というフロントエンドのフレームワークを学ぶ中で気づかされた。

Redux はフロントエンドの状態管理を行うためのフレームワークである。オブジェクト指向に慣れているとアプリケーションの状態は個々のオブジェクトに分散するのが当たり前であるため、何故状態だけを独立して管理する必要があるのか、今ひとつピンと来ないこともあるかもしれない。GUIとオブジェクト指向というのは歴史的にも結びつきが強いため、この領域に関数型を適用するというのはどういうことなのか、これは筆者にとっても長年の疑問であった。

Redux は React というフレームワークとペアになっている。React はユーザーインタフェースをレンダリングするためのテンプレートエンジンで、入力データをテンプレートに当てはめて結果を出力するという、まさに関数としてデザインされている。そのため、テンプレート内で状態管理をすることは推奨されていない。状態は外部で管理して React 側に提供する必要がある。その状態管理を担うのが Redux である。

redux

Redux の仕組みは、上に書いたような関数型の状態管理そのままである。筆者にとって重要だったのは、これが JavaScript という、比較的慣れ親しんだ言語の上に構築されたフレームワークであったことだ。これによって「ああ、つまり関数型というのはフレームワーク(デザイン)なのだ」という気づきを得ることが出来た。

関数型デザインによって実現するシンプルな状態遷移のメリットは、Redux のプラグイン redux-logger によって出力されるログを見ると分かりやすい。

redux-logger

上のように、Redux で状態を管理していれば、そこで発生する全ての状態遷移は、

  1. prev state
  2. action
  3. next state

の繰り返しによってアトミックに行われる。何か問題が発生した時は、開発者自身があちこちにログを仕込んだりデバッガを活用せずとも、この状態遷移の内容を見れば何が起きたのか一目瞭然である。

Redux のドキュメントでは、関数型(functional)という言葉を出来るだけ使わずに説明を試みているようである。しかしながら、Prior Art にあるように、Redux の直系の先祖はブラウザで動く(ほぼ)純粋な関数型の環境 Elm であり、Elm は Haskell をベースにデザインされていることから、Redux は純粋関数型の思想を純粋関数型でない言語の上に実現したフレームワークだと言って差し支えないだろう。

ビジョンを曇らせる誘惑: 技術偏重、一貫性

James Hague (@dadgumjames) 氏は、少年時代に一世を風靡した『パックマン』などのゲームに影響を受けてプログラミングを始めた。高校時代にはコンピュータ雑誌の常連投稿者として活躍、大学卒業後もゲームプログラマーとしてSNES(海外版のスーパーファミコン)向けのゲームを開発した。当時はアセンブリ言語で開発していた彼も、次第にマンネリを感じて高級言語を物色し始め、丁度その頃オープンソース化された Erlang で関数型プログラミングの門戸を叩いて今に至る。

パックマン from Wikipedia

そのようなキャリアを持つ彼が、長らくプログラマとして活動する内に気がついた事があると言う。それは、いつの頃からか新しいアプリケーションをデザインする時には、技術的・実装的な見地から逆算的に物事を考えるようになり、ユーザーやプレイヤーの観点から完成品を想定することが少なくなっていたということだった。

技術者は長くその専門領域に棲息するうちに、その領域の尺度だけで物事の良し悪しを判断するようになる。技術者のコミュニティでは、そのような尺度の元に序列付けが行われて、個々の技術者にプレッシャーを与え続ける。その結果として、何のためにそのアプリケーションを開発するのかということは脇に置かれ、パフォーマンスやプログラムデザインのような技術的達成が優先的に考慮されるようになる。

Hague 氏がプログラミングを始めた頃、プログラミングは彼のビジョンを実現するための道具に過ぎなかった。しかしそれが、プログラミング自体にのめり込むにつれて次第に自己目的化してしまった。そのねじれを解消するために、もう一度原点に戻ろうという思いがこの The Recovering Programmer というタイトルに込められている。

前回の記事「TDD再考 (8) – 凝集性(cohesion)とは何なのか?」で紹介した論理的凝集と機能的凝集の対立もこの問題と密接に関係している。アプリケーションの目的から考えれば機能的凝集を目指すべきところでも、油断すれば技術偏重に陥り、論理的凝集を優先してしまうことになる。そしてこの問題は Hague 氏だけでなく、このブログでも紹介してきたように、ビジネスと技術の対立を解消すべく現れたアジャイルムーブメントによってもフォーカスされてきた。特にその流れの中で提案されたドメイン駆動設計(Domain-driven design, DDD)は、機能的凝集に集中するための方法論だと考えると分かりやすいかもしれない。

アジャイルが指摘したのは、機能的凝集は動くターゲットであるということであった。それまでのソフトウェア開発で念頭に置かれていたような静止したゴールは存在しない。継続的に効果を得るためには、ユーザーとのインタラクションを重ねて、ユーザーにとって意味のある「文脈」を構築していかなければならない。

デジタルマーケティングの世界で長年活躍してきた Joanna Lord (@JoannaLord) 氏は、ブランドマーケティングの観点から、一貫性への過剰な執着がブランドの機能的凝集(coherence)を損なう要因になり得ると指摘する。

彼女によれば、ブランドの一貫性とは、裏を返せばそのブランドが変化せず、(環境が変わっても)常に同じ体験を提供し続けるときに実現されるものだと言う。コメント欄で指摘されているように、コカコーラのような長い歴史の中でブランドが確立しているようなケースではその一貫性を維持する理由があるが、現代の状況の中で新しいブランドを構築していかなければならない場合、一貫性より機能的凝集を目指さなければ生き残りは難しい。

一貫性というのは分かりやすい価値であるが故に過剰に追求されやすい。そして、一貫性を保とうとすれば、新しいアイデアを試したり、大胆な変更を行う事に対して臆病になってしまう。その結果としてユーザーにとっての価値、つまり機能的凝集が時間とともに失われてしまうことになる。

TDD再考 (8) – 凝集性(cohesion)とは何なのか?

Is TDD Dead? 会談

David Heinemeier Hansson氏の「TDD is dead」は、アジャイル界隈に醸成されていたある種の信仰を、過激な論調で批判したことによって大変なセンセーションを巻き起こした。強い言葉に対しては、やはり感情的な反応が多くなる。中には坊主憎けりゃ袈裟まで憎いという感じで、TDDを超えてRailsの設計思想を批判する人まで現れたが、「TDD is dead」を受けて行われた「Is TDD Dead?」という歴史的な会談の内容を見るに、DHH氏の発言は、規律(信仰)に対する単純なカウンターなどではなく、そこにはプログラムデザインについての重要な論点が含まれているように思える。

Is TDD Dead? 会談は、2014年の5月から6月にかけて、TDD提唱者の Kent Beck 氏と今回の騒動の主である DHH 氏、そしてホスト役である Martin Fowler 氏の三者で行われた。全5回の会談の内、今回は第二回までの内容を検討してみたい。

結局のところデザインの意思決定を行うのは開発者自身である

まず全体的な印象として、DHH氏と、TDD肯定派のBeck氏、Fowler氏の意見が真正面から対立しているという感じはそれほどなく、お互いに別のところを見ていて話がすれ違っているような印象を受けた。James Shore氏の以下のツイートは、この件に関するTDD肯定派のフラストレーションを代弁している。

DHH氏の想定するようなTDDは、Beck氏やFowler氏の想定するTDDとは異なり、単にやり方が悪いからうまく行かないだけで、DHH氏の発言は「わら人形論法」に過ぎないというのが、TDD肯定派の反論である。それに対して「うまく行かないのはやり方が間違えているからであって、正しく実践すればいつかは正解に辿り着ける」というのは「faith-based TDD」という信仰であって、そのようなものは認められないというのが DHH氏の立場であり、ここがすれ違いの中心になっている。

例えばBeck氏は、DHH氏のTDD批判は、車を運転していて見当違いな場所に行ってしまったからと言ってそれを車のせいにするようなものだと言う。TDDがプログラムデザインに悪影響を与えると言った場合、デザインの意思決定を行っているのはプログラマ本人であり、TDDがデザインを決定しているわけではないと。それに対してDHH氏は、TDDを実践する事によって、あるいは実践しようとすることによって、プログラムデザインにある種の傾向が現れるのは否定出来ないと反論する。

「結局のところデザインの意思決定を行うのは開発者自身である」という話については、「TDD再考」の第二回で紹介した James O Coplien氏も同じような話をしていた。

TDDを実践すれば自然にプログラムデザインが改善するなどということはあり得ず、良いデザインを実現するためには結局のところ自分の頭で考えなければならないというのが、Coplien氏の場合はTDDを批判する根拠の一つになっていた。ところが、TDD提唱者のBeck氏や、肯定派のFowler氏も、この件に関しては全く同じように考えており、TDDというのは彼らにとっては思考を助ける道具に過ぎないと発言している(なので適材適所で使う場合もあれば使わない場合もある)。つまり本当の対立は、TDDを肯定するか否定するかという部分にあるのではなく、それを使えば自動的に結果が好転していくような銀の弾丸として祭り上げている勢力とそれ以外の対立という事で、このブログで繰り返し取り上げてきた問題と全く同形の問題のように思える。

凝集性と結合性は頻繁に対立する?

第二回目の会談では、「TDD再考 (6)」でも紹介した、TDDが及ぼすプログラムデザインへの悪影響について検討を行っている。Railsのようなフレームワークを利用した開発で(厳密な)TDDを実践しようとすると、各層を単独でテスト出来るようにするために間接層(indirection)やモックオブジェクトを導入する必要が出てくる。それが結果としてコードを複雑にし、メンテナンスを難しくするというのがDHH氏の指摘だ。もちろん間接層が絶対悪というわけではなく、間接層にも依存モジュールを置き換えやすくするというメリットがあるわけだが、それをテストをやりやすくするという目的のためだけに導入するのは間違っているのではないかという指摘である。

この一連の話の中で最も興味深いと思ったのが、「凝集性と結合性は頻繁に対立する(cohesion and coupling are often opposed)」というDHH氏の主張である。

凝集性と結合性、あるいは凝集度と結合度は、プログラムデザインの良し悪しを計るための最も基本的な尺度だと言われる。凝集度はより高い方が望ましく、結合度はより低い方が望ましいとされる。モジュール間の依存関係はプログラムの構造的にはっきりと見えるので、結合性というコンセプトは比較的理解しやすいが、凝集性というのは具体的にどのようなものを指しているのだろうか?

凝集性・結合性は、多くの場合、相関があるとされる。具体的には「凝集度が高いモジュールは他との結合度が低いことが多く、逆に凝集度が低ければ結合度が高くなる傾向がある(Wikipediaより)」と言われている。しかし、DHH氏は「凝集性と結合性は頻繁に対立する」と言う。

DHH氏の主張を簡単に図解してみよう。

layer

Railsのようなフレームワークには、モデルとデータベースを密結合させた Active Record という仕組みがある。アプリケーションロジックを担当するコントローラは、この Active Record を直接操作するため、そのままだとデータベースにアクセスせずにアプリケーションロジックをテストする事は出来ない。そこでまずはアプリケーションロジックをコントローラから切り離して Action Runner というオブジェクトに移してフレームワークへの依存をなくし、その上で Action Runner は Repository という間接層を経由して Active Record の機能にアクセスするようにする。このようにすればアプリケーションロジックを Rails の枠組みに載せる事なくテスト出来るようになるというわけである。

この改造によって、確かにレイヤー間の結合度は下がったと言える。しかし、それと引き換えに凝集性が犠牲になっているというのが DHH氏の主張だ。ここで言う凝集性とは具体的に何を指しているのだろうか?

論理的凝集と機能的凝集

「凝集性」という日常的に馴染みのない言葉だと分かりづらいが、凝集性とは平たく言えば「まとまり度合い」のことを指す。例えば、今回問題にされているレイヤーアーキテクチャ一般について考えてみると、レイヤーアーキテクチャとは、アプリケーションの構造の中での役割分担を見出して、それらを疎結合となるように分割したものだと言える。分割されたそれぞれの層は単一の役割を引き受けており、その意味では全体としては凝集性が高くなる、つまりまとまりのある構造になっているはずである。

実は上のような凝集性を、分類的には「論理的凝集(Logical Cohesion)」と呼ぶ。論理的凝集とは、プログラムの構造、あるいは技術的な特徴によってまとまりを作ることであり、論理的凝集の尺度で言えば、レイヤーアーキテクチャの凝集性は高いことになる。

そのように考えると、DHH氏の言う凝集性は論理的凝集ではなく「機能的凝集(Functional Cohesion)」のことを指しているのだと思われる。この論理的凝集と機能的凝集の違いをクリアに説明している素晴らしいコメントが StackExchange にある。

論理的凝集は機能的な特徴よりも技術的な特徴でグルーピングを行うという意味であまり望ましくないと言える。… 例えば、データアクセスを行うモジュールをグルーピングすれば、それは論理的凝集を実現したことになる。… しかし、実際にモジュールの境界を決めるのは技術ドメインではなくビジネスドメインなので、論理的凝集はここで問題となる。論理的凝集を実現することによって、結果的に機能的凝集を失う事になる。

機能的凝集はビジネスドメインの概念によって決まるので、先ほどのレイヤーアーキテクチャの例で言えば、レイヤーを分割する事によって、ビジネスドメイン上の一つのコンセプトが複数のレイヤーの間に引き裂かれて分散してしまうことになる。

cohesion

一つのビジネスオブジェクト上の変更が複数のレイヤーにまたがって影響を及ぼすというケースは、フレームワーク上で開発する多くの人が経験しているのではないだろうか。

Cohesion と Coherence

凝集性は、英語では「cohesion」と呼ばれている。この言葉には以前から違和感を感じていたのだが、cohesion はライティングにおいて「文と文との繋がり具合」を指す言葉である。これは単に文の論理的なつながり、つまり構造的なまとまり表すだけで、意味のまとまりを表すのには別に「coherence」という言葉がある。どちらも「まとまる」という意味の「cohere」から来ているようであるが、それぞれ「構造的なまとまり」と「意味的なまとまり」で指す意味が微妙に違う。

ソフトウェアデザインにおける cohesion も、あくまで構造的なまとまり、つまり論理的凝集を指す言葉とし、機能的凝集については coherence という言葉を当てた方が良いのではないだろうか。というのは、上の話を鑑みても、この二つを言葉(コンセプト)としてはっきりと区別しておくことが重要であるように思われるからである。

Elixir試飲録 (6) – exrm で hot upgrade すると static assets のパスが古いバージョンのまま更新されない問題への対処

前回のElixir試飲録exrm (Elixir Release Manager) を利用した Phoenixアプリケーションのデプロイ自動化について紹介した。この仕組みの上で、手始めに簡単な Web API を開発している間は何の問題もなかったのだが、JavaScript と CSS を使って UI を作り始めるとすぐに表題の不具合に遭遇した。JavaScript (static assets) を更新してデプロイしても、稼働中のアプリケーションに変更が反映されないのである。

同じ問題に遭遇している人はやはりいて、Issue #206 で報告されていた。

Phoenixアプリケーションの config/config.exs を見ると以下のような設定がある。

config :example_app, ExampleApp.Endpoint,
  ...
  root: Path.dirname(__DIR__),
  ...

Issue #206 では、この Path.dirname/1 という関数が、前回紹介した「config内の環境変数参照がリリースパッケージをビルドする際に評価されてしまう問題(Issue #75 · bitwalker/exrm)」と同様、ビルド時に評価されてしまうので assetsへのパスが更新されないのではないか、と説明されている。

なので、config/prod.exs についてだけ、

config :example_app, ExampleApp.Endpoint,
  ...
  root: ".",
  ...

のように変更すれば問題は解決するとのこと。

しかし解せないのは、root のパスがビルド時のもの(しかも絶対パス)になってしまうのであれば、たまチームのようにビルド環境(CircleCI + Docker)と実行環境(AWS EC2)が異なる場合、そもそもこの設定が機能しないように思えるのだが…?

という疑問はさておき、とりあえず上のように修正して再びデプロイすると、無事 assets が更新されていることを確認できた….と思いきや、再び JavaScript を修正してもう一度デプロイを行うと、更新されない… その後は何度やっても更新されなかった。つまり、問題は解決されていない。

同じく Issue #206 に書かれている対策を施しても解決されないという Issue を投稿して放置されている人がいた(友よ)。

この問題がここまで放置されているということは、こちらのやり方がおかしいのか、あるいはそもそもこのツールを使って無停止デプロイをやっている人がほとんどいないのではないかという疑念が湧いてくるが、解決できなければ快速無停止デプロイ生活は終焉を迎えてしまう。

諦めてなるものかと試行錯誤を続けていると、どうやら root: "." の部分は、この問題とは関係がなく、単に :example_app, ExampleApp.Endpoint の設定がデプロイ時に更新されていれば、assetsのパスが正しく更新されることに気付いた。

試しに、以下のようなダミーの設定を追加して、

config :example_app, ExampleApp.Endpoint,
  ...
  version: "1",
  ...

デプロイの度にこのversionの値を更新すると、assetsのパスが正しく更新されることを確認できた。というわけで、今はこの方法で運用している。

具体的には、Conformの設定ファイルに以下のような項目を追加しておき、

example_app.conf

...
Endpoint.version = "default_version"
...

example_app.shema.exs(Conformの変換ルール)

    ...
    "Endpoint.version": [
      commented: false,
      datatype: :binary,
      default: "default_version",
      doc: "Workaround for https://github.com/bitwalker/exrm/issues/253",
      hidden: false,
      to: "example_app.Elixir.ExampleApp.Endpoint.version"
    ],
    ...

upgradeの際に以下のように値を置き換える。

upgrade.sh(このファイルの詳細についてはこちらを参照)

...
cp ${APP_HOME}/app.conf ${DEPLOY_TO}/${VERSION}/${APP}.conf
sed -i -e "s/default_version/${VERSION}/" ${DEPLOY_TO}/${VERSION}/${APP}.conf
...

この現象について Issue の方に報告したところ、開発者の Paul Schoenfelder氏の方でも問題に気付いて頂けたようである。近い将来に修正されるのを楽しみに待ちたい。

さて、以上のように、この問題は「Configに変更がない場合に、リフレッシュされずに古い状態が残ったままになる」という問題のようなので、keichan34氏が最初のIssueに投稿している以下の対策でも機能するのではないかと思う。

Elixir試飲録 (5) – Elixir/Phoenixのホットデプロイ完全自動化(2016年1月版)

いよいよ先月から、たまチームの新規プロジェクトとして Elixir/Phoenix によるWebサービスの開発が始まった。というわけで、今回はたまチームでの事例第一弾として、この半月で構築したホットデプロイ自動化の仕組みについて紹介したい。まだプロジェクトも初期段階なので未熟な部分も多いが、これまで Elixir/Phoenix に触れたことがない人でも、その雰囲気を感じて頂ければ幸いである。

 

自動化の全体像

開発を始めるに当たって、まず最初にやっておきたいのが CIとデプロイの仕組みを整備することである。これまでたまチームは Java系の言語で開発を行っていたのだが、サービスを停止させずにプログラムを更新する方法として Blue-Green Deployment 手法を採用していた。CIのビルドでマシンイメージを作り、デプロイの際はそれらのイメージから新しいサーバーを立ち上げて、ロードバランサーの向き先を旧バージョンのサーバー群から新バージョンに切り替えるという手順だ(「Immutable Infrastructureを導入する」)。この、マシンレベルで切り替える方法であれば、言語や環境に依存せずに無停止デプロイを実現できる。

blue-green

Immutable Infrastructureの恩恵もあり、これまでこの方法で比較的安全に運用出来てはいたのだが、マシンイメージのビルドやデプロイに時間がかかるのが悩みの種であった(ビルドからデプロイまでトータルで20分ぐらいかかる)。また、マシンイメージを使った Blue-Green Deployment を完全に自動化するのも難しかったため、誰でも気軽にデプロイというわけにもいかず、若干の手順を共有する必要があった。仮にUIを微調整するだけで20分かつ若干の手作業となると、そういった微調整的な更新を本番に適用する機会は先延ばしされることが多くなる。そして、そのようにしてデプロイの粒度が大きくなって行くと、プロジェクトは減速してしまう。

Elixir/Phoenix で是非とも試したいと思っていたのが、Hot Code-Swapping によるアプリケーションのホットデプロイ(無停止更新)である。アプリケーション(のラインタイム)にホットデプロイの仕組みが備わっているなら、わざわざビルドの度にマシンイメージを作ってマシンごと入れ替える必要もなくなるし、自動化も遥かにやりやすいだろう。

結論から言えば、半月ほど試行錯誤した結果、ホットデプロイ完全自動化の形はなんとか出来上がった。ビルドからデプロイまでにかかる時間も簡単なPhoenixアプリで5、6分程度にまで削減された。今のところステージング環境だけの運用でトラブル無く更新出来ているが、本番で同じように運用出来るかは開発を進めながら様子を見ないとなんとも言えないところである。

以下が今回の自動化の全貌である。

elixir-phoenix-deploy

Developer Machine(左下)でプログラムを更新して、git push すると、CircleCI上でビルドが行われ、最終的には App Server 上で稼働中のアプリケーションが停止すること無く更新されるところまで、全自動で行われる。

図中の赤文字の部分が今回の自動化を実現しているパーツである。以降のセクションでは、(1) から順番に、これらのパーツを一つ一つ仔細に見て行く。

(※) 今回の環境構築に利用した言語やフレームワークのバージョンは以下の通りである。

  • Elixir 1.2.1 (実行環境はUbuntu用の最新版パッケージが用意されてなかったので 1.2.0)
  • Phoenix 1.1.3
  • exrm 1.0.0-rc7
  • conform 1.0.0-rc8
  • Node.js 5.1.0 (CircleCIで利用出来る最新版)

 

(1) mix.ex – バージョン番号の自動生成と Elixir Release Manager の導入

Elixirには標準で Mix というビルドツールが同梱されている。色んなタスクコマンドが提供されていて、Elixirのコードをコンパイルしたり、依存関係を自動で取得してくれたりと、Java の Maven や、Node.js の npm に相当するツールである。プロジェクトをビルドするためには、Maven の POMファイル、あるいは npm の package.json のようなプロジェクト定義ファイルが必要となる。それが以下の mix.exs ファイルだ。


defmodule ExampleApp.Mixfile do
use Mix.Project
def project do
[
app: :example_app,
version: version(),
elixir: "~> 1.2.0",
elixirc_paths: elixirc_paths(Mix.env),
compilers: [:phoenix, :gettext] ++ Mix.compilers,
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
aliases: aliases,
deps: deps
]
end
defp version do
{{year, month, day}, {hour, minute, _}} = :calendar.local_time()
version = :io_lib.format("~4..0B.~2..0B~2..0B.~2..0B~2..0B", [year, month, day, hour, minute])
|> List.flatten
|> to_string
File.write! "VERSION", version
version
end
def application do
[
mod: {ExampleApp, []},
applications: [
:phoenix,
:phoenix_html,
:cowboy,
:logger,
:gettext,
:phoenix_ecto,
:postgrex,
:conform,
:conform_exrm
]
]
end
defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
defp elixirc_paths(_), do: ["lib", "web"]
defp deps do
[
{:phoenix, "~> 1.1.3"},
{:phoenix_ecto, "~> 2.0"},
{:postgrex, "0.10.0"},
{:phoenix_html, "~> 2.3"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.9"},
{:cowboy, "~> 1.0"},
{:exrm, "~> 1.0.0-rc7", override: true},
{:conform, "~> 1.0.0-rc8", override: true},
{:conform_exrm, "~> 0.2.0"}
]
end
defp aliases do
["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"]]
end
end

view raw

mix.ex

hosted with ❤ by GitHub

mix.exs で定義されている内容は他言語の場合とほとんど変わりない。ここでは今回の自動化に関連する箇所に絞って説明して行く。

バージョン番号の自動生成

7行目で、このプロジェクト(:example_app という名前が付いている)のバージョンを定義しているのだが、通常 0.0.1 のように固定のバージョン番号を書くところを、自前のversionという関数(18行目)を指定して、ビルド時点でのタイムスタンプからバージョン番号を自動生成するようにしている。git pushする度にデプロイ可能な新しいパッケージをビルドするので、その都度手動でバージョン番号を書き換えるのはめんどうだし、ライブラリと違ってアプリのバージョンを人力で決定する意義は少ないように思う。

Elixirアプリのバージョン番号は、Semantic Versioning Specification (SemVer) に従わなければならないので、version 関数を見ると、タイムスタンプを無理矢理その形式に変換しているのが分かるだろう。このやり方で本当に問題ないか、いささか心許ないところもあるのだが、今のところは問題なく機能しているようだ。

バージョン番号を生成する時に、その番号を VERSION というファイルに書き出している(23行目)。このファイルはビルド用のシェルなどがバージョン番号を参照する時などに利用する。

Elixir Release Manager

47行目の deps 関数でプロジェクトが利用する外部モジュール(依存関係)を定義する。ここに指定されている :exrm が、今回のホットデプロイの主役を務める Elixir Release Manager だ。exrm を依存関係に追加して mix do deps.get, compile すれば、リリース用のパッケージをビルドするためのタスク mix release が利用出来るようになる。

$ mix help | grep release
mix release             # Build a release for the current mix application
mix release.clean       # Clean up any release-related files
mix release.plugins     # View information about active release plugins

実は、今回の自動化環境を作る際の苦労の多くは、この exrm に起因している。現時点ではまだ version 1.0 がリリースされてないので、成熟してないが故の制限がいくつかあった:

  • プロジェクトの依存関係を漏れなく application 関数にも指定しておかなければならない
    • exrm – Common Issues – https://hexdocs.pm/exrm/extra-common-issues.html
    • application に指定しておかないと、リリースパッケージをビルドする時に依存モジュールを知る術が無いらしい。しかし、既に書いたように deps にも依存関係を定義しているので、これはDRY原則に反する。
    • しかも困ったことに、推移的な依存についても application に指定しなければならないケースがある(依存先のモジュールの application に指定されてない場合)
  • configの内容を動的に決定することができない
    • 今回最も悩まされた問題がこれである。通常、Elixirでは設定ファイルの中で、System.get_env("HOGEHOGE") のような形で関数を使い、環境変数を参照することが出来る。ところが、exrmでビルドされたパッケージだとこれが実行時に評価されず、ビルド時に評価されてしまうという問題がある。この問題の対策については以降のセクションで説明する。

参考文献

Conform – 設定ファイルのテキストファイル化

deps 関数には、:conform:conform_exrm という依存モジュールが追加されている(57, 58行目)。この Conform は、exrm と同じく Paul Schoenfelder 氏によって開発されている Elixir のコンフィグレーションを支援するためのツールだ。

基本的にElixirプロジェクトの設定情報は config/config.exs というファイルに書くことになっている。拡張子から分かるように設定内容は Elixir のコードとして表現される。以下は config.exs の簡単なサンプルだ。


use Mix.Config
config :example_app,
key1: "value1",
key2: "value2"
import_config "#{Mix.env}.exs"

view raw

config.exs

hosted with ❤ by GitHub

最後の import_config で環境ごとの設定ファイルを読み込んでいる。具体的には、開発環境用の dev.exe と自動テスト実行時用の test.exs、そして本番用の prod.exs が、それぞれ config ディレクトリの中に用意されている。

設定内容をElixirコードで表現出来るので、単なる値だけではなく式などを指定することも出来る。Elixirのコーディングに慣れているプログラマーにとってはとても便利な仕組みであるが、 Elixirに慣れていないオペレーターがコンフィグレーションを行う場合、あまり理解しやすい形式とは言えないかもしれない。この問題に対処するためのツールが Conform である。

Conformには、以下の二つの機能がある。

  1. Elixirの設定ファイルを Key-Value 形式のシンプルなテキストファイルに変換する(その際、変換ルールを定義したスキーマファイルも同時に生成される)
  2. Key-Value 形式の設定ファイルをアプリケーション起動時に読み込んで Erlang の設定に変換する

:conformdeps に追加して、mix do deps.get, compile すれば、mixに以下のような関連タスクが追加される。

$ mix help | grep conform
mix conform.configure   # Create a .conf file from schema and project config
mix conform.effective   # Print the effective configuration for the current project
mix conform.new         # Create a new .schema.exs file for configuring your app with conform

 

Conformを利用して、アプリケーション起動時(あるいはアップグレード時)に設定を適用する

さて、今回のプロジェクトで Conform を採用したのは Key-Value 形式の設定ファイルが欲しいからではなく、exrm のセクションで言及した、設定内容を実行時に決定出来ない(環境変数を利用出来ない)問題に対処するためである。

exrm の作るリリース用パッケージというのは、結局のところ、Erlang用のパッケージである。リリースやデプロイの仕組みが Erlang によって提供されているので、当然と言えば当然のことなのだが、このパッケージをビルドする際に、Elixirの設定ファイルをErlang用の設定ファイル(sys.config)に変換する必要があり、このときにElixirの設定ファイルが評価されてしまうのである。

設定内容を環境変数で与えたいと思っても、それがビルド時に評価されてしまうため、本番デプロイ時に設定することができない。これはかなり重大な問題である。ビルド時に評価されるとなると、データベースのパスワードのような機密情報をElixirの設定ファイルに直接書かなければならず、しかもビルド対象とするためにそれを GitHub 上に上げなければならなくなる。

上記リンクの三番目、Issue #90 では exrmへの機能追加として、環境変数経由で設定出来るような仕組みを提供するという予告がなされているが、そこから一年が経過して、この Issue はまだ Open のままである。

Erlang用の設定ファイル(sys.config)の中では、Elixirのように関数などを利用することは出来ず、基本的に静的な値しか保持出来ない。ところが、Issue #90 の中で RELX_REPLACE_OS_VARS という環境変数を設定すれば、sys.config の中でも環境変数を参照出来るようになるよ、という情報を見つけ、これだ! と思って試してみると、確かにアプリケーションを起動した時には環境変数を参照出来ているが、ホットデプロイの時には参照出来ない…

最終手段として、デプロイ時にリリースパッケージ内にある sys.config を直接編集してしまえば良いのだが、そのままでデプロイできるパッケージ(*.tar.gz形式のアーカイブ)を展開して処理するのには抵抗があった。色々と調べ回って Conform の設定ファイルはリリースパッケージと同じディレクトリに置いておけば起動時に適用されることが分かり、最終的にこの Conform を利用する方法に辿り着いた。

コンフィグレーションの経路を図示すると以下のような感じになる。

elixir-config

設定内容は、アプリケーションサーバーを立ち上げる時に Cloud-init 経由で渡すようにし、新しいバージョンのリリースをデプロイする度にその設定を適用する。設定内容を変更したい場合は、直接 app.conf を編集するか、cloud-boothook.sh を変更してアプリケーションサーバーを作り直す(基本的に後者の方が好ましい)。cloud-boothook.sh は、Terraform の構成ファイルと共に同じ GitHub のリポジトリで管理されているが、リポジトリ内では暗号化されている。アプリケーションサーバーの更新をする際に、作業マシン上で復号化して Cloud-init に渡す、という流れである。

実際には以下のような内容のファイルを暗号化してリポジトリに入れている。


#cloud-boothook
#!/bin/sh
# Application config
cat <<EOF > /opt/example_app/app.conf
logger.level = info
Endpoint.url.host = "example.com"
Endpoint.url.port = 443
Endpoint.secret_key_base = "rRy7Q2mUhmcgYPJfN06DiQKaUM99CDvnQGVyP4b2Zm47bHalfjZbkCe/l2oHGzmC"
Repo.username = "dbuser"
Repo.password = "dbpass"
Repo.database = "exampledb"
Repo.hostname = "database-host.com"
Repo.pool_size = 20
EOF

 

(2) circle.yml – ビルド全体の流れ

ビルド・デプロイ自動化の中心にあるのが CircleCI というCIサービスで、そこでのタスクを定義するのが circle.yml というファイルである。この circle.yml の内容を見れば、今回の自動化の全体的な流れが分かる。


machine:
timezone:
Asia/Tokyo
services:
docker
node:
version: 5.1.0
dependencies:
cache_directories:
~/docker
override:
cp ~/.ssh/id_circleci_github ci/docker/github.pem
if [[ -e ~/docker/image.tar ]]; then docker load -i ~/docker/image.tar; else cd ci/docker && docker build -t trusty .; fi
if [[ ! -e ~/docker/image.tar ]]; then mkdir -p ~/docker; docker save trusty > ~/docker/image.tar; fi
cd deploy && bundle install
test:
override:
docker run -v `pwd`:/build -v /etc/localtime:/etc/localtime:ro -i -t trusty /bin/sh -c 'service postgresql start && cd /build && ./ci/test.sh'
deployment:
master:
branch: master
commands:
./ci/deploy-to-staging.sh # ensure that the latest version is running in staging
./ci/get-prev-rel.sh
docker run -v `pwd`:/build -v /etc/localtime:/etc/localtime:ro -i -t trusty /bin/sh -c 'cd /build && ./ci/build-release.sh'
|
PACKAGE="./rel/example_app/releases/`cat VERSION`/example_app.tar.gz"
DEST="s3://example-app-packages"
aws s3 cp "${PACKAGE}" "${DEST}/`cat VERSION`.tar.gz"
aws s3 cp "${PACKAGE}" "${DEST}/latest.tar.gz"
aws s3 cp ./VERSION "${DEST}/VERSION"
./ci/deploy-to-staging.sh

view raw

circle.yml

hosted with ❤ by GitHub

 

(3) Dockerfile – ビルド環境と本番環境を合わせる

exrm が生成するリリースパッケージは、Erlangのランタイムを含む完全な All-In-One パッケージである。つまり、このパッケージファイルさえあれば、Erlang や Elixir の環境を用意しなくてもそのままアプリケーションを動かすことが出来る。これは環境構築の手間を省けて便利なのだが、一方でマルチプラットフォームには対応できない。例えば、MacでビルドしたものがLinuxで動かないのはもちろんだが、同じLinuxであってもディストリビューションやバージョンが異なると動かない可能性がある。試しに、CircleCIの環境(Ubuntu 12.04 (precise))でビルドしたものを Amazon Linux 上で動くかどうかテストしてみたが OpenSSLバージョンの違いが原因で動かなかった。

というわけで、exrm のリリースパッケージをビルドする環境は、アプリケーションが稼働する環境と合わせる必要がある。ビルドはCircleCIに任せたいが、本番環境をCircleCIに依存させるのは嫌なので、CircleCI上で本番環境に近いDockerコンテナを立ち上げ、その中でビルドを行うことにした。

今のところ、本番環境には Ubuntu 14.04 (trusty) を利用している。その本番向けのビルドを行うための Dockerfile が以下である。


FROM ubuntu:trusty
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update -q
RUN apt-get -y install language-pack-ja openssl libssl-dev ncurses-dev curl git
RUN update-locale LANG=ja_JP.UTF-8
# SSH
RUN mkdir -p /root/.ssh
RUN echo "Host github.com\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config
ADD github.pem /root/.ssh/id_rsa
RUN chmod 700 /root/.ssh/id_rsa
# Node.js
RUN apt-get -y install rlwrap && \
curl -o /tmp/nodejs.deb https://deb.nodesource.com/node_5.x/pool/main/n/nodejs/nodejs_5.5.0-1nodesource1~trusty1_amd64.deb && \
dpkg -i /tmp/nodejs.deb && \
rm -rf /tmp/nodejs.deb
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
# Erlang and Elixir
RUN curl -o /tmp/erlang.deb http://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && \
dpkg -i /tmp/erlang.deb && \
rm -rf /tmp/erlang.deb && \
apt-get update -q && \
apt-get install -y erlang-base=1:18.2 erlang-dev=1:18.2 erlang-eunit=1:18.2 erlang-parsetools=1:18.2 erlang-xmerl=1:18.2 elixir=1.2.0-1 && \
apt-get clean -y && \
rm -rf /var/cache/apt/*
RUN mix local.hex –force && mix local.rebar –force
# PostgreSQL
RUN apt-key adv –keyserver keyserver.ubuntu.com –recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list
RUN apt-get update && apt-get -y -q install python-software-properties software-properties-common \
&& apt-get -y -q install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
# Alter postgres user for test
USER postgres
RUN /etc/init.d/postgresql start \
&& psql –command "ALTER USER postgres WITH SUPERUSER PASSWORD 'postgres';"
USER root

view raw

Dockerfile

hosted with ❤ by GitHub

Ubuntu 14.04 のイメージをベースに、Erlang と Elixir、自動テストの際に必要となる PostgreSQL をインストールしている。9行目から13行目でSSHのキーをインポートしているが、これは開発中のプロジェクトが GitHub のプライベートリポジトリにあるモジュールを参照しているためで、そのような依存関係がなければ本来必要ない部分である。

このDockerコンテナをどのように利用するかは circle.yml の方を参照して欲しい。ビルドの度に Dockerfile からイメージをビルドしていたら時間がかかってしまうので、ビルドしたイメージを ~/docker ディレクトリにキャッシュして、二度目以降はそちらを利用するようにしている(11行目、14, 15行目)。

テストを実行する時のコマンドは以下のような感じである(20行目):

docker run -v `pwd`:/build -v /etc/localtime:/etc/localtime:ro -i -t trusty /bin/sh -c 'service postgresql start && cd /build && ./ci/test.sh'

CircleCI側のプロジェクトディレクトリをDockerコンテナ内の /build にマウントして、service postgresql start でデータベースを起動しておき、./ci/test.sh でテストを実行する。/etc/localtime:/etc/localtime:ro でコンテナのタイムゾーンをホスト側(Asia/Tokyo)と合わせる。これでビルド時のタイムスタンプが日本時間になる。

リリースパッケージをビルドする時のコマンドは以下(28行目):

docker run -v `pwd`:/build -v /etc/localtime:/etc/localtime:ro -i -t trusty /bin/sh -c 'cd /build && ./ci/build-release.sh'

 

(4) test.sh – 自動テストの実行

Dockerコンテナ内で実行される自動テストのシェルは以下のような簡単なものだ。


#!/bin/bash
export MIX_ENV="test"
mix do deps.get, deps.compile, compile, test

view raw

test.sh

hosted with ❤ by GitHub

 

(5) get-prev-rel.sh – 直前のバージョンを取得する

exrm のタスク、mix release を実行すると、リリースパッケージがビルドされて rel ディレクトリ以下に保存される。このとき、rel ディレクトリに直前のバージョンがあると、そこからアップグレードを行うためのファイル(relup)も一緒に生成される。常に同じマシンでリリースパッケージのビルドを行っているのであれば問題なくアップグレードファイルが出来上がるが、CIのように、毎回リポジトリからコードを持ってきてビルドする方式だと、当然以前のリリースパッケージが含まれないのでアップグレードファイルが出来ない。これに対処するため、リリースパッケージのビルド前にパッケージリポジトリ(S3)から直前のバージョンを取得して、rel ディレクトリ以下に展開している。


#!/bin/bash
export APP="example_app"
aws s3 cp s3://example-app-packages/latest.tar.gz /tmp/${APP}.tar.gz
mkdir -p rel/${APP} && rm -rf rel/${APP}/*
tar zxvf /tmp/${APP}.tar.gz -C rel/${APP}

view raw

get-prev-rel.sh

hosted with ❤ by GitHub

 

(6) build-release.sh – リリースパッケージをビルドする

rel ディレクトリに前バージョンのパッケージを用意したら、リリースパッケージのビルドを行う。


#!/bin/bash
set -ex
mix do deps.get, deps.compile, compile
node -v
npm install
export MIX_ENV="prod"
mkdir -p priv/static
node node_modules/brunch/bin/brunch build
mix do phoenix.digest, compile, release –verbosity=verbose

Production用のパッケージをビルドするため、brunchによるアセットファイルのビルドも合わせて行っておく必要がある。

リリースパッケージは以下の場所に出来上がる。

rel/example_app/releases/(VERSION)/example_app.tar.gz

このファイルと、同時に出来上がった VERSION ファイルをパッケージリポジトリにアップロードする(circle.yml – 30行目〜34行目):

PACKAGE="./rel/example_app/releases/`cat VERSION`/example_app.tar.gz" 
DEST="s3://example-app-packages"
aws s3 cp "${PACKAGE}" "${DEST}/`cat VERSION`.tar.gz"
aws s3 cp "${PACKAGE}" "${DEST}/latest.tar.gz"
aws s3 cp ./VERSION "${DEST}/VERSION"

これでめでたくリリースの完了である。

CircleCI用 IAM User の準備

パッケージリリースの処理にもあるように、ビルドの際、CircleCIからAWS上の各種リソースにアクセスする必要がある。そのために必要な最低限の権限を付与した IAM User を作成し、CircleCIのプロジェクトに設定しておく。

以下は今回のプロジェクト用に作成した、リリース・デプロイ担当の IAM User に付与したポリシー(Inline Policies)の例である:


{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::example-app-packages/*"
]
},
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:revokeSecurityGroupIngress"
],
"Resource": "*"
}
]
}

パッケージリポジトリであるS3へのアクセスと、デプロイ時に必要になるEC2に対する権限を設定している。

 

(7) deploy-to-staging.sh – リリースパッケージをアプリケーションサーバーにホットデプロイする

パッケージのリリースが完了したら、いよいよホットデプロイを行う。

デプロイには Capistrano という自動化ツールを使い、対象となるアプリケーションサーバーにSSHでログインして、アップグレード処理が書かれたシェルを実行する。

CircleCIからEC2インスタンスへのSSHアクセスを一時的に許可する

アプリケーションサーバーにSSHするためには、CircleCI側からアクセス出来るようにセキュリティグループ(ファイアウォール)の設定をしておかなければならない。CircleCIに対するアクセス許可を永続的に行うのには抵抗があるし、そもそもCircleCI側のIPは常に変わる。

そこで、ビルドの度にCircleCIコンテナのIPアドレスを調べて、そのIPアドレスに対してデプロイの時だけ一時的にSSHアクセスを許可するようにする。


#!/bin/bash
set -ex
export AWS_DEFAULT_REGION="ap-northeast-1"
SGID="sg-xxxxxxxx"
MYIP="`dig +short myip.opendns.com @resolver1.opendns.com`"
cd $(dirname $(readlink -f $0))
trap "aws ec2 revoke-security-group-ingress –group-id ${SGID} –protocol tcp –port 22 –cidr ${MYIP}/32" 0 1 2 3 15
aws ec2 authorize-security-group-ingress –group-id ${SGID} –protocol tcp –port 22 –cidr ${MYIP}/32
cd ../deploy && ./deploy-to-staging.sh

自身のIPアドレスを調べる方法として、curl -s ifconfig.me というやり方がよく見られるが、これがかなり遅い上に失敗することも度々あるので DNS を利用したやり方にしてある(8行目)。

aws ec2 authorize-security-group-ingress でSSH接続を許可して(13行目)、デプロイ後、aws ec2 revoke-security-group-ingress で許可を取り消すのだが(12行目)、許可取り消しを確実に実施するために trap コマンドを利用する方法はこちらを参考にさせて頂いた(感謝)。

Capistranoでデプロイを実施する

SSH接続の許可を行った後にまた別の deploy-to-staging.sh を実行しているが(14行目)、このシェルは以下のように単に Capistrano のタスクを実行しているだけである。

#!/bin/sh
bundle exec cap staging deploy:main $1

肝心の Capistranoタスクの内容(deploy/config/deploy.rb)は以下の通り、


require 'aws-sdk'
lock '3.4.0'
set :application, 'example_app'
set :pty, true
set :user, "ubuntu"
set :use_sudo, true
set :ssh_key, "~/.ssh/example_app.pem"
set :role_name, 'example_app'
set :env_name, 'staging'
REGION = 'ap-northeast-1'
Aws.config.update({
region: REGION
})
namespace :deploy do
task :main do
invoke "deploy:set_target_instances"
invoke "deploy:upgrade_to_latest"
end
task :set_target_instances do
count = 0
ec2 = Aws::EC2::Client.new
ec2.describe_instances(
filters:[
{ name: "tag:Environment", values: [fetch(:env_name)] },
{ name: "tag:Role", values: [fetch(:role_name)] },
{ name: 'instance-state-name', values: ['running'] }
]
).reservations.each {|r|
r.instances.each {|i|
puts "#{fetch(:role_name)}(#{count += 1}): #{i.public_dns_name}"
server i.public_dns_name, roles: %w(app), user: fetch(:user), ssh_options: {
keys: [File.expand_path(fetch(:ssh_key))]
}
}
}
end
task :upgrade_to_latest do
on roles(:app) do |host|
upload! "upgrade.sh", "/tmp/upgrade.sh"
execute "chmod +x /tmp/upgrade.sh && /tmp/upgrade.sh"
end
end
end

view raw

deploy.rb

hosted with ❤ by GitHub

処理の流れは、EC2インスタンスに付けたタグ Environment, Role の値と、稼働中であることを条件にしてインスタンスを検索し、それらのインスタンスにアップグレード用のシェル upgrade.sh をアップロードして実行するというものである。

開発用マシンからもデプロイが実施出来るように10行目でSSHキーの場所を指定しているが、CircleCIの場合は SSH Permission にキーを設定しておく必要がある。

 

(8) upgrade.sh – デプロイ処理の実際

Capistranoによって実行される upgrade.sh が実際のデプロイ(アップグレード)処理を行う。


#!/bin/bash
export AWS_DEFAULT_REGION="ap-northeast-1"
APP="example_app"
APP_HOME="/opt/${APP}"
DEPLOY_TO="${APP_HOME}/releases"
S3_FOLDER="s3://example-app-packages"
# Get the specified or latest version number
aws s3 cp ${S3_FOLDER}/VERSION /tmp/VERSION
VERSION=${1:-"`cat /tmp/VERSION`"}
echo "Trying to deploy ${APP} ${VERSION}"
# Deploy
if [ ! -d "${DEPLOY_TO}/${VERSION}" ]; then
mkdir -p ${DEPLOY_TO}/${VERSION}
aws s3 cp ${S3_FOLDER}/${VERSION}.tar.gz ${DEPLOY_TO}/${VERSION}/${APP}.tar.gz
cp ${APP_HOME}/app.conf ${DEPLOY_TO}/${VERSION}/${APP}.conf # Apply config
else
echo "${APP} ${VERSION} already exists."
fi
# Upgrade
if [ ! "${VERSION}" = "`curl -s http://localhost:8080/api/public/version`" ]; then
# Upgrade
${APP_HOME}/bin/${APP} upgrade ${VERSION}
# Set the current and prev versions
if [ -e "${DEPLOY_TO}/current" ]; then
mv ${DEPLOY_TO}/current ${DEPLOY_TO}/prev
fi
echo $VERSION > ${DEPLOY_TO}/current
else
echo "${APP} ${VERSION} is already running."
fi

view raw

upgrade.sh

hosted with ❤ by GitHub

デプロイ対象のバージョンは、デフォルトでパッケージリポジトリの VERSION ファイル(つまり最新版)を参照するが、シェルへの引数として指定することも出来る(12行目)。ただ、今のところは指定出来るというだけで任意のバージョンをデプロイ出来るわけではない。

20行目で、前述のConformについてのセクションで説明したアプリケーションの設定ファイル ${APP_HOME}/app.conf を、デプロイ対象のパッケージを置いた(19行目)ディレクトリにコピーしている。これでデプロイ時に本番用の設定を適用することが出来る。

稼働中のアプリケーションのバージョンを予め用意したAPI経由で調べて(26行目)、デプロイ対象のバージョンと同じでなければ、デプロイ処理を実行する(28行目)。現段階で対応しているのはアップグレード処理だけだが、同じ要領でダウングレードも実現出来るはずだ。

以上が、git push した後に、CircleCI上で行われる処理の全貌である。今のところ、たまチームの環境では、これら全ての処理が完了するまでにかかる時間はおよそ5、6分程度である。これからプロジェクトのサイズが大きくなるに従ってこの時間は長くなると思うが、それでも以前の環境とは比較にならないぐらい快適なデプロイ環境を実現することが出来たと思う。今回の改善の多くは、Elixir/Phoenixのホットデプロイ機能に負うところが大きいが、このホットデプロイがどこまで安定して運用出来るかはまだ未知数である。今回実現した完全自動化は今のところステージング環境で運用しているが、本番で利用する際には、安全のため、アップグレードのテストを行うためのリハーサル機を用意しようと考えている。

 

アプリケーションサーバーの構成管理

これまでのセクションでCircleCI上の自動化については一通り見てきたが、デプロイ対象となるアプリケーションサーバー内の構成がどうなっているかについては触れてこなかった。デプロイを自動化するためには、当然のことながら、アプリケーションサーバー側の設定も必要になってくる。というわけで、このセクションではその辺の設定について紹介して今回の記事の締めくくりとしたい。

今回のプロジェクトでは、アプリケーション用のプロジェクト(app)とは別に、アプリケーションサーバーのマシンイメージを作るためのプロジェクト(app-image)を別途 GitHub で管理している。このプロジェクトには、サーバープロビジョニング用の Ansible コードや、プロビジョニングの結果をテストするための serverspec コードが含まれており、git push されると、CircleCI上でそれらを実行してマシンイメージ(AMI)を作るようになっている。

既に書いたように、exrm によるリリースパッケージはランタイムを含む All-in-One パッケージなので、それらの環境を予め用意しておく必要はなく、プロビジョニングが比較的楽に行える。

アプリケーションサーバーのAnsibleタスクは以下のようになっている。

app-image/ansible/roles/app/tasks/main.yml


name: Download package
command: aws s3 cp "{{ package_url }}" /tmp/app.tar.gz
environment:
AWS_ACCESS_KEY_ID: "{{ lookup('env','AWS_ACCESS_KEY_ID') }}"
AWS_SECRET_ACCESS_KEY: "{{ lookup('env','AWS_SECRET_ACCESS_KEY') }}"
name: Make app directory
file: state=directory path={{ item }}
with_items:
"{{ app_home }}"
"{{ app_home }}/log"
name: Install app
unarchive: src=/tmp/app.tar.gz dest={{ app_home }}
name: Put app config
template: src=app.conf dest={{ app_home }}/
name: Ensure app directory belongs to ubuntu
file: "state=directory path={{ app_home }} owner=ubuntu group=ubuntu recurse=true"
name: Symlink log directory
file: src={{ app_home }}/log path=/var/log/example_app state=link
name: Put upstart config
template: src=example_app.conf dest=/etc/init/
name: Start and enable services
service: name={{ item }} state=started enabled=yes
with_items:
example_app
nginx
name: Wait for app to start
wait_for: port={{ app_port }} delay=10
name: Ensure app is running
uri:
url: http://localhost:{{ app_port }}/api/public/version
status_code: 200
timeout: 600

view raw

main.yml

hosted with ❤ by GitHub

パッケージリポジトリからリリースパッケージを取ってきてインストール、設定ファイルやログ関連の準備をして、サービスを起動するだけである。

マシンイメージのデフォルト設定

17行目のタスクで、アプリケーションの設定ファイル app.conf を配置している。「Conformを利用して、アプリケーション起動時に設定を適用する」で説明した通り、この設定ファイルは、マシンイメージからサーバーインスタンスを起動する時に Cloud-init 経由で実際の値に書き換えられるため、マシンイメージに含めるファイルの内容は、以下のようなデフォルトの設定にしてある。

app-image/ansible/roles/app/templates/app.conf

logger.level = info

Endpoint.url.host = "example.com"
Endpoint.url.port = 443

Endpoint.secret_key_base = "default_secret_key_base"

Repo.username = "postgres_user"
Repo.password = "postgres_pass"
Repo.database = "gyron_connect_dev"
Repo.hostname = "localhost"
Repo.pool_size = 20

実際の設定は Cloud-init で行われるとは言え、マシンイメージにあるデフォルトの設定でもアプリケーションを起動出来るようにしておきたい。そうしておくことで、マシンイメージの動作確認やテストがやりやすくなる。

アプリケーションを Upstart に登録する

マシンイメージから新しいサーバーを立ち上げるとき、あるいは何らかの理由でサーバーが再起動された場合に、アプリケーションも一緒に起動するようにしておきたい。そのためにはアプリケーションをサービスとして登録しておく必要があるが、Ubuntu 14.04 には Upstart という仕組みがあるのでそれを利用する。

上のAnsibleタスクでは、27行目でUpstart用の設定ファイル(/etc/init/example_app.conf)を配置している。

app-image/ansible/roles/app/templates/example_app.conf


description "Example App"
setuid ubuntu
setgid ubuntu
start on runlevel [2345]
stop on runlevel [016]
expect stop
respawn
env MIX_ENV=prod
export MIX_ENV
env PORT=8080
export PORT
env HOME={{ app_home }}
export HOME
pre-start exec /bin/sh {{ app_home }}/bin/example_app start
post-stop exec /bin/sh {{ app_home }}/bin/example_app stop

関数型つまみ食い: モナドが難しいと思われている理由

(「モナドについて書かれたものを読む度に自分で書いてしまいたくなる。同じ過ちを繰り返すだけだから止めた方がいいんだけど。」という話を受けて、)

世の中に溢れる粗悪なモナド入門のせいで Philip Wadler氏の論文が埋もれているからモナドが難しいと思われるんだ。

ほほう、その論文を読めばモナドとやらを簡単に理解出来るのかな、と思いつつ読んでみると、

全然簡単じゃないんですけど…

でも、この論文でモナドの意義と基本的な仕組みはぼんやりと見えたような気もしなくはない。その理解をここに簡単にまとめておくことで、同じ過ちを繰り返してみることにしよう。

まずモナドを理解するためには、関数型プログラミングにおける、純粋な関数とそうでないものの区別をきちんとさせておく必要がある。

純粋な関数とは何か?

そもそも関数とは何か? それは、入力を出力に「変換」するものである。

関数: 入力 =(変換)=> 出力

至ってシンプルである。関数型プログラミングでは、この関数を組み合わせて、変換の連鎖としてアプリケーションを表現する。

さて、この関数が以下の二つの条件を満たすとき、その関数を「純粋」な関数と呼ぶ。

  1. 同じ入力に対しては常に同じ出力を返すこと
    • 例えば 1 を渡したら 2 が返ってくる関数があったとして、どのようなタイミングや事前条件で関数を実行しても 1 => 2 という同じ結果になること、そしてそれが他の入力パターンについても同様に成り立つこと。
  2. 関数は入力を出力に変換するものだと説明したが、その変換以外のことが一切起こらないこと
    • この変換以外の出来事を「副作用」と呼ぶ。

純粋な関数のメリットと痛み

Elixir試飲録 (4) – オブジェクト指向と関数型の違い」で触れたように、オブジェクト指向ではシステムの構成要素であるオブジェクトの「インターフェース」にフォーカスすることによって、システムの表現を単純化し、さらには疎結合による柔軟性を獲得していた。しかし一方で、インターフェースに現れない内部的なデータの流れは隠蔽されるため、インターフェースだけでシステムの(厳密な)状態遷移を把握することは困難になる。

object

少ない記述で多くを実現しようとするオブジェクト指向に対して、関数型、特に純粋関数型では、原理的に関数(入出力の変換)として書かれたことしか起こらないので、不測の事態が起こりづらく、より信頼性の高いシステムを構築しやすいと言える。しかし、逆に言えば、全てのデータの流れを入出力の変換として明示的に表現しなければならないので、時にはコードが冗長になってしまうケースがあるだろう。

function

この関数型の欠点とも呼べる冗長さを軽減するために導入されたのが「モナド」と呼ばれるコンセプトである。

モナド概念の導入

「関数型の欠点とも呼べる冗長さ」とは具体的にどのようなものだろうか? 以下のような例で考えてみる。

ここに「倍返し」という関数がある。名前の通り、受け取った数値を倍にして返す関数である。

倍返し: 数値 =(変換)=> 数値

この関数に 1 を渡すと 2 が返り、2 を渡すと 4 が返る。

この関数が「受け取った数値を倍にする」ことだけに専念しているとき、この関数は純粋であると言える。

しかし、世知辛い現実世界では、単に「倍返し」の純潔を守っているだけでは生き残って行けない。色んな要求が舞い込んでくる。例えば、倍返しに失敗したらどうしてくれるんだ?、だったり、倍返ししたことをちゃんと記録しておけよ、等々。そこで以下のような要求があった場合、どのように対応すれば良いだろうか?

  1. エラーが発生したら、それをきちんと知らせて欲しい
  2. 何回「倍返し」したのか記録しておいて欲しい
  3. 倍返しする度にログを吐いて欲しい

もし、関数が純粋でなくても良いなら、話は簡単である。エラー処理については、出力以外にも「例外」という経路を設けて、そこからエラーを知らせればよい。実際に多くのプログラミング言語ではこの方式を採用している。倍返し回数の記録についてはグローバルな変数を用意してそれをカウントアップして行けばよいし、ログについては深く考えることなく、普通にログを吐けばいい。

しかし、関数を純粋に保ったまま、つまり純粋な関数のメリットを維持したまま、このような機能を実現するためにはどうすればよいだろうか? 単純に考えると、出力にそのような情報を追加するしかない。

倍返し1: 数値 =(変換)=> 数値, エラー
倍返し2: 数値 =(変換)=> 数値, 回数
倍返し3: 数値 =(変換)=> 数値, ログ

エラーを投げたり、回数を記録したり、ログを吐いたりするのは「副作用」に当たるので、関数の純粋性を守るためには、これらの処理は「予定」として、関数の出力に含めて表現することになる。

これは明確さという観点から言うととても分かりやすい。関数の定義を見ればどのようなことが起こるのかが一目瞭然だからである。しかし一方で、関数の定義に、本来の処理「倍返し」以外の情報が紛れ込んでくるし、要求が増える度にいちいちこのような関数のバリエーションを増やして行かなければならないのだろうか?

そこで、このような「入出力に紛れ込む副作用」を本来の処理から隠蔽して冗長さを軽減するために導入されたのが、いわゆる「モナド」である。

「倍返し」の例を一般化すると、

倍返し: 数値 =(変換)=> 数値, おまけ

となるが、この関数に、どのような「おまけ」があるにしろ、そのことを意識せずに一番純潔な

倍返し: 数値 =(変換)=> 数値

として扱えれば便利である。そのために必要となるのが以下の仕組みである。

  1. まず前提としておまけ付きの出力: [本来の出力, おまけ]
  2. 本来の出力をおまけ付きに変換してくれる仕組み: 出力値 => [本来の出力, おまけ]
  3. おまけ付きの値を、そのままおまけ無しの値として関数に渡せる仕組み: [本来の入力, おまけ] => 本来の入力

この三つの組み合わせを「モナド」と呼ぶ。これらの道具立てがあれば、関数本来の計算がおまけに埋没することなく、かつ関数の純粋さを保つことができるというわけである。


[2017/05/18追記]

久しぶりにこの記事を読んでみて、もう少し説明を加えればモナドに対する理解がよりクリアになるような気がしたので、試しに書き加えてみる。

モナド三兄弟

関数型プログラミング(特に純粋関数型)は、システムの状態遷移を余すところなく関数の入出力として表現することで、システムの動きを明確にしようとする。全てを入出力として表現するので、ビジネスロジックにとって重要な「本来の入出力」以外の「おまけの入出力」がどうしても必要になってくる。

つまり、関数と関数の間を受け渡されていくデータには、常に何らかのおまけが含まれている可能性があるということだ。そのおまけは、例えば、処理に失敗した際のエラー情報だったり、値が存在しないかもしれないという可能性だったり、あるいは入出力には現れない文脈的なデータだったりする。このおまけの応用範囲がとても広いというのが、モナドのつかみどころのなさの一因になっていることは間違いなさそうだ。

しかし、関数の方はこのおまけを解釈するようにはできていないかもしれない、あるいはむしろ、特定のおまけを想定して関数を書いてしまうと、その関数の汎用性が大きく損なわれてしまうので、できれば本来必要な計算だけに集中したい。

そこで、おまけを想定しない関数を、おまけ付きの入出力を持つように変換するアダプター的な仕組みがあれば、本来の計算とおまけを切り離せる。この仕組みがモナドである。

より正確に言うと、この変換には三つのタイプがあり、その中の一つがモナドと呼ばれている。

具体的には、

a) 値 =(変換)=> 値  (全くおまけを想定してない関数)
b) 値 =(変換)=> 値, おまけ (おまけ付きの値を返す関数)
c) (値 =(変換)=> 値), おまけ (関数自体におまけが付いてる!?)

のような関数を、あたかも、

値, おまけ =(変換)=> 値, おまけ

のように扱えるようにしたい。この変換に対応するアダプターをそれぞれ、

a. Functor (ファンクタ)
b. Monad (モナド)
c. Applicative (アプリカティブ)

と呼ぶ。

ところで、上記三つの名前を見て、それぞれの機能が想像できるだろうか? これらは数学の「圏論 (category theory)」という分野由来の言葉で、そのままで意味をイメージできる人はかなりの少数派ではないかと思う。この名前の問題も、モナドが難しいというイメージを与える一因になっている。

そこで、以下のようにもっと分かりやすい名前で呼ぶようにしたらどうかという提案がなされているようだ。

  • Functor -> Mappable
  • Monad -> Chainable/Joinable/FlatMappable
  • Applicative -> Zippable

厳密にはこれらの語が意味するところとは異なるので(圏論的なニュアンスが抜け落ちるので)適切ではない、というのが専門家の見解であるようだが、これらの概念を理解する際の参考になることは間違いない。

まず Functor の Mappable という別名はとても分かりやすい。おまけ付きの値でも、[値 =(変換)=> 値] の関数でマッピングできるようにしてくれるものが Mappable というわけだ。

Functor で最もポピュラーだと思われるものが、値が「繰り返し」というおまけを持つ場合(これはつまりコレクションのこと)に対応する Functor だ。この Functor は、まさに map という名前で、多くの言語に導入されてお馴染みになった。コレクションを別のコレクションに変換するあの関数だ。この map に渡される変換用の関数は、値がコレクションの中に入っているという「おまけ」を気にすることなく、単に一つの値を別の一つの値に変換することだけに集中することができる。「本来の計算とおまけの分離」である。

Monad の Chainable も分かりやすい。

値 =(変換)=> 値, おまけ

という関数をつなげて (Chaining) 使いたい場合に、入力を変換するものが Chainable になる。

Applicative – 複数の入力値を取る関数をおまけ付き入出力に対応させる

Applicative はちょっとややこしい。これは、複数の入力値を取る関数に Functor 的な変換を行う際に使う。これまでの話では、あくまで一つの入力値を取るケースだけを想定して話をしてきた。

例えば、以下のように二つの入力を取る関数があるとする。

A, B =(変換)=> C

関数型プログラミングをサポートする言語の一部には、関数の「部分適用」という仕組みがある。これは複数の入力を受け取る関数があるときに、その中の一つの入力だけを渡して、その入力を受け取った状態の新たな関数を作る機能だ。

具体的に見てみよう。上の関数に、一番目の入力、A だけを与えると、入力を B 一つだけ受け取り、C を返す関数ができる。

A
↓
A, B =(変換)=> C
↓
B =(変換[A入力済み])=> C

さて、この部分適用という仕組みを踏まえて、二つの入力を取る関数におまけ付きの値を渡すためにはどうすれば良いだろうか?

まずは一つ目の入力値を Functor で渡してみよう。

A, おまけ
↓
A, B =(変換)=> C
↓
(B =(変換[A入力済み])=> C), おまけ

Functor は、入出力をおまけ付きに変換するので、部分適用して出来上がった関数がおまけ付きになってしまった。

というわけで、ここで Applicative の登場である。

すでに書いたように、Applicative は、

c) (値 =(変換)=> 値), おまけ

値, おまけ =(変換)=> 値, おまけ

に変換してくれる。つまり、

B, おまけ
↓
(B =(変換[A入力済み])=> C), おまけ
↓
C, おまけ

となって、最終的に欲しかった「C, おまけ」を手に入れることができた。

つまり、これら(Functor と Applicative)を組み合わせると、

A, おまけ and B, おまけ
↓
A, B =(変換)=> C
↓
C, おまけ

という感じで、複数の入力値を取る関数をおまけ付き入出力に対応させることができる、というわけである。

Elixir試飲録 (4) – オブジェクト指向と関数型の違い

古いブックマークを漁っていたら、オブジェクト指向と関数型の違いに関する面白い記事を見つけた。

記事は Michael Feathers 氏の興味深いツイートから始まる。

  • 試訳
    • オブジェクト指向は、可変部分をカプセル化(隠蔽)することによって、コードを分かりやすくする
    • 関数型は、可変部分を最小化することによって、コードを分かりやすくする

筆者なりに解釈すれば、オブジェクト指向はインターフェースにフォーカスし、可変部分を捨象することによって複雑なシステムを単純化し、関数型は副作用のない関数を組み合わせることによってシステムの可変部分を最小限に保ち、システムの動的な性質を掴みやすくしようとする。

オブジェクト指向の「可変部分を捨象する」という考え方がシステムを単純化する一方、インターフェースの裏側で起こることは予測し辛くなる。インターフェースの「契約」に現れない状態変化、極端な例を挙げれば「重要なデータを削除しないこと」など、それら全てについて自動テストを用意するのは現実的ではないため、オブジェクト指向のシステムが提供出来る信頼性には自ずと限界があることになる。

そして興味深いのは、オブジェクト指向の上に関数型の仕組みを乗っけるのは止めた方が良いという主張だ。この記事を書いた John D. Cook 氏は、関数型のメリットを享受したいのなら、まずは関数型だけに集中し、プログラムの大きなパーツ(モジュールなど)を管理する時にオブジェクト指向的な枠組みを利用すれば良いのではないかと書く(「functional in the small, OO in the large」)。

コードの中に、関数型の部分とそうでない部分が混在してしまうと、全体としては関数型の恩恵を受け辛くなるのだろう。この話題はこれからもちょくちょく出てくると思うが、Scala と Elixir の比較という観点からも興味深い指摘である。Scalaは基本的にオブジェクトから出発して関数型の流儀を徐々に入れて行くの対し、Elixirはまず関数型から出発する。そして、そこから関数をグルーピングするための Module や、Moduleにインターフェースの仕組みを導入するための Behaviour、そしてポリモルフィズム(多態)のような仕組みを実現する Protocol などが出てくる。

James Hague 氏は、100%純粋な関数型を実現するのは非現実的であり、大体85%ぐらいを目指せばいいのではないかと主張する。そして、残りの15%は注意深く分離してコードの中に分散しないようにすべきであると。

副作用がある部分を切り離すデザインは、Phoenix Frameworkで採用されているデータアクセスのフレームワーク、Ectoの中にも見られた。Ectoでは副作用のない Model と、副作用を担当する Repository を分けるという、いわゆる Repositoryパターンが採用されている。Railsの Active Record が Model 自身に副作用を内包するのとは対照的である。

Elixir試飲録 (5) – Elixir/Phoenixのホットデプロイ完全自動化(2016年1月版)

Elixir試飲録 (3) – マルチコア危機によるパラダイムシフト: オブジェクト指向から並行指向へ

マルチコアCPUの登場

ソフトウェアにおける並行性への根本的な転換」。この記事が書かれた2005年というのは、インテルが「Pentium D」「Pentium XE」、AMDが「デュアルコアOpteron 2xx」「Athlon 64 X2」という初めてのマルチコアCPUを発売した年である(参考: ASCII.jp:CPUの勢力図は変わるか? デュアルコアCPU4種対決!)。

それまでムーアの法則に従って指数関数的に向上していた半導体の性能は2003年に転機を迎える。以下のグラフは件の記事で紹介されているもので、CPUのトランジスタ数とクロック数の時系列変化を表している。

Intel CPU introductions (sources: Intel, Wikipedia)
Intel CPU introductions (sources: Intel, Wikipedia)

トランジスタ数は記事の書かれた2005年当時まで、さらには下の2011年までのグラフで分かるように、それ以降もムーアの法則に沿って向上しているが、クロック数の上昇については2003年を境に急激に減速している。クロック数が限界を迎えつつあるのは、それまで以上の高クロック数で動作させたときに発生する「熱」と「消費電力」が許容範囲を超えてしまうようになったからである。

2005年以前、CPUの性能を決定するのは、大きく分けて以下の三つの要因があると言われていた。

  1. クロック数
  2. 命令実行の最適化
  3. キャッシュ

クロック数は単位時間辺りの処理能力に直結する数字で、CPUメーカーは基本的にこの数字を上げることで指数関数的な性能向上を実現してきた。

しかし、クロック数には早晩限界が来ると分かっていたCPUメーカーの開発者は、それでも性能を向上させなければならないというプレッシャーの中、実行対象のプログラムの意味を変えてしまいかねないような大胆な最適化を実装したという。このような最適化を有効にしてプログラムを実行すると、速度自体は向上するものの、プログラマーの期待と異なる動作をする可能性があり、デバッグが困難になるため、通常は無効になっているようだ。

キャッシュの容量を増やすという方法は2005年以降も有効に機能しているようであるが、当然のことながら、それまでのクロック数のように性能を指数関数的に伸ばすというところまでは至らない。

2016年1月現在、インテルから発売されているCPUのクロック数を見てみると、一番高いもので 4.00GHz というのがあるが、これは2005年当時の3.4GHzと比較しても大幅な向上をしているとは言い難く、クロック数についてはほとんど横ばい状態になっていると言って良いだろう。

これまでと同じ方法では同じペースでの性能向上は見込めない。そこで生み出されたのが、マルチコアと呼ばれる、1つのプロセッサ・パッケージ内に複数のプロセッサ・コアを搭載する技術である。これによって、クロック数が頭打ちになっても、CPU全体のトランジスタ数は依然としてムーアの法則に従って向上させて行くことが出来るようになった。

A plot of CPU transistor counts against dates of introduction (sources: Moore's law, Wikipedia)
A plot of CPU transistor counts against dates of introduction (sources: Moore’s law, Wikipedia)

不可避となった並行指向プログラミング

  • マルチコア時代のCPU性能三大要因
    1. ハイパースレッディング
    2. マルチコア
    3. キャッシュ

1つのCPUの性能が頭打ちになったから、複数繋げて1つのCPUに見せかけるというのは、冷静に考えると安直な方法に思えなくもない。実際にインテルの最初のマルチコアCPUである Pentium D は、単純に2つのCPUのダイ (Die) を1つのパッケージに封入したマルチコア・マルチダイ形式だった。

重大な問題は、コアが2つ3つになったからと言って、性能がそのまま2倍3倍になるわけではないということだ。それどころか、それまでのパラダイムで書かれた一般的なプログラムの場合、どう頑張っても一つのコアしか利用することが出来ず、マルチコアの恩恵を受けることが出来ない。

これまではプログラム自体に特に工夫がなくても、時間が経てばCPUの性能が倍々で向上し、あらゆる問題が自然に解決されてきた。しかし、2005年以降、そのようなことを期待することは年々難しくなり、プログラムをマルチコアに合わせて設計し直さない限り、性能向上の恩恵を受けることが出来なくなっている。それどころか、マルチコアの時代においては、一つのコア(一つのスレッド)辺りの性能はむしろ低下する可能性さえあるという。

これまでの逐次的なプログラミングでは年々進歩するCPUの恩恵を受けることは難しくなった。そこで必要になってくるのが、件の記事の主題になっている「並行性」、つまり、マルチスレッドやマルチプロセスを利用したプログラミングである(ここでは、これらを総称して「並行指向プログラミング」と呼ぶことにする)。

今回の記事の著者である Herb Sutter氏は、マルチコア時代における並行指向プログラミングの登場は、1960年代に生まれ、1990年代以降に隆盛を極めたオブジェクト指向プログラミングの登場に匹敵する出来事なのだと主張する。

振り返ってみれば、オブジェクト指向の隆盛はCPUのクロック数が指数関数的に増えて行った時代と丁度重なっており、より複雑でよりリッチなソフトウェアを、よりリッチなハードウェア資源の上で実現するためには最適の手法だったと言えるのかもしれない。

マルチコア時代になって、CPU資源を最大限に活用するために並行性が必須となった。クラウド上で動くプログラムも分散・並行が基本であり、この流れは不可避である。しかし、並行指向プログラミングの最大の問題は「難しい」ということであった。並行処理の難しさは多くのプログラマーによって共有されるところで、安全に並行性を実現するためには、新しいプログラミングモデルが必要になる。そして、ここでオブジェクト指向が足かせになってしまう。オブジェクト指向は処理の前後で意図しない状態変化を引き起こす恐れがあり、安全に並行性を導入するには難がある。そこで注目を浴びているのが副作用のない処理を組み合わせる関数型プログラミングだ。

オブジェクト指向に慣れたプログラマーが並行指向プログラミングに移行するのは、数十年前、構造化プログラミングに慣れたプログラマーが手探りでオブジェクト指向を学んだ時と同じぐらいの試行錯誤があるだろうと、Sutter氏は言う。

今回のネタ帳 – https://oinker.me/room/marubinotto/multicore-crises

Elixir試飲録 (4) – オブジェクト指向と関数型の違い

Elixir試飲録 (2) – カルチャーショックに戸惑う: 並行指向プログラミング

昨夜、Dave Thomas氏の「Programming Elixir」を読み終えて、こうしてブログを書いている。

読み終えて「とんでもないところに足を踏み入れたな」と思う。漂うのは、何もかもが今までのプログラミングモデルとは違うという圧倒的なアウェイ感である。

Dave Thomas氏は度々このブログにも登場しているが、『達人プログラマー』の著者であり、アジャイルソフトウェア開発宣言の発起人の一人でもある。近年はRubyの人という感じであったが、ElixirにRuby以来の衝撃を受けて、この「Programming Elixir」を執筆するに至ったとのこと。

この本が素晴らしいのは、元々はオブジェクト指向のプログラマーであった著者が関数型言語であるElixirに出会って、徐々にそのコンセプトに体を慣らせて行く過程を、読者が追体験できるところである。この本で扱っているのは Elixir やその基礎となっている Erlang/OTP のほんの導入部分のみであり、主眼はオブジェクト指向プログラマーが関数型あるいは並行指向プログラミングへとパラダイムシフトするための手助けをするといった趣向になっている。

「Programming Elixir」のサンプルプログラムを動かしながら一通りのトピックを見て回り、Elixir(というかErlang)で最も重要なのは、関数型プログラミングではなく、「並行指向プログラミング」と呼ばれる、そのプロセス管理の仕組みであることが分かった。関数型言語はあらゆるプラットフォームで利用出来るが、この並行処理のモデルはおそらくErlangにしかないものなのだろう。

Elixirの並行処理の威力を見るにはサンプルコードを実行するのが手っ取り早い。

まず、Elixirのサイトにある一番始めのサンプルコード(Hello world)が、いきなり並行処理の例になっている。

parent = self()

spawn_link(fn ->
  send parent, {:msg, "hello world"}
end)

receive do
  {:msg, contents} -> IO.puts contents
end

これを hello.exs というファイルに保存して、

$ elixir hello.exs 

のように実行すると、

hello world

となる。

Elixirを知らない人でも、このコードが何をしているかは、何となく分かるのではないだろうか。

  1. spawn_linkという関数に渡された関数が、このプログラムを実行しているメインのプロセスとは別のプロセスとして起動されて、関数の内容を実行する。
  2. 新しく作られたプロセス側では、メインプロセス側(parent)に “hello world” というメッセージを送る。
  3. メインプロセス側は、どこからかメッセージが来ないかを待ち受けて(receive)、メッセージが来たらそれをコンソールに表示する。

このように、spawn でプロセスを作り、sendreceive でメッセージのやり取りをするという単純な仕組みである。プロセスの間でのやり取りはこのメッセージ交換だけで、可変データを共有するということがないので、従来の並行処理について回る排他処理などが必要にならないというのが Elixirの強みである。

この例では一つのプロセスを作っただけなので、他の言語でも同様のことは簡単に出来るかもしれない。では、以下の例だとどうだろうか。

defmodule Chain do
  def counter(next_pid) do
    receive do
      n -> send next_pid, n + 1
    end
  end

  def create_processes(n) do
    last = Enum.reduce 1..n, self,
      fn (_, send_to) -> spawn(Chain, :counter, [send_to]) end
    send last, 0
    receive do
      final_answer when is_integer(final_answer) ->
        "Result is #{inspect(final_answer)}"
    end
  end

  def run(n) do
    IO.puts inspect :timer.tc(Chain, :create_processes, [n])
  end
end

いきなり複雑な例になったが、やっていることは単純である。run関数が呼ばれると、指定された数(n)だけのプロセスを次から次に立ち上げて、それらのプロセスには順番に次のプロセスへの参照を持たせて数珠つなぎのような構造にする(終端はメインプロセス)。全てのプロセスを立ち上げたら、先頭に数値「0」を送ると、プロセスの間でバケツリレーをしながら、その数値に「1」を加えて行く。最後はメインプロセスに到達して結果の値をコンソールに書き出す。この一連の処理は、Erlangの関数である :timer.tc によって時間が計測されており、結果とともに経過時間も出力される。

さて、このコードを chain.exs というファイルに保存して、以下のように実行したらどうなるだろうか?

$ elixir --erl "+P 1000000" -r chain.exs  -e "Chain.run(1_000_000)"

生成するプロセスの数はなんと100万個である。デフォルトのセッティングでは同時並行プロセス数の上限に達してしまうので、オプション(--erl "+P 1000000")で上限を変更している。

これを筆者のPC(Macbook Pro – 3 GHz Intel Core i7, 16GB RAM)で実行した結果が以下である:

{7011658, "Result is 1000000"}

一つ目の数字の単位はマイクロ秒なので、実に100万個のプロセスを起動して連携させる処理がたったの7秒程度で終わったということになる。

何故このようなことが可能なのかと言えば、これらのプロセスがOSで管理されているプロセスやスレッドなどではなく、Erlang VMの中で管理されている独自のプロセスだからである。このErlangが提供する極めて軽量なプロセスモデルが、Elixir/Erlang環境が単なるプログラミング言語以上のインパクトを持つ所以となっている。

このようにElixirでは、一つのプロセスが極めて軽量なため、一つのプロセスを作るのは、オブジェクト指向システムで一つのオブジェクトを作るのと同じぐらいの感覚であると「Programming Elixir」では説明されている。実際プロセスの中には状態を持つものもあり、それらがメッセージでやり取りをするとなれば、モデル的にはオブジェクトと変わりないように思える。Elixirでは、状態を持つプロセスを「Agent(エージェント)」として容易に管理する仕組みがあり、これは、かつてポスト・オブジェクト指向の有力候補と呼ばれていたエージェント指向ではないかと、はたと思い当たったが、それは多分違うような気がする。

Elixir試飲録 (3) – マルチコア危機によるパラダイムシフト: オブジェクト指向から並行指向へ

Elixir試飲録 (1) – 今、プログラミング言語を選ぶということ: Scala, Go, Elixir

たまチームでは、これまでJava系の言語・環境を使ってWebサービスを開発してきた。言語で言えば、Java, Scala, Groovyなどであり、フレームワークで言えば、Spring, Play, Scalatra, Grailsなどである。しかし、来年から新しいプロジェクトを始めるに当たって、心機一転、新しい環境で頑張ってみようということになり、年の瀬も押し迫った今、少しずつ準備を進めているところである。

ここ数年のWeb開発三大勢力と言えば、Ruby on Rails, PHP, Java (JVM) という感じになるだろうか。これらに共通する大きな枠組みは「オブジェクト指向」である。Web開発に限らず、オブジェクト指向はかれこれ20年以上ソフトウェア開発の主流だったと思うが、数年前から徐々に「関数型」という考え方が職業プログラマの間にも浸透してきて、最近では無視することの出来ないムーブメントになりつつある。

2015年の今、関数型プログラミングの流行を牽引している最有力の言語はおそらくScalaだろう。ScalaはJVM上で動くプログラミング言語で、オブジェクト指向と関数型双方の考え方を取り入れたハイブリッドなデザインになっている。オブジェクト指向に慣れ親しんだプログラマが、Javaより簡潔に書ける、いわゆる「Better Java」として使い始めることができ、必要に応じて関数型のプログラミングを取り入れることも出来る。アーリーアダプターの評価も高く、TwitterがRubyからScalaに移行した事案も話題になった。Scalaはたまチームの開発でも有力な選択肢の一つである。

しかし最近、Scalaに関して、このようなブログ記事が話題になった。

著者は CrowdStrike社でテクニカルディレクターを務めるJim Plush氏。CrowdStrike社での主要な開発言語をScalaからGo言語に切り替えたという話である。

Plush氏によれば、Scalaプログラマーは以下の二つの派閥に分かれる傾向があるらしい:

  1. Scalaを「Better Java」として使い、関数型の機能はおまけ程度に考えていて深く追求はしない。
  2. Scalaを関数型言語として扱い、より深く関数型のコンセプトを追求する。
Scala developers typically travel down two paths

小規模のチームであれば、それほど大きな問題にはなり辛いが、プロジェクトが成長して規模が拡大すると、これらの派閥間のすれ違いが無視出来なくなるという。端的に言えば、関数型追求派のプログラマーの書いたコードを、Better Java派はメンテナンス出来なくなる。記事の事例では、緊急に対応しなければならない障害が発生して、現場にいたScalaプログラマーがコードを見ると意味不明な記号が使われている。IDEやGoogleの力を借りてもなかなか解読出来ない、コードを書いた本人はバカンス中である。というわけで、数分で解決出来るような些細な問題に数時間を要したという。このような問題を目の当たりにして、主要言語としてはもっと開発要員の確保しやすい手続き型言語のGoに移行することにしたという経緯である。

ちなみに筆者は、関数型の経験がほとんどない(ちょっとScalaをかじった程度の)、重度のオブジェクト脳プログラマーであるが、Plush氏が指摘している問題については、自分がこれまでぼんやりと感じていたこととそれほど相違はないと感じた。

まず前提として、当たり前のことではあるが、どのようなプログラミング言語でも読み辛く、あるいは分かり辛くコーディングすることは可能である。ただ、手続き型やオブジェクト指向は、コーディングスキルが向上するほどコードが読みやすくなっていくように思えるのに対して、関数型はスキルが高いほどコードは難解になっていく印象がある。単に自分がオブジェクト脳だからという可能性も否定出来ないが、関数型の抽象性の問題ではないかと個人的には考えている。つまり、オブジェクト指向が日常的な抽象性をベースにしているのに対して、関数型は数学的な抽象性、つまり日常的な抽象性の延長としては理解し難い領域の抽象性を扱っているからではないかと推測している。この辺もこの連載で追求して行けたらと思う。

しかし、こういった関数型プログラミングに対する印象も、一度も実プロジェクトで使ったことがなければ、単なる表面的な思い込みになってしまう。いずれはきちんと取り組んでおかなければと考えていたところに遭遇したのが以下の記事だった。

Elixirは、今から三年ほどまえの2012年に最初のバージョンがリリースされ、そして去年、v1.0 がリリースされたばかりのかなり新しい言語である。このブログを読んでいる方なら耳にしたことのある方が多いかもしれない。

Elixirは、Erlangのvirtual machine(BEAMと呼ばれている)上で動く言語で、ErlangとElixirの関係は、丁度JavaとScalaの関係に似ている。Erlangという言語とその環境については度々耳にしていたが、一部のハイスケーラビリティを要求される特殊な世界の話で、なんとなく自分とは関係のない別世界の話のように感じていた。ところが件の記事を読んで、Erlang環境上でWebアプリケーションを作る基盤が整ってきていることを知り、俄然興味が湧いてきたわけである。

Elixirを作ったのは、Ruby on Rails開発チームのコアメンバーである Jose Valim氏である。だからなのか、Elixirはいわゆる関数型言語であるが、Scalaのようなハードコアな感じはない。言語のシンタックスを見る限り、もっと気軽に関数型プログラミングを始められそうな親しみやすさが漂っている。更に言えば、Railsコミュニティとその周辺でよく聞かれる「一番大事なのは目の前の仕事を片付けることである」という哲学を踏襲しているように感じられる。

一方、Erlangは、スウェーデンの通信機器メーカーであるエリクソンが1980年代に開発した言語環境で、元々は電話交換機のプログラム用に開発されたということである(件の記事によれば、今でもモバイルの4Gネットワーク上で通信すれば、Erlangプログラムが動いている機器を経由する可能性があるらしい)。電話のネットワークは、当然のことながら高い耐障害性を求められるし、常に誰かが通話しているので、ネットワーク上のプログラムをアップデートする度にネットワークを遮断するわけにはいかない。というわけで、そのような特殊なニーズに基づいて以下のような要件を満たす言語環境が設計された。

  • 分散可能
  • 耐障害性(フォルトトレラント)
  • リアルタイム性
  • 無停止

Erlangはこれらの要件を極限まで追求しているため、プログラムを稼働したまま更新出来る「ホットスワップ」と呼ばれる機能を始めとして、いわゆるこれまでのメインストリームの言語環境と比較してもかなり特殊な進化を遂げている。

アメリカのメッセンジャーアプリの最大手 Whatsappでは、一つのサーバーで200万同時接続を実現するシステムをErlangで実装しているという。高度なリアルタイム性と大量の同時接続を要求するオンラインゲームの世界でもErlangは活躍しており、Call of Duty や Game of War といったゲームで利用されているということだ。

Erlangは、これほどの歴史と能力を持ちながら、その言語や環境の特殊性でメインストリームの開発環境とはなってこなかった。そこに一石を投じたのが Elixirである。次回以降では、上に挙げたErlangの特徴について、Elixirの観点から掘り下げながら、この新しいプラットフォームについての理解を深めて行きたいと思う。

Elixir試飲録 (2) – カルチャーショックに戸惑う: 並行指向プログラミング

TDD再考 (7) – テストはどこまで書けば良いのか?

「テストはどのレベルまで書けば良いのか?」という Stack Overflow の質問。明快な答えが出るような質問ではないので Stack Overflow ではオフトピックだとして既にロックされているが、「歴史的な意義がある」ということで残されている。

なぜ歴史的な意義があるかと言えば、TDDでテストを書こうとする人は誰でも抱くであろうこの疑問に対して、Kent Beck氏自らが回答しているのがその理由の一つだと思われる。そして、その回答が聴衆にとっては少なからず意外な内容だったことも、この質問の重要性に寄与している。

Beck氏の回答はシンプルである。

「私は機能するコードに対してお金を貰っているのであって、テストコードの対価を受け取っているわけではない。なので自分の哲学としては、テストにかける労力は必要な自信を得るための最小限度に抑えることにしている」

「必要な自信を得るための最小限度」は人によって異なるだろうし、チームで開発する場合は、チームを構成するメンバーのスキルや性格に依存することになる。つまり、完全にその人(チーム)次第になるということだ。

これはアジャイルの思想からすれば自然なことに思えるが、Beck氏の回答に対する反応を見る限り、少なからぬ驚きをもって受け止められているようである。「Kent Beckがこんな考えを持っていたなんて青天の霹靂である。TDDの信奉者は100%あるいはそれに準じる高いカバレッジ率を目指す傾向があって、Kent Beckも当然そうしていると考えているはずだ。」

Beck氏の回答に対して正面から反対している人もいる。コードは書いた本人だけでなく、事情を知らない他人がメンテナンスする可能性があり(そしてその人はその時点でチームのメンバーではないかもしれない)、当事者だけの文脈でテストの範囲を決めるのは良くないという指摘だ。あるいは、TDDはテストファーストが前提なのだから、一部分だけテストするということがそもそも成り立たないのではないか、等々。

別の回答では、TDDのT、つまり「テスト」という表現が「テストはきちんとやらなければいけない」という、TDDの本来の目的とは異なる品質保証の文脈を呼び込み、その結果としてカバレッジ云々の話になってしまうのではないかと指摘している。TDD界隈では頻繁に耳にする指摘である。その反省を踏まえて、BDD(Behavior Driven Development)というものが生み出された。

TDD再考 (6) – テスト容易性 ≠ 良いデザイン

ソフトウェア開発の世界にTDDが紹介された当時、最も説得力があり、そして魅力的に響いた格言は「テスト容易性(Testability)を追求すれば、自然に良いプログラムをデザイン出来る」というものだった。あるモジュールをユニットテストするためには、そのモジュールをシステムの他の部分から切り離して、独立して動作させる必要がある。ということは、テストしやすいモジュールは必然的に良いモジュール設計の指標である「低結合度(low coupling)」を実現することになる。

しかし、テスト容易性は必ずしも良いデザインに結びつくわけではないと、DHH氏は言う。テストをしやすくするために、過剰な「間接層(indirection)」が導入されることによって、コードの簡潔さが失われてしまうと。

過剰な間接層の例として紹介されているのが、Hexagonal architecture というデザインパターンで、アプリケーションのコアとなるドメインモデルと外部のインタフェースを明確に分離するためのテクニックである。階層化アーキテクチャの一種と言っても良い。ドメインモデルを「ポート」と呼ばれるAPI的な層(「ポート」という語はネットワークのプロトコル・ポートや電子機器の接続を行うポートを意識している)で取り囲み、さらにその外側に「アダプタ」と呼ばれる、様々な外部システムとのやり取りをポートに適合するよう変換するためのレイヤーを配置する。

Hexagonal architecture http://alistair.cockburn.us/Hexagonal+architecture

上図のシステムでは、administration, trigger data, notifications, database という4つのポートがあり、それぞれのポートに外部システムあるいはインタフェースごとのアダプタがある。

さらに今回の記事で紹介されているのは、Ruby on Rails の Controller や Active Record といったパーツを他のレイヤーに依存させずにユニットテストするためにこの Hexagonal architecture を導入するという例で、これは完全にやり過ぎであると、DHH氏は批判しているわけである。

それぞれのレイヤーを単独でテスト出来るようにするため、Controller からは直接 Active Record にアクセスさせずに Repositoryパターンを経由するようにし、Controller内のロジックは Commandパターンとして切り出す。つまり、ロジックを Rails に依存しないモジュールに配置して、それらを Rails から切り離して高速にテストできるようにするためのテクニックである。

DHH氏は、Hexagonal architecture自体を非難しているわけではなく、多種多様な外部システムに対応しなければならないシステムで役に立つ可能性については認めている。問題にしているのは、ただ「ユニットテストをやりやすくするため」だけの目的でこの複雑な構造を導入することについてである。つまり、間接層を導入してモジュールの置換可能性を高めるのは、機能的な要求があれば合理的な選択となるが、ただユニットテストのためだけにこれを導入するのは、犠牲にされたプロダクトコードの簡潔さとその見返りの釣り合いが取れないのではないかという指摘だ。

そもそもControllerの責務は、ユーザーからのリクエストとモデルからのレスポンスを統合することなのだから、ユニットテストではなく結合テスト(Integration test)で対応するべきだ、というのもなかなか説得力がある。そのように考えると、システムの中でユニットテストの対象となる部分はかなり限定されることになりそうだ(ユニットテストの定義をどう捉えるかによるが)。ドメインモデルのテストについても、データベースから切り離してテストしようとするのは時代遅れであると切り捨てている。これは筆者も同意する部分で、モデルにおいてはデータベースとのやり取りがむしろ一番重要なところであり、昨今のORMではインメモリデータベースを利用して簡単かつ比較的高速に結合テストが実施出来るため、これをやらない理由はほとんど無いように思える。

さて、TDD陣営の人たちはこの過剰な間接層問題に無頓着だったのだろうか? 過去の資料を当たる限りそんなことはなさそうだ。Martin Fowler氏による有名な書籍『リファクタリング』の中に、Kent Beck氏による「間接層とリファクタリング」というコラムがある。

「コンピュータサイエンスは、間接層(indirection)を設けることであらゆる問題が解決できるという信念に基づいた学問である。― Dennis DeBruler」

TDDに欠かせないプラクティスであるリファクタリングは、つまるところプログラムに間接的な部分を加えるということである。この間接層というものの重要性を強調しつつも、

しかし、間接層はもろ刃の剣であることに注意しなければなりません。1つのものを2つに分割するということは、それだけ管理しなければならない部分が増えるということなのです。また、オブジェクトが、他のオブジェクトに委譲を行って、その先もさらに委譲を繰り返すような場合、プログラムが読みにくくなるのも事実です。つまり間接層は、最小限に絞り込むべきなのです。

TDD再考 (7) – テストはどこまで書けば良いのか?


TDD再考 – これまでのコンセプトマップ

これまでの資料まとめ - https://oinker.me/room/marubinotto/TDD
これまでの資料まとめ – https://oinker.me/room/marubinotto/TDD

規模の経済と理想主義

クラウドファンディングで有名な Kickstarter のCEO、Yancey Strickler氏の提言。

利潤のみを追求する大資本が地域社会や多様性を壊して行く。数字至上主義に対して我々は何ができるか?

先月、UberやAirbnbに代表されるシェアリング・エコノミーについてのTim O’Reilly氏の論考を紹介したが、そこで経済的効率性からフランチャイズチェーンが生まれる過程について触れた。そういったチェーン、つまり大型のスーパーやファーストフード、コンビニと言ったものが、地域社会に根付いていた商売 ― 商店街などが象徴的であるが ― そういったものを根こそぎにしてしまうというのは高度経済成長期以降の日本でもよく見られた光景である。筆者の地元(千葉の田舎)にあって、かつては賑わいを見せていたアーケード街も、数年前に帰省した際に訪れてみたら、アーケード自体が撤去されて、営業している店もほとんどなく「兵どもが夢の跡」状態になっていた。

ニューヨークでは、新しく入ってくるビジネスの多くが銀行であるという。現在では1800以上の銀行支店がニューヨークに存在し、10年前より60%以上増加している。ニューヨークタイムズの調査では、ほんの158の家庭がアメリカ大統領選に投入される資金のほぼ半分を提供していることが判明している。音楽業界では、全米で行われるコンサートの8割がTicketmasterというチケット販売会社に独占され、レコードレーベルの多様性も失われつつあり、ヒットチャートTOP40の内、驚くべき割合の曲がたった4人の北欧出身の人間によって書かれているという。そして、ハリウッドでは、リスクを回避するための続編や前日譚といったシリーズ物が溢れている。

「お金」という単一文化(モノカルチャー)に我々はどのように対抗したら良いのか? 資本主義社会の中で生活する以上、その社会からドロップアウトして隠遁生活でも始めない限り、そこから逃げるのは至難の業のように思える。

Strickler氏は、そういった社会の中で生活しながらも、現実主義に陥らずに理想主義を貫くことが大事だと説く。自分が働く業界の標準に迎合しないこと。自分が正しいと信じる道を貫くこと。現状に満足せず、常に新しいアイデアを探し求めること。そして、短期の利益を追い求めるばかりに、長期の努力を軽視しないこと、持続可能性を考えること、など。

世の中が「規模の経済(economies of scale)」に向かって邁進して行く中、Strickler氏以外にも、その価値観に疑義を呈する人たちがいる。その中でも有名なのが、このブログの連載「TDD再考」に度々登場する Ruby on Railsの作者、David Heinemeier Hansson (DHH) 氏である。

ビジネスの世界ではより大きな数字を追い求める数字至上主義が蔓延しているが、例えば、ハーバードやオックスフォードといった有名大学の価値はそのような「数字」では計れない。であるならば、ビジネスの世界でも、組織ごとに適切なサイズというものがあって良いのではないか? 「小さい」ことを大きくなるための通過点として捉えるのではなく、「小さい」こと自体をゴールにしても良いのではないか?

DHH氏は2008年にもYcombinatorが主催するスタートアップスクールで、「ベンチャー・キャピタルからお金をもらって次のFacebookを狙うのをやめよう!」というスピーチをしており、投資を受けてイグジットを目指すシリコンバレー的なスタートアップ文化を批判していた。

  • 次のfacebookを目指してなんの意味がある? Ruby on Rails 作者が語る「お金を生み出して幸せになるためのたった1つの方法」 – ログミー

Facebookのような巨大企業を目指すためにあらゆることを犠牲にするよりも、自分達が良いと信じられるものを、自分達のペースで継続するのに必要最低限の規模を目指すこと。

ほかにもイタリアンレストランがたくさんあるなかで小さなイタリアンレストランでうまくいっているところがたくさんあるようにたくさんの勝者がいていいし、小さな問題を解決している会社が世の中には山のように存在する。そういった会社は2000とか10000の顧客を持っていて、何度も生まれて来た素晴らしい企業の多くは200の顧客からスタートしている。例外はFacebookのような会社で紙の上では2年で1.5兆円の価値になっている。あんな会社をロールモデルにしてはいけない。

そして、そのようなイタリアンレストランを目指すことは第二のFacebookを目指すよりも遥かに容易であるし、実現できれば、たとえ規模はささやかであっても素晴らしい達成なのだとDHH氏は言う(「人生の楽しみ方にはいろんな部屋がある」)。

TDD再考 (5) – ユニットテストの「ユニット」とは何を指すのか?

この「TDD再考」シリーズの第三回で「TDD is dead」に対する Kent Beck氏の反応を紹介したが、今回は Martin Fowler氏の反応について検討してみたい。この後、David Heinemeier Hansson, Kent Beck, Martin Fowlerの三者は「Is TDD Dead?」という対談を展開して行くことになる。

「TDD is dead」が公開されて激しく燃え上がっている中、Fowler氏はまず「UnitTest」というタイトルの記事を自身のサイトに投稿している。

この記事の中でFowler氏は、DHH氏の記事の内容には直接触れずに、そもそもユニットテストの定義って何なのだろうという話を展開している。それは、DHH氏やJames O Coplien氏の批判の矛先がユニットテスト(の価値)にあり、議論に先立ってまずユニットテストってそもそも何なの? という部分をはっきりさせておきたいということがあったのだろう。

まず、ユニットテストと言った場合、それが低レベルかつシステムの中の小さなパーツを対象としていること、基本的にはプログラマー本人によって書かれること、といった部分については多くの人の同意を得られそうであるが、それとは反対に、最も意見の食い違いが発生する部分が「コラボレーター」をどのように扱うかという問題だと言う。

とあるモジュールをテストする場合、そのモジュールが単独で機能するケースはどちらかと言えば少なく、何らかの依存するモジュールが必要となる場合が多い。この依存モジュールを「コラボレーター」と呼ぶ。ユニットテストは「ユニット」つまり単体のモジュールを対象としているのだから、コラボレーターはテストの対象から切り離すべきであり、そのためにモックオブジェクトを積極的に利用すべきだというのが Mockist style と呼ばれる勢力で、反対にコラボレーターも一緒にテストしてしまって問題ない、あるいはむしろそうすべきだと考える勢力を Classsic style と呼ぶ。

Fowler氏は Classsic派だったので、コラボレーターを切り離さないのはユニットテストの定義から外れるのではないかと批判されたそうだが、テストのターゲットはあくまでユニットの振る舞いであり、その内部構造は問題でないと考えているようだ。

Indeed this lack of isolation was one of the reasons we were criticized for our use of the term “unit testing”. I think that the term “unit testing” is appropriate because these tests are tests of the behavior of a single unit. We write the tests assuming everything other than that unit is working correctly.

ここで筆者はうーんと考え込んでしまった。

Fowler氏自身が書いているように、何をユニットと見なすかは状況に依存するので、いわゆる振る舞いがユニットを形成してさえいれば、その内部構造がいかに複雑であっても、それをユニットと見なせる可能性があるということだ。これはシステムというものが基本的には入れ子の構造になっていることを考えると当然のことだとも言える。概念的には入れ子のどこを切ってもユニットになり得る。

となると、もはやユニットという言葉に意味はないような気がしてくる。それよりも、どのようなタイプのインタフェースに対してテストをするのか(プログラムモジュールなのか、REST APIなのか、UIなのか)、コラボレーターのセットアップにどれぐらいの手間や時間がかかるのか、そしてそのコストはプロジェクトにとって許容範囲内なのか、というような指標の方がテストというものを分類するためには適切なように思える。

TDD再考 (6) – テスト容易性 ≠ 良いデザイン

フィーチャーブランチを利用した開発はチームを継続的インテグレーションから遠ざける

つい最近、たまチームでは Git Flow を利用したブランチ管理に移行したばかりだったので、この記事を見かけた時は一瞬ムムッとなったが、実際に読んでみて、なるほどと思える反面、どのようなブランチ管理を導入するかはそのプロジェクトの性格に依る部分も大きいのではないかと感じた。というわけで、今回はブランチ管理とアジャイルの関係、そしてその理想からはやや距離を取ることになったたまチームの現状についてまとめてみたい。

Git Flow による Integration Feature Branching

以下は、Steve Smith氏によるブランチ管理方法の4分類:

これまでたまチームは、Gitのブランチ管理に関しては何も方針を決めておらず(単純に master に push されたものがステージング・本番、双方へのデプロイ可能なバージョンとしてビルドされる)、結果的に Trunk Based Development という、今回の記事で推薦されている、ブランチを極力作らないシンプルな開発モデルになっていたのだが、後述する事情で以下のような Vincent Driessen氏提案のブランチ管理に移行した。

このブランチモデルについては、Driessen氏による図を見てもらうのが一番分かりやすい。

git-model@2x

基本のブランチとして master と develop の二つを用意し、developが開発用の基本ブランチ、masterが本番用のブランチとなる。開発の基本的なフローは、「フィーチャーの開発」と「リリース」の二つが軸となり、それぞれ、

  • フィーチャー
    • フィーチャーブランチを作って、終わったら develop にマージ
  • リリース
    • リリースブランチを作って、終わったら master と develop の双方にマージ
    • masterにマージしたところで、リリースバージョンのタグを付けておく

これらのフローにまつわる git の操作を簡単にしてくれるのが、git-flow というgitコマンドの拡張である。以下のようなコマンドを打つだけで、ブランチの作成から移動・マージまで全部自動でやってくれる。

$ git flow feature start feature_name
... (code and commit)
$ git flow feature finish feature_name

Smith氏は、この Git Flow によるブランチ管理を Integration Feature Branching と呼び、継続的インテグレーションを実現するためには全くお勧め出来ないとしている。

フィーチャーブランチと継続的インテグレーション

フィーチャーブランチと継続的インテグレーションの関係については、2009年に書かれた Martin Fowler氏の記事でより詳細に説明されている。

今回の InfoQ や Fowler氏の記事で説明されているフィーチャーブランチの問題点を一言で言えば、共有ブランチに対する変更の粒度が大きくなるため、マージの際のトラブルが起こりやすくなる、ということになるだろうか。

フィーチャーブランチのメリットは、共有のブランチに影響を与えることなく個々の機能を独立して開発出来ることである。一つ一つの機能がブランチとして分かれることによって、リリース(マージ)する機能をその場で選択する cherry-picking のような運用が可能になる。しかし、独立しているが故に、個々の機能の間のコミュニケーションが不十分になり、いざ共有ブランチでマージしようとした時に、解決の難しい衝突を引き起こす可能性があるとFowler氏は指摘する。

衝突のリスクは開発者に心理的な影響を及ぼす。例えば、自分のフィーチャーブランチで行った変更が、他人のフィーチャーブランチに対して意図せぬ影響を及ぼすのが怖くなり、大胆なリファクタリングがやりづらくなる。これは一つ一つのフィーチャーブランチが大きくなればなるほど顕著になるだろう。

継続的インテグレーションのそもそもの目的は、プログラム全体を頻繁に結合することによって、結合に関する問題の粒度を小さくしようということであった。つまり、フィーチャーブランチの考え方は、そもそも継続的インテグレーションと相容れないということになる。

インクリメンタルな開発を実現するためのコスト

継続的インテグレーションの理想は、全てのコミットがリリース可能な単位になることである。その理想状態の元では、当然のことながらブランチを分けて開発する必要は無くなる。しかし、そのようなインクリメンタル開発を実現するコストは決して低くない。例えば、一つの機能の開発が一つのコミットで完了することはむしろ稀だろう。一つのコミットで終わらない場合、その中途半端に実装された機能を抱えたシステムをどのようにリリースすれば良いのか? その問題に対処するために提案されているのが FeatureToggleBranchByAbstraction と言ったテクニックである。

上のようなテクニックがあったとしても、全てのコミットをリリース可能にするためには、Steve Smith氏も認めているように、かなりの訓練と経験を必要とする。これは以前の記事でも書いたように、システムをそれぞれ完結した vertical slice 単位で開発しなければならないアジャイルの本質的な難しさである。

たまチームの場合

理想的な継続的インテグレーション、さらにはその到達点である継続的デリバリーを実現するためには、いつでもカジュアルに本番を更新できるような体制と環境を整えておかなければならない。それは技術的な面だけではなく、文化的な面でも高いハードルをクリアしておく必要があり、残念ながらたまチームの場合は、まだそこまでのレベルに達しているとは言い難い。カジュアルに本番を更新出来ない場合、つまり本番更新の頻度が比較的少ない場合、ステージングと本番でビルド(ブランチ)を共有しているといろいろと不便なところが出てくる。例えば、どのビルドが本番に上がっているかが分かりづらくなったり、本番に対する Hotfix がやりづらくなったりする。そういった問題に対処するためにステージングと本番でブランチを分けておいた方が良いのではないかという話になり、Git Flow によるブランチ管理を採用するに至った。

フィーチャーブランチについては、現状だと一つのリポジトリは基本的に一人の開発者が担当していることが多いので、それほどこだわらずに開発者個人の裁量に任せる感じで問題ないように思える。しかし、これが複数人で共同開発するようになった場合は、フィーチャーブランチの扱いには十分気をつけなければならないだろう。

UberやAirbnbが経済にもたらす革命的なインパクト

オライリーメディアの創始者であるTim O’Reilly氏の論考。「シェアリング・エコノミー」と呼ばれる、UberやAirbnbといった新時代のサービス。このようなサービスの出現は、単なる雇用形態の変化などではなく、これまで我々が当たり前だと思っていた経済の構造を、根本から変えてしまうようなインパクトを持つ歴史的な出来事なのだと O’Reilly氏は主張する。

「会社」というものが存在する理由

現代社会で働いて生活する我々は、ほとんど例外なく何らかの形で「会社組織」と関係している。多くの人は会社に所属して働いているだろうし、そうでない人もどのような形であれ会社というものに関わりを持っているはずである。今となっては我々の経済活動から会社というものを切り離すのは不可能であるように思える。では、この会社というものが存在する理由は一体何なのだろうか?

アダム・スミスが『国富論』を発表した18世紀。多くの経済学者は、経済システムがうまく機能するために「中央管理」は必要ない、むしろ有害でさえあると考えていた。市場の自律的な仕組みに任せておけば、いわゆる「(神の)見えざる手」によって自然にバランスが保たれ、経済はうまく回るはずであると。

この分散型自律経済の考え方はその後もしばらく経済学の主流であったが、20世紀に入って初めてその考え方に疑問を呈したのがアメリカの経済学者ロナルド・コース(Ronald Coase)である。コースは、市場の中で取引が成立するためには、自律型経済の考え方では想定されていないコストが必要になる事を発見した。例えば、需要と供給はどうやってお互いを発見するのか、価格を決定するための交渉はどうするのか、契約も締結しなければならないし、立場の違いから揉め事も起こるだろう。このような問題を解決するためのコストを「取引コスト(transaction costs)」と呼び、このコストこそがマネジメントや会社組織の存在が要請される根本要因だとした。

21世紀型フランチャイズの出現

コースの理論によれば、会社によって提供されるマネジメントのコストが市場で本来かかる取引コストを下回る限り、会社は存在し続ける事ができる。しかし、会社が大きくなって、その管理コストが市場の取引コストを上回るようになると、会社の存在意義は怪しくなる。この規模拡大によるコストを抑えようとして生まれたのがフランチャイズの仕組みである。マネジメントを行う組織を出来るだけ小さくし、各地に分散した事業者はネットワークを形成してそのマネジメントのサービスを受ける。

しかし、もし市場でかかる取引コストが劇的に下がって、マネジメントにかかるコストを下回った場合、どのようなことが起こるだろうか。そのようなことが実際に起きているのが、今我々が現在進行で経験している「インターネットの時代」である。インターネットが取引コストを劇的に削減する、というのは、GoogleやAmazonがそれまでの市場をどう変えたかを考えれば、そのインパクトを容易に想像出来る。それまでのフランチャイズの仕組みでは避けようがなかった店舗や販売にかかるコストがほぼゼロになり、さらには経験を積んだ管理職が担当していた、商品と顧客のマッチングもソフトウェアによって置き換えられた。つまり、それまでに存在していた会社組織は、その多くが(店舗や一部のマネジメントに至るまで)不要となり、その存在意義や構造を大きく見直さなければならなくなった。

このインターネットによる革命の新たな波がUberやAirbnbといったシェアリング・エコノミーである。シェアリング・エコノミーによって生まれた新しい経済構造を、O’Reilly氏は「個人のフランチャイズ化(The franchise of one)」と呼ぶ。テクノロジーの進歩によって、フランチャイズの末端を構成していた小さな事業者のネットワークは、自身のリソース(Uberでは車とその運転、Airbnbでは家)をパートタイムで提供する「個人」のネットワークに置き換わった。

個人のフランチャイズ化によって、会社のフランチャイズでは提供出来なかったような広大な選択肢(「excess capacity」と呼ばれる)を顧客に提供出来るようになる。例えばUberの場合で言えば、広大な選択肢とは、これまで想定しなかったような場所やタイミングでタクシーを利用出来るようになるということであり、この選択肢の増大が結果としてUberの利用者を増やし、それが価格を下げてさらに需要を拡大するという好循環を生むことになる。

集中型から分散型、そしてまた集中型へ

ロナルド・コースによって理論的な根拠が与えられた集中型の経済構造は、インターネットが出現して18世紀にアダム・スミスが信奉した分散型に形を変えた。個人のフランチャイズ化によって、その傾向はますます強くなっているように見えるが今後はどのように変化していくのだろうか。O’Reilly氏は、いずれまた集中型へ向かうだろうと予測する。インターネットが出現したとき、それは個人と個人を結びつけるネットワークだった。それが時を経て、今では情報の流通を仲介するYahooやGoogleような数多の企業が生まれている。シェアリング・エコノミーの世界でも同様の事が起こるだろうというのは確かに想像に難くない。

アジャイルがうまくいかないのは何故か?(2015年版)

The GROWS Method™

「アジャイルの失敗」という話は、今となっては特に目新しいものでもない。筆者が記憶している限りで古いものだと、2008年に書かれた James Shore氏の「The Decline and Fall of Agile」という記事がある。

「ソフトウェア開発に銀の弾などない」と、あらゆるIT専門書のイントロダクションに断わりが入れてあるのに、未だに「○○を試したけど効果がない」という苦情が後を絶たないのは何故なのだろうか。

今回のブログ記事「The Failure of Agile」を書いた Andrew Hunt氏は、「達人プログラマー」の共著者であり、「アジャイルソフトウェア開発宣言」の発起人17名の中の一人でもある。彼は、7年前の James Shore氏と同じく、アジャイルがうまく行かないのはアジャイルを正しく実践していないからだと指摘する。

具体的にはこういうことである。初心者がアジャイル開発を始めるに当たっては、何かリファレンスになるような書籍やルールを参照するのが普通である。しかし、ほとんどの初心者はルールに従うという最初の段階から先に進む事ができない。アジャイルは参加者に「変化」を求める。既存のルールに問題があれば、そのルールを変えて、自分たちの状況にあったルールを新たに生み出し、そしてそれを継続的に更新していかなければならない。アジャイルを継続的に行うためには、アジャイルの書籍に書かれている事にこだわっていてはいけない、というねじれを乗り越えて行かなければならない。その上、コミュニティの中には正規のプラクティスを実践しているかどうかに執拗にこだわるアジャイルポリスみたいな人たちがいる(cf. TDDポリス)。ますますアジャイル本来の意図からは遠ざかるわけである。

この状況を打開するため、Hunt氏は「GROWS™」という手法を提案している。GROWSは「GRowing Real-World Oriented Working Systems」の略語にもなっており、これは商標として扱われている。誰でも自由に使えるようにしてその本来の意味を歪められてしまったアジャイルの轍を踏まないように、というのが商標にした理由らしい。

GROWS™の詳細は現段階でまだ明らかにされていないが、基本のステップを「実験」と捉えることや、フィードバックに対するエビデンス(証拠)ベースの判断等、かなりリーン・スタートアップに近い手法なのではないかという印象を受けた。

さて、言葉を占有して解釈を限定させようという方法は、本当に良い方法だろうか? 個人的にはそう思えない。オブジェクト指向やアジャイルという考え方は、本質的には、物事のInsight(見識)を提供してくれるものであって、何らかの手順を踏めばプロジェクトがうまく行くといったような処方箋を与えるものではないと思う。もしそれらの考え方が役に立たなかったのであれば、それは単に受け手側の問題である。自分(の状況)には合わなかったと思って次をあたるべきだ。しかしながら現実には、以前の記事で書いたように、ある考え方が流行るとその周辺をコンサルタントやコンサルティング会社が商機を求めて集まってくる。そこで「○○を使ったらお宅のプロジェクトもうまく行きますよ」という具合に喧伝する。その中でことごとく裏切られた期待が炎上を引き起こし、Andy Hunt氏や James Shore氏のようなコミュニティを代表する人たちが火消しに走らなければならなくなる。この風景はソフトウェア開発産業に限らず、セールスマンが存在する限り、あらゆるところで繰り返されているのだろう。

さらに思うのは、アジャイルのような手法が難しい原因は、ほとんど政治的な問題に収斂するのではないかということである。その内容が誰かに歪められたから正しいアジャイルが実践されなくなったというよりも、従来的な組織の中でアジャイルになるというその事自体が根本的な矛盾を孕んでいるのではないか。アジャイル以後にリーン・スタートアップが出てきたのも、このことに関係しているのかもしれない。そもそもスタートアップのようなラディカルな環境でないとアジャイルというのは成立しないのではないか。

アジャイルを完全体にするインパクトマッピング

ソフトウェア開発は、開発の根拠となる「必要」をどう捉えるか、その考え方の変遷とともに進化してきたと言えるかもしれない。

例えばウォーターフォール開発では、「必要」は疑いの無いもの、予め決定可能なもの、固定のものとして存在していた。製造業において当たり前のように繰り返されて来た、青写真(設計図)があって、それを実現(施行)するという手法である。その後、ソフトウェア開発において「必要」は予め決定出来ないという問題意識の元、計画・実施を細かく繰り返してフィードバックを得られるようにし、ビジネスと開発で密なコミュニケーションを取りながら本当の「必要」を探る、アジャイルという手法が生み出された。そして更には、計画段階で提案された仮説を、計測に基づく客観的な検証にかけて「必要」の精度を上げようというリーンスタートアップに辿り着いた。

インパクトマッピングは、「必要」の背後にある根拠をマインドマップで図示化することによって、アジャイルやリーンスタートアップが見過ごしていた部分を補完し、ソフトウェア開発を更に一歩先に進めるための技法である。

インパクトマッピングの問題意識は、アジャイルと同じところから出発する。我々が見当違いなものを作ってしまう、あるいは必要とされるスピード感がなかなか得られないのは、開発プロジェクトが役割で分断されているからである。ビジネスはビジネスの言葉を語り、エンジニアはエンジニアリングの言葉を語る。利害も一致しない。このようなコミュニケーションの問題をまず解決すべきであるというのがアジャイルの問題意識であった。そこでアジャイルはフィードバックにフォーカスを当てる。イテレーションによりフィードバックの機会を増やし、決して読まれない重厚なドキュメントを廃して、ユーザーストーリーと動くソフトウェアによるコミュニケーションの効率化を計った。

アジャイルによって色んな役割の人間が同じ船に乗り込み、効率の良いコミュニケーションが可能になったが、それでも最後に大きな問題が残されていた。それは、その船がどこに向かっていて、そして何故そこに向かっているかという航海のビジョンをどのように共有するかという問題である。いくらフィードバックを頻繁に密に実施していても、何のためにその作業をするのかという根拠が、単にステークホルダーやマネジャーがそのように言っているから、というだけでは、エンジニアは依然として下請け作業者のままである。チーム全体で「何故」を共有しなければ、マネジャーはエンジニアから知恵を引き出す事は出来ず、ゴールに最短で辿り着くのは難しくなってしまう。

そこでインパクトマッピングは、「我々はどこに行くのか?」というゴールをマインドマップの中心に配置し、そこから具体的なプロダクトを導出する事によって、作業に根拠を与えて、開発チームが自律的に動けるようにする。

map

上図のように、インパクトマッピングは極めてシンプルである。「ゴール(Why?)」に辿り着くためには、「誰に(Who?)」に働きかけて、「どのような変化(How?)」を生じさせるか? そしてその変化(これを「インパクト」と呼んでいる)を実現させるためには「何(What?)」をすればよいのか(作れば良いのか)? おそらく、ゴールを合理的に説明する手法として、これよりシンプルな方法はないのではないだろうか?

何を作るかの根拠は、いつでも中心のゴールを念頭に考える事が出来るので、作業の優先順位や重要度は、このインパクトマッピングを共有する人間全てがクリアに理解出来るようになる。今まではステークホルダーの頭の中だけにあったものが、マインドマップとして視覚化されるわけである。さらにこのマップは、情報の共有だけでなく、新たな「必要」を発見するための強力なツールとしても機能する。誰に働きかけるべきなのか、何を作るべきなのかは、もうマネジャーだけが考えるべき問題ではない。インパクトマッピングによって、参加者誰もがゴールを念頭にアイデアを出す事が出来るようになった。

以下は、インパクトマッピングの公式サイトで紹介されているオンラインゲーム開発におけるマッピングの例である。中心には「100万プレイヤー」というゴールが掲げられている。

im_example

創業者CEOがうまく行かないのは何故か? – プロダクト指向CEOのパラドックス

シリコンバレー最強のベンチャーキャピタルリストの一人」と呼ばれるベン・ホロウィッツ氏の記事。プロダクトの発案者がそのまま起業し、創業者CEOとなった場合に起こる問題について。

組織の規模が500人になるぐらいまでは、経営をしつつ、プロダクトの細かいところまで管理・コントロールすることが可能で、プロダクトの競争力も維持出来る。ところが、それ以上の規模になってくると、プロダクトに関する全ての意思決定に関わることが難しくなり、中途半端な意思決定をしてしまったり、現場への過剰な介入として従業員から反発を受けるようになる。かと言って、そこで現場に権限を委譲してしまうと、プロダクトは徐々にそのフォーカスを失い、日和見的な凡庸でつまらないものへと変貌を遂げてしまう。

ホロウィッツ氏によれば、マイクロソフトのビル・ゲイツ、オラクルのラリー・エリソン、スティーブ・ジョブズ、マーク・ザッカーバーグのような著名な経営者たちは、大規模な会社を経営しつつも、プロダクトの意思決定から完全に手を引く事なく、以下のような、プロダクトに関する本質的な意思決定にのみに関わる事によって、このパラドックスを回避しているらしい。

  • ビジョンの方向性を維持する
    • ビジョンの全体を作る事はせず、その方向性をコントロールする。
  • 品質基準を維持する
  • プロダクトの統合を担当する
    • プロダクトラインに共通する部分についての意思決定。
  • メトリクスに現れない情報について考えさせる

現実にこのようなシフトを行うのは容易ではなく、そのために必要なプロセスは人それぞれであるが、一般的に役に立つであろうプラクティスとして以下のものが紹介されている。

  • 口頭やメールで伝えるのではなく、正式なドキュメントを書く
    • ビジョンを明確化しつつ、過剰な参画を避ける。
  • 正式な定例レビューを行う
    • レビューではビジョンとの整合性、デザイン品質、進捗等をチェックする。中途での方向性転換は避けること。
  • 正式な経路以外で現場のやり方に口出ししないこと
    • 状況を把握するために現場の人間とコミュニケーションを取るのは問題ないが、恣意的なディレクションのような介入は避ける事。

TDD再考 (4) – TDDはプログラム初心者にとって自然な方法になり得るか?

前回取り上げた、TDDが解決しようとした問題の一覧を眺めていると、それらの問題は Dave Thomas氏が言うところのアジャイルの価値とも共通する部分が多く、TDDというのはアジャイル手法全体のメタファーとなるような、言わばミニアジャイルのような存在と言っても差し支えないように思える。

間違っているかもしれないが、アジャイル以前から長くプログラムを生業としていた人ほど、テストファーストという考え方に抵抗感を抱くのではないだろうか?

TDD、あるいはアジャイルという考え方は、外部からの要件(「機能」)が、結果として、その機能を実現するために必要な「構造」を生み出す、という考え方である。これは、この命題だけに注目する限りは当たり前の事実のようにも思える。しかし、プログラマは、経験を積み重ねる内に同じような構造のパターンに何度も遭遇することになる。そうやって頭の中に有用なコンセプトが蓄積されて行く。それらがイディオムとかデザインパターンなどと呼ばれたりする訳だが、TDDの問題意識は、それらのコンセプトの適用範囲は思ったほど広くなく、そして耐用年数は思ったよりも短く、濫用すれば過剰性能を作り込む罠に落ちるというものであった。

しかし、人間はどうしても知識を貯えてしまうので、解法を先に考えてしまうというバイアスから逃げるのは至難の業である。そしてこれが、経験者ほどテストファーストに抵抗感を抱くのではないかという、推測の根拠である。

要件が構造を生み出す、というロジックはとても反論しづらい。何のためにソフトウェアを作るのかということを考えれば、それが至極当然の流れのように思える。ミニアジャイルであるTDDがプログラマの間でドグマ化してしまうのも頷ける気がする。

では、経験者のバイアスが存在しない初心者のプログラマにとって、TDDは自然な手法になり得るのだろうか?

TDD以前では、プログラムデザインのフォーカスはやはりプロダクトコード側にあって、より良いモジュール、あるいはより良いオブジェクトのデザインは何かということについて、様々なデザイン原則やパターンなどを学習し、デザインの質を高める事によって、プロダクトの保守性や信頼性を担保するという方法が一般的であった。

TDDの出現によって、プログラミングのフォーカスは、プロダクトコードからテストコードに移動する。テストをしやすいようにプロダクトコードをデザインすると、結果的に、モジュール間の疎結合を実現でき、自然によいデザインへと導かれるというわけだ。テストコードを検証すれば要件との齟齬をチェックでき、テスト自体を実行してプロダクトコードの正しさも確認出来るという、まさに一石二鳥・三鳥の方法であった。

実際に筆者も以前、初心者はTDDのやり方から学べば、自然にプログラムデザインも学べるし、管理者視点からは要件との齟齬もチェックしやすく、なかなか良いアプローチではないかと考えていた時期もあった。

しかし、実際には様々な障害があることが分かった。例えば、テスト駆動で小さなステップを踏むことの難しさである。

think of a test that will force you to add the next few lines of production code. This is the hardest part of TDD because the concept of tests driving your code seems backwards, and because it can be difficult to think in small increments. (James Shore: The Art of Agile Development: Test-Driven Development)

TDDでは、API視点からプログラムをデザインすると言っても、実際はインクリメンタルに開発するために、どういうテストを書けばプロダクトコードの変更を最小限に出来るかというアクロバティックなことを考えなければならない。これは、TDDに限らず、vertical slice単位で開発するアジャイルの難しさの中心的な課題といってもよく、とても初心者が太刀打ち出来る問題とは思えない。

もう一つ例を挙げると、James O Coplien氏の指摘にも通じるが、自動テストが(現実的に)カバー出来る範囲というのは思ったより広くない、ということである。

これはつい最近、たまチームで遭遇したばかりの出来事だが、テスト(外部)からは期待通り動作しているように見えるのだが、実はプラスアルファの余計なことをやっていて、関係ないデータを壮大に破壊していたという事例があった。原因は、ライブラリのAPIについての理解が足りなかったという、ごく初歩的な問題である。こういった問題を考えると、テストプログラムで担保出来る信頼性など、特に初心者を対象とした場合、取るに足らないことが分かる。まさに、Coplien氏が言うところの「テストがコードのクオリティを上げる訳ではない、上げるのは開発者自身である」。

TDD再考 (5) – ユニットテストの「ユニット」とは何を指すのか?

TDD再考 (3) – TDDが解決しようとした問題は何か?

DHH氏の「TDD is dead」を受けて、TDDの生みの親である Kent Beck氏が Facebook に投稿した記事。

いささかアイロニカルな反論ではあるが、TDDが解決していた(とBeck氏が考える)問題の一覧が挙げられているので、以下にまとめておく。

  • 過剰性能(Over-engineering)
    • どうやって作り過ぎに対応するか?
    • アジャイルでいう YAGNI
    • 要件を満たす最低限の実装に留める(Just enough)
  • APIフィードバック
    • API設計の妥当性をどのように検証するか?
    • プログラムを利用者視点(API視点)でデザインできる
  • 論理エラー
    • コンパイラでは捕捉出来ないエラーをどうやって発見・防止するか?
  • ドキュメンテーション
    • プログラム(API)の利用方法をどのように伝えるか?
  • 大きな問題
    • 一度に解決しようとするとハマってしまいそうな大きな問題にどう立ち向かうか?
    • 簡単に解ける小さな問題を積み上げていく(小さなステップ)
  • インタフェースと実装の分離
    • どうやってAPIが実装依存にならないようにするか?
  • 問題共有
    • どうやってプログラマ同士の問題共有を正確に行うか?
  • 不安
    • どうやってプログラムが期待通りに動作している事を確認するか?

このようにして、TDDが解決しようとしていた問題点を並べてみると、問題そのものの妥当性にはそれほど異論はないように思えるが、その解決策については必ずしもTDDに限る必要はないようにも思える。前回の記事に書いたように同じ価値を実現できるなら、必ずしも誰もが同じ方法をとる必要はない。

テストを先に書かなくても、一つ一つのステップを小さくする事は出来るし、小刻みな Test-Last アプローチでも、API視点でプロダクトコードを検証する事は出来る。エラーの抑止、リグレッションテスト、あるいはドキュメンテーションという観点から言えば、テストをいつ書くかは問題にならないはずである。

あるいは、DHH氏やJames O Coplien氏が指摘しているように、ユニットテストにフォーカスし過ぎる事は、より重要で本質的な問題、つまりビジネス価値の軽視につながる可能性もある。これは、TDDの手法そのものではなく、その問題設定に対する疑義である。

TDD再考 (4) – TDDはプログラム初心者にとって自然な方法になり得るか?