SocialCalc

スプレッドシートは 30 年以上の歴史を持ちます。最初のスプレッドシートプログラム VisiCalc は Dan Bricklin によって 1978 年に構想され、1979 年に完成しました。根本的な考え方はとても単純です: 二次元に無限に広がる表があり、各マス (セル) にテキスト・数・式が入るというものです。式では基本的な算術演算やたくさんのビルトイン関数が利用でき、他のセル要素を値として参照できます。

スプレッドシートの考え方は単純ですが、応用は多岐にわたります: 会計、物品管理、リスト管理などはその一例です。その可能性は事実上無限であり、VisiCalc は個人用コンピューターにおける最初の「キラーアプリ」となりました。

スプレッドシートはそれから数十年の間に登場した Lotus 1-2-3 や Excel といったソフトウェアで段階的に改善されましたが、その中心的な考え方は変わりません。スプレッドシートはディスクにファイルとして保存され、編集のときにメモリに読み込まれます。このファイルベースのモデルでは、協調動作が非常に難しくなります:

幸いにも、エレガントな単純性を持ってこういった問題を解決する wiki という新たな協調モデルが生まれました。wiki は Ward Cunningham によって 1994 年に開発され、2000 年代初頭に Wikipedia によって広まりました。

wiki モデルはファイルではなくサーバーでホストされるページを扱います。ページは特別なソフトウェアをインストールせずともブラウザから編集が可能であり、ハイパーテキストを使って互いをリンクしたり、他のページの一部分を取ってきて大きなページを作ることができます。利用者はデフォルトで最新のバージョンを閲覧、編集し、編集履歴はサーバーによって自動的に管理されます。

wiki モデルに影響されて、Dan Bricklin は WikiCalc の開発を 2005 年に開始しました。WikiCalc の目標は、wiki が持つ複数人編集の簡単さと、スプレッドシートが持つなじみ深い視覚的な書式と計算の考え方を両立することです。

WikiCalc

WikiCalc の最初のバージョン (図 19.1) には、当時の他のスプレッドシートとは一線を画す機能がいくつかありました:

WikiCalc 1.0 のインターフェース
図 19.1. WikiCalc 1.0 のインターフェース
WikiCalc のコンポーネント
図 19.2. WikiCalc のコンポーネント
WikiCalc のフロー
図 19.3. WikiCalc のフロー

WikiCalc 1.0 の内部アーキテクチャ (図 19.2) と情報フロー (図 19.3) は単純でありならもパワフルであるよう注意深く設計されています。中でも特に有用なのが、マスターのスプレッドシートを小さなスプレッドシートに分割する機能です。例えば営業担当の社員が自身の成績をスプレッドシートのページに打ち込み、営業部長が部下の成績を地域スプレッドシートにまとめ、VP がそれをさらにトップレベルのスプレッドシートにまとめる、といった使い方ができます。

あるスプレッドシートが更新されると、そのシートを参照する全てのシートが更新されます。詳細が知りたい場合には、何回かクリックすれば更新があったスプレッドシートを確認できます。この機能のおかげで、数字の更新を複数の場所に打ち込むという冗長でミスの起こりがちな作業は必要なくなり、さらに目にする全ての情報が最新のものであることが保証されます。

再計算が最新のデータを使うことを保証するために、WikiCalc は全ての状態データをサーバーに保存する薄いクライアント (thin-client) デザインを採用しています。スプレッドシートはブラウザ上で <table> 要素として表されます。セルを編集すると ajaxsetcell の呼び出しがサーバーに送信され、これを受けてサーバーはブラウザにどのセルを更新するかを返します。

当たり前ではあるのですが、このデザインはブラウザとサーバーの間の高速な接続を仮定しています。レイテンシが高い環境では、ユーザーはセルの値の更新が反映されるまでの間に “Loading...” というメッセージを頻繁に目にすることになります (図 19.4)。入力をいじってインタラクティブに式を編集するような、結果がリアルタイムに見える必要のある使い方をするユーザーにとって、これは特に問題です。

読み込みメッセージ
図 19.4. 読み込みメッセージ

さらに <table> 要素がスプレッドシートと同じ次元を持つので、100×100 のグリッドは 10,000 個もの <td> DOM オブジェクトを生成します。これはブラウザのメモリリソースを食いつぶし、ページのサイズがさらに制限されます。

こういった欠点があったので、WikiCalc は localhost で動作するスタンドアローンのサーバーとしては便利だったものの、ウェブベースのコンテンツ管理システムとしてページに埋め込むという使い方はあまり実用的ではありませんでした。

2006 年、Dan Bricklin は Socialtext とチームを結成し、SocialCalc の開発を開始しました。SocialCalc は WikiCalc を JavaScript で根本から書き直したものであり、一部はオリジナルの Perl コードを元にしています。

この書き直しでは地理的に離れた大規模なユーザーの協調が目標とされ、デスクトップアプリケーションと変わらない見た目と操作感が追求されました。その他の設計目標を次に示します:

三年の開発期間と数回のベータリリースを経て、Socialtext はこの設計目標を満たした SocialCalc 1.0 を 2009 年にリリースしました。SocialCalc システムのアーキテクチャを見ていきましょう。

SocialCalc

SocialCalc のインターフェース
図 19.5. SocialCalc のインターフェース

図 19.5図 19.6 に SocialCalc のインターフェースとクラスをそれぞれ示します。WikiCalc と比べると、サーバーの役割は大きく削減されています。サーバーの唯一の役目は HTTP GET を受け取ったときに save フォーマットのスプレッドシートを送り返すことだけです。ブラウザがデータを受け取ると、それからの更新の検出や計算、およびユーザーとの対話は全て JavaScript で実行されます。

SocialCalc のクラスダイアグラム
図 19.6. SocialCalc のクラスダイアグラム

JavaScript 要素は層化 MVC (layered Model/View/Controller) スタイルで設計されており、それぞれのクラスが一つのことだけに注力します:

属性
datatype t
datavalue 1Q84
color black
bgcolor white
font italic bold 12pt Ubuntu
comment Ichi-Kyu-Hachi-Yon
表 19.1 セルの要素と書式

クラスの利用を最小限にしたオブジェクトシステムを採用しており、合成と移譲は単純で、オブジェクトのプロトタイプや継承は一切使っていません。シンボルは全て SocialCalc.* に配置され、名前の衝突を避けるようになっています。

シートの更新は全て ScheduleSheetCommands メソッドを経由します。このメソッドは実行する編集を表すコマンド文字列を入力として受け取ります (例を 表 19.2 に示します)。SocialCalc を埋め込むアプリケーションから独自のコマンドを追加することも可能であり、そのときには SocialCalc.SheetCommandInfo.CmdExtensionCallbacks オブジェクトに名前を付けたコールバック関数を追加して startcmdextension で呼びます。

コマンドの例
set sheet defaultcolor blue
set A width 100
set A1 value n 42
set A2 text t Hello
set A3 formula A1*2
set A4 empty
set A5 bgcolor green
merge A1:B2
unmerge A1
erase A2
cut A3
paste A4
copy A5
sort A1:B9 A up B down
name define Foo A1:A5
name desc Foo Used in formulas like SUM(Foo)
name delete Foo
startcmdextension UserDefined args
表 19.2 SocialCalc のコマンドの例

コマンド実行ループ

動作をレスポンシブにするために、SocialCalc はセルの値の再計算と DOM の更新をバックグラウンドで行います。このためユーザーが複数のセルを立て続けに変更したとしても、エンジンがコマンドキューを使って変更を順番通りに処理します。

SocialCalc のコマンド実行ループ
図 19.7. SocialCalc のコマンド実行ループ

コマンドが実行されている間、TableEditor オブジェクトは busy フラグを true にします。この間その他のコマンドは deferredCommands キューにプッシュされ、実行の順番が保たれるようになります。図 19.7 中のイベントループが示すように、Sheet オブジェクトは StatusCallback イベントを事あるごとに送って現在コマンド実行のどの段階にあるかをユーザーに伝えます。コマンドの実行には四つのステップがあります:

全てのコマンドは実行時に保存されるので、操作の完全なログが自然に手に入ります。Sheet.CreateAuditString メソッドを使えばコマンドが一行ごとに含まれる改行で分割された履歴文字列を得られます。

ExecuteSheetCommand は実行するコマンドごとに undo コマンドを生成します。例えば A1 に “Foo” が入っている状況でユーザーが set A1 text Bar を実行すると、undo コマンド set A1 text Foo が undo スタックにプッシュされます。ユーザーが Undo をクリックするとこの undo コマンドが実行され、A1 の値が元の値に戻ります。

表エディタ

続いて TableEditor レイヤーを見ていきます。このレイヤーは RenderContext のスクリーン上の座標を計算し、垂直および水平のスクロールバーを二つの TableControl インスタンスを使って管理します。

TableControl のインスタンスがスクロールバーを管理する
図 19.8. TableControl のインスタンスがスクロールバーを管理する

RenderContext クラスによって管理されるビューレイヤーも WikiCalc の設計と異なっています。各セルを <td> 要素に結び付けるのではなく、ブラウザの表示領域に収まる固定サイズの <table> を最初に作り、前もって <td> 要素を詰めておくという設計になっています。

ユーザーが特別に描画されたスクロールバーを使ってスプレッドシートをスクロールすると、SocialCalc は最初に詰めておいた <td> 要素の innerHTML を更新します。こうすると多くの場合 <tr> 要素や <td> 要素を生成・削除しなくて済むので、反応速度が大きく向上します。

RenderContext は見えている領域だけをレンダリングするので、Sheet オブジェクトのサイズを任意に大きくしてもパフォーマンスは低下しません。

TableEditor には CellHandles オブジェクトも含まれます。これは現在編集中のセル (current editable cell, ECell) の右下にある放射状のメニューで、fill/mode/slide の操作が行えます (図 19.9)。

Current Editable Cell (ECell)
図 19.9. Current Editable Cell (ECell)

入力ボックスは二つのクラスが管理します: InputBoxInputEcho です。InputBox は格子の上にある編集行を管理し、InputEcho は ECell に被さってタイプと同時に更新されるプレビューレイヤーを管理します (図 19.10)。

二つのクラスによって管理される入力ボックス
図 19.10. 二つのクラスによって管理される入力ボックス

通常、SocialCalc エンジンがサーバーと通信をするのは編集するスプレッドシートを開くときと、編集が終わったスプレッドシートをサーバーに保存するときだけです。このときには Sheet.CreateSheetSave メソッドが save フォーマットの文字列を Sheet オブジェクトにパースし、Sheet.CreateSheetSave メソッドが Sheet オブジェクトを save フォーマットに変換します。

セルの式では URL を使って任意のリモートスプレッドシートの値を参照できます。recalc コマンドは参照されている外部スプレッドシートを再フェッチし、Sheet.ParseSheetSave を使ってもう一度パースします。このときキャッシュにパース結果を保存して、同じリモートスプレッドを参照する他のセルがあった場合にフェッチせずに値を読めるようにします。

save フォーマット

SocialCalc が使う save フォーマットは標準的な MIME (multipart/mixed) フォーマットであり、四つの text/plain; charset=UTF-8 パートを持ちます。各パートは改行で区切られたテキストであり、フィールドはコロンで区切られます。四つのパートは次の通りです:

例えば 図 19.11 のスプレッドシートには三つのセルがあり、A1 には 1874 が、A2 には 2^2*43 という式が、A3 には SUM(Foo) という式があり、A1 が ECell で、A3 は太字でレンダリングされています。また A3 は Foo と呼ばれる区間 A1:A2 を参照しています。

三つのセルを持ったスプレッドシート
図 19.11. 三つのセルを持ったスプレッドシート

このスプレッドシートをシリアライズした save フォーマットは次のようになります:

socialcalc:version:1.0
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=SocialCalcSpreadsheetControlSave
--SocialCalcSpreadsheetControlSave
Content-type: text/plain; charset=UTF-8

# SocialCalc Spreadsheet Control Save
version:1.0
part:sheet
part:edit
part:audit
--SocialCalcSpreadsheetControlSave
Content-type: text/plain; charset=UTF-8

version:1.5
cell:A1:v:1874
cell:A2:vtf:n:172:2^2*43
cell:A3:vtf:n:2046:SUM(Foo):f:1
sheet:c:1:r:3
font:1:normal bold * *
name:FOO::A1\cA2
--SocialCalcSpreadsheetControlSave
Content-type: text/plain; charset=UTF-8

version:1.0
rowpane:0:1:14
colpane:0:1:16
ecell:A1
--SocialCalcSpreadsheetControlSave
Content-type: text/plain; charset=UTF-8

set A1 value n 1874
set A2 formula 2^2*43
name define Foo A1:A2
set A3 formula SUM(Foo)
--SocialCalcSpreadsheetControlSave--

save フォーマットは人間が読めるように設計されており、プログラムで生成するのも比較的簡単です。このため例えば Drupal による Sheetnode プラグインを使えば、PHP を使ってこのフォーマットを Excel (.xls) や OpenDocument (.ods) といったよく使われるフォーマットへ変換できます。

SocialCalc の各部分がどう結び付くか理解できたと思うので、次は現実世界の例において SocialCalc がどのように拡張されているかを見ていきます。

リッチテキスト編集

最初の例は SocialCalc のテキストセルを wiki のマークアップ言語で拡張して、表エディタでリッチテキストをレンダリングするものです (図 19.12)。

表エディタ内でリッチテキストをレンダリングする
図 19.12. 表エディタ内でリッチテキストをレンダリングする

この機能はバージョン 1.0 がリリースしてすぐに追加されました。画像やリンク、テキストのマークアップを統一した記法で利用したいというリクエストが多くあったためです。当時 Socialtext は既にオープンソースの wiki プラットフォームだったので、wiki の記法を SocialCalc でも使うのが自然でした。

これを実装するために、私たちは text-wiki という textvalueformat に対するカスタムレンダラ―を実装し、テキストセルのデフォルトフォーマットを変更しました。

textvalueformat が何かって? 読み進めてください。

タイプとフォーマット

SocialCalc のセルは datatypevaluetype を持ちます。例えばテキストあるいは数値の入ったデータセルはそれぞれ text と numeric という valuetype を持ち、datatype="f" である式セルは numeric あるいは text の値を生成します。

Render ステップにおいて Sheet オブジェクトがセルから HTML を生成していたことを思い出してください。このとき Sheet はセルの valuetype を調べます。もし valuetypet で始まるなら、セルの textvalueformat 属性を使って HTML の生成方法を決めます。もし n で始まるなら、代わりにセルの nontextvalueformat を使います。

しかしセルの textvalueformatnontextvalueformat がどちらも設定されていない場合には、セルの valuetype からデフォルトのフォーマットが検索されます。この様子を 図 19.13 に示します。

value type
図 19.13. value type

value format text-wiki に対する処理は SocialCalc.format_text_for_display に埋め込まれています:

if (SocialCalc.Callbacks.expand_wiki && /^text-wiki/.test(valueformat)) {
    // wiki マークアップを行う
    displayvalue = SocialCalc.Callbacks.expand_wiki(
        displayvalue, sheetobj, linkstyle, valueformat
    );
}

ここでは wiki 記法を HTML に変換する処理を format_text_for_display にインライン化することはせずに、新しいフックを SocialCalc.Callbacks に定義しています。これは SocialCalc のコードベースで推奨されるスタイルであり、様々な方法で wikitext を拡張できるようにしながらも、この機能を必要としない埋め込み用途での利用も可能にしています。

wikitext のレンダリング

次に使うのは wikitext と HTML の間の相互変換を提供する JavaScript ライブラリ Wikiwyg1 です。

セルのテキストを受け取ってそれを Wikiwyg の wikitext パーサーと HTML エミッターに渡す関数 expand_wiki を定義します:

var parser = new Document.Parser.Wikitext();
var emitter = new Document.Emitter.HTML();
SocialCalc.Callbacks.expand_wiki = function(val) {
    // val を wikitext から HTML に変換する
    return parser.parse(val, emitter);
}

最後のステップとして、set sheet defaulttextvalueformat text-wiki コマンドをスプレッドシートの初期化の直後にスケジュールします:

// DOM に <div id="tableeditor"/> があることを仮定している
var spreadsheet = new SocialCalc.SpreadsheetControl();
spreadsheet.InitializeSpreadsheetControl("tableeditor", 0, 0, 0);
spreadsheet.ExecuteCommand('set sheet defaulttextvalueformat text-wiki');

以上をまとめると、図 19.14 に示す Render ステップとなります。

Render ステップ
図 19.14. Render ステップ

これで完了です! この拡張によって、wiki マークアップシンタックスによる様々なリッチテキストが SocialCalc で利用可能になります:

 *bold* _italic_ `monospace` {{unformatted}}
 > indented text
 * unordered list
 # ordered list
 "Hyperlink with label"<http://softwaregarden.com/>
 {image: http://www.socialtext.com/images/logo.png}

A1 に *bold* _italic_ `monospace` と入力すれば、図 19.15 のようにレンダリングされたリッチテキストが表示されます。

wikywyg
図 19.15. wikywyg

リアルタイム共同編集

次に見る例は、共有スプレッドシートの複数人によるリアルタイム編集です。一見すると複雑に見えるこの問題も、SocialCalc のモジュラーな設計のおかげで、オンラインのユーザー全員に他のユーザーのコマンドをブロードキャストする処理を追加するだけで済みます。

ローカルのコマンドとリモートのコマンドを区別するために、ScheduleSheetCommands メソッドに isRemote パラメータを追加します:

SocialCalc.ScheduleSheetCommands = function(sheet, cmdstr, saveundo, isRemote) {
   if (SocialCalc.Callbacks.broadcast && !isRemote) {
       SocialCalc.Callbacks.broadcast('execute', {
           cmdstr: cmdstr, saveundo: saveundo
       });
   }
   // ...元々の ScheduleSheetCommands のコードが続く...
}

こうすれば後はコールバック関数 SocialCalc.Callbacks.broadcast を実装するだけです。この関数が動作すれば、あるユーザーが実行するコマンドが同じスプレッドシートに接続している全てのユーザーにブロードキャストされます。

この機能は OLPC (One Laptop Per Child2) 向けに SEETA の Sugar Labs3 で 2009 年に最初に実装されました。broadcast 関数は OLPC/Sugar ネットワークにおける標準的な転送方式である D-Bus/Telepathy への XPCOM 呼び出しを使っています (図 19.16)。

OLPC 実装
図 19.16. OLPC 実装

これで同一 Sugar ネットワーク内にある複数の XO インスタンスが共通の SocialCalc スプレッドシートを同時編集できるようになり、上手く行きます。しかしこの方法は、Mozilla/XPCOM というブラウザプラットフォームおよび D-Bus/Telepathy メッセージングプラットフォームに依存しています。

ブラウザ間の転送

ブラウザと OS をまたいだ動作を可能にするために、私たちは Web::Hippie4 を利用しました。このライブラリは WebSocket を通した JSON 通信を抽象化するもので、便利な jQuery バインディングと WebSocket が使えない場合の MXHR (Multipart XML HTTP Request5) へのフォールバックを持ちます。

WebSocket がサポートされていなくても Adobe Flash プラグインがインストールされていれば、web_socket.js6 プロジェクトが提供する Flash を使った WebSocket エミュレーションを利用できます。このエミュレーションは多くの場合 MXHR よりも高速で高い信頼性を持ちます。この様子を 図 19.17 に示します。

ブラウザ間の処理フロー
図 19.17. ブラウザ間の処理フロー

クライアントサイドの SocialCalc.Callbacks.broadcast は次のように定義されます:

var hpipe = new Hippie.Pipe();

SocialCalc.Callbacks.broadcast = function(type, data) {
    hpipe.send({ type: type, data: data });
};

$(hpipe).bind("message.execute", function (e, d) {
    var sheet = SocialCalc.CurrentSpreadsheetControlObject.context.sheetobj;
    sheet.ScheduleSheetCommands(
        d.data.cmdstr, d.data.saveundo, true // isRemote = true
    );
    break;
});

これは非常に上手く動作しますが、まだ解決すべき問題が二つあります。

衝突の解決

最初の問題はコマンドの実行順序に関する競合状態です: ユーザー A とユーザー B が同じセルを書き換える操作を同時に行ってその後コマンドを互いにブロードキャストすると、最終的な二人のスプレッドシートの状態が同じになりません (図 19.18)。

競合状態と衝突
図 19.18. 競合状態と衝突

この問題は SocialCalc に組み込みの undo/redo の仕組みを使って解決します。図 19.19 にこの様子を示します。

競合状態と衝突の解決
図 19.19. 競合状態と衝突の解決

衝突は次のように解決されます。クライアントがコマンドをブロードキャストするとき、まずコマンドを Pending キューに追加します。そしてクライアントがリモートコマンドを受け取ったときには、そのコマンドが Pending キューにあるかどうかをチェックします。

もし Pending キューが空なら、そのコマンドはリモート操作としてそのまま実行します。もしリモートコマンドと全く同じコマンドが Pending キューにあるなら、ローカルのコマンドをキューから取り除きます。

それ以外の場合には、クライアントはキューにあるコマンドが受信したコマンドと衝突するかを調べ、もし衝突するコマンドがあった場合にはその Undo 処理を行い、後で Redo するために印を付けておきます。衝突するコマンドを全て Undo し終わったら、受け取ったリモートコマンドを通常通り実行します。

その後 redo の印の付いたのと同じコマンドがサーバーから送られてきた場合には、クライアントはそれをもう一度実行し、キューからコマンドを取り除きます。

リモートカーソル

競合状態が解決されても、ユーザーが編集中のセルに誤って書き込んでしまうという事態を防ぐにはまだ不完全です。簡単にできる改善の一つに、カーソルの位置を他のユーザーにブロードキャストし、編集しているセルが見えるようにするというのがあります。

このアイデアを実装するには、MoveECellCallback イベントにも broadcast を追加します:

editor.MoveECellCallback.broadcast = function(e) {
    hpipe.send({
        type: 'ecell',
        data: e.ecell.coord
    });
};

$(hpipe).bind("message.ecell", function (e, d) {
    var cr = SocialCalc.coordToCr(d.data);
    var cell = SocialCalc.GetEditorCellElement(editor, cr.row, cr.col);
    // ...リモートユーザーに応じてセルに色を付けるなどの処理...
});

他ユーザーのセルをスプレッドシート内で目立たせるために、セルを色付きの枠で囲うことがよくあります。しかし、セルには既に border プロパティが定義されている可能性があり、その場合 border は単色なので一つのカーソルしか表すことができません。

そのため CSS3 をサポートするブラウザでは、box-shadow を使って同じセルにある複数のピアカーソルを表現するようになっています:

/* 同じセルにある二つのカーソル */
box-shadow: inset 0 0 0 4px red, inset 0 0 0 2px green;
一つのスプレッドシートを編集する四人のユーザー
図 19.20. 一つのスプレッドシートを編集する四人のユーザー

教訓

私たちが SocialCalc 1.0 をリリースしたのは 2009 年 10 月 19 日であり、最初の VisiCalc がリリースされてからちょうど 30 年後です。Dan Bricklin の指導の下 Socialtext 社で同僚と関わった経験は私にとって非常に価値のあるものでした。そのときに学んだ教訓をいくつかここで共有します。

主任設計者は明確なビジョンを

[Bro10] で Fred Brooks は、複雑なシステムを構築するときには、首尾一貫した設計コンセプトに集中すると (派生した表現を使ったときに比べて) 会話がずっと直接的になると主張しました。Brooks によると、そのような首尾一貫した設計コンセプトは一人の人間の頭にあるのが一番だと言います:

整合したコンセプトを持つことが偉大な設計の一番重要な要素であり、そしてそれは一人ないし数人の頭脳が 心を一つする (uni animo) ときに達成されるものであるから、賢明なマネージャーは設計のタスクを才能ある主任設計者に一任する。

SocialCalc の場合には、主任ユーザーエクスペリエンスデザイナの Tracy Ruggles がプロジェクトのビジョンを共通化する上での鍵でした。内部の SocialCalc エンジンは非常に柔軟なので機能を好き勝手に追加してしまう誘惑はとても大きかったのですが、そんな中で Tracy の設計スケッチを使ったコミュニケーションはユーザーにとって直観的な機能の提示方法を模索する上で大きな助けとなりました。

プロジェクトの継続性のために wiki を

SocialCalc プロジェクトに私が加入した時点で、設計と開発は二年に渡って続いていました。にもかかわらず私は一週間もしないうちに必要な知識を得て、コントリビューションを開始できました。これが可能だったのは全てが wiki に書いてあるからです。最初期の設計ノートから最新のブラウザサポートマトリクスに至るまで、開発プロセスの全てが wiki ページと SocialCalc スプレッドシートにまとめられていました。

プロジェクトのワークスペースを読み漁ることで他人と同じステージに立つことができ、新しいチームメンバーに付き物の細かい指導は必要ありませんでした。

議論が IRC やメーリングリストで行われ、wiki が (存在したとしても) ドキュメントや開発リソースへのリンクにしか使われない伝統的なオープンソースプロジェクトではこれは不可能だったでしょう。構造化されていない IRC のログやメールアーカイブから文脈を読み取るのは新参者にとって非常に困難です。

タイムゾーンの違いを受け入れる

Ruby on Rails の作者 David Heinemeier Hansson は、地理的に離れたメンバーからなるチームの利点に 37signals に加わってすぐ気付いたそうです: 「コペンハーゲンとシカゴの間にタイムゾーンが 7 つあるおかげで、互いに干渉せずにたくさんの仕事を片付けることができる」 SocialCalc の場合は台北とパロアルトの間の 9 つのタイムゾーンでしたが、私たちも同様の経験をしました。

私たちは設計-開発-QA というフィードバックサイクルを基本的に 24 時間で回し、それぞれの段階は現地時間昼間の 8 時間で行われました。この非同期的な労働スタイルは分かりやすいアウトプット (設計スケッチ、コード、テスト) につながり、互いの信頼関係を大きく向上させました。

楽しさを最適化する

2006 年の CONISLI カンファレンスのキーノート [Tan06] で、私は地理的に離れたメンバーからなるチームで Perl 6 言語を実装したときの経験をまとめました。そこで触れた「ロードマップを必ず持て」 「許しを請う > 許可を求める」 「デッドロックを排除せよ」 「探すのはアイデアであって合意ではない」 「アイデアをコードでスケッチせよ」といった観察は、規模の小さいチームであっても当てはまります。

SocialCalc の開発において私たちは、コードに触れる可能性のあるチームメンバーとの知識の共有に細心の注意を払い、重大なボトルネックになってしまうメンバーが出ないようにしました。

さらに意見が衝突したときには、複数の選択肢を実際にコードして設計空間を探り、前もって衝突を解決しました。より良い設計が出て来たときには現在動作しているプロトタイプを置き換えることも躊躇しませんでした。

こういった文化的な特徴は物理的に顔を合わせることない中で開発の見通しを明確化し、チームの結束を高めるのに一役買いました。ルールは最小限で済み、SocialCalc に取り組むのはとても楽しいものになりました。

ストーリーテスト駆動開発

Socialtext に加わる前の私は「テストと仕様に交互に取り組む」アプローチが良いと思っていました。例えば Perl 6 の仕様書7のように、言語仕様に公式のテストスイートを付けるというやり方です。しかし SocialCalc の QA チームの Ken Pier と Matt Heusser は、私の考えを次のレベルへと開眼させました。テストを実行可能な仕様として書く、というやり方です。

[GR09] の第十六章で、Matt はストーリーテスト駆動開発について次のように説明しています:

作業の基本単位は「ストーリー」である。ストーリーとは要件を記した極端に短いドキュメントであり、機能の簡単な説明と共に完了したときに起こるべきことの例を記す。「受け入れテスト (acceptance test)」と呼ばれるこの例は平易な英語で記述する。

ストーリーの初稿はプロダクトオーナーが受け入れテストを誠実に書くよう努力する。開発者とテスターがこれを補強し、それから初めてコードを書く。

ストーリーテストはその後 Ward Cunningham の FIT フレームワーク8に影響を受けたテーブルベースの仕様記述言語 wikitests に翻訳され、Test::WWW::Mechanize9Test::WWW::Selenium10 を使ってテストされます。

要件を表現・検証するための共通言語としてストーリーテストを使うことの効果は計り知れません。要件の取り違えを減らし、月例のリリースからは回帰テストがほとんど消えました。

CPAL を使ったオープンソース

忘れてはいけないのが、SocialCalc のオープンソースモデルから得られた教訓です。

Socialtext は SocialCalc のために Common Public Attribution License11 (CPAL) を作成しました。CPAL は Mozilla Public License に基づいており、ソフトウェアのユーザーインターフェースに原作者の名前を表示することを強制できるように設計されています。またネットワークを通じた利用における条項として、派生作品がネットワーク越しにホストされたとしても同じライセンスで公開することを要求します。

Open Source initiative12 と Free Software Foundation13 に認証されてからは Facebook14 や Reddit15 といった巨大サイトでもプラットフォームのソースコードの公開に CPAL が採用され始め、大きな励みになりました。

CPAL は「弱いコピーレフト」のライセンスなので、他の自由ソフトウェアおよびプロプライエタリソフトウェアと組み合わせて使うことができ、そのときは SocialCalc に対する変更だけを公開するだけで済みます。これによって様々なコミュニティが SocialCalc を採用し、CPAL はより素晴らしいものになりました。

SocialCalc というオープンソースのスプレッドシートエンジンにはたくさんの可能性があります。もしあなたが気になるプロジェクトに SocialCalc を組み込む方法を見つけたら、ぜひ知らせてください。


  1. https://github.com/audreyt/wikiwyg-js[return]

  2. http://one.laptop.org/[return]

  3. http://seeta.in/wiki/index.php?title=Collaboration_in_SocialCalc[return]

  4. http://search.cpan.org/dist/Web-Hippie/[return]

  5. http://about.digg.com/blog/duistream-and-mxhr[return]

  6. https://github.com/gimite/web-socket-js[return]

  7. http://perlcabal.org/syn/S02.html[return]

  8. http://fit.c2.com/[return]

  9. http://search.cpan.org/dist/Test-WWW-Mechanize/[return]

  10. http://search.cpan.org/dist/Test-WWW-Selenium/[return]

  11. https://www.socialtext.net/open/?cpal[return]

  12. http://opensource.org/[return]

  13. http://www.fsf.org[return]

  14. https://github.com/facebook/platform[return]

  15. https://github.com/reddit/reddit[return]