Github を使って雑誌原稿を書く

今日はこのあと Github の Tokyo Drinkup January 2014 に行くのだが、先方から、もしかしたら 10分ほど Github について話してもらうかも、と打診された。話すか話さないかわからないが、もし話すとしたらと仮定し内容の整理も兼ねて以下「Github を使って雑誌原稿を書く」ということについて書いてみようと思う。

Github を使って雑誌原稿を書く」もしくは「Github を使った雑誌編集者とのコラボレーション」について、である。

Web+DB PRESS の連載

ご存知の方もいるかもしれないが、このところ技術評論社Web+DB PRESS で連載をしている。連載を始めて、もう一年近く経った。以前にも Perl に関する連載をしていて、そのときも数年ぐらい続けたので、間があきつつも、なんだかんだでそれぐらいの付き合いになる。

最近は特にテーマは決めずに、隔月ごとに、そのとき旬な物あるいは自分が強く興味がもっていることについて書いている。RubyMotion の話を書いたかと思えば、serverspec の話を書いたりとかそんな感じ。もし良かったら読んでみて下さい。宣伝終わり。

Github 以前

数年前の当時、まだ Github が世の中にリリースされるかされないか、その頃の雑誌原稿のやりとりは基本メールだった。連載〆切りが近づいてくると、編集者さんから「そろそろいかがですか」的なメールがきて、あとはそのメールをピンポンしながらやりとりをする。原稿も適当なフォーマット (当時ははてな記法)で書いて、メールに添付して送る。

しばらく待っていると、原稿が PDF になるなり何なりして、そこから校正作業が始まりなんどかやり取りしたところで完了となる。

まあ、特にこれで何か特段困ったことがあったかというと実はそういうことはなかった。

当然、原稿そのものは手元で Subversion、その後 Git でバージョン管理はしていたし、Github を使うようになった今でもコミュニケーションのピンポンを繰り返してやりとりしていって校了となる・・・というフローそのものは変わっていない。

Github 以降

このメール時代の頃から原稿を Git で管理することはしていたけれど、それはローカルだけの話でリモートに push したりはしていなかった。

それを、今回の連載を開始するにあたり、そろそろ Githubコモディティになってきたことだしということで、担当の @inao さんに github に push するからそこでコミュニケーションしてみませんか、と提案した。ただ、このときは特に深い狙いはなく、どうせ手元でバージョン管理してるんだからリモートに上げるのもすぐだし、メールに添付するよりはそっちのほうが共有が楽とかそのぐらいしか考えてなかった。

それが、Github を使い始めてから作業のやり方に色々とアイデアが出てきて、今では原稿は Markdown で書き、お互い基本的に Pull Request で飛ばす方法が定着した。Pull Request はいわゆる「Working In Progress」な WIP Pull Request として出す。つまり、原稿を書き上げたところで Pull Reuqest するのではなくて、いきなり Pull Request を出してしまってあとはその Pull Request に、原稿の進捗に合わせてコミットを重ねていく。

このブログで再三にわたって紹介しているが、WIP Pull Request については

このスライドが分かりやすいと思う。

Pull Request で原稿をやりとりする

以下がその WIP Pull Request の様子。簡単な TODO を共有し、少しずつ原稿を追加していっている様子が伝わると思う。編集者は「naoyaさん原稿書いてますか?」とかメールを送ってこなくても、進捗が把握できる。途中でレビューを加えようと思えばいつでもそれは可能だ。

原稿をやりとりするフローでは、最初は著者側にボールがあって、PDF化の作業なんかが始まるとボールは編集側に移る。PDFを確認するタイミングになると著者側にボールが移る。その繰り返しである。そして、自分と @inao とのやりとりでは、Pull Request を出した側がそのボールを持っているという関係になっている。

例えば、ぼちぼち著者の手元での原稿が固まってきたな・・・という頃に @inao がその Pull Request をメインブランチにマージする。マージした段階で「あ、向こうにボールが渡ったな」というのが分かるのでこちらはレビュワー側に回る。

以下はその、編集側から飛ばしている Pull Request。

コミットログを綺麗に整理してくれているので、こちらも、自分の原稿にどんな修正が入ったかがよくわかる。ああ、結構 typo 編集してくれてるんだなとか、用語統一とか、読みづらいところを校正してくれてるんだなとか。今だから言えるが、以前メールで作業していたときは、こういう細かい所まで作業してもらっているということにあまり気づいていなかった。

Git をお互いに使っている、という利点が活きる場面も結構多い。

例えば現在の原稿はちょっと書きすぎてしまってページ数がその分確保できるか怪しいという話があって内容を少し削ったのだが、仮にページ数を確保できた場合には元に戻そうという話をしていて、実際確保できそうだったので削除コミット自体を revert して元に戻す、なんてことをした。メールでやりとりしていたときは、こういうコラボレーションはなかなか難しかった。

このように、Github + Pull Request の利点を上手く活かして編集とやりとりすることができている。

自分たちはそこまでやっていないが、知人のエンジニアは、Jenkins を回して git push の度に PDF のビルドとか用語統一とかそういうのを自動化しているなんて話もしていて面白かった。

わかったこと

メールでやりとりしていたときにも、そこまで困ったことはなかったけれども、Github にしてからはずいぶんとやりやすくなった。

Pull Request による進捗とコミュニケーションの一本化

色々と良かったところはあるのだけれど、ざっくりいうとそれはお互いの作業状況が見えること、そしてその作業状況にコミュニケーションがうまく集約されることとだと思う。

Pull Request ベースの開発をしたことがある人ならよくわかると思うが、Pull Request は単にパッチを取り込んでもらう依頼が簡単になるということだけでなく、その Pull Request のスレッド上でコミュニケーションを重ねることで、作業進捗とコミュニケーションが一つにまとまった形で可視化されるところがすごく良い。大袈裟にいえばワークフローとコミュニケーションの一体化、とも言える。

雑誌原稿の執筆作業のワークフロー自体は、関連する人間は2人から3人程度だしシンプルなのだけれども、その昔メールでピンポンしていたやり取りが原稿の commit や typo の修正といった一つ一つの作業と関連づけられる形でスレッドになるのは、後日見返したときにすごく安心感がある。どちらが作業のボールを持っているかも明確になるし、数日おいて原稿の状況を確認するにあたって「どんな議論になってたっけかな・・・」というのを思い出すのも Pull Request の状況を見るだけでほとんどストレスなく思い出すことができる。

元々 Github を使い始めた当初は Pull Request ではなく、Issue で原稿の進捗を管理していた。@inao から、〆切り日にあわせてこの日までにアウトラインが欲しいとか、これこれを終わらせてくださいとか、その単位で Issue を切っていた。でも、実際には個別の Issue にするほどそれぞれの作業は独立していなくて、基本原稿書きは逐次で物事が進んでいくので、WIP な Pull Request に一本化してその上でやりとりしていくほうが面倒もすくなく、且つ分かりやすかった。

こうして作業が可視化され、ストレスなく状況を把握しやすくなったことによって、著者と編集者の間でのちょっとした行き違いみたいなことがだいぶ少なくなった。結果的に「ああ、こんなに自分の誤字脱字を編集してくれてるんだな」とか今まであまり気づいていなかった作業についても自覚することができるようになり、より良い信頼関係を築くに至っている・・・と @inao は知らないけど、自分は思っている。

Github がソフトウェア開発以外で活きてくる可能性

そんなわけで、Github はソフトウェア開発の分野以外にも、コラボレーションが必要な領域で色々と応用の可能性を持っている、と自分は思っている。

実際、昨年 Github が資金調達をした際に今後はいろいろな分野に Github を広げていくみたいな話をしていたように記憶しているので、そういう利用シーンもこれから増えていくのではないかと思っている。雑誌の原稿編集だけでなく、自分でも気づいてない分野の応用例なんかがでてくることをとても期待している。

問題は、@inao のように Git と Github を使いこなせる編集者が世の中にほとんどいない、という点ではないかという話があるのだがそれについては今は考えないでおくとしよう。

CROSS 2014 「現場に聞く!テスト/CI/DevOps、実際のところどうなの」

新年あけましておめでとうございます。本年もよろしくお願いします。

えー、もう明日になってしまって今更宣伝してもという感じではあるのですが、明日開催される CROSS 2014 で、12:00 から 60分ほどセッションを開催します。「現場に聞く!テスト/CI/DevOps、実際のところどうなの」というタイトルでのパネルディスカッションです。

自分が司会で、他はクックパッドid:secondlife (@hotchpotch)、KAIZEN platform Inc. CTO の @iandeth、はてなid:hakobe932 (@hakobe) の4人と話します。

パネルディスカッション、というとテーマをあげてそれぞれの思うところを喋って貰う、みたいな形式もありますが、明日は自社アピールというよりももうすこし雑談形式でその雑談から垣間見える本音みたいなところを聴いてもらいたいということで、座談会に近い形式でのセッションにできたらなあと思ってます。うまくいくか、やってみないとわかりませんが。

アジェンダとしては、昨今話題になったブログ記事やプレゼンテーションなんかを肴に話を膨らませていくという、rebuild.fm でのやり方を真似してみることにしました。採り上げたい URL は以下にまとめてます。

話題になった、というより3人のゲストに質問してみたいこと一覧、みたいな感じですけどね。

というわけで明日 1/17 (金) 12:00 より、西新宿はベルサール新宿グランドで会いましょう。新宿には「ベルサーレ」が他にもあるのにお間違いないよう。

ちなみに 16:30 からの技術書の未来はどっちだ!というセッションにも出る予定なのですが、今のところその後の連絡が何もないのでどうなるのかちょっとドキドキしています。

アップル厨の俺が2013年に買って良かったものまとめ

自他共に認めるアップル厨の俺が2013年に買って良かったもの、それは・・・

アップルピーラー。これはすごい

なんと皮が3秒で剥ける!! 本当に3秒です。軸に挿してレバーを回すだけ!! 過去何百個もナイフで皮を剥いてきた自分も、これにはびっくりです。でも、お高いんでしょう? いいえ! Amazon.co.jp ならなんと 1,399円。

さて、この季節と言えばやはりサンふじですね。サンふじと言えば長野県産、青森産などが定番だと思いますが東北出身の私としてはぜひ青森県産のものをオススメしたいところです。ちょっと気が早って11月上旬くらいに 5kg を注文したのですが、さすがに11月上旬ではまだピークに到達しておらず、甘さは十分なもののサンふじのあのシャキシャキ感が不足していました。

が、今月12月に入ってからは万全です。シャキシャキ感と甘みが高次元で両立しています。本当に美味しい。私も朝晩毎日2個を消費しています。いままでは毎日食べたくても皮むきが億劫で食べない日もありましたが、このアップルピーラーを購入してからというもの、食べたいと思ったその3秒後には皮が剥けています。ライフチェンジングです。正直、感動しました。

おすすめです。

パール金属(PEARL METAL) アップル ピーラー リンゴ 皮むき器 C-140

え、アップル違い? えっ

Chef Solo の Environments

今年3月に入門Chef Soloを書いた時点では、Chef Solo は Environments の機能をサポートしてなかったため解説は省略しました。

その後、Chef はバージョン 11.6.0 (現在は 11.8.2) で Chef Solo での Environments をサポートし、入門Chef Solo で推薦している knife-solo も 10月末にリリースされた 0.4.0 から Environments をサポートしました。というわけで、現状 Chef と knife-solo が最新版であれば Environments を利用することができます。

たまたま今手をつけている仕事で Environments のことを調べたので備忘録的に記しておきます。

Environments とは

Chef の Environments は、例えば development や production など環境ごとに設定内容を切り分ける場合に使える機能です。Rails における RAILS_ENV みたいなものだと思えばだいたい合ってる。実際には環境ごとに設定を切り分けると言っても、できることは Attribute の値を環境によって差し換えることができる、程度。

つまり、Attribute によってうまく環境差を吸収するようなクックブックを構成しておく必要はあります。

Environments の使い方

Environments を使うには、まずノードオブジェクト (nodes/localhost.json とかのあれ) に、値をセットする。

{
  "environment": "development",
  "run_list":[
    "recipe[hello]"
  ]
}

二行目の environment ですね。

次に、environments/ ディレクトリ以下に development.json という名前で、その環境に依存する設定などを書く。

{
  "name": "development",
  "description":"This is it",
  "chef_type": "environment",
  "json_class": "Chef::Environment",
  "default_attributes": {
    "hello": {
      "message": "This is development environment"
    }
  },
  "override_attributes": {}
}

ここでは default_attributesnode['hello]['message'] で参照されるべき属性を指定しました。

デバッグのために、environments で設定した値を出力するレシピを用意。

# site-cookbooks/hello/recipes/default.rb
log node[:hello][:message]

試しにサーバーを converge (クックブックを適用) してみるとどうなるか。

$ bundle exec knife solo cook localhost
Running Chef on sandbox...
Checking Chef version...
Installing Berkshelf cookbooks to 'cookbooks'...
Uploading the kitchen...
Generating solo config...
Running Chef...
Starting Chef Client, version 11.8.2
Compiling Cookbooks...
Converging 1 resources
Recipe: hello::default
  * log[This is development environment] action write

Chef Client finished, 1 resources updated

ごちゃごちゃ出てますが、* log[This is development environment] action write となって確かに先の値が参照できていることがわかります。

development とは別の環境、たとえば production が欲しいとなったら、environments/production.json を用意する。

{
  "name": "production",
  "description":"This is it",
  "chef_type": "environment",
  "json_class": "Chef::Environment",
  "default_attributes": {
    "hello": {
      "message": "This is production environment"
    }
  },
  "override_attributes": {}
}

そしてそれをノードオブジェクトに指定してやれば OK。

{
  "environment": "production",
  "run_list":[
    "recipe[hello]"
  ]
}

で、converge すると

Running Chef on sandbox...
Checking Chef version...
Installing Berkshelf cookbooks to 'cookbooks'...
Uploading the kitchen...
Generating solo config...
Running Chef...
Starting Chef Client, version 11.8.2
Compiling Cookbooks...
Converging 1 resources
Recipe: hello::default
  * log[This is production environment] action write

Chef Client finished, 1 resources updated

確かに production.json 側の値が使われているのがわかります。

なお、knife solo で converge する場合

$ bundle exec knife solo cook localhost -E production

として -E オプションで一時的に環境を切り替えることもできます。

ちなみに Environments の中で指定した default_attributes や override_attributes は、クックブック内の同値やレシピ、あるいは Role よりも高い優先順位で上書きされるので、それら優先度の低い場所で定義した初期値を Environments によって切り替える、ということができます。その辺詳しくは

このドキュメントに載ってます。

cookbook のバージョンロック

ちなみに Environments 内で指定できる値に cookbookcookbook_versions という属性があって

...
  "cookbook_versions": {
    "couchdb": "= 11.0.0"
  },
...

こんな感じで書くと、各環境でのクックブックのバージョンをロックすることができる・・・のですがこの機能は Chef Solo ではなく Chef Server の機能ですね。バージョン、とここでいってるのは Chef Server にクックブックをアップロードしたときに付与されるバージョンのこと。詳しくは Chef + Environments = Safer Infrastructure - Speaker Deck この辺をみると分かるのですが、このバージョンロックの機能を使うとより安全にプロダクション環境のクックブックのアップデートができたりしますよ、ということです。

というわけで、Chef Solo では効果のない機能です、たぶん。

まとめ

  • 最新版の Chef Solo + knife-solo では Environments が使える
  • development, testing, staging, production とかで切り分けたい設定があったら利用を検討すると良い
  • Environments で設定した Attribute は高い優先度を持つ
  • ただし、クックブックのバージョンをロックする機能は Chef Solo では関係ない

というものでした。

Infrastructure as Code

今年の3月に 入門Chef Solo - Infrastructure as Code という本を書いた。

その名の通り Chef の入門書なのだけど、このサブタイトルは "Configuration Management Tool (構成管理ツール)" でもなく "Provisioning Framework (プロビジョニングフレームワーク)" でもなく、はたまた "Automated Infrastructure (自動化されたインフラ)" でもなく、"Infrastructure as Code" にした。

この一年で Chef や Puppet にはずいぶんと注目が集まった。おそらく、AWS をはじめとするクラウドサービスがより広いユーザーに浸透したことで仮想化環境が前提になって、以前よりも頻繁にサーバーを構築し直したりする機会が増えたとかその辺がひとつ理由として挙げられると思う。頻繁にインスタンスを作りなおしても、その作業が再生可能になっていれば気に病む必要はない。Chef で手作業だったサーバー構成を自動化できた、良かった・・・という声を良く耳にするようにもなった。

確かに自動化されてハッピー。ただ、自分としては Chef / Puppet などの道具を使ったときにもっと重要なことがあると常々思っている。それを "Infrastructure as Code" というサブタイトルに込めたつもりだった。ん、どういうこと?

そこのアナタ、Infrastructure as Code ですよ!!

この Github の Pull Request をみると、すぐ伝わる。伝わることを期待する。


これは dstat というソフトをサーバーにインストールするための Pull Request である。サーバーにソフトを入れましょう、という話なのに github の上でやりとりが行われているではないですか。

作業手順が全部コードになる

Chef や Puppet などの構成管理ツールを使うと、サーバーの構成変更にまつわるあらゆる作業はプログラムコードになる。まあ、コードになるといっても

package "nginx" do
  action :install
end

service "nginx" do
  action [ :enable, :start ]
end

こんな程度で、設定ファイルを書く程度のものでそんな大袈裟なものではない。

さて、今まで手作業でやってたことが全部がコードになった。だったら git に入れてバージョン管理すればいいじゃない。git に入れたなら、github を使えばいいじゃない。github を使うなら Pull Request ベースでコードを構成すればいいじゃない。あれ、気づけばインフラも Social Coding できてしまった。

あれあれ、これってワークフローの変革ではなくって?

インフラもコードレビュー

次のスクリーンショットも見てみよう。

これは先日 最近のWebサービス開発現場では Github やなんやを使って開発しているよ、というエントリ の中でも紹介した株式会社じげんでの、実際の現場の Github の様子。

じげんでは数ヶ月前からサーバーの構成管理に Chef を使うようになった。そして、コード化されたものはすべて Github に突っ込んで、Pull Request ベースでその変更を回すようにしている。

「あのサービスの nginx の設定をこんな感じで変えましょう。」「コードできたし push しました。この変更で大丈夫か。レビューお願いします」「大丈夫だ、問題ない。マージして反映します」・・・的な流れ。作業の履歴はすべてバージョン管理され、レビュー結果は Pull Request の中に残る。

次の方がもっと生々しくて面白いかも知れない。インフラチームに配属になったばかりの新人が、構成変更手続きを先輩にソースレビューしてもらって、指摘事項を徐々に修正しながら進めていっている様子。

昨今、コードレビューは、品質の保証だけが目的ではない。pull request を利用した開発ワークフロー - Speaker Deck このスライドから引用する。

チームに配属されたばかりの新人が、Pull Request によるインフラ構成変更作業に伴うレビュー通じてその会社でのやり方、基準を共有される。

従来のサーバーの構成変更といえば、インフラ担当みたいな人が依頼を受けて手作業でやっているというのが普通だった。ちゃんとしているところは作業記録をドキュメントに残したり、それを共有することでその後のオペレーションミスが発生しないようにと注意してると思う。でも、世の中の8割方、多くの場合そんなにきちっとしていなくって、担当が書き換えてそのまま、何がどういう設定になっているかは作業した本人も数週間後には忘れていて実際のサーバーみないと分からないとかそんなもんだと思う。

それじゃあ流石に拙いよね。でも、人手で頑張ってドキュメント残して気合いと根性でカバーする・・・というのじゃあ流行らない。

仮想化されてハードウェアもソフトウェアみたいになったんだし、だったら作業手順も全部コードにしちゃえば、そういう問題も解消されるんじゃないかというのを突き詰めたらここまで来た。

「インフラをコードで構成する」と聞くとまず第一には自動化される・・・ということがやっぱり思い浮かぶかも知れない。でも、そのうま味がもし自動化だけだったら、サーバーが1台2台しかないような環境にはまったく魅力的に映らない。作業手順がコードになるということは、それが(再生可能な形で)ちゃんと形に残るということである。形に残っているから、git や github で管理できる。いちいちサーバーに見に行かなくても、github 上で何がどうなっているか見ることができる。アプリケーション開発の世界で日常的に行われつつているワークフローを、インフラチームも取り入れることができる、というわけである。

インフラの継続的インテグレーション

おっと大事なことを言い忘れていた! (わざとらしい)

先の Pull Request のここに注目いただきたい。

"Your test passed on CircleCI" で緑色。これは、この Pull Request により変更されるであろう箇所にテストを適用した結果だ。テストと言ってもこれはもちろん人手ではなく、serverspec を使って Rspec で書いたものが、Jenkins や CircleCI のような継続的インテグレーションの仕組みによって自動的に行われる。

インフラに何か変更を加えたい、というのを Pull Request すると、その Pull Request を感知して CI サーバーが自動でその内容をテストする。テストは Vagrant によって新規の仮想サーバーが立ち上がり、その Pull Request 変更の内容含めすべてのサーバー構成コードを実行した上で、試験される。サーバーに変更がある度、新しいサーバーを一度作ってすべてのテストを流す・・・インテグレーションテストが行われる。これらのテストの起動や報告は CI サーバーと Github が連携して自動で行われる。開発者はいつものレポジトリにテストとコードを git push するだけ。何か問題があったら当然、Pull Request は赤色になり、そのままマージすると問題が起こることがわかる。

設定ファイル一行書き換える度にサーバー作り直してテスト、なんてことは人手でやってたのでは絶対無理だった。

この界隈の第一人者であろう @ryuzee のネタを借りると、テストをせずに本番環境に変更を加えることは、エイヤっと清水の舞台から飛び降りるようなものだ。インフラの世界ではまあそういうことが日常的に行われているような現場も多く、みんなびくびくしながら清水の舞台からジャンプしていた。で、たまに事故ってお客さんのデータを全部消去してしまいましたバックアップもありませんでした・・・みたいな悲劇が起こったりしている。

そうではなく石橋を叩いて渡りましょう。でも、石橋を叩いた結果、牛歩で歩かないといけないのは嫌だ。だったら超高速に石橋を叩けばいい・・・ そこでインフラの継続的インテグレーション

これもインフラがコードになったからできる芸当である。

先の、じげんでは Jenkins + Github pull request builder + Chef + serverspec でこのワークフローを実現している。

改めて Infrastructure as Code とは

"Infrastructure as Code" というのはこういうことで、単にサーバー構成変更を自動化しましょうという話ではなく、インフラをすべてソフトウェアとして、コードで扱うことでアプリケーション開発で行われてきたいろいろな "ワークフロー" をインフラ作業の世界にも導入しましょうねと、そういうことであります。


なので、せっかく Chef や Puppet を使っても、ただ自動化するだけのために使うのではその御利益の20%も享受できないし、Chef や Puppet をただの自動化ツールとして見て「オーバースペックだし要らない」とするのも、ちょっと待った! というものである。Infrastructure as Code、という部分まで含めてみてなお自分のプロジェクトにはオーバースペックだと思ったら、それはまあ必要ないでいいのだけど。

蛇足: Immutable Infrastructure との関係

この話、Immutable Infrastructure/Disposable Infrastructure の文脈での話にもちょっと関係してくる。

最近「Immutable Infrastructure になったら冪等性とかもう大袈裟だし Chef / Puppet みたいなのは要らないよね」みたいな声を聴くことがあるけど、Configuration Management Tool の本質的なところはここまで述べたようにワークフローに変革をもたらしたことであって、その部分を無視して必要/必要ないという話をするのは早計。

「冪等性とかオーバースペックな部分は必要ないけど、Infrastrcture as Code なワークフローは維持したい」というのが良い問いで、その際、新しい Configuration Management Tool の登場を期待するのか、Chef/Pupplet をそのまま使うのか、(あるいは Docker 前提なら Dockerfile だけで十分なのか) とかそういう風に見ていくべきではないか、と思います。

それにしても、Infrastructure as Code って口に出したとき噛む頻度は異常。

HBFav 2.6、バックグラウンドフェッチによるタイムラインの自動更新

HBFav を 2.6 にバージョンアップしました。新機能としてタイムラインの自動更新機能を追加しました。

これまではタイムラインなどで新着記事を取得する場合、都度、手動で Pull to Refresh (引っ張って更新) を行う必要がありましたが、新しいバージョン 2.6 からはその必要がなくなります。ただし新ユーザーページ設定がオンの場合にのみ限り、本機能が有効になります。

我ながら、本機能でずいぶんと使い勝手が良くなったなあと思いました。特にプッシュ通知と併用すると、プッシュで配信されたエントリが HBFav を開いたときにはもう反映されるようになり、もはやネットワークの通信待ちすら発生しません。これは想像以上に快適で、あるエントリを読んでる間に配信されてきたブックマークもすぐにチェックできるので中毒性が増・・・ じゃなかった、とても便利です。



なお、プッシュが有効じゃなくても更新されるように配慮していますので、プッシュ通知は要らないなあという方もぜひ(新ユーザーページを有効にして)本機能をご利用くださいませ。

以下は新機能の詳細です。ちょっとややこしい所もあるので段階的に説明していきます。とりあえず使えればいいや、という方ははじめのセクションだけ参考にして下さい。

では、今後も HBFav をご贔屓に。気に入っていただけた方は Twitter でつぶやいたり App Store でレビューしたりすると、主に作者が喜びます。

新機能の概要がとりあえず知りたい方

  • タイムライン、人気/新着エントリーが自動で更新されます。強制的に読み込みたい時以外、手動更新の必要がなくなります
  • 本機能を利用する場合は以下の設定を行ってください
  • さらに iOS 7 を使っていて且つ HBFav のプッシュ通知を設定しているとより良い感じで自動更新が行われます

はてなブックマーク本体で新ユーザーページが有効になっているユーザーにのみ有効な API を利用している都合上、自動更新にはこの設定が必須になります。

HBFav 側の設定は以下の画面です。

はてなブックマーク本体を旧ユーザーページのまま HBFav 側で本設定を行ってしまうと、タイムラインが正常に取得できませんのでご注意ください。

どんな契機にアップデートが行われるか等の仕様を知りたい方

基本、以下の契機にタイムラインの自動更新が行われます

  • アプリをバックグラウンドから立ち上げたとき
  • 他のアプリケーションから HBFav にアプリを切り替えたとき
  • タイムラインに限り、アプリがフォアグラウンドでアクティブな場合は2分間に一回更新

更に、以下の条件でも自動更新が行われます

  • iOS 7 でプッシュ通知を有効にしている場合は、プッシュ通知が着信した時

従って

  • HBFav 2.6 を利用
  • iOS 7 を利用
  • 新ユーザーページ設定を有効
  • プッシュ通知を設定

すると最も理想的な状態で自動更新が行われるようになります

タイムラインの更新にあたっては意図しない場面で勝手に画面が書き換えられてしまうことがないよう配慮しました。具体的には、新着の更新に当たってその時点で画面に表示していたブックマーク一覧を維持したまま画面外上方に新着ブックマークを挿入するようにしました。

なお、自分のブックマークだけは、自動更新に今のところ対応していません。

どうやって実現しているかを知りたい方

アプリの切り替え時の更新は、素直に iOS のアプリを切り替えた契機にタイムラインを更新するよう実装しています。2分に一回の更新も、タイマーでポーリングをしているだけです。

一方、プッシュ通知からの更新は、iOS 7 で新しく追加されたプッシュ通知からの Background Fetch 機能を利用して実現しています。

iOS 6 までは、アプリ内で何か特定の処理をバックグラウンドであらかじめ実行しておくという処理は実装が不可能だったのですが、iOS 7 からプッシュ通知を契機にしたバックグラウンドジョブの実行が可能になました。この新 API を使って、プッシュ通知が届いたタイミングでフィードを再取得し、新着ブックマークをタイムラインに挿し込むようにしています。

プッシュと iOS 7 を併用している場合にのみ限り、理想的な状態で更新が行われるのは以上が理由です。

はてなの中の方

Background Fetch にあたって、毎回フィード新着 25 件を取得してしまっているので since パラメータがあると嬉しいですね。あと、until パラメータの実装、とても助かってます。

RubyMotion を1年以上使い続けてみての雑感

RubyMotion Advent Calendar 2013 に何か書こう、ということでエントリ。

ご存知のように iPhone アプリの HBFav は RubyMotion で作っています。Objective-C ではなく。以前は Titanium Mobile で作っていましたが、去年にバージョン2として作り直すにあたって RubyMotion に移行しました。

RubyMotion に関しては以前、以下のエントリで概要を説明しています。

それから、今年 5月に開催した RubyMotion カンファレンスのスライドなどもあります。

RubyMotion が発表されたのは 2012 年の5月 とかで、それからずっと使い続けているので1年半近くが経ったことになります。App Store に配信しているアプリを、それなりの期間継続的に開発してきて知見も溜まってきましたし、ここで RubyMotion を使い続けての実感みたいなものを述べていこうと思います。

パフォーマンス、アーキテクチャ的な点について

RubyMotion を使って個人的に最も良かったことは、やはりパフォーマンスです。まあ Objective-C で普通にネイティブアプリを作っている場合とはパフォーマンスは変わらないのですが、自分の場合は比較対象が Titanium Mobile だったので、ここは大きく改善しました。

なぜ RubyMotion は (一般にネイティブな言語に比べて遅いという印象がある Ruby であるにも関わらず) 速いのか。そしてその速度は Objective-C での実装とほぼ差がないのか。

これは、RubyMotion の説明になると避けては通れない話であり、また最も RubyMotion について誤解を招きやすい部分でもあります。

RubyMotion は Objective-C のジェネレーターではありません。 RubyMotion は、RubyObjective-C のコードに変換してそれからアプリをビルドするのではなくRuby のコードを直接 LLVMバイトコードコンパイルし、それを Objective-C ランタイムや Cocoa フレームワークとリンクしてアプリをビルドします。このとき重要なのは、Rubyバイトコードコンパイルされるということではなく「Objective-C ランタイムや Cocoa フレームワークと直接リンクされる」という点です。

RubyMotion は実は、RubyiOS アプリを作れるといっても実際にはかなり薄い実装で、Objective-C という言語が Objective-C ランタイムシステム (その実体は C言語関数群そのもの) の API を呼ぶ言語であるのと同様に、Ruby という言語で Objective-C ランタイムシステムAPI を呼んでいるだけの処理系なのです。(それから、RubyMotion の型も Core FoundationCocoa Foundation と同じように、Toll-free bridge です。)

そして、iOS アプリ開発というのは、実際には Objective-C によって iOSフレームワークAPI を呼び出し、その組み合わせによってアプリを構成することにほぼ等しい。最近の Webアプリケーション開発などもそうですが基本的に自分でプリミティブなロジックを組むということはほとんどなく、基本的にはライブラリやフレームワークが持っている API を呼んで、実際の計算処理のほとんどはそのライブラリ/フレームワーク側が担当することになります。

従って、Objective-CiOS アプリを作った場合でも、RubyMotion で作った場合でも、その計算処理のほとんどは Objective-C ランタイムおよび Cocoa フレームワークの内部で行われるので、その両者にパフォーマンス的な違いが出ることはほとんどありませんし、同様の理由から、挙動的な部分でも差異が出ることもまずありません。すなわち、速度面でも機能面でも、Objective-C で開発したときに出来て RubyMotion では出来ないことは(ほぼ)ないと言い切れるのですね。

iOS アプリを Ruby で作れる」というとなんとなく Ruby という言語処理系あるいはエコシステムがあって、それらを使って iOS アプリを作れるという印象が先行しますが、実際には Objective-CCocoa フレームワークのテクノロジースタック上で、言語のシンタックス部分だけが Ruby に置き換わったもの、という方が RubyMotion を正しく捉えた説明かなという気もします。実際、Ruby インタプリタガベージコレクションその他、普通の MRI Ruby の実行に必要なコア部分なんかは RubyMotion には含まれていません。

このアーキテクチャ的な特徴からくるパフォーマンスの良さや、Objective-C + Xcode な実装との互換性の高さの部分が、RubyMotion を使っているときに最も嬉しいと感じられる部分で、この観点についての RubyMotion の評価は概ねポジティブです。

Ruby 的な表現力について

このように RubyMotion は Objective-C Runtime の上に乗った薄い実装なのですが基本的なオブジェクト、コレクション、正規表現などに関しては RubyMotion 側で追加の実装が施されており (これも最終的には NSObject や NSArray、NSDictionary の API を呼ぶのですが) 、おかげで、シンタックス的には普段の Ruby の表現力が活かせます。クラス・モジュール構文や Mixin なんかも同じくです。

例えば配列を操作したいときには

@bookmarks << bookmark
@bookmarks.uniq! { |bookmark| bookmark.id }
@bookmarks.each do |bookmark|
  ...   
end

といつもの Rubyシンタックスで書くことができ、且つこれが100% ネイティブのコードになる。Objective-C の NSArray や NSDictionary の API だけで複雑なコレクション操作を行おうとすると気が滅入ることもありますが、その辺の負担がかなり軽減されます。派手な機能ではないものの開発しながら、地味にありがたいなあと思わされることが多いです。

一方、繰り返し述べたとおり RubyMotion は言語のシンタックスだけが Ruby になった iOS SDK みたいなものですから、こういったコレクション操作等以外の普段のコードは

def applicationWillEnterForeground(application)
  notify = NSNotification.notificationWithName("applicationWillEnterForeground", object:self)
  NSNotificationCenter.defaultCenter.postNotification(notify)
end

こんな感じで Cocoa Touch の API を呼ぶ、あまり Ruby らしくないコードを書くことになります。

当然、折角なので Ruby らしく書きたいと思う人は世の中にはたくさんいて、bubble-wrap や sugarcube、あるいは ProMotion といった、Ruby らしいシンタックスに置き換えるシンタックスシュガーライブラリが RubyMotion 用の gem として、結構活発に開発されています。この辺を使ったときにビフォア、アフターがどうなるかは ProMotion - naoyaのはてなダイアリー あたりにも以前に書きました。

それで、自分も最初は bubble-wrap や sugarcube なんかを積極的に導入して、なるべく Ruby っぽく書けるようにとやっていたのですが、これは正直失敗だったかなあというのが今となっての感想です。

確かに bubble-wrap や sugarcube で長い APIRuby らしく置き換えることはできるのですが、ある程度以上の規模のアプリを作っているとそれらライブラリがカバーしていない API 領域の API を使う頻度が増えてくる。そうすると、同じコードの中に、一方では Ruby らしくしようと頑張ったコード、一方は Cocoa Touch なコードと二つの体系が混ざってしまう。よかれと思って Ruby らしいスタイルを取り入れようとした結果、かえって中途半端な感じになってしまいました。コードを書くにあたって「今回はどちらのスタイルで書こうか・・・」と迷いが生じ、それを明確に判断する基準を自分の中に確立することができませんでした。

結局、昨今は基本 Objective-C 的なスタイルに寄せて、Ruby らしいスタイルは敢えてあまり使わなくなりました。非同期 HTTP リクエストを簡単に書ける BW::HTTP や、あまり大袈裟でない sugacube の一部シンタックスシュガーのみ控えめに使う・・・といった具合です。

最初は長ったらしい Cocoa フレームワークAPI をなんとか短い表現にラップしてから書いてやろうと思っていたのですが、慣れてくると、その長い APIAPI の動作をきちんと自己記述的に説明しているものであることが分かってきて、案外悪くもないなと思うようになったという心境の変化も大きかったと思います。いろいろすみませんでした。

サードパーティライブラリのエコシステムについて

こうして、RubyMotion の一つのウリであろう、RubyMotion 用にか書かれた rubygems ・・・ Top 10 Software Development Companies なんかでも多数公開されてますが、これらには当初期待していたほどの魅力は、自分は昨今はあまり感じなくなりました。前述の通り bubble-wrap や sugarcube のごく一部の API だけを使うに留めています。

一方で、RubyMotion は Objective-C ランタイムや Cocoa フレームワークと直でリンクするという性質上、rubygems だけでなく、Objective-C 用のサードパーティライブラリが凝ったブリッジなどを用意しなくても、そのまま使えるという特徴を持っています。Cocoapods にある実装なんかももちろん使える。

例えば以下は閲覧中のURLを外部サービスの Pocket に送る機能のコードです。

def performActivity
  SVProgressHUD.showWithStatus("保存中...")
  PocketAPI.sharedAPI.saveURL(@url, handler: lambda do |api, url, error|
      if error
        SVProgressHUD.showErrorWithStatus(error.localizedDescription)
      else
        SVProgressHUD.showSuccessWithStatus("保存しました")
      end
    end
  )
  activityDidFinish(true)
end

SVProgressHUD は Cocoapods で公開されている、画面上に半透明のメッセージウィンドウを出すことができる Objective-C ライブラリ。PocketAPI が Pocket の iOS SDK ですね。これらの API を、普通にリンクして呼び出すことができています。

RubyMotion を始めた当初は、どちらかというと機能の派手さや新鮮度から先の RubyMotion 用 rubygems に目がいっていましたが、ひとつのプロダクトを継続的に作り込んでいくとこちらの Objective-C ライブラリ群にアクセスできる、Objective-C エコシステムの資産を活かして巨人の肩に乗れる、ということの利点の方がずっと重要であることに気づきました。

この Pocket の例のように HBFav はいくつかの外部サービスを使っていますが、それら外部サービスを iOS アプリに組み込むための SDK はすべて Objective-C ライブラリとして公開されています。はてなブックマークはもちろん、Pocket、MBaaS の Parse.com、Google Analytics など。これらの SDK も、RubyMotion なら普通に使うことができます。

iOS アプリを他の言語で書く、的なものは Titanium Mobile や RubyMotion 以外にもいろいろな実装がありますが、この芸当が可能なのは、おそらく RubyMotion あるいは RubyMotion 同様のアーキテクチャを採用した実装 (例えば PerlMotion) ならではと言えます。

Objective-C でいいのでは? 論

しかし、こうして見てみると

  • パフォーマンスは高速であるとはいえ、Objective-C で普通に書いた場合とは、当然同じ
  • RubyMotion 用の rubygems は最初は有り難かったけど、使い込むうちに限定的にしか使わなくなった
  • 無理に Ruby 的なスタイルを採用するのはやめて、Objective-C スタイルに寄せるようになった
  • Cocoapods を初めとする Objective-Cサードパーティを使うことができる、とはいえこれは Objective-C であれば当然のこと

という感想になっていて、合理的に考えると、Ruby 組み込みのクラスが便利だという意外は、普通に Objective-C + Xcode で書けばそれでいいじゃないかという結論に達しそうな雰囲気があります。Objective-C の処理系も年々バージョンアップして簡単になってきているし、RubyMotion での (Rubyらしさをあまり追求しない) コードと Objective-C で書かれた同じコードを比較しても、そこに明らかな生産性の差が現れるかというと、そんなことも全くないと思います。

しかし実際には、RubyMotion でのアプリ開発には、Objective-C + Xcode でのそれとの間に何か言語化しづらい性質の違いがあるように思います。自分はそれほど Ruby という言語に思い入れのある方ではありませんが、生粋の Rubyist が何か Ruby に「気持ちがいい」とか「楽しい」とか、個別機能論ではなく総体としての Ruby にそういう体験を感じたりするように、RubyMotion にもまた、個別の事象だけを切り取って議論したときにはわからない手触り感の良さのようなものがあるように思います。その手触り感の魅力に関しては一年以上使い続けても色あせないばかりか、使い込むほどますます手に馴染んでいくので、ちょっと RubyMotion なしでの iOS 開発というのは考えにくい。そんなわけで自分はこれからも、HBFav のようなライフワークには変わらず RubyMotion を使い続けようと思っています。

逆に、例えば仕事でやるプロジェクトなんかで、Objective-C ではなく RubyMotion を採用すべき! ということは、自分は主張しません。各論で見た場合にその主張に説得力を持たせることができませんし、複数人数での開発では、やはり標準的な環境、Objective-C の堅牢さみたいなもののメリットの方が重要になってくると思います。その辺を十分分かった上でRubyMotion で本格的なアプリを開発しているというプロジェクト、例えば 37 Signals なんかを、面白いなあと思ってウォッチしたりはしています。

その他にもこの辺が良かった/課題だな、ということに

  • 開発元の HipByte のサポートとか、ユーザーとの近さ
  • (RubyMotion が薄い実装であるがゆえに) iOS 6 から iOS 7 へのキャッチアップに一切のタイムラグがなかったこと
  • 普段 Web 開発で使い慣れてる道具が使える
  • Objective-C と RubyMotion の間での知識移転が楽
  • 日本の RubyMotion コミュニティが楽しい (毎月のもくもく会がなかったら、途中で開発の手が止まっていたかもしれない・・・)
  • テストが書きやすい、という利点があまり活かせてない

みたいなことも挙げられますが、長くなってきたのでこの辺にしておきましょう。