Promise本の取り組み

JavaScript Promiseの本の開発で取り組んだ事と取り組んでおくべき(考慮しておくべき)だったことについてのメモ書き

技術書のディレクトリ構成とか参考になるものがあんまりなくて適当に作ったので、その辺を整理をしたい目的で書いてます。

はじめに

ここで書いたPromise本のディレクトリ構成やCI、テスト手法、使ったツールなどについては以下に大体書いてあります。

なので、結構省いて書いてるのでちゃんと見たい人は付録とかの方を見て下さい。

後、書籍の文章のフォーマットはAsciidocというOreillyが使っているものを使っていて、それのRuby実装であるAsciidoctorを使って書かれています。

多言語対応を考える

章毎に文章とソースコードとテストとリソースをディレクトリでまとめていた。

それをルート直下に配置していたため、多言語対応が取りにくい構成になってた。(実際、ディレクトリ浅くするためにこうしてたので、考えてなかった)

├── Appendix-Reference
│   └── readme.adoc
├── Ch0_Introduction
│   └── readme.adoc
├── Ch1_WhatsPromises
│   ├── embed
│   ├── img
│   ├── promise-overview.adoc
│   ├── readme.adoc
│   ├── src
│   ├── test
│   ├── what-is-promise.adoc
│   └── writing-promises.adoc
├── Ch2_HowToWrite
├── Ch3_Testing
├── Ch4_AdvancedPromises

書いた後に中国語の翻訳や韓国語の翻訳などもでてきたので、多言語対応を考えるまではいかないでも、以下みたいに一段ディレクトリを落としたほうが対応しやすいと思った。

docs/
├── Appendix-Reference
├── Ch0_Introduction
├── Ch1_WhatsPromises
├── Ch2_HowToWrite
├── Ch3_Testing
├── Ch4_AdvancedPromises

この辺は一般的な多言語に対応してるサイトの構成を参考にするのがいいのかと思った。

文章とソースコードとテストとリソースを章毎にディレクトリでまとめるという戦略自体は実際書きやすくなるので悪くないとは思ってるけど、他言語化すると重複するので分離するべきかは考える必要がありそう。

そもそもなんで章毎に全部リソースをまとめたかというと、プレビューのし易さを考えた時にそうなった。

Markedというプレビューするアプリを使っていて、以下のようなシェルスクリプトを作ってMarkedのCustom Markdown Processorのパスに指定して使っていた。

#!/bin/sh

/Users/azu/.rbenv/shims/asciidoctor -a icons=font -a source-highlighter=coderay \
 --backend html5 --base-dir "`dirname $MARKED_PATH`/" -

章ごとにディレクトリが切られていて、リソースもその下にあることでプレビューするときにbase directoryを指定して見ることができるのでそうなった。(上位ディレクトリとか参照できなかったというもあったけど、今どうなんだろ?)

コードの多言語化

できなかったこと

Asciidoc(Asciidoctor)では、 // <1> というコメントをコードに書いて、CodeBlockの外にその説明(<1> 説明文)を書くという事ができる。

こういう参照ってどういう時に使うべきなんだろというのが曖昧だったけど、訳す必要があるところを外部に出すという意味で有用そうな気がする。

[source,javascript]
.Promiseを使った非同期処理の一例
----
// CodeBlock
var promise = getAsyncPromise("fileA.txt"); // <1>
promise.then(function(result){
    // <2> 
}).catch(function(error){
    // <3>
});
----
<1> promiseオブジェクトを返す
<2> 取得成功の処理
<3> 取得失敗時の処理

多言語化を考えた時コードはできるだけ書き換えたくないと思うので、解説をコードの外に出せるという意味で使い道がありそう。

コードを外部ファイルとして管理してる場合は、コードが多言語間で共有しやすくもなる。 ただやり過ぎると読みにくいし、場合によっては複雑になりすぎるのでバランスが大事そう。(もしくはビルド時にコメントととして埋め込むとか)

実際に上の例で既に読みにくいので、書籍内だと以下みたいになってた。

[source,javascript]
.Promiseを使った非同期処理の一例
----
var promise = getAsyncPromise("fileA.txt"); // <1>
promise.then(function(result){
    // 取得成功の処理
}).catch(function(error){
    // 取得失敗時の処理
});
----
<1> promiseオブジェクトを返す

できたこと

JavaScript Promiseの本のサンプルコードはテストするために外部ファイルにしていて、Asciidocにファイルを読み込んで展開する機能があるのでそれを利用した。

----
include::embed/embed-promise-all-xhr.js[]
----

という感じで読み込める。

しかし、そのまま読み込んで使うとサンプルコードのモジュール化が難しくなってしまった。

モジュール化が上手くできないとテストを書くことが難しくなる。 そこで、サンプルコード(src)と表示用コード(embed)にわけることにした。

実際にはサンプルコードから表示用コードを自動生成して使っていた(余計な情報を取り除いたら結合したりするツールを書いた)

そのため、直接Asciidocに書くのは上記の例みたいに動かないけど説明的に必要なものが中心だったと思う。

その直接書いてるコードもシンタックスチェックだけをAsciidoc上のインラインコードテストという感じで行ってた。

電子書籍のテストの種類

先ほどの被るけど、Promise本だとテストを4種類のテストをやっていた。

できたこと

できなかったこと

今年のOSS活動振り返り @ 2014 | Web Scratchでも書いたけど、その後幾つかチェックツールを書いたので自動化できるんじゃないと思ってきたので次回やるならこれも行いたい。(Asciidocはパース難しそうなのでMarkdownとかがよさそう…)

後は文法チェックの自動化もRedPenとかでやりたかった(書いてる時まだなかった)

後、Google IME使うと普通にtypo多くなるのでATOKとか使ったほうが良さそうな気がしました。

Asciidoc

Asciidoc(処理系としてAsciidoctorを使用)を初めて使って書きましたがこれは間違いではなかったと思います。

一番最初にTODOに上げた時はMarkdown + Leanpubで書きたいなーと思ってました。

Markdownはインポート機能がないので、それをできるようにする拡張とかを書きながら色々考えましたが、結局はオレオレMarkdownになるだけでした。

以前から気になってたAsciidoctorを触ってみたらデフォルトのHTML出力もいい感じだし、機能不足がなくてreStructuredTextほど複雑な書き方ではなかったので選択しました(Markdownにかなり近い書き方ができる)。

HTMLでの公開がメインでPDFはおまけな感じでしたが、調べたら日本語もフォント指定すればできると分かったので大丈夫でした。

Asciidoctorは無駄に多機能ですが、ドキュメントがよくできてるので大体そこを見れば解決するので良い感じでした。

一方、Markdownの方が馴染みあるので LeanpubのMarkdown処理系であるMarkuaの公開に期待しましょうという感じです。

またMarkdownの方がASTを吐いてくれるパーサ/ジェネレータとかあるのでツールを作りやすいとは思います。

まあ、Asciidocでも意外とpull requestされたので、そこまで複雑な使い方しないでガイドラインとか設ければどちらでもいい気がしました。

Issueドリブンなワークフロー

できたこと

Promise本はGitHub Issueを使ったワークフローになってます。

主に移動中に書いていたので、GitHub Issueにそのセクションの書きたい内容のアウトラインや検証結果をちょっとづつ書いていって、ある程度内容が固まったら書き出すというスタイルをとっていました。

なので、自分自身にPull Requestを送るスタイルでやっていて、実際の細かなワークフローについては以下のスライドに書いてあります。

できなかったこと

Pull Requestに対するプレビュー環境がイマイチ上手くできてなかった。

現在もPull Requestされると自動でビルドしてpreview-htmlにpushすることで、それをrawgithub.com経由でHTMLがプレビュー出来るようになってます。

ただ、イマイチ感があるのでPull RequestごとにHerokuにデプロイしてプレビュー出来るようにしたい気がする。(masterへマージするとGitHub pagesにデプロイするのは自動化されています)

公開後の更新

今年のOSS活動振り返り @ 2014 | Web Scratchにも書いてましたが、いまでも少しづつ更新しています。

こういう形態で書籍を公開したのは、常に書籍が更新出来るようにしたいからでもあります。 -- JavaScript Promiseの本を書きました | Web Scratch

更新はモチベーションの問題も大きいですが、どれだけ簡単に更新できるかが大事な気がします。

  • CIによるテスト
  • デプロイの自動化

は一応できてるので、GitHub上で直接編集するだけ更新出来るようになってます。

GitHubのエディタがイマイチなのでどうにかして欲しい

今はtypoや直したいところを見つけたらとりあえずIssueを立てて、定期的にまとめて直してる感じです。

文章のレビューをしてる時も思いましたが、先にIssueに直したい理由と修正案を書いておいて、 実際の修正は後でまとめてやった方がリズムよくできるので自分的にはそういうやり方をしてます。(コミットにIssueのリンクを貼ることで、コミットするときに変更理由の詳細を書くの省ける)

その他