Hatena::ブログ(Diary)

kazuhoのメモ置き場

2014-02-17

クローズドソースなnode.jsライブラリの依存関係をdedupeする話

node.jsで開発してると、社内で作ってるライブラリの依存関係がごちゃごちゃしてくることがありますよね。

app --+--> libA ----> libB
      |
      +--> libB

みたいな。こういうときlibBがnpmに登録されたモジュールであればnpm dedupeコマンド一発でlibBをひとまとめにしてくれるけど、gitに登録されてやつだとしてくれない(のは以前も書いた)。で、まあやっぱり不便だし、代替策があるかどうかも良くわからんので、ぱぱっと書いた。

force-dedupe-git-modules - npm

このコマンド使うと、github.comのプライベートレポジトリGitHub Enterpriseに置いてあるnpm形式の社内ライブラリについて、強制的にdedupeかけることができる。

簡単。

2014-02-04

テストファースト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のワークフローとして最小限の形で定式化するとこうなるという話として理解することも可能かと思います。

トピックブランチ内の変更を無視してgit bisectする方法

merge commitについては1st parentのみをたどりながらgit bisectしたいことってありますよね? (参照: テストファーストなGitワークフローについて - kazuhoのメモ置き場)

そういうとき、1st parent以外に属するcommitをgit bisect skipするの、どうやったらきれいに書けるのかなと思ってたのですが、以下のやりかたが一番よさげ。

$ git bisect skip $(comm -23 <(git rev-list HEAD | sort) <(git rev-list --first-parent HEAD | sort))

thanks to: ひろせ31さんのツイート: "@kazuho comm -2 -3 <(cat A|sort) <(cat B|sort) とか?", Masakazu Takahashiさんのツイート: "@kazuho 訂正。comm -23 A B でした"

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

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

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

2014-02-03

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できないという限界があって、この点が、社内で大量にモジュールを書いていたりすると問題になりがちだった。

以上が前置き。

で、この問題についてはno titleによると修正が存在する(が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パッケージの作り方についても整理が必要かなぁ

2014-01-28

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上で全二重の通信を行う場合、

ということがわかる。

参考:

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

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

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

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

2013-12-23

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

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


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


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


(中略)


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


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

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

おもしろい話。

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

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

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

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

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

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