テストファーストなGitワークフローについて

Gitのワークフローに関する話題が、また盛り上がっているようなので、僕が好んで使っているワークフローについて書きます。

対象としているソフトウェアは、GitHubGitHub Enterprise等を使って開発されている、リリースブランチを切らずにmasterにリリースタグを打っていくだけで十分な程度の、ウェブサービス(の部品)やオープンソースプロジェクトです。

まず、以下の2点を原則として考えています。

  • origin masterを壊さない
    • origin masterの(1st parentをたどるツリー)にテストを通らないcommitを入れないよう努めます
  • 変更の主題を常に明確にする

前者の理由は、masterをいつでもリリース可能な品質に保つためと(←12:44追記)git bisectするときに困らないようにするため。そして、これらの原則から、以下のようなワークフローで作業することが多いです。

  1. 変更を施す際には、まずトピックブランチを切る
  2. トピックブランチの最初のコミットは、「未解決の課題」を表すテスト(つまり失敗するテスト)
  3. 次に、この「課題」を解決する(1つ以上の)コミットを実装する*1
  4. その過程で、問題が見つかれば、さらにテストのcommitと課題解決のcommitを繰り返す
  5. 最後に、トピックブランチで作業している間にmasterが大きく進んでしまった場合は、トピックブランチにmasterをmergeして、conflictを解決する*2
  6. トピックブランチをmasterにmerge --no-ff*3し、テストが通ることを確認してpush(テストが通らなかった場合は5からやり直し)

なぜ、トピックブランチの最初のcommitを、「failするテスト」にするのか。それは、漫然とコードを書いてしまわないようにするためです。まず、ゴールを明確化し(必要ならチームメンバーで共有した上で)、そのために最適な実装を行うということを繰り返すためにも、この方法が良いのではないかと考えています。


参照:
誰得UNIX: git-flowでもgithub flowでもない、Git本家推奨のワークフロー
いまさら聞けない、成功するブランチモデルとgit-flowの基礎知識 (1/2):Gitブランチを使いこなすgit-flow/GitHub Flow入門(1) - @IT

PS. 単純な変更については、直接masterにコミットしても良いと思います。

PS2. BDD(ビヘイビア駆動開発)をGitのワークフローとして最小限の形で定式化するとこうなるという話として理解することも可能かと思います。

*1:たとえば、リファクタを行い、次に課題を解決するコードを実装するような場合は、コミットを分けるということ

*2:rebaseではなくmergeを使うのは、トピックブランチで行ってきた各commitの文脈を破壊しないため。rebase -iでコミットをひとつひとつ確認/修正しながらrebaseしてもいいですが、そこまでしてcommit treeの綺麗さを保つ必要には迫られていません

*3:このワークフローの場合、fast-forward mergeは第一原則に反する結果を産むので禁止

npm (node package manager)の菱形依存問題とdedupeと社内モジュールとJSX

npmで色々コードを書いていると、以下のような依存関係ができてしまうことがある*1

a
+-- b <-- depends on c@1.0.x
|   `-- c@1.0.3
`-- d <-- depends on c@~1.0.9
    `-- c@1.0.10

このように「bが依存するc」と「dが依存するc」が異なるものになってしまうと、(2回cがロードされる分)プログラムが無駄に大きなものになってしまったり、(bとdがお互いで生成したcを使う場合に)片方で生成されたcのインスタンスをもう片方で「instanceof c」してもfalseが返ってきてしまう、などの問題が発生する。

この問題を解決するために、npmはsemantic versioningという枠組みとnpm dedupeというコマンドを提供していて、両者を使うことで、依存構造を以下のような形に変換して問題を回避することができる(npmのモジュール探索は、子から始めて見つかるまでツリーをルート方向に上って行くため、この例では、bとdは隣接するcを参照するようになる)。

a
+-- b
+-- d
`-- c@1.0.10

ただ、npm dedupeコマンドには、npmに登録されたモジュールはdedupeできるが、GitHub(あるいはGitHub Enterprise)にあるモジュールをpackage.jsonから参照している場合はdedupeできないという限界があって、この点が、社内で大量にモジュールを書いていたりすると問題になりがちだった。

以上が前置き。

で、この問題についてはuser/repo dedupe issue · Issue #4213 · npm/npm · GitHubによると修正が存在する(がpull-requestにはまだ至っていない)ようなので、困ってる人は是非お願いコメントをつけると良いのではないか、というのが1点。

第2の点としては、最近JSXのライブラリをNPMで公開できるようにしたんだけど、JSXにおいてこの問題をどう解決するか。こちらについては、version 0.9.76より--add-search-pathオプションで指定したパスをnode_modules/よりも優先して探索するようにJSXコンパイラの機能を拡張した。よって、冒頭で示したようなグラフがある場合、

jsx --add-search-path node_modules/d/node_modules/c a.jsx

のようにコンパイルオプションを設定することで、bが参照するcについてもd/cが参照されるようになる*2

*1:グラフはいずれもnpm dedupeのマニュアルより引用

*2:ただし、--add-search-patchはimportするファイルがそのディレクトリに存在する必要があるので、この場合はcのpackage.jsonには"main":"c"と書かれている必要がある。このような(npmとしても直指定でも使える)dual-lifeなJSXパッケージの作り方についても整理が必要かなぁ

WebSocket(RFC 6455)上で使用するプロトコル設計についての備忘録

一般論として、全二重の通信プロトコルを実装するにあたっては、いくつか注意すべき点があって、具体的には、到達確認と切断シーケンスについて定めておかないと、送達されたはずのメッセージがロストしていたり、切断タイミングによってエラーが発生*1したりする。

具体例をあげると、たとえばTCP/IPにおいてshutdown(2)を用いずに、いきなりclose(2)を呼んでいると、read(2)やwrite(2)がエラー(ECONNRESET)を返す場合がある。

翻って、WebSocket (RFC6455)の場合はどうなってるか? だいたい以下のような感じっぽい。

  • ws.close()が呼び出されるとWebSocketをCLOSING状態に変更し、Closeフレームを送信する
  • ws.onmessageはWebCosketがCLOSING状態にある間も呼ばれるかもしれない*2
  • 相手からCloseフレームを受け取った場合、自身がまだCloseフレームを送信していなければ可及的速やかにこれを送信し、自らの状態をCLOSEDに変更し、ws.oncloseを呼び出す
  • 相手からCloseフレームを受け取った場合、自身が既にCloseフレームを送信していれば、自らの状態をCLOSEDに変更し、ws.oncloseを呼び出す
  • TCP/IPソケットは両者がCloseフレームを送信した後に閉じられる
  • WebSocket実装は、Closeフレームの送信後にデータを送信してはならない

以上より、WebSocket上で全二重の通信を行う場合、

  • アプリケーションレイヤでの切断シーケンスの実装は不要*3
  • 通信相手にメッセージが届いたという保証はWebSocketでは提供されない*4

ということがわかる。

参考:

*1:通信エラーなのかか切断時のタイミング依存の問題化切り分け出来ないケースが発生する

*2:RFC6455 5.5.1によると、呼ばれないかもしれない

*3:サーバ側クライアント側いずれにおいて任意のタイミングでws.close()を呼び出してもエラー発生(ws.onerrorの呼出)の危惧なく切断処理が可能という意味

*4:そのような保証がいる場合はアプリケーションレイヤでACKを送受信する仕組みが必要

自社サービスからエコシステムへ進化する話 (Re: UI変更批判バトルと複数のバージョンのウェブサービスを同時に配信することについて - hitode909の日記)

ウェブサービス,UI変えると,改悪とか,元に戻してとか,そういう意見が出る.


サービス提供する側の立場では,新しいUIのほうが使いやすかったり,機能が増えたり,収益が増えたりするので,新しい方を多くの人に提供することに価値がある.使いやすいかとか,儲かるかとかは,リリースまでに調べておく必要があり,リリースの結果使いにくくなったり収益減ったりしたら,失敗ということになる.


UI変更するたびに怒られるのは大きな問題で,ユーザーの心情を考えるみたいな気をつけてやるみたいなのじゃなくて,仕組みで解決しないといけない.


(中略)


APIとクライアントという形に完全に分離して,オープンソースにすればましになるかとか考えてたけど,いろいろある.


なんかいいプランがあったら教えてください.

UI変更批判バトルと複数のバージョンのウェブサービスを同時に配信することについて - hitode909の日記

おもしろい話。

ビジネス的には、自社製品を統合型からエコシステム型*1に変更するにあたり、エコシステムに参入するサードパーティから手数料をとれないか、という観点になると思う(エコシステムを維持するコストよりも、サードパーティからの手数料収入が大きくなればなんの問題もないので)。

で、そのようなアプローチは一般的にある。NTTドコモiモードのコンテンツプロバイダ、Apple AppStore、モバゲー。みんなそう。

どれも、自社の資産(ソフトウェアだったりユーザーベースだったりその他の情報だったり)へのアクセス権と引き換えに手数料をとってる。そうして、多数のサードパーティデベロッパを引きつけることで、ユーザーの多様なニーズに対応でき、結果、エコシステムが拡大するし自社の売上も増える*2

140文字で収まらなかったのでここに書いた。

*1:主流じゃないUIについて、開発費も保守費も払いたくない以上他者の参入を促すのが自然

*2:Twitterは、サードパーティのクライアント開発者から手数料とればいいのにと思ってる

「今日使われているプログラミング言語の多くは、なぜ1990年前後に誕生したものなのか」に関する一考察

若い人たちは、「文字列型」があるプログラミング言語しか知らないかもしれない。だが、汎用的な文字列型が一般的になったのは、プログラミング言語の歴史の中でも比較的最近のことである。

たとえば、1972年に誕生したC言語には文字列型がない。1980年代に良く使われていたPascalの文字列型は最大255文字しか格納できなかった。

なぜか? それはメモリが貴重なリソースだったから。

1980年代のPCの搭載メモリは多くて数メガバイト。これに対し、長編小説の長さは1MB程度に達する*1

当時、メモリはとても貴重な資源であり、テキストを処理するプログラムを開発するにあたっては、文字列をどのようにメモリ内に展開するかプログラマが細かくコーディングする必要があった。

だから、汎用的な「文字列型」というのは「夢」にすぎなかった。CあるいはPascalにおける文字列(CのASCIIZ文字列あるいはPascalの文字列型)は、簡易な処理を書くため、あるいはOSやライブラリとやり取りをするための取り決めとしての側面が大きかったのである*2

また、より簡易な処理を行いたい場合には、AWKのような「行ごとにデータを読み込んで処理」するという、それ専用に設計された処理系を使うアプローチが採られていた*3

ところが、1990年代に入ると半導体技術の進歩により計算機の搭載メモリ量が増加し、「文字列処理におけるメモリ使用量」は最早問題ではなくなってくる。これを象徴するのが、テキストVRAMを前提とするCUIから、文字を画像へメモリ内で展開して描画するGUIへの移行である*4

同様の影響はプログラミング言語においても現れた。プログラミング言語において汎用的な「文字列型」を導入しても、その言語の応用分野が狭まることはなくなったので、1990年前後に誕生したプログラミング言語である C++, Perl, Python, Java, JavaScript, Ruby は、いずれもその言語仕様の一部として汎用性のある「文字列型」を備えている*5

今日、広く使われているプログラミング言語の多くが1990年前後に誕生したものなのは、決して偶然ではない。それら諸言語は、メモリ空間の拡大にともないテキスト処理の手法が変わるというタイミングで産まれた新しい生存空間に早期に進出し、根を広げたプログラミング言語なのである *6 *7

メモリ空間の拡大はインタプリタ型言語の一般化にとっても重要な役割を果たしているが、その話はまた改めて。

PS. 本記事は問に答える視点を紹介するものであって、唯一解を述べようとしているわけではないです。プログラミング言語の理論と実装の発達史を振り返るには「コーディングを支える技術」著者公式ページが良書だと聞きますが、半導体技術の発展とあわせて考えてみるのも面白いと思います。

*1:参照: 図書カード:吾輩は猫である

*2:参照: String (computer science) - Wikipedia

*3:簡易な処理系としては、他にも表計算ソフトやそのマクロ、バッチファイル等が既に存在した。このことはつまり、計算機の利用形態において処理速度が既に絶対的問題ではなくなっていたことを示している

*4:Unicodeのような世界共通の文字コードが現実的になったのも同様の理由によると考えることができる

*5:構造化されたBASICであるQuickBasicをその嚆矢とする考えもあるだろう

*6:これら新世代のプログラミング言語の有力な適用分野であるウェブ自体も、メモリ空間の増大にともなって可能になったテキストベースのシステムである

*7:scalar/array/hashという汎用的なデータ構造の上にアプリケーションのアルゴリズムを実装するというメモリを潤沢に使うプログラミング手法が一般化するのも同時期である

GCとかロケット科学すぎてウェブサービス屋さんには似合わない

※タイトルは釣りです。

先のエントリで、

採るべき技術的アプローチに関しては、ソフトウェアの修正コストによってかわるという議論があって、ウェブサービスの場合にはソフトウェア製品(やSI)と比べて圧倒的に修正コストが安い。ウェブサービスの場合にロケット科学的な「正しいけど大げさ」なアプローチよりも、小さく作って動かしながら修正していく手法が好まれるのにはそういう背景もある

ソフトウェアのアップデートとウェブサービス運用における継続性リスクについて - kazuhoのメモ置き場

と書いた件、GCを例にあげて説明します。

ソフトウェアを製品として出荷するビジネスは、修正コストが高いです*1。企業向けソフトウェアなら、管理者がアップデートをインストールしなけりゃいけないし、それによって不具合が発生していないか結合試験が必要になるかもしれないし、場合によって南極まで出張して…ごにょごにょ。

こういうビジネスにおいては、そもそもバグを出さない仕組みが重要になります。GCとかいいですね。循環参照でメモリリークが出る参照カウントとかクソですね。そんなバグ、いちいち修正してはリリースしてたら、お客さんからそっぽ向かれますね。

でも、ウェブサービス屋さんにとっては違うんです。参照カウントのほうが使いやすかったりする。突然の停止とかないし、パフォーマンス予測できるし、メモリリークがあったら運用で対処したっていいし、問題になる所だけ、問題になる度に修正していけばいい。

以上、たとえばということでGCを例にあげてみました*2。っていうか、某GC屋さんたち、叩かれすぎでかわいそうだと思う。すごくいい仕事してると思うんですけどね。

ちなみに僕は今、ゲーム業界に身をおいているので、停止時間が予測しやすいCopy GCが好きです。

*1:ここでいうコストとは、バグを修正するコストだけではなく、実際にその修正を適用する作業も含む

*2:例えば、言語処理系を選択する際にはGC方式は検討要素のひとつでしかありません。GCを使うソフトウェアを避けるべきと一概に主張しているわけではありません

ソフトウェアのアップデートとウェブサービス運用における継続性リスクについて

Webサービスのようなプロダクトについての議論について教えて下さい - Kentaro Kuribayashi's blog で呼ばれたような気がしてたけど放置してた。でも今日、express という node.js 上で動作するメジャーなウェブアプリケーションフレームワークを作っているチームが、次世代の製品に取り組み始めたと聞いたので、メモを以下に貼ります。

------------------------------ ✂ ------------------------------

ソフトウェア技術の配布手法のトレンドは以下のように推移してきた。

ハードウェアにしても、プロプライエタリから業界標準主導なアプローチにかわってきている。

つまり、時代とともに、よりリスク回避的なアプローチになってきた。もちろん、その背景として基盤技術のコモディティ化を見逃すことはできない。

他業界の例でいうと、供給サイドに長いサプライチェーンをもつ企業では、震災以降そのリスクマネジメントに関する話題がニュースにも取り上げられるようになっている。たとえば、自動車部品の仕様共通化だったり、半導体原料の代替調達先確保だったり。これは、ソフトウェアでいうとオープンシステムの段階。

ただ、これらの業界は有体物を取引しているのであって、無体物を取引するソフトウェア産業とは異なる(取引先にリスクがあるからといって、工場を自社で抱えるのは無理)。ソフトウェアは生産(複製)コストがゼロだからオープンソースが成り立っているという点に注意すべき。

以上がOSSに関する一般論だけど、特にウェブサービス関連のミドルウェアにおいてはまだコモディティ化していない技術でも積極的にOSS化される傾向にある点が特徴的かも。この理由付けについてはKazuho's Weblog: もうひとつの知られざるオープンソース 〜 ウェブ企業のOSS戦略でも言及した。

ただ、野良犬じゃあるまいし、おいしそうなものが公開されたからって片っ端から拾い食いするのが良くないのは言うまでもない。という訳で表題の件に話は飛ぶけど、原則は、

  • より枯れた競合があるならそっちを使う
  • まだ進化中の非コモディティ製品に価値があると思うなら、その対価を払う*1
  • 必要もないのに互換性を壊すようなOSSは避ける

みたいな感じかなと思ってる。実際、Perl使ってる人たちはそういう意識が高いので、互換性を壊すような変更は嫌われる。そういう人たち以外はもうPerl使ってないのかもしれませんが。

追記: 採るべき技術的アプローチに関しては、ソフトウェアの修正コストによってかわるという議論があって、ウェブサービスの場合にはソフトウェア製品(やSI)と比べて圧倒的に修正コストが安い。ウェブサービスの場合にロケット科学的な「正しいけど大げさ」なアプローチよりも、小さく作って動かしながら修正していく手法が好まれるのにはそういう背景もあると思うけど話が長くなるのでこの辺で。

*1:サポート費用払うとか、自社プロダクトをその進化にあわせてアップデートしてくコストとか、自社でメンテナ抱えるとか