blog.tai2.nethttps://blog.tai2.net/2024-03-14T00:00:00+09:00Utility-first CSS(Tailwind CSS)が合理的であることの説明と、CSSによるUI開発小史2024-03-14T00:00:00+09:002024-03-14T00:00:00+09:00tai2tag:blog.tai2.net,2024-03-14:/utility-first-css.html<p class="first last">CSSを記述するための手法は、カスケーディングというCSSの難点を克服しつつ、UIコンポーネントを開発するために進化してきた。Tailwind CSSを使えば、子要素への名付けなど余計なことに煩わされずに、コンポーネント実装に集中できる。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#css" id="toc-entry-1">CSS小史</a></li>
<li><a class="reference internal" href="#suit-css-css" id="toc-entry-2">SUIT CSS - 命名規約ベースのCSS方法論</a></li>
<li><a class="reference internal" href="#styled-components-css-in-js" id="toc-entry-3">styled-components - CSS in JS</a></li>
<li><a class="reference internal" href="#tailwind-css-utility-first-css" id="toc-entry-4">Tailwind CSS - Utility-first CSS</a><ul>
<li><a class="reference internal" href="#section-1" id="toc-entry-5">なぜインラインスタイルではダメなのか</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-2" id="toc-entry-6">まとめ</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-7">タイムライン</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-8">参考リンク</a></li>
</ul>
</div>
<div class="section" id="css">
<h2><a class="toc-backref" href="#toc-entry-1">CSS小史</a></h2>
<p>CSSでアプリのUIを実装するための手法は、これまでいくかの変遷を辿ってきた。</p>
<p>はるか昔、CSSが生まれて間もないころには、関心の分離という文脈から、FONT要素などの物理タグはよくないものとされ、
コンテンツ(HTML)とスタイル(CSS)をきっちりと分離することが奨励されはじめた。
そこでは、HTMLはあくまで文書であり、CSSのクラスセレクタという接点でコンテンツと見た目が隔離されることで、それらは別世界のものとして管理されていた。
また、大規模サービス開発においていかにCSSを管理するかという問題意識はまだなかった。</p>
<p>時が経ち、HTMLは次第に複雑な対象を記述するようになった。モジュールやメンテナンス性といったことがCSSコーダーの関心時になると、
BEMに代表される「方法論」が発明され、発展してきた。
これは、カスケーディングというCSSの「欠点」を克服する努力であると共に、スタイルの再利用への道を開いた。</p>
<p>そして、Reactのような、JavaScriptを使ってHTMLをUIコンポーネントのツリーとして記述する手段が発明されると、
CSS in JSという新しい考えかたが生まれた。
すべてのクラスが自動的にユニークであることが保証される世界において、マークアップエンジニアの頭痛の種であった詳細度の管理という問題は一掃され、
スコープの不存在というCSS最大の問題は完璧に解決された。
いまや、コンテンツとスタイルの分離やスタイルの再利用という考え方は希薄になり、HTMLとCSSはひとまとまりに記述されるのが当たり前のこととして受け入れられた。</p>
<p>最後に、Adam Wathanが、HTML/CSSにおける関心の分離という概念に含まれる矛盾を指摘してTailwind CSSを発明すると、
UIコンポーネント時代のフロントエンドエンジニアから一定の支持を得て広まっていった。
CSSクラスの再利用性は最大化し、HTMLとコンテンツの依存性はいまや完全に断ち切られ、クラスはコンテンツから独立した存在となった。</p>
</div>
<div class="section" id="suit-css-css">
<h2><a class="toc-backref" href="#toc-entry-2">SUIT CSS - 命名規約ベースのCSS方法論</a></h2>
<p>CSSで素朴にスタイルを記述すると、 <a class="reference external" href="https://www.phase2technology.com/blog/used-and-abused-css">いとも簡単に特定の要素に対するルールの衝突が発生する。</a>
ルールが衝突したときにも、いい感じにスタイルが当たるように用意されているのが、CSSの名前の所以たるカスケーディングと、
詳細度にもとづいたルールの解決…なのだけど、これは非常に制御しづらく、往々にして予期しない出力を生み出す。</p>
<p>そこで先人達は、そもそもルールの衝突が発生しないように、一貫した規則に基いてクラス名を名付けることで問題を解決しようとした。
この命名規則の下では、ルールのセレクタは基本的にクラス一個なので詳細度が0-1-0に保たれる。
これならば、カスケーディングによる複雑で制御の難しいルール解決メカニズムをスキップできる。</p>
<p>SUIT CSSで <a class="reference external" href="https://www.stubbornella.org/2010/06/25/the-media-object-saves-hundreds-of-lines-of-code/">media object</a> を記述すると、以下のようになる。</p>
<iframe width="100%" height="400px" src="https://stackblitz.com/edit/media-object-suit-css?embed=1&file=src%2FMedia.css&view=editor"></iframe><p><a class="reference external" href="https://stackblitz.com/edit/media-object-suit-css?file=src%2FMedia.css">Media object - SUIT CSS</a>
(CSSコーディングが下手糞かもしれないけど、大目に見てください...。)</p>
<p><a class="reference external" href="https://github.com/suitcss/suit/blob/master/doc/naming-conventions.md">SUIT CSS</a> は、BEMに代表される命名規則によるCSS方法論の派生型のひとつ。
UIの状態を表現するための記法があるなど、UIコンポーネントのために使うことを意図した設計になっている。
react-native-webの作者である <a class="reference external" href="https://nicolasgallagher.com/">Nicolas Gallagher</a> が考案した。
CSS方法論は数多くあるけど、ぼくがCSS設計を教わった師匠からおすすめされて以来、Reactなどのモダンでない環境でCSSを書くときには、いまでもこれを使ってる。</p>
<p>これでCSSで大規模UIを記述する際のもっともつらい問題はたしかに緩和される。だけど、問題もある。
コンポーネント内の子要素に対して、クラスをどのように区切ってスタイルを適用するのがいいのかが自明ではなく、しばしば迷いが生じる。
CSSはHTMLから独立して(HTMLに依存せずに)考えるのが理想とされるにも関わらず、実際には、しばしばHTMLの構造を考えながらCSSを記述しなければならない。
なんならHTMLで構造を書いて、そこにクラスを振ってから、スタイルの中身を書いていくということも普通だ。
また、上のサンプルコードを見ればわかるように、CSSの世界でコンポーネントの構造を表現した上で、さらにHTMLの世界でも再度対応する構造を
表現している。非常に冗長だ。コンポーネントについて考えたり書くときには、つねにこの二度手間が発生する。</p>
<p>ぼくは、CSS方法論による実装は、いまとなっては、どうしても必要でなければ避けたほうがいいと考えている。これは実体験にもとづく。
いままでいくつかのプロジェクトで、CSS方法論を導入してみてわかったのだけど、多くのプログラマ、とくにフロントエンド技術にあまり興味
のないバックエンドエンジニアには、これを正しく使いこなすのは難しいのだと思う。
ふつうのプログラマは、CSSのカスケーディングや詳細度の存在すら知らないし、なぜCSS方法論が必要なのかが、そもそもわからない。
そして、CSS方法論は、なにか強制力のあるメカニズムではなく、あくまでただのコーディング上の約束事に過ぎない。
だから、クラスの衝突を避け、詳細度を一定に保つという方法論の基本原則から、簡単に逸脱してしまう。</p>
<p>もちろん、時間をかけて、それらの意味について教えて理解してもらうことはできると思うけど、以下に説明するように、現代では、
CSS方法論を用いずに、もっと賢くカスケーディングの問題を解決する方法が、他にある。</p>
</div>
<div class="section" id="styled-components-css-in-js">
<h2><a class="toc-backref" href="#toc-entry-3">styled-components - CSS in JS</a></h2>
<p>CSS in JSは、ReactなどのJSを使ってUIコンポーネントを記述する仕組みを前提としている。
代表的なモジュールのひとつがstyled-componentsで、これは、テンプレートリテラル内に記述されたCSSから、
JSXコンポーネントと、そのコンポーネント専用のユニークなクラスを動的に生成する仕組みになっている。</p>
<p>styled-componentsで、先程のmedia objectを書き直すと以下のようになる。</p>
<iframe width="100%" height="400px" src="https://stackblitz.com/edit/media-object-styled-components?embed=1&file=src%2FMedia.tsx&view=editor"></iframe><p><a class="reference external" href="https://stackblitz.com/edit/media-object-styled-components?file=src%2FMedia.tsx">Media object - styled-components</a></p>
<p>CSS in JSの優れた点は、機械的にユニークなクラス名を生成するので、スタイルの衝突がけっして起きないことだ。
<sup id="sf-utility-first-css-1-back"><a href="#sf-utility-first-css-1" class="simple-footnote" title="ただし、styled-componentsの記述内に自動生成でないふつうのクラスを入れ子にすることも許可しているので、この場合は衝突が発生し得る">1</a></sup>
これはふつうに使っているだけでスタイルを曖昧さなく当てられるので、非常に大きな進歩だ。
もはやめんどうな方法論の規則を覚える必要はない。ただAPIの使いかたを知ればいいだけなので、だれでも無理なく使える。</p>
<p>ただし、styled-componentsのAPI設計には、一点だけどうしても気に食わない点がある。
ぼくは、styled-componentsもいくつかのプロジェクトで使ってきたけど、使えば使うほど不便と感じるようになってきた。
上記のサンプルコードを見ればわかるように、styled-componentsでは、スタイルを記述するために、
必ずひとつReactコンポーネントを作らなければならない。結果として、ほんとうに作りたいコンポーネントのまとまりひとつ(この場合
Mediaコンポーネント)に対して、いくつもの小さなコンポーネントを不必要に作らなければならないのだ。
もちろん、それぞれに対して命名するという作業も必要になる。これが相当わずらわしい。
ただスタイルを持つだけで、とくだん機能も論理的な意味もない無数の小さなコンポーネントが定義されているのはノイズだし、
スタイルとDOM構造が離れた場所に定義されているのも、どのような表示結果になるのかを一見して把握しづらくしている。</p>
<p>ここ最近、Metaが内製のCSS in JSライブラリであるstylexを公開したことで、ゼロランタイムと言われるCSS in JSの別の一派が注目されているようだ。
これは、ビルド時にスタイル定義を解決することで、ランタイム処理を最小限にすることを狙っている。
また、Atomic CSSなので、CSSファイルのサイズも非常に小さくなる。
<sup id="sf-utility-first-css-2-back"><a href="#sf-utility-first-css-2" class="simple-footnote" title="Atomic CSSは、ひとつのクラスに対して複数のルールを記述する旧来の方法よりファイルが小さくなることが 知られている。 ">2</a></sup>
stylexでは、1コンポーネント1スタイルという一対一関係こそ強制されないものの、ドキュメントで <a class="reference external" href="https://stylexjs.com/docs/learn/thinking-in-stylex/#readability--maintainability-over-terseness">述べられている通り、</a>
本質的にDOMとスタイルを切り離して、スタイルの塊に何らかの意味のある名前をつけるのを良しとしているという点で、
styled-componentsと本質的には変わらないと思う。これでは、ぼくがstyled-componentsに対して持っている不満点は解消されない。</p>
</div>
<div class="section" id="tailwind-css-utility-first-css">
<h2><a class="toc-backref" href="#toc-entry-4">Tailwind CSS - Utility-first CSS</a></h2>
<p>CSSにおけるユーティリティークラスとは、単一の機能に特化したクラスのことで、そのほとんどは、プロパティーをひとつだけ持つ。
たとえば、さきほど紹介したSUIT CSSにも <a class="reference external" href="https://github.com/suitcss/suit/blob/master/packages/utils-flex/lib/flex.css">ユーティリティークラス郡</a> が含まれている。以下はその一例。</p>
<div class="highlight"><pre><span></span><span class="p">.</span><span class="nc">u-flex</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">flex</span><span class="w"> </span><span class="cp">!important</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">.</span><span class="nc">u-flexInline</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="kc">inline-flex</span><span class="w"> </span><span class="cp">!important</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">.</span><span class="nc">u-flexRow</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">flex-direction</span><span class="p">:</span><span class="w"> </span><span class="kc">row</span><span class="w"> </span><span class="cp">!important</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">.</span><span class="nc">u-flexRowReverse</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">flex-direction</span><span class="p">:</span><span class="w"> </span><span class="kc">row-reverse</span><span class="w"> </span><span class="cp">!important</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>これらは、コンポーネントで表現し切れない部分、あるいはすべてをコンポーネント化して表現しようとすると無理が出てくる細かい部分を
補う目的で用意されている。
あるものの再利用性というものは、その責務が小さければ小さいほど高まる。ユーティリティークラスは、
単一責任に特化しているがゆえに、再利用性が最大化されているクラスだと言える。</p>
<p>これを推し進めて、ユーティリティークラスのみでスタイルを記述するという考えかたに発展させたのが、Utility-first CSSであり、
Utility-first CSSをサポートするCSSライブラリの中でも、現在最も支持を得ているのがTailwind CSSだ。
Tailwind CSSで、media objectを実装すると以下のようになる。</p>
<iframe width="100%" height="400px" src="https://stackblitz.com/edit/media-object-tailwind?embed=1&file=src%2FMedia.tsx&view=editor"></iframe><p><a class="reference external" href="https://stackblitz.com/edit/media-object-tailwind?file=src%2FMedia.tsx">Media object - Tailwind CSS</a></p>
<p>このファイルには、もはやわれわれがほんとうに作りたいMediaコンポーネント以外の、不要なものはなにもない。
内部にある各要素をどのようなコンセプトでくくって、どのような名前をつければいいかなどと考えなくていい。
欲しい見た目と機能を実装するために必要なこと、コンポーネント実装の本質に最短で取りかかって集中できる。
これこそ、まさにぼくがUtility-firstが合理的であると考える理由だ。</p>
<p>また、上記のflexユーティリティーの例を見ればわかるように、ユーティリティークラスの大半は、Atomicでもある。
したがって、CSSのサイズは自然とコンパクトになる。Tailwindなら、高速にコーディングできるし、高速にページがロードされる。
ランタイム時のオーバーヘッドももちろんゼロだ。</p>
<p>サイズに関して言うと、Tailwindは、アプリのコードをスキャンして、参照されているクラスのみを出力するという仕組みになっている。
この仕組みのシンプルさもまた、すばらしくクールで気に入っている点だ。バンドラーのような複雑な仕組みはまったく必要ない。
Reactなど特定のライブラリと結びついているわけではないので、やりたければRailsのサーバーサイドテンプレートと組み合わせて使うことだってできる。</p>
<p>もうひとつ注目してもらいたいのが、SUIT CSSの例では、 <code>Media-button</code>, <code>Media-date</code>, <code>Media-separator</code> に個別に色を指定していたのに対して、
Tailwindの例では、親要素で一度色を指定して、子要素はそれを継承するだけで済んでいる点だ(styled-componentsの例も本質的に同じ)。
もちろん、SUIT CSSの例でも、親要素にクラスを付与して共通の色指定をすることは可能だ。
しかし、この例では、右側を複数の行(Meida-row)でまとめるという考え方で記述していた。1行目と2行目は色が違うので、Media-rowに直接
色を指定することはできない。では、Media-rowのモディファイアーを作るか。それともMedia-1stRow, Media-2ndRowというまとめかたをするか。
名付けが必要であるがゆえに、このように本質的でない悩みがいちいち発生する。
Tailwindなら、日付とボタンが同じ色だから、その親要素に色を指定するというシンプルな判断ができて、迷う余地はない。
なんと快適なことだろう。</p>
<p>Tailwindには、他にも特筆すべき点がある。それは、色やサイズなどすべてにおいて、指定できる値が限定されているということだ。
この使用可能な値の集合は、設定ファイルで指定できる。これは、実質的に、Tailwindを使えば自動的にデザイントークンが導入されて、
一貫したトーン&マナーのUIになるということだ。もちろん、他のどの方法でもデザイントークンに基いた実装をすることはできるけど、
Tailwindなら、なにも考えなくても強制的にそうなる。</p>
<p>以上見てきたように、Tailwindは独自のクラス名体系があって学習曲線が多少高かったり、デザイントークンが勝手に導入されるなど、
SUIT CSSやstyled-componentなどと比べて、かなり主張の強めな選択肢であることは否めないかもしれない。
また、Tailwindでは、カスケーディングによるクラス名衝突の問題は、実はあまり解決されていない。
クラス名にプレフィックスを付与する設定はあるので、ある程度緩和はできるけど、CSS in JSのように衝突を避けるメカニズムがあるわけではないからだ。
だから、既存プロジェクトに段階的に導入するなど、実際にクラス名の衝突が危惧される状況では注意が必要だ。
しかし、最初からTailwindですべて記述すると決めて始めたプロジェクトであれば、衝突の心配はないだろう。</p>
<p>ともかく、一度ハマってしまえば、そのシンプルさから来る開発体験の快適さは、クセになること請け合いだ。</p>
<div class="section" id="section-1">
<h3><a class="toc-backref" href="#toc-entry-5">なぜインラインスタイルではダメなのか</a></h3>
<p>もしかすると、Tailwindでスタイルを記述するくらいなら、style属性にスタイルを直接記述すればいいと思われるかもしれない。
しかし、style属性には、機能的な制限がある。メディアクエリーや、疑似クラス、アニメーションといったことが記述できないのだ。
最近は、style属性を拡張して疑似クラスなどを記述できるようにする <a class="reference external" href="https://css-hooks.com/">CSS Hooks</a> のようなライブラリも
あるようだけど、それでも拡張できる範囲は限られている。
もし今後、style属性からCSSの全機能にアクセスできる技術が出てくれば、そのときはstyle属性で済ませてもいいのかもしれない。</p>
</div>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-6">まとめ</a></h2>
<ul class="simple">
<li>CSSを記述するための手法は、カスケーディングというCSSの難点を克服しつつ、UIコンポーネントを開発するために進化してきた。</li>
<li>Tailwind CSSを使えば、子要素への名付けなど余計なことに煩わされずに、コンポーネント実装に集中できる。</li>
<li>Tailwindおすすめ。</li>
</ul>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-7">タイムライン</a></h2>
<table border="1" class="docutils">
<colgroup>
<col width="21%">
<col width="79%">
</colgroup>
<tbody valign="top">
<tr><td>1996/12/17</td>
<td><a class="reference external" href="https://www.w3.org/TR/2008/REC-CSS1-20080411/">CSS 1</a></td>
</tr>
<tr><td>1998/5/12</td>
<td><a class="reference external" href="https://www.w3.org/TR/2008/REC-CSS2-20080411/">CSS 2</a></td>
</tr>
<tr><td>2009/5/20</td>
<td><a class="reference external" href="https://rubygems.org/gems/less/versions/0.7.0">less 0.7.0</a></td>
</tr>
<tr><td>2010/9/22</td>
<td><a class="reference external" href="https://rubygems.org/gems/sass/versions/3.1.0.alpha.2">sass 3.1.0.alpha.2</a></td>
</tr>
<tr><td>2013/7/18</td>
<td><a class="reference external" href="https://github.com/twbs/bootstrap/releases/tag/v1.0.0">Bootstrap 1.0.0</a></td>
</tr>
<tr><td>2013/7/3</td>
<td><a class="reference external" href="https://github.com/facebook/react/releases/tag/v0.3.0">React 0.3.0</a></td>
</tr>
<tr><td>2013/10/16</td>
<td><a class="reference external" href="https://github.com/bem/bem-core/releases/tag/v1.0.0">BEM CORE 1.0.0</a></td>
</tr>
<tr><td>2014/3/23</td>
<td><a class="reference external" href="https://www.npmjs.com/package/suitcss/v/0.3.0">SUIT CSS 0.3.0</a></td>
</tr>
<tr><td>2014/10/30</td>
<td><a class="reference external" href="https://www.npmjs.com/package/jss/v/0.2.0">jss 0.2.0</a></td>
</tr>
<tr><td>2015/2/12</td>
<td><a class="reference external" href="https://www.npmjs.com/package/atomizer/v/0.2.0">atomizer 0.2.0</a></td>
</tr>
<tr><td>2015/3/7</td>
<td><a class="reference external" href="https://www.npmjs.com/package/tachyons/v/1.1.0">tachyons CSS 1.1.0</a></td>
</tr>
<tr><td>2016/10/13</td>
<td><a class="reference external" href="https://www.npmjs.com/package/styled-components/v/1.0.0">styled-components 1.0.0</a></td>
</tr>
<tr><td>2017/11/2</td>
<td><a class="reference external" href="https://github.com/tailwindlabs/tailwindcss/releases/tag/v0.1.0">tailwindcss 0.1.0</a></td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-8">参考リンク</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://blog.decaf.de/2015/06/24/why-bem-in-a-nutshell/">‘Why BEM?’ in a nutshell</a> なぜBEMを使うべきか。詳細度の問題についての解説。</li>
<li><a class="reference external" href="https://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/">MindBEMding – getting your head ’round BEM syntax</a> なぜBEMを使うべきか。BEMの可読性についての解説。</li>
<li><a class="reference external" href="https://nicolasgallagher.com/about-html-semantics-front-end-architecture/">About HTML semantics and front-end architecture</a> SUIT CSS作者Nicolas Gallagherによる、フロンエンド開発におけるクラスの役割についての考察。クラス名は、コンテンツにもとづいて命名されるべきではなく、デザインのパターンに基いた名前を持つべき。</li>
<li><a class="reference external" href="https://www.smashingmagazine.com/2013/10/challenging-css-best-practices-atomic-approach/">Challenging CSS Best Practices</a> Yahoo!で使われているAtomic CSSのメリットについて述べている。従来的なセマンティックなクラス命名との比較。</li>
<li><a class="reference external" href="https://acss.io/frequently-asked-questions.html">Frequently Asked Questions | Atomizer</a> Atomic CSSのFAQ。それが解決する問題について。詳細度の問題が起きない、サイズが小さくなるなど。多くは、Utility-first CSSにも当てはまる。</li>
<li><a class="reference external" href="https://adamwathan.me/css-utility-classes-and-separation-of-concerns/">CSS Utility Classes and "Separation of Concerns"</a> BEMのようなCSS方法論でコンポーネントを記述することの不安定さを解いている。再利用性を付きつめていくと、意味に基いて命名されたクラスの多くはいつのまにかなくなってしまう。Tailwindの思想的な背景を説明している。</li>
<li><a class="reference external" href="https://www.youtube.com/watch?v=9JZHodNR184">Building the New Facebook with React and Relay | Frank Yan</a> StylexによるAtomic CSS化でfacebookが達成したCSSの軽量化について触れている。413KBから74KBに。</li>
<li><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance">Cascade, specificity, and inheritance</a> カスケーディング、詳細度、継承がどのように組み合わさって、実際の見た目を形成するかを具体例に基いて解説している。(あなたが読んでいる)この記事で解説している方法を使えば、これらのうちカスケーディングと詳細度はほとんど意識しなくてよくなる。</li>
<li><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade">Introducing the CSS Cascade</a> カスケーディングの詳細な説明。</li>
<li><a class="reference external" href="https://www.phase2technology.com/blog/used-and-abused-css">Used and Abused – CSS Inheritance and Our Misuse of the Cascade</a> カスケーディングがもたらす具体的な害の解説。複数の詳細度がまぜこぜになると、あるルールをいじったときに、どこでなにが起きるか予測できなくなる。</li>
<li><a class="reference external" href="https://html.spec.whatwg.org/multipage/dom.html#classes">HTML Standard</a> HTML標準では、クラス名で見た目ではなくコンテンツの性質を記述することが奨励されている。</li>
<li><a class="reference external" href="https://www.stubbornella.org/2010/06/25/the-media-object-saves-hundreds-of-lines-of-code/">The media object saves hundreds of lines of code</a> media object 具体的なコンテンツの意味に言及しないクラス設計の有効性を示した有名な例</li>
<li><a class="reference external" href="https://csszengarden.com/">CSS Zen Garden: The Beauty of CSS Design</a> HTML構造とCSSクラスを固定したまま、スタイルシートの変更だけでさまざまなデザインをする試み。コンテンツにもとづいた意味を持つクラス名の興味深い活用例。CSS in JSやUtility-first CSSでは、こういうことはできない。</li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-utility-first-css-1">ただし、styled-componentsの記述内に自動生成でないふつうのクラスを入れ子にすることも許可しているので、この場合は衝突が発生し得る <a href="#sf-utility-first-css-1-back" class="simple-footnote-back">↩</a></li><li id="sf-utility-first-css-2">Atomic CSSは、ひとつのクラスに対して複数のルールを記述する旧来の方法よりファイルが小さくなることが <a class="reference external" href="https://sebastienlorber.com/atomic-css-in-js">知られている。</a> <a href="#sf-utility-first-css-2-back" class="simple-footnote-back">↩</a></li></ol>DenoでCLIを作るのはとても快適2023-11-05T00:00:00+09:002023-11-05T00:00:00+09:00tai2tag:blog.tai2.net,2023-11-05:/deno-cli.html<p class="first last">最近ちょっと必要があって、DenoでCLIコマンドを作ってみた。いままでこういったCLIコマンドはNodeやPythonで作ることが多かったのだけど、Denoの使い心地が知りたくて作ってみたら、思いのほかすごく快適だったので紹介する。</p>
<p>最近ちょっと必要があって、DenoでCLIコマンドを作ってみた。
いままでこういったCLIコマンドはNodeやPythonで作ることが多かったのだけど、Denoの使いごこちが知りたくて作ってみたら、
思いのほかすごく快適だったので紹介する。</p>
<p>作ったのは、 <a class="reference external" href="https://github.com/tai2/decor">decor</a> というマークダウン変換ツールだけど、なにを作ったかよりも、
どう作ったかが重要なので、この記事では深掘りしない。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#deno" id="toc-entry-1">Denoとは</a></li>
<li><a class="reference internal" href="#deno-1" id="toc-entry-2">Deno気持ちいい</a></li>
<li><a class="reference internal" href="#section-1" id="toc-entry-3">プログラムの配布</a><ul>
<li><a class="reference internal" href="#section-2" id="toc-entry-4">ソースコード以外のアセットを配布する</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-3" id="toc-entry-5">依存モジュールの自動更新</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-6">マークダウンパーサー</a></li>
<li><a class="reference internal" href="#dom" id="toc-entry-7">DOMパーサー</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-8">まとめ</a></li>
</ul>
</div>
<div class="section" id="deno">
<h2><a class="toc-backref" href="#toc-entry-1">Denoとは</a></h2>
<p>Denoは、Node.jsのクリエイターである
<a class="reference external" href="https://tinyclouds.org/">Ryan Dahl</a> が、
<a class="reference external" href="https://yosuke-furukawa.hatenablog.com/entry/2018/06/07/080335">Node.jsでの反省点</a>
を盛り込んで作り直したJavaScriptランタイム。セキュリティーを念頭に置いて設計されている。詳しくは検索してください。</p>
</div>
<div class="section" id="deno-1">
<h2><a class="toc-backref" href="#toc-entry-2">Deno気持ちいい</a></h2>
<p>まずなにがいいって、TypeScriptがファーストクラスの言語として組み込まれていて、わずらわしい設定なしに、すぐTypeScript
でプログラムを書きはじめられる。tsconfigを読み込ませることも可能ではあるけど、設定を変更するのは推奨されていない。
それでいい。</p>
<p>そしてそれだけでなく、フォーマッター、リンター、テストランナーなども組み込みコマンドとして付いてくる。いまどきの言語環境
らしく必要なもの全部入り。なんだかんだでJavaScript(TypeScript)が最も手に馴染んだ言語のひとつであるぼくのような
プログラマーにとっては、とにかくお手軽にTypeScriptでプログラムを作れるdenoという環境が、ものすごくありがたい。</p>
<p>Denoはセキュリティーを念頭に置いて設計された環境でもある。具体的には、ファイルの読み込み、書き込み、
ネットワークアクセス、環境変数アクセスなど、どれも明示的な許可が必要になる。権限は、コマンドライン引数で指定するか、
または、ユーザーが都度インタラクティブに与えることもできる。なんとも先進的でクールだ。</p>
<p>パッケージについては、Nodeのnpmのように中央集権的なリポジトリがあるわけではない。基本的には、GitHubのリポジトリがその
ままパッケージという形になる。<a class="reference external" href="https://deno.land/x">https://deno.land/x</a> というDenoのパッケージを一覧できるサイトもあるけど、本質的には
GitHubリポジトリにアクセスするためのCDNに過ぎない。 <code>package.json</code> でパッケージを管理する必要はなく、</p>
<div class="highlight"><pre><span></span><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">DOMParser</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">'https://deno.land/x/deno_dom@v0.1.42/deno-dom-wasm.ts'</span><span class="p">;</span>
</pre></div>
<p>こういうふうにリモートURLから直接インポートする。</p>
<p>ただし、npmもサポートしていて、 <a class="reference external" href="https://docs.deno.com/runtime/manual/node/compatibility">Node互換API</a>
も完全にではないにしろ用意されてはいるので、Node.js向けに公開されているパッケージもある程度利用できる。</p>
<p>感覚としては、npmの <a class="reference external" href="https://blog.tai2.net/node-quiz-about-npm-install.html">そこそこ複雑なモジュール解決の仕組み</a>
がガッツリないことで、ブラックボックス部分が少なく、シンプルに使えるという感覚がある。モジュール解決に柔軟性を持たせる
仕組みとして、JS標準の <a class="reference external" href="https://docs.deno.com/runtime/manual/basics/import_maps">Import Maps</a> も
サポートしている。</p>
</div>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-3">プログラムの配布</a></h2>
<p>CLIコマンドを書いたら配布したくなる。Denoで書いたプログラムを配布するのはとても簡単だ。ユーザーシステムにdenoがすでに
インストールされている前提であれば、</p>
<div class="highlight"><pre><span></span>deno<span class="w"> </span>install<span class="w"> </span>--allow-write<span class="w"> </span>--allow-read<span class="w"> </span>https://deno.land/x/decor/src/decor.ts
</pre></div>
<p>このようにソースコード(エントリポイント)を指定してinstallコマンドを実行するだけでいい。この際、指定した権限がインスト
ールされるスクリプトに付与されるので、ユーザーが毎回権限を指定する必要はない。</p>
<p>また、ユーザーにdenoランタイムのインストールを要求したくない場合は、Typescriptのソースコードをネイティブバイナリ
ファイルにコンパイルすることもできる。</p>
<div class="highlight"><pre><span></span>deno<span class="w"> </span>compile<span class="w"> </span>--target<span class="w"> </span>x86_64-unknown-linux-gnu<span class="w"> </span>--allow-write<span class="w"> </span>--allow-read<span class="w"> </span>src/decor.ts
</pre></div>
<p>WindowsやmacOSなど各種環境へのクロスビルドが可能。便利。この機能を使って、リリースごとに <a class="reference external" href="https://github.com/tai2/decor/blob/main/.github/workflows/build.yml">CIで各種プラットフォーム向けの
バイナリをビルドし、アセットとして添付するようにした。</a>
denoで作ったプログラムを配布するのがいかに簡単かについては、HomebrewのクリエイターであるMax Howeellも
<a class="reference external" href="https://deno.com/blog/tea-simplifies-distributing-software">詳しく紹介している。</a></p>
<p>また、ぼくはふだんmacOSを使っているので、コマンドはなるべく <a class="reference external" href="https://brew.sh/">Homebrew</a> で管理したい。
なので、Homebrew用の <a class="reference external" href="https://github.com/tai2/homebrew-brew/blob/main/Formula/decor.rb">Formula</a>
も用意した。Denoプログラム用には、 <a class="reference external" href="https://github.com/Homebrew/brew/blob/ff404fe5ab7491b074568b3e20ef001b5ca39595/Library/Homebrew/language/node.rb">Node.jsのように便利なユーティリティー関数</a>
もいまのところ用意されていないものの、denoの仕組み自体シンプルなので素直に実装できた。</p>
<div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">install</span>
<span class="w"> </span><span class="n">prefix</span><span class="o">.</span><span class="n">install</span><span class="w"> </span><span class="s2">"src"</span>
<span class="w"> </span><span class="nb">system</span><span class="w"> </span><span class="s2">"deno"</span><span class="p">,</span><span class="w"> </span><span class="s2">"install"</span><span class="p">,</span><span class="w"> </span><span class="s2">"--allow-read"</span><span class="p">,</span><span class="w"> </span><span class="s2">"--allow-write"</span><span class="p">,</span><span class="w"> </span><span class="s2">"--root"</span><span class="p">,</span><span class="w"> </span><span class="s2">"."</span><span class="p">,</span><span class="w"> </span><span class="s2">"</span><span class="si">#{</span><span class="n">prefix</span><span class="si">}</span><span class="s2">/src/decor.ts"</span>
<span class="w"> </span><span class="n">bin</span><span class="o">.</span><span class="n">install</span><span class="w"> </span><span class="s2">"bin/decor"</span>
<span class="k">end</span>
</pre></div>
<p>このように必要なコード一式をインストールしてから、 <code>deno instal</code> を実行して、ラッパースクリプトを生成する。
そして、最後に生成されたラッパースクリプトをインストールすればいい。</p>
<div class="section" id="section-2">
<h3><a class="toc-backref" href="#toc-entry-4">ソースコード以外のアセットを配布する</a></h3>
<p>Nodeであれば、パッケージに内包されるファイルは、 <code>npm install</code> 時に、配布先システムのnode_modules内に
配置される。しかし、denoにはパッケージをインストールするという概念がないし、バンドラーのように任意のファイルをimport
することもできない。つまり、denoでは、任意のアセットをパッケージの一部として、手軽に配布する機能がない。</p>
<p>今回作成したプログラムでは、マークダウンファイルやHTMLをプログラムの一部として配布、というかプログラムから参照して使いた
かった。もちろんソースコード内部に文字列として定義することもできるが、できれば別ファイルに分離してアセットとして管理
したい。調べたところ、Denoでは、以下のようにJSONファイルへの依存関係をimportで記述できることがわかった。</p>
<div class="highlight"><pre><span></span><span class="k">import</span><span class="w"> </span><span class="nx">assets</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">'./assets.json'</span><span class="w"> </span><span class="nx">assert</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">type</span><span class="o">:</span><span class="w"> </span><span class="s1">'json'</span><span class="w"> </span><span class="p">}</span>
</pre></div>
<p>そこで、必要なアセットを <a class="reference external" href="https://github.com/tai2/decor/blob/main/src/build_assets.ts">JSONに変換しておくことで</a>
プログラムの一部としてアセットを配布できるようにするという方法を考えた。バイナリファイルなども、BASE64エンコードすれば
JSONに入れておくことができるだろう。</p>
</div>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-5">依存モジュールの自動更新</a></h2>
<p>Nodeであれば、dependabotやrenovateといったツールで依存モジュールの自動更新ができるが、どちらのツールも現在のところ
Denoをサポートしていない。</p>
<p>Denoでは、 <a class="reference external" href="https://github.com/hayd/deno-udd">udd</a> というツールで依存ファイルの更新検出とアップデートができる
ので、これを <a class="reference external" href="https://github.com/tai2/decor/blob/main/.github/workflows/udd.yml">スケジュールジョブで回して</a>
依存ライブラリの更新をするようにした。</p>
<p>ただし、更新してくれるのは直接依存しているファイルのみで、依存の依存までは見てくれないため、依存ライブラリが依存している
ライブラリに脆弱性が発見されたときに、部分的に依存を更新するといったケースは、この仕組みでは対応できない
(Nodeであれば、 <code>package-lock.json</code> や <code>yarn.lock</code> の更新で対応できるケース)。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-6">マークダウンパーサー</a></h2>
<p>decorはマークダウンを扱うツールなので、マークダウンパーサーが必要だった。TypeScriptで使えるマークダウンパーサーには
いくつか選択肢があるが、機能とAPIを検討した結果、今回は、 <a class="reference external" href="https://github.com/markedjs/marked">marked</a> を
使うことにした。型情報が後付けではなく、最初からTypeScriptで開発されているのも、高評価ポイントのひとつ。</p>
<p>ただし、markedをDenoから使うにもいくつか方法がある:</p>
<ol class="arabic simple">
<li>npmモジュールとして利用する方法: <code>import { marked } from 'npm:marked';</code></li>
<li>deno.landで <a class="reference external" href="https://deno.land/x/marked@1.0.2">配布されているバージョン</a> を利用する方法</li>
<li>GitHubから直接参照する方法</li>
</ol>
<p>方法1の場合は、元々TSで書かれたものが、JSにコンパイルされて型情報と分離された状態で配布される形になる。Denoは、Nodeで
TypeScriptを使ったときと違って、<code>d.ts</code> から自動的に型情報を見つけてはくれない。型情報の定義先も
<a class="reference external" href="https://docs.deno.com/runtime/manual/advanced/typescript/types#providing-types-when-importing">別途明示的に記述する必要がある。</a>
何だか面倒なのでもっといい方法があるなら、そちらを選択したい。</p>
<p>2は、どこかの誰かが、markedをdenoから利用しやすいようにdeno.landな形にして登録したものらしい。試してみたところ、本来
得られて欲しい型情報がanyになっていたりして、いまいちだった。中身がどうなっているのか見てみると… <a class="reference external" href="https://github.com/prettykool/marked-deno/blob/main/mod.ts">単にnpm specifierで
importしているだけだった。</a></p>
<p>よくよく考えると、さきほども書いたようにmarkedのコードはそもそもTSで記述されている。そして、Denoはリモートのソース
コードを直接参照できるので、GitHubから直接importすればいいだけなのではないか。つまり、こうなる。</p>
<div class="highlight"><pre><span></span><span class="k">export</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">marked</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">'https://raw.githubusercontent.com/markedjs/marked/v9.1.4/src/marked.ts'</span>
</pre></div>
<p>TypeScriptのソースコードをそのままなので、型情報も完璧。Denoでは、このようにリポジトリから直接importできるので、
TypeScriptで書かれたライブラリを選択するモチベーションがより高くなると思う。</p>
</div>
<div class="section" id="dom">
<h2><a class="toc-backref" href="#toc-entry-7">DOMパーサー</a></h2>
<p>decorではHTMLのパーサーも必要だったので、いくつか選択肢を検討した。</p>
<p>Denoのサイトでも紹介されているように、DenoでHTMLをパースするときには、いくつかの選択肢がある。主なものは:</p>
<ul class="simple">
<li><a class="reference external" href="https://docs.deno.com/runtime/manual/advanced/jsx_dom/jsdom">js-dom</a></li>
<li><a class="reference external" href="https://docs.deno.com/runtime/manual/advanced/jsx_dom/deno_dom">deno-dom</a></li>
<li><a class="reference external" href="https://docs.deno.com/runtime/manual/advanced/jsx_dom/linkedom">LinkeDOM</a></li>
</ul>
<p>どれも型情報は提供されている。deno-domは唯一Deno向けにTypeScript(+Rust)で書かれたものなので、せっかくだから
deno-domを使ってみることにした。途中、うまく動かない箇所があり、バグ報告しつつLinkeDOMのほうも試してみたら、こちらは
こちらで型情報が間違っていて、目的が達成できないなどのトラブルがあった。そうこうしているうちに、deno-domのバグが修正
してもらえたので、けっきょくdeno-domで実装することができた。deno-domは報告したらけっこうサクッと問題修正してもらえ
たので、安心感がある。</p>
<p>いずれもHTMLのDOM APIに寄せて作られているので、乗り換えはそこまで大変ではないと思う。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-8">まとめ</a></h2>
<ul class="simple">
<li>Denoは設定ファイルほとんど不要ですぐに開発環境が整うので快適</li>
<li>TypeScriptまわりのあれこれについてもopinionatedにこうすると決めてくれているので、なにも考えずついていけばいいだけなのは楽</li>
<li>TSコードを単独で動作するネイティブバイナリにコンパイルできるので配布も楽</li>
<li>npmを参照したり、GitHubからTypeScriptソースコードを直接importできるので、サクッとアプリを作りやすい環境も整っている</li>
</ul>
</div>
プロセスよりもスレッドのほうが高速にコンテキストスイッチできることを検証する2022-11-30T00:00:00+09:002022-11-30T00:00:00+09:00tai2tag:blog.tai2.net,2022-11-30:/context-switch-experiment.html<p class="first last">プロセスとスレッドでコンテキストスイッチの時間が実際に違うのか検証してみたい</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-2" id="toc-entry-1">はじめに</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-2">コンテキストスイッチとは</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-3">実験1: パイプを使って計測する</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-4">実験2: より実際的なモデル</a></li>
<li><a class="reference internal" href="#vs" id="toc-entry-5">プロセス VS スレッド</a></li>
</ul>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-1">はじめに</a></h2>
<p>すこし前に、Chromeの開発をされているをやられている方のこんなスライドが回覧されてきた。</p>
<p>で、それを見てすこしひっかかったのが <a class="reference external" href="https://docs.google.com/presentation/d/12wd3hLkXVny0b5LnzizmH_3xe8zJ2WY5_9JprfIkp-o/edit#slide=id.g82989a6582_1_399">15ページ</a> のところ。
スレッドとプロセスの比較だけど、スレッドについて、このような言及がある。</p>
<blockquote>
そのぶんプロセスより軽い(メモリ使用量、コンテキストスイッチ)</blockquote>
<p>あれ、コンテキストスイッチの時間って、プロセスとスレッドで違うんだっけ…。</p>
<p>ぼくはアプリケーションプログラマなので低レイヤーのことは詳しくないのだけど、
プロセスもスレッドも、スケジューラーから見ると同じタスクという抽象であるみたいな話をどこかで聞いたことがあって、
コンテキストスイッチの時間にも違いはないのかと、なんとなく思い込んでいた。</p>
<p>これを実際に検証する方法はないだろうか。
コンテキストスイッチにかかる時間を測る方法を検索してみたら、このようなStack Overflowの回答があった。</p>
<p><a class="reference external" href="https://stackoverflow.com/a/7463383">context switch measure time</a></p>
<p>ふむふむ、これはおもしろい。ちょっとこの方法でプロセスとスレッドでコンテキストスイッチの時間が実際に違うのか検証してみたい。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-2">コンテキストスイッチとは</a></h2>
<p>現代のOSでは、限られたCPUの個数よりも多くのプログラムを同時に実行しているように見せかけるための、 <a class="reference external" href="https://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%AB%E3%83%81%E3%82%BF%E3%82%B9%E3%82%AF">マルチタスク</a> という仕組みがある。</p>
<p>マルチタスク環境では、一つのCPU上に、次から次へと異なるプログラムを載せかえて実行する。</p>
<div class="figure">
<img alt="CPUとタスク。タスクはCPU上で次々と切り替わりながら実行される" src="https://blog.tai2.net/images/context-switch-experiment/tasks_on_cpu.jpg">
<p class="caption">CPUとタスク。タスクはCPU上で次々と切り替わりながら実行される</p>
</div>
<p>このプログラムの載せかえのことを <a class="reference external" href="https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81">コンテキストスイッチ</a> と呼ぶ。レジスタの状態などを退避・復元する必要があるため、コンテキストスイッチには、それなりの時間がかかる。</p>
<p>マルチタスク処理を実現するときに、アプリケーションプログラマが使える選択肢は何種類かある。
実行環境によっては、軽量スレッドと言われるようなアプリケーション(VM)レベルで実現されているマルチタスクの機構を利用できる、あるいはそれしか利用できない場合もある。けれど、多くの言語で利用できる、OSレベルで提供されるマルチタスクの手段は、プロセスとスレッドだ。プロセスとスレッドの最も大きな違いは、プロセスは独立したメモリ空間を持つが、スレッドはメモリ空間を共有するということ。</p>
<p>Unixプログラミング環境では、プロセスによるマルチタスクにはfork API、スレッドによるマルチタスクにはpthread APIを使う。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-3">実験1: パイプを使って計測する</a></h2>
<p>先のStack Overflowの回答で示されているアイデアは単純だけど興味深い。</p>
<p>タスクは、readのようなブロッキングI/Oと呼ばれるAPI<sup id="sf-context-switch-experiment-1-back"><a href="#sf-context-switch-experiment-1" class="simple-footnote" title="ファイルディスクリプタが非ブロッキングモードに設定されれば、まったく異なる挙動になる。その場合、ブロックは発生しない。">1</a></sup>が呼ばれると、十分なデータが読める状態になるなど再度の実行が可能となるまで、CPUを他のタスクに明け渡す。<sup id="sf-context-switch-experiment-2-back"><a href="#sf-context-switch-experiment-2" class="simple-footnote" title="コンテキストスイッチのために特定のAPIコールが必要というわけではない。プリエンプティブなマルチタスク環境では、コンテキストスイッチはいつでも発生し得る。">2</a></sup></p>
<p>回答で示されたコードでは、パイプからのreadでブロックが発生し、読み込めるデータもないために以降の行に遷移することができない。次いで、writeでデータが書き込まれることで、パイプの他端にデータが到達し、readのブロックが解除されることが期待される。つまり、writeが待機中のタスクを起こすための手段として使われている。これは、Unixプログラミングで、タスク間での同期を実現するときに、実際に使われることがある便利なテクニックでもある。</p>
<p>write前に取得したタイムスタンプをパイプで送ってから、read側でもタイムスタンプを取得し差分を取ることで、コンテキストスイッチにかった時間を計測しようというアイデアが、Stack Overflowの回答だ。なかなか賢い。
これを、プロセスとスレッドでそれぞれ実行し、計測してみた。</p>
<ul class="simple">
<li><a class="reference external" href="https://github.com/tai2/context-switch-experment/blob/main/switch_thread.c">実験1 スレッド版</a></li>
<li><a class="reference external" href="https://github.com/tai2/context-switch-experment/blob/main/switch_process.c">実験1 プロセス版</a></li>
</ul>
<p>実行してみると、スレッドのほうがコンテキストスイッチは軽く、だいたい何マイクロ秒くらい違う、というような、期待どおりの結果は得られなかった。まず、データに数十分以上の周期の成分が含まれているようで、実行するたびに平均値が大きく変動してしまい、うまく比較できない。スレッドとプロセスでどちらが早いとも言えない、という感じだった。値の範囲としては、だいたい10〜30マイクロ秒くらい。</p>
<p>アプリケーションの外側でどんなことが起きているのかはブラックボックスだし、実際にコンテキストスイッチにかかる時間というのはこのくらい不安定なのだという可能性も否定はできないものの、いまいち腑に落ちない。APIから取得した値によると、タイマーの解像度は42ナノ秒ということになっているけれど、もしかすると、この実験が必要とするほどの精度はないのかもしれない。<sup id="sf-context-switch-experiment-3-back"><a href="#sf-context-switch-experiment-3" class="simple-footnote" title='manページによると、タイマーの値は"This value may be smaller than the actual precision of the underlying clock, but represents a lower bound on the resolution."'>3</a></sup></p>
<p>そもそもこの方法で正しくコンテキストスイッチの時間を計測できているんだろうか。
このコードで計測しているものを図にすると以下のようになる。</p>
<div class="figure">
<img alt="read()開始から終了までのイベント。計測される範囲には、コンテキストスイッチ以外の部分が含まれている" src="https://blog.tai2.net/images/context-switch-experiment/measured_span_contains_sleeping_time.jpg">
<p class="caption">read()開始から終了までのイベント。</p>
</div>
<p>Aはread()がはじまって、タスクが休眠状態になってから、復帰してデータを読み込み終了するまで全体の時間。
その中には、Cのコンテキストスイッチにかかる時間も含まれる。
Bは、このコードで実際に計測している、write()の直前からread()の直後までの時間だ。
しかし、よくよく考えてみると、write()が発行されてから、即座にコンテキストスイッチが起きるという保証はなにもない。
スケジューラーが、対象のスレッドを選択するまでに、コンテキストスイッチ自体よりも長い時間がかかるとしたら、このコードでは意味のある計測をできていないことになる。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-4">実験2: より実際的なモデル</a></h2>
<p>なにかもっとうまくコンテキストスイッチのパフォーマンスを比較できる方法はないだろうか。
そもそも知りたいことは、スレッドとプロセスで、(メモリはさておき)実行時間的な意味でパフォーマンス上の差異あるのかどうかだ。スレッドのかわりにプロセスを使うことで、理論的に、パフォーマンスが低下する可能性があるのかどうかを把握しておきたい。</p>
<p>そこで、次のような単純な方法を考えた。
タスクとパイプを2つずつ用意し、タスク間で、整数をインクリメントしながら、ひたすら往復させる。
これで、単位時間によりたくさんメッセージを送信できたほう(整数の数が多かったほう)が勝ち、という方法。
送受信に使っているAPIや処理も同じで、異なるのはタスクの種類がプロセスかスレッドかという部分だけだ。
これで結果が異なるなら、どちらがより早くコンテキストスイッチできるのかの競争になっていると思う。
まとまった量の処理を計測するので、タイマー精度についての懸念もない。</p>
<div class="figure">
<img alt="パイプを2本用意して、タスク間で送信し合う" src="https://blog.tai2.net/images/context-switch-experiment/message_pingpong.jpg">
<p class="caption">パイプを2本用意して、タスク間で送信し合う</p>
</div>
<ul class="simple">
<li><a class="reference external" href="https://github.com/tai2/context-switch-experment/blob/main/pingpong_thread.c">実験2 スレッド版</a></li>
<li><a class="reference external" href="https://github.com/tai2/context-switch-experment/blob/main/pingpong_process.c">実験2 プロセス版</a></li>
</ul>
<p>この方法でやってみると、スレッドとプロセスで実際に差が出た。
手元のMac環境だと、スレッドは秒間40万スイッチ、プロセスは秒間36万スイッチくらいで、プロセスのほうがすこし回数がすくない。</p>
<p>プロセスよりも、スレッドのほうが、高速にコンテキストスイッチできることがわかった。</p>
</div>
<div class="section" id="vs">
<h2><a class="toc-backref" href="#toc-entry-5">プロセス VS スレッド</a></h2>
<p>われわれアプリケーションプログラマにとって重要なのは、アプリケーションで並行処理を設計するにあたって、スレッドとプロセスどちらのAPIを選ぶのが適切なのかということだ。</p>
<p>パフォーマンスという点だけを考えるとプロセスを選択する理由はなさそうにも思えるけど、設計上のすばらしいメリットがあると思っている。それは、互いがより厳密に隔離されているという点だ。メモリなどの資源についても、それぞれのプロセスレベルで管理されているので、プロセスが終了すればOSが勝手に回収してくれる。つまり、プロセスのほうが、考えることがすくなく、実装がシンプルになる。</p>
<p>一方で、プロセス間ではメモリ空間が共有されないため、より緊密に連携したかったり、メモリ資源を節約したかったりする場合には、スレッドが必要になる。スレッドのほうが、同期のための手段も豊富にある。</p>
<p>単位時間にスイッチできる回数という点で、スレッドのほうが多少有利なことが今回わかったけど、そこまで著しい差があるわけではない。だから、選択にあたって、コンテキストスイッチのコストはそこまで重視する部分ではないと思う。それよりも、他の特性を考慮して、アプリにあった手段を選ぶべきだ。</p>
<p>最後に、こういうケースだとコンテキストスイッチのコストが違ってくるよ、こういうツールや方法を使えば、もっとうまく測れるよなどの知恵をお持ちの方は、ぜひ教えてください。</p>
</div>
<ol class="simple-footnotes"><li id="sf-context-switch-experiment-1">ファイルディスクリプタが非ブロッキングモードに設定されれば、まったく異なる挙動になる。その場合、ブロックは発生しない。 <a href="#sf-context-switch-experiment-1-back" class="simple-footnote-back">↩</a></li><li id="sf-context-switch-experiment-2">コンテキストスイッチのために特定のAPIコールが必要というわけではない。プリエンプティブなマルチタスク環境では、コンテキストスイッチはいつでも発生し得る。 <a href="#sf-context-switch-experiment-2-back" class="simple-footnote-back">↩</a></li><li id="sf-context-switch-experiment-3">manページによると、タイマーの値は"This value may be smaller than the actual precision of the underlying clock, but represents a lower bound on the resolution." <a href="#sf-context-switch-experiment-3-back" class="simple-footnote-back">↩</a></li></ol>Proton Mail 〜おれが本物のプライバシーファーストなウェブメールを見せてやりますよ〜2022-11-13T00:00:00+09:002022-11-13T00:00:00+09:00tai2tag:blog.tai2.net,2022-11-13:/proton-mail.html<p class="first last">この記事では、Proton Mailの特徴や、技術的に興味深い点を紹介する。</p>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-1">はじめに</a></h2>
<blockquote>
If you’re not paying for the product, you are the product itself.
(製品にお金を払っていないということは、あなたが製品そのものということだ)</blockquote>
<p>これまで、しっくりくるメール受信環境がなかなかみつからず、いろいろな変遷をたどってきた。</p>
<p>Gmailを愛用していた時期もあったけど、あるとき、ふと開眼し、広告モデルのウェブメールをやめたいと思うようになった。
そこからは苦難の道だった。Mac標準のメールアプリは検索がときどきバグったり、フィルターがいまいちだったりした。
<a class="reference external" href="https://blog.tai2.net/mutt-and-notmuch.html">OSSを使って快適なCLIメール環境の構築を試みもした</a> けど、ときどきバグってメンテが大変だったりした。</p>
<p>けっきょくのところ、いまどきローカルにメールボックスを持つのは特定のマシンに縛られて不便だし、自前で環境が壊れないようにメンテするのもしんどい。
かといって、Gmailに戻るのも嫌だ。それなら、有料でウェブメールを提供しているサービスを探せばいいのではないかと思い立った。</p>
<p>探してみるといくつかの選択肢があったけど、 <a class="reference external" href="https://proton.me/mail">Proton Mail</a> というサービスが良さそうだったので使いはじめた。<sup id="sf-proton-mail-1-back"><a href="#sf-proton-mail-1" class="simple-footnote" title="当時調べた中でmbox形式のインポートをサポートしていた唯一のメールサービスだったのも大きい">1</a></sup> 早いもので、ユーザーになってから、かれこれもう4年ほど経つ。</p>
<p>この記事では、Proton Mailの特徴や、技術的に興味深い点を紹介する。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-1" id="toc-entry-1">はじめに</a></li>
<li><a class="reference internal" href="#proton-mail-2" id="toc-entry-2">Proton Mailとは</a><ul>
<li><a class="reference internal" href="#proton-mail-3" id="toc-entry-3">Proton Mailの特徴</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-2" id="toc-entry-4">公開鍵暗号おさらい</a></li>
<li><a class="reference internal" href="#proton-mail-4" id="toc-entry-5">Proton Mailユーザー同士のメール交換 — エンドツーエンド暗号化</a></li>
<li><a class="reference internal" href="#proton-mail-5" id="toc-entry-6">Proton Mail外からのメール受信 — ゼロアクセス暗号化</a></li>
<li><a class="reference internal" href="#proton-mail-6" id="toc-entry-7">Proton Mail外へのメール送信 — パスワード保護付きメール</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-8">メール検索</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-9">スパムフィルタ</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-10">メールのインポート</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-11">データ漏洩 — 国家はデータを監視している</a></li>
<li><a class="reference internal" href="#gmail" id="toc-entry-12">ところで、Gmailはユーザーのメールを売り物にしてるの?</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-13">クライアントのソースコード</a><ul>
<li><a class="reference internal" href="#section-8" id="toc-entry-14">認証から秘密鍵取得までの過程</a></li>
</ul>
</li>
<li><a class="reference internal" href="#proton-mail-8" id="toc-entry-15">Proton Mail所感</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-16">参考リンク</a></li>
</ul>
</div>
</div>
<div class="section" id="proton-mail-2">
<h2><a class="toc-backref" href="#toc-entry-2">Proton Mailとは</a></h2>
<p>Proton Mailは、プライバシーを主眼に置いて設計されたウェブメールサービスで、ウェブアプリ、iOS、Androidネイティブアプリが提供されている。</p>
<p>Proton Mailは有料の月額課金モデルで運営されている。<sup id="sf-proton-mail-2-back"><a href="#sf-proton-mail-2" class="simple-footnote" title="無料で利用することも可能。無料ユーザーのコストは、有料ユーザーが支えている">2</a></sup> だから、Gmailのように広告を見せられることはない。また、ユーザーのメールに含まれる情報を売って商売しているわけでもない。</p>
<p>このサービスのすごいところは、プライバシー保護をとことん突き詰めていることだ。
どこまで突き詰めているかというと、暗号化技術により、Proton Mail自身でさえもサーバーに保存されたメールが読めないようになっている。
だから、たとえデータ漏洩があっても第三者はメールの中身を読めないことが暗号技術によって保証されている。暗号を解除するための鍵はユーザー自身しか知らない。</p>
<p>Proton Mailの暗号化は、 <a class="reference external" href="https://www.openpgp.org/">OpenPGP</a> ベースのエンドツーエンド暗号化(E2EE)と、ゼロアクセス暗号化に分けられる。
暗号化を前提とした上で、どのようにスパムフィルタやメール検索を実装するのかというあたりも公式ブログで説明されていておもしろいので、順番に説明していく。</p>
<div class="section" id="proton-mail-3">
<h3><a class="toc-backref" href="#toc-entry-3">Proton Mailの特徴</a></h3>
<p>Proton Mail自身は、先進的なセキュリティー上の特徴として、 <a class="reference external" href="https://proton.me/blog/is-gmail-secure">以下を挙げている:</a></p>
<ul class="simple">
<li>あなただけが、あなたのメールを読めます: エンドツーエンド暗号化は、TLSよりさらに一歩踏み込こんでいて、Protonでさえメールを読めません。</li>
<li>データ漏洩に強いセキュリティー: ゼロアクセス暗号化とは、あなたのアカウントの全メールが暗号化されるということです。Proton以外のユーザーから受信したメールでさえも。</li>
<li>すべての相手にエンドツーエンド暗号化を: パスワード保護されたメールをどんなメールアカウントにも無料で送信できます。</li>
<li>追跡しないし、ログも取りません: Googleが、フリーアカウント上のすべての行動を追跡する一方、Proton Mailはなにも記録しません。</li>
<li>スイスのプライバシー: 米国を拠点とするGoogleと違い、Proton Mailはスイスのプライバシー法に統治されます。スイスのプライバシーは、世界でも最高レベルに厳格です。</li>
</ul>
</div>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-4">公開鍵暗号おさらい</a></h2>
<p>Proton Mail自体の話に入る前に、Proton Mailのプライバシーの基礎になっている暗号システムの概要について、ざっと説明する。</p>
<p>共通鍵暗号とは、暗号化と復号に同じ鍵(パスフレーズなど)を使う暗号方式だ。</p>
<div class="figure">
<img alt="共通鍵暗号は、同じ鍵で暗号化も復号もできる" src="https://blog.tai2.net/images/proton-mail/symmetric-key-encryption.png">
<p class="caption">共通鍵暗号は、同じ鍵で暗号化も復号もできる</p>
</div>
<!-- stateDiagram-v2
暗号化されたデータ - -> 平文のデータ: 共通鍵で復号
平文のデータ - -> 暗号化されたデータ: 共通鍵で暗号化 -->
<p>共通鍵暗号だけでメッセージをやりとりしようと思うと、自分と相手双方が何らかの方法で共通鍵を事前に共有していなければならない。
メールのように不特定多数の相手とやりとりする場合、これは現実的ではない。</p>
<p>公開鍵暗号を使えばこの欠点を克服できる。公開鍵暗号システムでは、秘密鍵と公開鍵という一組の鍵が使われる。
公開鍵は、メッセージを暗号化する際に使われ、暗号化されたメッセージは、対応する秘密鍵を持っていないと復号できない。
公開鍵は、その名の通り公にしても問題ないものなので、通信する二者が互いに公開鍵を交換しあえば、簡単かつ安全に通信できる。(メールアドレス自体と違って、名刺などに手軽に掲載して手入力できるような長さの文字列ではないので、最初にどう配布するかという問題はあると思う)</p>
<p>また、PGPでは、秘密鍵を使ってメッセージに電子署名をすることができる。これにより、受信者はメッセージがたしかに秘密鍵の所有者によって作成されたものであること(真性性)、またそのメッセージが改竄されていないこと(完全性)を、公開鍵を使って検証できる。</p>
<p>ちなみに、公開鍵暗号システムと言っても、メッセージそれ自体の暗号化は共通鍵暗号を使って行われ、暗号化に使われる共通鍵の交換だけが公開鍵暗号を使って行われる。</p>
<div class="figure">
<img alt="公開鍵暗号は、暗号と復号に異なる鍵を使う" src="https://blog.tai2.net/images/proton-mail/public-key-encryption.png">
<p class="caption">公開鍵暗号は、暗号と復号に異なる鍵を使う</p>
</div>
<!-- stateDiagram-v2
暗号化された共通鍵 - -> 平文の共通鍵: 秘密鍵で復号
平文の共通鍵 - -> 暗号化された共通鍵: 公開鍵で暗号化 -->
<p>近年、公開鍵暗号システムの実装で標準的に使われるようになった楕円曲線暗号(ECC)という公開鍵暗号方式<sup id="sf-proton-mail-3-back"><a href="#sf-proton-mail-3" class="simple-footnote" title="Proton Mailでも現在はこれがデフォルト">3</a></sup> では、メッセージの署名はできるが、暗号化機能自体はない。DH(Diffie-Hellman)鍵交換というアルゴリズムを使えば、機密性を担保できるが能動的攻撃による改竄の恐れがある。そこで、DH鍵交換と楕円曲線暗号を組み合わせることで、機密性と完全性を両方担保するという構成になっている(と、ぼくは理解している)。ECCでは、昔から使われてきたRSAよりもすくない計算資源で、効率良く暗号化を実現できる。</p>
</div>
<div class="section" id="proton-mail-4">
<h2><a class="toc-backref" href="#toc-entry-5">Proton Mailユーザー同士のメール交換 — エンドツーエンド暗号化</a></h2>
<p>Proton Mailユーザー同士のやりとりでは、自動的にPGPによるエンドツーエンド暗号化が行われる。公開鍵のインポートなどの事前準備も必要なく、ふつうにメールを送るだけなのでなにも意識することはない。メールが暗号化されるかどうかは、送信先のアイコンで判別できる。</p>
<div class="figure">
<img alt="アイコンによってメッセージが暗号化されることがわかる" src="https://blog.tai2.net/images/proton-mail/encrypted-icon.png">
<p class="caption">アイコンによってメッセージが暗号化されることがわかる</p>
</div>
<p>なお、PGPでのエンドツーエンド暗号化の範囲に、件名を含むメタデータは含まれない。暗号化で保護されるのは、あくまで本文のみだ。
誰が誰に、いつどのくらいメールを送ったかといったデータは、メールプロバイダーや、民間企業へデータを要求できる政府には、つつぬけと考えたほうがいいと思う。</p>
</div>
<div class="section" id="proton-mail-5">
<h2><a class="toc-backref" href="#toc-entry-6">Proton Mail外からのメール受信 — ゼロアクセス暗号化</a></h2>
<p>非Proton MailユーザーがProton Mailユーザーにメッセージを送信するケースについて考える。
相手がPGPユーザーなら、もちろんエンドツーエンド暗号化が可能だ。こちらの公開鍵を何らかの方法で相手に伝えておけばいい。</p>
<p>しかし、相手がPGPユーザーでない場合は、どうしてもメッセージが平文で送られてくる。<sup id="sf-proton-mail-4-back"><a href="#sf-proton-mail-4" class="simple-footnote" title="通信路はTLSで保護されるけど、アプリケーションは平文で受け取る">4</a></sup> エンドツーエンド暗号化はできない。
この場合でも、Proton Mailは、ユーザーの公開鍵で暗号化を行ってから、受信したメールを保存する。Proton Mailは、この仕組みをゼロアクセス暗号化と呼んでいる。
だから、たとえProton Mailサーバーからのデータ漏洩があったとしても、メッセージ本文はユーザー以外読むことができない。</p>
<p>ただし、これにはいくつか穴がある。まず、メールを受信してから、ストレージに保存するまでの間であれば、Proton Mailは自由にメッセージを読むことができる。実際、Proton Mailは、メモリ上にロードされたメッセージデータを使ってスパムフィルタ処理などを行っている。つまり、エンドツーエンド暗号化と違って、平文を読まれないことが技術的に保証されているわけではない。</p>
<p>また、メッセージを送信してきた相手方のメールボックスには、平文のままのデータが残るし、やりとりの履歴が残る。いくらこちら側で万全の保護をしたとしても、相手方から情報が漏れてしまえばどうにもならない。</p>
<p>ゼロアクセス暗号化の効果は限定的なものに留まると思う。</p>
</div>
<div class="section" id="proton-mail-6">
<h2><a class="toc-backref" href="#toc-entry-7">Proton Mail外へのメール送信 — パスワード保護付きメール</a></h2>
<p>Proton Mail外へのメール送信は、相手がPGPユーザーであれば、Proton Mailユーザーのように通常のエンドツーエンド暗号化が可能だが、そうでない大多数のユーザーとは、暗号化した状態でメールをやりとりすることができない。
そこで代替手段として、パスワード保護付きメッセージという手段が用意されている。
これは、パスワードによる保護をかけた状態でメッセージをProton Mailサーバー上に保存した上で、メール本文には、メッセージへのリンクを記載し、Proton Mailサーバー上でメッセージのやりとりをするというものだ。パスワードは共通鍵なので、なんらかの方法でプライベートに共有しておく必要がある。
この場合もメッセージはエンドツーエンド暗号化される。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-8">メール検索</a></h2>
<p>Proton Mailでは、メールの検索機能も提供されている。サーバーサイドでメール本文にアクセスできない状況で、どうやってメール検索を実装するのか。
これについては、Proton Mailから <a class="reference external" href="https://proton.me/blog/engineering-message-content-search">解説記事が公開されている。</a></p>
<p>世の中には、 <a class="reference external" href="https://atmarkit.itmedia.co.jp/ait/articles/1509/29/news003.html">検索可能暗号(Searchable Encryption)</a> という技術が存在しており、それを使えば、暗号化されたデータに対して直接検索をかけられる可能性があるらしい。
が、Proton Mailでは、これらの技術は、まだ研究途上であり、実用段階にはいたっていないと判断し、導入しなかった。</p>
<p>結論として、Proton Mailは、単純にクライアントサイド(Web版ならブラウザ内)で、検索を行っている。</p>
<p>Proton Mail(Web版)は、ブラウザ内のIndexedDBに検索用インデックスを構築する。メッセージはOpenPGPで暗号化されているので、まずは復号する必要がある。そして、タグの除去など、メッセージを検索しやすいように整形した上で、メタデータと共に <a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API">WebCrypt API</a> でAES-GCMで暗号化して、検索用インデックスに格納する。Proton Mailは、メッセージIDをキーとするフォーワードインデックスを採用している。なお、検索時に毎回復号しているわけではなく、平文のインデックスがキャッシュされる。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-9">スパムフィルタ</a></h2>
<p>暗号化を前提とする環境で、Proton Mailは、スパムフィルタをどのように実現しているのだろうか。
スパムフィルタについても、エンドツーエンド暗号化がされているケースと、そうでないケース(ゼロアクセス暗号化)で対応が違ってくる。</p>
<p>ゼロアクセス暗号時のスパム対応については、 <a class="reference external" href="https://protonmail.com/blog/encrypted-email-spam-filtering/">公式ブログに解説がある。</a></p>
<p>Proton Mailの解説によると、非暗号化メール受信時には、送信元IPアドレスのチェック、ベイジアンフィルタ、メッセージチェックサムとスパムデータベースの照合、DMARC等による真性性の検査、ユーザー定義のスパムフィルタ、といった処理を暗号化する前にメモリ上で行っているようだ。</p>
<p>エンドツーエンド暗号化環境でのスパム処理については、ドキュメントはないものの、その性質上、重要な処理は必ずブラウザ内で起きているはずなので、論理的には、ソースコードを読めばどのように対処しているのかわかるはずだ。だれかコードを読んで教えてください。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-10">メールのインポート</a></h2>
<p>メールは重要なデータベースなので、メールサービスを新しく使いはじめるのであれば、いままで受信したメールデータも移行したい。
ぼくの場合は、Maildir形式でローカルディスクにメールを保持していたので、それをインポートできるかどうかが、サービス選定にあたって重要なポイントのひとつだった。</p>
<p>Proton Mailでは、 <a class="reference external" href="https://proton.me/support/export-emails-import-export-app">専用のアプリ</a> で Mbox形式のインポートをサポートしていたので、PythonでMaildirからMboxに変換して、専用アプリでMboxをインポートすることで、無事全メールをProton Mailに移行することができた。当時、Mboxのインポートをサポートしていたのは、候補に上がったサービスの中ではProton Mailだけだった。</p>
<p>ちなみに、Gmailや他のプロバイダーからのデータ移行も <a class="reference external" href="https://proton.me/support/easy-switch">もちろんサポートしている。</a></p>
</div>
<div class="section" id="section-6">
<h2><a class="toc-backref" href="#toc-entry-11">データ漏洩 — 国家はデータを監視している</a></h2>
<p><a class="reference external" href="https://www.ted.com/talks/glenn_greenwald_why_privacy_matters">プライバシーは人間にとって本質的に大事なものだ。</a> 自分が明かしたいと思った相手以外に、私的なやりとりを勝手に覗き見されたくはない。</p>
<p>なぜ、PGPによるエンドツーエンドの暗号化(やゼロアクセス暗号化)が必要なのだろうか。TLSで、通信路が暗号化されているなら、それで十分ではないのか。
理由はいくつか考えられる。たとえば、Yahoo Mailは、 <a class="reference external" href="https://proton.me/blog/protonmail-security-advisory-regarding-yahoo-hack">2013年に30億アカウントのデータ漏洩を起こした。</a> Proton Mailでも同様の事故が今後起きることは十分考えられる。しかし、エンドツーエンド暗号化されていれば、たとえサーバーからデータが漏洩したとしても、他人に中身を読まれることはない。</p>
<p>それから、運営者がデータを利用または悪用することもできない。エンドツーエンド暗号化でなければ、運営者が勝手に自分のメールを他人に渡したり、広告など個人を追跡するために使ったりしないという保証はない。すくなくとも技術的には。</p>
<p><a class="reference external" href="https://www.theguardian.com/world/interactive/2013/nov/01/snowden-nsa-files-surveillance-revelations-decoded">エドワード・スノーデンの告発</a> であきらかになったとおり、国家権力は、インターネット技術を利用して国民を監視している。スノーデン以降、国家による直接の大量監視に制限はかかったものの、現在でも、 <a class="reference external" href="https://www.wsj.com/articles/federal-agencies-use-cellphone-location-data-for-immigration-enforcement-11581078600">民間企業のデータを通じて間接的な監視は行われているようだ。</a> また、国家は、必要であれば裁判所を通じて、インターネット企業にデータの提出を要求できる。しかも、民間企業はそれらのデータを提出した事実を <a class="reference external" href="https://en.wikipedia.org/wiki/Gag_order">秘匿しなければならないこともある。</a> そういったケースは、 <a class="reference external" href="https://transparency.fb.com/data/government-data-requests/?source=https%3A%2F%2Ftransparency.facebook.com%2Fgovernment-data-requests">企業が公表している透明性データ</a> などにも表れてこないかもしれない。 国家による監視がどこまで行われているのかについては、 <a class="reference external" href="https://www.amazon.co.jp/dp/4794222378/">「超監視社会: 私たちのデータはどこまで見られているのか? 」</a> という本に詳しく書かれている。こういった標的を定めない大量監視の効果は、エンドツーエンド暗号化が使われていれば、かなり弱めることができる。</p>
<p>ただし、 <a class="reference external" href="https://protonmail.com/blog/protonmail-threat-model/">Proton Mail自身が勧告している</a> ことだけど、国家が本気になって個人を標的にした場合、エンドツーエンド暗号化をもってしても秘密を守りきれない可能性が高い。だから、たとえばスノーデンのように国家には歯向かって秘密を暴露しようとしている人などには、Eメールの使用自体おすすめできない。</p>
</div>
<div class="section" id="gmail">
<h2><a class="toc-backref" href="#toc-entry-12">ところで、Gmailはユーザーのメールを売り物にしてるの?</a></h2>
<p>Proton Mailのブログを読んでいると、しきりに、Gmailの利用にはプライバシー上の懸念があるという主張がなされている。
これは、ほんとうだろうか。</p>
<p>まず、事実として、Gmailは2004年のリリースから長らく、 <a class="reference external" href="https://privacyrights.org/resources/privacy-and-civil-liberties-organizations-urge-google-suspend-gmail">ユーザーのメールボックスをスキャンして</a> 物品の購買情報等個人情報を把握し、それを元にユーザーに合わせた広告を表示してきた。</p>
<p>しかし、2014年ごろから、エンタープライズユーザーの拡大と顧客企業からの懸念を背景に、段々とメールボックスのスキャンをやめてきた。
そして、2017年には、広告を目的としたメールボックスのスキャンを <a class="reference external" href="https://www.nytimes.com/2017/06/23/technology/gmail-ads.html">完全に停止した。</a> Googleは、現在、広告のためにGmailのメッセージをスキャンしないし、個人情報を売ることはないと <a class="reference external" href="https://support.google.com/mail/answer/6603">表明している。</a></p>
<p>現在、Gmailについて指摘されているプライバシー上の問題には、以下のようなものがある:</p>
<ul class="simple">
<li>Gmailはメールの自動返信や予定のカレンダーへの自動登録など、ユーザーの利便性を理由とするメールボックスのスキャンは、 <a class="reference external" href="https://proton.me/blog/is-gmail-secure">今でも行っている。</a></li>
<li>Gmailにはアドオンと呼ばれるサードパーティーによる拡張機能とそのためのAPIがある。ユーザーが許可をすればサードパーティーは自由にメールボックスにアクセスできる。いくつかのアドオンは、ユーザーの意図しない形でメールにアクセスしていた。</li>
</ul>
<p>Return Pathというマーケティングツールの事例が <a class="reference external" href="https://www.wsj.com/articles/techs-dirty-secret-the-app-developers-sifting-through-your-gmail-1530544442">WSJによって報道された。</a> Return Pathは多くのアドオンベンダーと提携しており、Return Path提携企業の提供する数多くのアドオンが流通している。Earny社による商品価格自動比較アドオンもその一つだった。これをインストールしたユーザーは、知らぬ間に、自身のメールをReturn Pathのマーケティングツールのためのデータとして提供することになる。</p>
<p>ただ、サードパーティーへのアクセスを許可する際には、必ず読取権限を明示的に要求されるので、たとえその利用方法が(Earnyのケースのように)想定外だったしても、ユーザーは許可をしていることにはなる。
Google自体は、現在ではプライバシーに配慮したメールボックスの取り扱いをしているかもしれないが、サードパーティーはその限りではない。アドオンに権限を渡す際には注意が必要だろう。
この記事を書くにあたって色々調べてみて、個人的には、Gmailは、もはや以前ほど不健全ではないのかもしれないという感触を持った。</p>
<p>しかし、企業である以上、政府から要求されればデータは提供するしかない。そして、エンドツーエンド暗号化でない以上、メールのコンテンツに第三者でもアクセスできてしまう。その意味で、究極的には、Gmailに対するプライバシー上の懸念は永遠に払拭されることはない、というのはそうなのだろうと思う。</p>
</div>
<div class="section" id="section-7">
<h2><a class="toc-backref" href="#toc-entry-13">クライアントのソースコード</a></h2>
<p>Proton Mailはクライアントサイドのコードをオープンソースソフトウェアとして、GitHub上で公開している。
メインの <a class="reference external" href="https://github.com/ProtonMail/WebClients">ウェブクライアント</a> はもちろん、iOSやAndroidその他各種ライブラリも含め積極的に <a class="reference external" href="https://github.com/ProtonMail">公開している。</a></p>
<p>ちなみに、Proton Mailのクアイアントは元々AngularJSで実装されていたけど、何年か前にReactに移行した。</p>
<p>ソースコードが公開されているのは、エンドツーエンド 暗号化を売りにしているProton Mailにとって意味のあることだ。
エンドツーエンド暗号化において、重要なことはすべてクライアントサイドで起こる。だから、われわれユーザーは、理論的には、ソースコードを精査することによって、情報が漏れることも改竄されることもなく相手に伝わると確証できる。</p>
<p>公開されているのはクライアントサイドのみで、サーバーサイドのソースコードは公開されていない。だから、非暗号化メールを受信したときの、スパムフィルタやゼロアクセス暗号化など、サーバーサイドで起こる部分については、なにが行われているのか、ほんとうにはわからない。もっとも、サーバーサイドでどんなコードが動いているのかは、本質的に不透明なので、たとえソースコードが公開されていてもあまり意味はないかもしれない。</p>
<div class="section" id="section-8">
<h3><a class="toc-backref" href="#toc-entry-14">認証から秘密鍵取得までの過程</a></h3>
<p>Proton Mailでは、一度もログインしたことのない端末からでも、アカウント名とパスワードさえ入力すれば、自分のメールを読むことができる。
つまり、ログイン時に秘密鍵が暗号化された状態でダウンロードされて、それを復号するという処理がブラウザ内で起きているはずだ。
そのあたりの処理がどうなっているのか気になったので、ソースコードから調べてみた。</p>
<p>まず、ブラウザ内でパスワードを入力するUIはわかっているので、そこを起点にブラウザのInspectorを使って調べていく。すると、input要素の属性などから、 <a class="reference external" href="https://github.com/ProtonMail/WebClients/blob/0631583898f1a9019969e0defe09b5253e1d4523/applications/account/src/app/login/LoginForm.tsx">LoginForm</a> というコンポーネントに辿りつき、 <a class="reference external" href="https://github.com/ProtonMail/WebClients/blob/0631583898f1a9019969e0defe09b5253e1d4523/packages/components/containers/login/loginActions.ts">loginActions</a> というファイルがログインまわりの処理をしていることがわかった。</p>
<p>認証時の処理については、 <a class="reference external" href="https://proton.me/blog/encrypted-email-authentication">ブログに詳しい解説がある。</a> Proton Mailでは、 <a class="reference external" href="https://github.com/ProtonMail/WebClients/blob/1215e18025ca1d39af95a08a0930b1e116f57d21/packages/shared/lib/authentication/loginWithFallback.ts#L38-L44">SRP(Secure Remote Password)による認証を行っている。</a> ログインパスワードがそのまま秘密鍵を得るための鍵になるので、パスワードはブラウザの外に出してはいけない。SRPは、DH鍵交換に似た仕組みで、これによりパスワードをProton Mailに送信せずに、認証を行える。
SRPの結果として、 <a class="reference external" href="https://github.com/ProtonMail/WebClients/blob/1215e18025ca1d39af95a08a0930b1e116f57d21/packages/shared/lib/authentication/interface.ts#L16-L30">ユーザーIDやアクセストークン</a> が得られる。</p>
<p>処理を追っていくと、どうやら認証後にサーバーから <a class="reference external" href="https://github.com/ProtonMail/WebClients/blob/0631583898f1a9019969e0defe09b5253e1d4523/packages/shared/lib/interfaces/User.ts#L19-L42">ユーザー情報</a> を取得し 、 そこには <a class="reference external" href="https://github.com/ProtonMail/WebClients/blob/0631583898f1a9019969e0defe09b5253e1d4523/packages/shared/lib/interfaces/Key.ts#L20">秘密鍵</a> が暗号化された状態で入っていることがわかった。</p>
<p>次に、ログインパスワードとソルトから、 <a class="reference external" href="https://github.com/ProtonMail/WebClients/blob/1215e18025ca1d39af95a08a0930b1e116f57d21/packages/srp/lib/keys.ts#L10-L18">秘密鍵復号用のキーを生成する。</a> それを用いて、 <a class="reference external" href="https://github.com/ProtonMail/WebClients/blob/1215e18025ca1d39af95a08a0930b1e116f57d21/packages/components/containers/login/loginHelper.ts#L21-L33">秘密鍵を復号する。</a>
なお、秘密鍵用のキーは、セッション情報の一部として、ローカルストレージにキャッシュされる。</p>
<!-- sequenceDiagram
participant Client
participant SRP module
participant Server
Client->>SRP module: ユーザー名、パスワード
SRP module->>Server: SRP認証
Server- ->>SRP module: ユーザーID、アクセストークン
SRP module- ->>Client: ユーザーID、アクセストークン
Client->>Server: User API
Server- ->>Client: Userモデル(暗号化された秘密鍵付き)
Client->>Server: Salt API
Server- ->>Client: ソルト
Client->>SRP module: パスワード、ソルト
SRP module- ->>Client: 秘密鍵用パスワード -->
<div class="figure">
<img alt="SRPによる認証、暗号化された秘密鍵取得、秘密鍵用の共通鍵生成" src="https://blog.tai2.net/images/proton-mail/authentication-with-srp.png">
<p class="caption">SRPによる認証、暗号化された秘密鍵取得、秘密鍵用の共通鍵生成(パスワードはサーバーに送信されない)</p>
</div>
<p>では、秘密鍵はどういうアルゴリズムで暗号化されているのだろうか。</p>
<p>どうやらWorkerの <a class="reference external" href="https://github.com/ProtonMail/WebClients/blob/0631583898f1a9019969e0defe09b5253e1d4523/packages/crypto/lib/worker/api.ts#L294-L320">importPrivateKey</a> というメソッドが秘密鍵の復号を行っているようだ。名前からわかるとおり、鍵の処理はワーカースレッドで行われるらしい。</p>
<p>その中身を追っていくと、 <a class="reference external" href="https://openpgpjs.org/">openpgp.js</a> (これ自体Proton Mailによってメンテナンスされている)の <a class="reference external" href="https://github.com/openpgpjs/openpgpjs/blob/2f8a8c1c9af37685e9f2c7af9c37324881935b48/src/packet/secret_key.js#L309-L368">decrypt</a> というメソッドにいきつく。</p>
<p>はじめて聞いたけど、 <a class="reference external" href="https://www.rfc-editor.org/rfc/rfc4880#section-3.7">S2K(String-to-Key)</a> という文字列を鍵に変換するための枠組みが規定されていて、どうやらそれに則った処理になっているようだ。
そこから、 <a class="reference external" href="https://developers.google.com/tink/aead">AEAD</a> 、またはAESのCFBモードに分岐しているらしいが、どういう条件なのかはよくわからない。</p>
<p>いずれにせよ、秘密鍵は、ユーザーが指定したパスワードとサーバーから取得されたソルトからハッシュ関数で生成されたパスワードによって、AES(対象鍵暗号化の標準)で暗号化されていることがわかった。</p>
<!-- sequenceDiagram
Client->>OpenPGP: 秘密鍵用パスワードと暗号化された秘密鍵
OpenPGP- ->>Client: 平文の秘密鍵 -->
<div class="figure">
<img alt="OpenPGPによる秘密鍵の暗号化解除" src="https://blog.tai2.net/images/proton-mail/decryption-with-openpgp.png">
<p class="caption">OpenPGPによる秘密鍵の暗号化解除</p>
</div>
<p>もちろん、これはあくまで1ケースで、実際には2FAが入るパターンなど色々な分岐がある。</p>
</div>
</div>
<div class="section" id="proton-mail-8">
<h2><a class="toc-backref" href="#toc-entry-15">Proton Mail所感</a></h2>
<p>世の中Gmailが支配的で、PGPもあんまり普及してないらしい。そして、Proton Mailユーザー同士やPGPユーザー相手じゃないとエンドツーエンド暗号化は機能しない。だとすると、けっきょくProton Mailを使ってたとしても、実質的にエンドツーエンド暗号化ではないじゃんとは思った。
そこが、Proton Mailについて調べてまず第一に気になったことだ。「エンドツーエンド暗号化であらゆるコミュニケーションがプライベートであることが、 <strong>技術的に</strong> 保証されています」、だったら、どれだけ話がわかりやすかったことか…。</p>
<p>ただ、それでも、現状のウェブシステムでは、電子メールはまだまだ必要不可欠なツールなので、どうしても使っていく必要がある。
その上で、Proton Mailは選択肢として悪くないし、Proton Mailの月額課金ビジネスモデルは健全だと思うので、今後とも使っていきたい。</p>
<p>純粋に対人でのコミュニケーションで、エンドツーエンドの暗号化を期待するのであれば、他の選択肢もある。たとえば、<a class="reference external" href="https://signal.org/en/">Signal</a> <sup id="sf-proton-mail-5-back"><a href="#sf-proton-mail-5" class="simple-footnote" title="ちなみにぼくはSignalユーザーでもある。ただし、いまのところSignalは、妻専用アプリと化していて、他の人とのやりとりでは、くやしながらLineを使っている。">5</a></sup> やTelegramなどのメールではないメッセージングアプリのほうが、アプリとしての使い勝手など、優れている面があるではないかと思う。これらならば、すべてのやりとりでエンドツーエンド暗号化が保証されるので、話としてもわかりやすい。ただ、けっきょく、PGPも含め、ツールを使えるかは相手ありきで、自分だけではどうにもならないのが、歯痒いところだ。</p>
<p>Proton Mail自体のメーラーとしての使い勝手は、悪くない。数年間使ってみて、スパムフィルタや検索なども問題なく、ふつうに使えている。
特定のサービスからのメールがどうしても届かないということが1、2回あったけど、<sup id="sf-proton-mail-6-back"><a href="#sf-proton-mail-6" class="simple-footnote" title="ためしにGmailで登録してみたら届いた">6</a></sup> まあ支障はない。
たぶん、Gmailには、もっと便利な自動分類機能や、他のサービスとの連携機能などが提供されているのだろうけど<sup id="sf-proton-mail-7-back"><a href="#sf-proton-mail-7" class="simple-footnote" title="ユーザーじゃないので、くわしくは知らない、">7</a></sup> メールにそこまで高度な機能は求めてないので、とくに問題ない。</p>
</div>
<div class="section" id="section-9">
<h2><a class="toc-backref" href="#toc-entry-16">参考リンク</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://protonmail.com/blog/privacy-under-attack/">Is Privacy Under Attack?</a> 監視資本主義を脱却して、サブスクリプションモデルに移行しよう。これこそウェブサービスのあるべき姿だ。</li>
<li><a class="reference external" href="https://www.ted.com/talks/glenn_greenwald_why_privacy_matters">Why privacy matters</a> プライバシーは人間にとって本質的に重要なものだ。</li>
<li><a class="reference external" href="https://protonmail.com/blog/protonmail-threat-model/">The ProtonMail Threat Model</a> Proton Mailの脅威モデル。Proton Mailは、どういうユーザーに向いていて、どういうユーザーには向かないか。</li>
<li><a class="reference external" href="https://proton.me/blog/encrypted-email">How encrypted email works</a> Proton Mailで使われている暗号技術の概要。</li>
<li><a class="reference external" href="https://protonmail.com/blog/what-is-end-to-end-encryption/">What is end-to-end encryption and how does it work?</a> Proton Mailによるエンドツーエンド暗号化の解説。</li>
<li><a class="reference external" href="https://protonmail.com/blog/zero-access-encryption/">What is zero-access encryption and why it is important for security</a> ゼロアクセス暗号化の解説。エンドツーエンド暗号化との違い。ゼロアクセス暗号化がデータ漏洩に効果的であること。</li>
<li><a class="reference external" href="https://protonmail.com/blog/what-is-pgp-encryption/">What is PGP encryption and how does it work?</a> Proton MailによるPGPの解説。</li>
<li><a class="reference external" href="https://proton.me/blog/encrypted-email-authentication">Improved Authentication for Email Encryption and Security</a> Proton MailによるSRP認証の解説。</li>
<li><a class="reference external" href="https://proton.me/blog/engineering-message-content-search">Behind the scenes of Proton Mail’s message content search</a> Proton Mailのメッセージ検索解説。</li>
<li><a class="reference external" href="https://protonmail.com/blog/encrypted-email-spam-filtering/">Effective Spam Filtering with Encrypted Email</a> Proton Mailのスパムフィルタ解説(外部からメッセージが到達したときの処理のみ)。</li>
<li><a class="reference external" href="https://protonmail.com/support/knowledge-base/encrypt-for-outside-users/">Encrypt Message for Non-ProtonMail Recipients</a> Proton Mailのパスワード保護メールの使いかた。</li>
<li><a class="reference external" href="https://proton.me/support/how-to-use-pgp">How to use PGP with Proton Mail</a> Proton MailでのPGPの使い方。公開鍵の送付、アップロード、信用など。</li>
<li><a class="reference external" href="https://protonmail.com/blog/switzerland/">Why Switzerland? An Analysis of Swiss Privacy Laws</a> スイスの法律は、他国に比べてプライバシー保護が強いという主張。</li>
<li><a class="reference external" href="https://protonmail.com/blog/swiss-surveillance-law/">Impact of Swiss surveillance laws on secure email</a> スイスで新設された大量監視法の影響についての分析。スイスの諜報機関は国内の案件にしか興味がないので、NSAなど諸外国に情報をオープンにすることはない。法律では、保存されたデータを提出させることはできるが、ユーザーを監視させることはできない。保存されたデータは、エンドツーエンド暗号化されているので安全である。(2015年)</li>
<li><a class="reference external" href="https://protonmail.com/blog/protonmail-vs-gmail-security/">Why Proton Mail Is More Secure Than Gmail</a> なぜProton Mailは、Gmailよりセキュアなのか。エンドツーエンド暗号化、SRP、スイスの法律、追跡やログがないこと、など。アプリの種類が少ないから攻撃面がすくないことも挙げているけど、MailとVPNしかなかった当時と比べて、いまではProtonもいろんなアプリを提供しているので、もう妥当とは言えない。</li>
<li><a class="reference external" href="https://protonmail.com/blog/ad-free-business-model/">Privacy isn’t free. Here’s why that’s a good thing.</a> Proton Mailがどうやって金銭を得て、それをどんなことに使っているのか。サービスの運営に加え、法的な活動、セキュリティー活動、OSS活動など。</li>
<li><a class="reference external" href="https://www.eff.org/deeplinks/2020/03/google-says-it-doesnt-sell-your-data-heres-how-company-shares-monetizes-and">Google Says It Doesn’t 'Sell' Your Data. Here’s How the Company Shares, Monetizes, and Exploits It.</a> GoogleはRTBと呼ばれる広告オークションの仕組みを通じて、実質的にユーザー情報を売っている。(2020年)</li>
<li><a class="reference external" href="https://support.google.com/mail/answer/6603">How Gmail ads work</a> Gmailの広告についての説明。Google自身はメールボックスのスキャンはしないと言っている。</li>
<li><a class="reference external" href="https://www.nytimes.com/2017/06/23/technology/gmail-ads.html">Google Will No Longer Scan Gmail for Ad Targeting</a> Googleは、今後広告のためのメールボックススキャンを止める。(2017年)</li>
<li><a class="reference external" href="https://variety.com/2017/digital/news/google-gmail-ads-emails-1202477321/">Google Will Keep Reading Your Emails, Just Not for Ads</a> Googleは、広告目的のスキャンをやめただけで、すくなくともユーザーの利便性のためにメールボックスをスキャンし、サーバーにデータを送信し続ける。(2017年)</li>
<li><a class="reference external" href="https://www.wsj.com/articles/techs-dirty-secret-the-app-developers-sifting-through-your-gmail-1530544442">Tech’s ‘Dirty Secret’: The App Developers Sifting Through Your Gmail</a> Googleは「アドオン」と称してサードパーティーへのメールボックスアクセスを許可している。そこからデータが漏洩している(2018年)。</li>
<li><a class="reference external" href="https://www.itmedia.co.jp/news/articles/1807/04/news055.html">Google、「サードパーティ開発者がGmailの内容を読んでいる」報道について説明</a> WSJのReturn Path報道に対する補足(2018年)。</li>
<li><a class="reference external" href="https://blog.google/products/gmail/g-suite-gains-traction-in-the-enterprise-g-suites-gmail-and-consumer-gmail-to-more-closely-align/">As G Suite gains traction in the enterprise, G Suite’s Gmail and consumer Gmail to more closely align</a> G Suiteユーザーと同様、今後は一般向けGmailユーザーのメッセージも、広告用のデータとしては使わない。</li>
<li><a class="reference external" href="https://protonmail.com/blog/yahoo-us-intelligence/">What Yahoo’s NSA Surveillance Means for Email Privacy</a> Yahooは、NSAとFBAからの要求で、ユーザーを監視するソフトウェアの設置を強制されていた。</li>
<li><a class="reference external" href="https://www.wired.com/2013/08/lavabit-snowden/">Edward Snowden's Email Provider Shuts Down Amid Secret Court Battle</a> スノーデンの使っていた米国のプライバシーファーストなメールプロバイダーLavabitは、おそらく、当局からの圧力の結果、10年間続いたサービスの幕を閉じた。</li>
<li><a class="reference external" href="https://protonmail.com/blog/google-fake-online-privacy/">Don’t be fooled by Google’s fake privacy</a> Goolgeは、世間がプライバシーを気にするようになってきたのに合わせて、気にしているようなそぶりを見せはしているが、以前として広告で儲けている企業である以上、真に受けてはいけない。</li>
<li><a class="reference external" href="https://protonmail.com/blog/encryption-backdoor/">The real problem with encryption backdoors</a> 当局は、数十年にわたって、暗号化へのバックドアを仕掛けようとしてきた。暗号化へのバックドアとは、意図的に暗号に弱点を作り込み、政府がアクセスできるようにすることだ。しかし、良い物だけが使えるバックドアなどというものは存在しないのだから、暗号化のへのバックドアは本質的に危険なものだ。</li>
<li><a class="reference external" href="https://proton.me/blog/why-we-created-protonca">Why we created ProtonCA</a> Proton Mailがなぜ独自CAを運営しているのか。Proton Mail自身がCAを持つことで、third-party signaturesを通じて、全Proton Mailユーザーの鍵の真性性を簡単に証明できるから。</li>
<li><a class="reference external" href="https://emailisbad.com/">email is bad</a> Eメールにはダメなところもたくさんあるけど、おおむね他のものよりはいいよという話。</li>
<li><a class="reference external" href="https://proton.me/blog/stop-using-sms">Why you should stop using SMS</a> SNSは、暗号化による保護がまったくないし、いくつかの弱点が知られているので、使うのをやめたほうがいい。認証には2FA認証アプリを使う。メッセージングには、iMessage, RCS, Signal, Telegram, WhatsApp, Meta Messangerなどを使う。</li>
<li><a class="reference external" href="https://transparency.fb.com/data/government-data-requests/?source=https%3A%2F%2Ftransparency.facebook.com%2Fgovernment-data-requests">Government Requests for User Data</a> Metaの公開している、政府からのデータ要求件数。おそらくGAGオーダーは含まれていない。しかし、 <a class="reference external" href="https://www.digitaltrends.com/social-media/facebook-government-requests-gag-order/">以前は含まれていた…?</a></li>
<li><a class="reference external" href="https://twitter.com/Snowden/status/878686842631139334">Here's an actual Top Secret document published in 2014 showing an example of NSA's "sorry, can't decrypt PGP" message. Cryptography works:</a> PGPによる暗号化がNSAの盗聴を防いだ例。</li>
<li><a class="reference external" href="https://variety.com/2017/digital/news/google-gmail-ads-emails-1202477321/">Google Will Keep Reading Your Emails, Just Not for Ads</a> Googleは、広告目的には使わないというだけで、ユーザーのメールを読むこと自体は続ける。</li>
<li><a class="reference external" href="https://proton.me/blog/privacy-user-data-requests">Massive corporate databases become government tools of surveillance</a> 民間企業のデータが、政府の監視ツールとなりつつある。政府が民間企業にデータを要求する件数は年々増えている。</li>
<li><a class="reference external" href="https://www.wsj.com/articles/federal-agencies-use-cellphone-location-data-for-immigration-enforcement-11581078600">Federal Agencies Use Cellphone Location Data for Immigration Enforcement</a> 米政府は、民間企業から、ユーザーの位置データを購入し、移民の監視に利用している。(2020年)</li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-proton-mail-1">当時調べた中でmbox形式のインポートをサポートしていた唯一のメールサービスだったのも大きい <a href="#sf-proton-mail-1-back" class="simple-footnote-back">↩</a></li><li id="sf-proton-mail-2">無料で利用することも可能。無料ユーザーのコストは、有料ユーザーが支えている <a href="#sf-proton-mail-2-back" class="simple-footnote-back">↩</a></li><li id="sf-proton-mail-3">Proton Mailでも現在はこれがデフォルト <a href="#sf-proton-mail-3-back" class="simple-footnote-back">↩</a></li><li id="sf-proton-mail-4">通信路はTLSで保護されるけど、アプリケーションは平文で受け取る <a href="#sf-proton-mail-4-back" class="simple-footnote-back">↩</a></li><li id="sf-proton-mail-5">ちなみにぼくはSignalユーザーでもある。ただし、いまのところSignalは、妻専用アプリと化していて、他の人とのやりとりでは、くやしながらLineを使っている。 <a href="#sf-proton-mail-5-back" class="simple-footnote-back">↩</a></li><li id="sf-proton-mail-6">ためしにGmailで登録してみたら届いた <a href="#sf-proton-mail-6-back" class="simple-footnote-back">↩</a></li><li id="sf-proton-mail-7">ユーザーじゃないので、くわしくは知らない、 <a href="#sf-proton-mail-7-back" class="simple-footnote-back">↩</a></li></ol>コードレビューはつまらないから丁寧なプルリクエストでチームの生産性向上を目指す2022-09-14T00:00:00+09:002022-09-14T00:00:00+09:00tai2tag:blog.tai2.net,2022-09-14:/how-to-code-review.html<p class="first last">この記事では、ぼくなりにたどり着いた、プルリクエストのレビューを上手に行うための心構えについて書く。</p>
<p>コードレビューにずっと苦手意識を持っていた。レビューは時間がかかるし、あまり気が乗らない。
がんばってやっても、うまくできたのかどうか自信が持てない。</p>
<p>もちろん、世にあるコードレビューに関する書籍や記事などはいくつも読んだ。
そこには、コードレビューをするときの観点や、コードレビューで望ましい言葉づかいなど、ためになることがたくさん書かれていた。
それでも、やはりコードレビューが苦手なことに変わりはなかった。</p>
<p>だけど最近ようやく、こうすればレビューをうまくこなせるのではないかという出口が、なんとなく見えはじめてきた。
この記事では、ぼくなりにたどり着いた、プルリクエストのレビューを上手に行うための心構え、つまりいかにしてレビューのつらみを減らすかについて書く。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-2" id="toc-entry-1">なぜコードレビューはつまらないのか</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-2">われわれは、なんのためにコードレビューをするのか</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-3">レビューのコスト</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-4">レビューの観点</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-5">文化を浸透させる</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-6">コードの質の担保について</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-7">レビュアーの負担を最小化すべき</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-8">丁寧なプルリクエストを起点にしてプルリクエストの回転数上昇を目指す</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-9">まとめ</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-10">参考リンク</a><ul>
<li><a class="reference internal" href="#eng-practices-1" id="toc-entry-11">eng-practices (日本語訳)</a></li>
<li><a class="reference internal" href="#thoughtbot-s-code-review-guide-1" id="toc-entry-12">thoughtbot's code review guide</a></li>
<li><a class="reference internal" href="#railsconf-2015-implementing-a-strong-code-review-culture-1" id="toc-entry-13">RailsConf 2015 - Implementing a Strong Code-Review Culture</a></li>
<li><a class="reference internal" href="#how-to-make-a-perfect-pull-request-1" id="toc-entry-14">How to Make a Perfect Pull Request</a></li>
<li><a class="reference internal" href="#how-to-write-the-perfect-pull-request-1" id="toc-entry-15">How to write the perfect pull request</a></li>
<li><a class="reference internal" href="#how-to-write-a-git-commit-message-1" id="toc-entry-16">How to Write a Git Commit Message</a></li>
<li><a class="reference internal" href="#stop-nitpicking-in-code-reviews-1" id="toc-entry-17">Stop Nitpicking in Code Reviews</a></li>
<li><a class="reference internal" href="#on-commit-messages-1" id="toc-entry-18">On commit messages</a></li>
<li><a class="reference internal" href="#code-review-decision-fatigue-1" id="toc-entry-19">Code Review Decision Fatigue</a></li>
<li><a class="reference internal" href="#the-code-review-pyramid-1" id="toc-entry-20">The Code Review Pyramid</a></li>
<li><a class="reference internal" href="#how-to-make-your-code-reviewer-fall-in-love-with-you-1" id="toc-entry-21">How to Make Your Code Reviewer Fall in Love with You</a></li>
<li><a class="reference internal" href="#my-favourite-git-commit-1" id="toc-entry-22">My favourite Git commit</a></li>
<li><a class="reference internal" href="#section-12" id="toc-entry-23">コードレビューの目的と考え方</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-1">なぜコードレビューはつまらないのか</a></h2>
<p>コードを読むのが得意なら、コードレビューもできてしかるべきなのだろう。
コードレビューが苦手なのは、スキルの欠如であり、コードを読む能力が低いからなのではないか。
熟達したプログラマなら、コードレビューに喜んで取り組み、上手に扱える人間である <strong>べき</strong> だ。
そんなふうに思っていた。</p>
<p>でも、いまはそう思わない。
ソースコードを読むことと、コードレビューは違う。
ソースコードを読むという行為には、未知のことがらを探索する楽しさや知る喜びがある。
ソースコードを読んで知りたいことにたどり着くと、知的好奇心が満たされる。
というのも、ソースコードを読むときには、対象のプログラムに興味があって、なにかしらの目的意識を持った状態で着手するからだ。</p>
<p>一方、コードレビューはそうではない。コードレビューは、検査であり間違い探しだ、という意識がある。
普段レビューするプルリクエストは、多くの場合、知的好奇心をくすぐるようなものではない。
すくなくとも、レビューを依頼されたその時点では興味を持っていない。
なぜなら、いつでも、最大の興味は自分が取り組んでいる別のタスクにあるので。
コードレビューは、自発的に開始するものではなく、自分の今現在の興味とは関係なく、いまこれを見ろと人から押し付けられるものだ。
だから、コードレビューはつまらないし、苦痛だ。</p>
<p>コードを読むこととコードレビューはまったく違う行為。だから、コードレビューをつまらなく感じるのは、しかたのないこと。</p>
<p>それから、コードレビューは、本質的に差分に対するチェックなので、その意味でも、ふつうのコードリーディングとは体験が違う。
GitHubのUI上では差分の周囲やファイル全体も見わたせるようになっているおかげで、だいぶ負担が軽減されはする。
けれど、それでも断片的な情報から判断しなければならないことには変わりがない。
差分は理解しづらい。これもコードレビューを苦痛にしているひとつの原因だと思う。</p>
<p>コードレビューのあるべき姿について考えるにあたって、これらのネガティブな点を認めた上で、はじめたい。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-2">われわれは、なんのためにコードレビューをするのか</a></h2>
<p>コードレビューには複数の機能がある。セキュリティーホールを防ぐ、コード品質を保つ、チーム内での知識やテクニックの伝達など。
中でも、設計的な誤りというのは後々への影響範囲が大きく、つまり修正も大変なので、コードレビューで正せれば見返りが大きい。
RDBのテーブル定義など、永続化されるデータの持ち方は、一度デプロイしてしまうと修正がめんどうなので、プルリクエストか、それよりも前の段階<sup id="sf-how-to-code-review-1-back"><a href="#sf-how-to-code-review-1" class="simple-footnote" title="事前の提案ドキュメントを通じて行われる設計レビューとか">1</a></sup>で、十分に議論して筋のいい形にしておきたい。</p>
<p>リファクタリングが活発に行われるという前提に立てば、ある問題の解決方法に対して、局所的なコードの記述方法が最適でないとかは、たぶん大きな問題ではない。気付いたらすぐに直すことができるから。もちろん、それらの小さなほころびも、積み重なってしまうと大きな問題になるので、日常的にリファクタリングが行われることが前提にはなるけど。</p>
<p>そういう意味では、チームの気付かない間に(望ましくない)変更が積み重なっていってしまうことが一番の問題だろう。
コードレビューは同期のための手段のひとつでもある。
チームが同期するための手段は、daily standupや、retrospective、slackでの非同期コミュニケーションなどいくつかあるけど、コードレビューもその重要な一つ。それらの手段が重層的に機能することで、チーム内での破滅的な認識の乖離を防ぐ。
社会のセーフティーネットでもセキュリティーでもそうだけど、単一の手段で満足するのではなく、複数の重なり合う防御手段を持つことが重要だと思う。</p>
<p>それから、コードレビューを欠陥を見つけるための検査だと考えるとモチベーションが上がらないと書いたけど、コードレビューを、プルリクエスト作者を助けるための行為、人助けと位置付ければ、すこしはやる気もでやすい……かもしれない。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-3">レビューのコスト</a></h2>
<p>チーム開発において、コードレビューは毎日何回も行う行為だ。
だから、前提として、一個一個のレビューにそんなに長い時間はかけられない。
できる限り効率的に行いたい。</p>
<p>上手に手際よくレビューできるかどうか、という問題は、実は自分の力だけで解決できる問題ではない。
なぜなら、レビューの効率性は、プルリクエストがうまく作られているかどうかに依存するからだ。
だから、プルリクエストが不十分なときの第一の対応は、効率よくレビューできるようにプルリクエストを
わかりやすく作り直してくださいと要求することになる。
そのためには、なにが不足しているのかをまずは判別できなければならない。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-4">レビューの観点</a></h2>
<p>われわれの目標は、手早く効率的にレビューすることだ。つまり、どこに時間を <strong>かけない</strong> かを見定めて、見るべき観点をなるべく少なく絞る必要がある。</p>
<ul class="simple">
<li>全体的な意図。なにをどうやって実現するのか(What, How)。</li>
<li>変更の文脈、位置付け。なぜこの変更が必要なのか(Why)。</li>
<li>複数の独立した問題が含まれているか<ul>
<li>含まれている場合、分割できないか検討する</li>
</ul>
</li>
<li>設計の誤りは影響が後を引く可能性があるので、なるべくちゃんと見ておきたい。とくに永続化されるデータ構造のミスは、リリースしてしまうと修正が面倒なので、注意する必要がある。設計の誤りとは、例えば:<ul>
<li>SQLアンチパターンに該当するようなテーブル設計になっている(実データが発生するとめんどう)</li>
<li>本来別のAPIを新設すべきところを、既存APIへの追加パラメータで無理矢理処理している(修正後の挙動への依存が増えるとめんどう)</li>
<li>手続の種類が増えても修正不要なように一般化できる(一度書けば済む)のに、手続の種類の数だけコード追加が必要な設計になっている(無駄な手数が増える)</li>
</ul>
</li>
<li>追加・変更される挙動について、テストケースが追加されているか、テストケースの見出しレベルで簡単に見る</li>
<li>特殊な理由のある変更など、コードだけから理解できなさそうな変更は、コメントに経緯が書かれているかを見る</li>
<li>UIの変更が含まれる場合、スクリーンショットが添付されているか</li>
<li>QA手順が記載されているか</li>
<li>これ以外のすべて: セキュリティー、局所的なコード品質などは最悪見なくてもいい。<sup id="sf-how-to-code-review-2-back"><a href="#sf-how-to-code-review-2" class="simple-footnote" title="効率よくレビューしつつ、セキュリティーをいかに担保するのかという点については、いまのところどうすればいいのかわからない。時間をかけずに効率よくレビューするという命題と、セキュリティー検査をしっかり行うという命題は相容れない気がする。ちなみにバグの発見はコードレビューの主目的ではないと思う。品質はQAで担保されるべきことなので。経験とか直感から、こういうバグがありそうなど、きな臭さを感じたときに重点的にチェックしたりするのは、もちろん良いことではある。">2</a></sup></li>
</ul>
<p>これらの大部分は、プルリクエストの説明が丁寧に書かれていれば、ほぼコードの詳細を読解しなくてもチェックできる点に注意。
コードを見ないならコードレビューじゃないじゃないかというツッコミが来そうだけど、詳細を隅々まで理解しようとすると時間がかかるので、実際なるべく立ち入りたくない。
もちろん、コードを眺めていてなにか気付いた点があるなら、それをフィードバックするのは何の問題もない。
ただ、一行一行目を注いで見るのは、つかれるし、時間もかかるので行わないということ。
コードレビューではなく、PRレビューとか、なにか別の、「体を表す」適切な名前があればいいんだけど…。</p>
<p>ちなみに、QA作業自体は、基本的にレビュアーは行わなくていいと思う。<sup id="sf-how-to-code-review-3-back"><a href="#sf-how-to-code-review-3" class="simple-footnote" title="もちろん、特定のエッジケースでどう動作するか気になるなど、自分で動かして確かめたほうが手っ取り早いこともある。">3</a></sup> レビュイー自身が責任を持って動作確認を行ってからレビュー依頼を出すのは前提条件だからだ。</p>
<p>これら必要な情報が提供されていないプルリクエストは、まず詳細を見る前に、情報を足してもらうよう催促する必要がある。</p>
</div>
<div class="section" id="section-6">
<h2><a class="toc-backref" href="#toc-entry-5">文化を浸透させる</a></h2>
<p>プルリクエストの書き方に注文があるなら、いちいち指摘するより、ガイドラインを書いた方がいいんじゃないかと思うかもしれない。実際ガイドラインは必要だけど、ガイドラインがあるだけで、だれもが理想的なプルリクエストを作ってくれて、自動的に問題が解決する、などということはまずない。実際には、ガイドラインを根付かせるために、絶え間ないフィードバックが必要。どういうプルリクエストが望ましいかについてフィードバックを与えることで、望ましいチーム文化の醸成を促進したい。</p>
<p>プルリクエストガイドラインには、お手本となるような、実際のプルリクエストの例もいくつか添えておけると良いと思う。</p>
</div>
<div class="section" id="section-7">
<h2><a class="toc-backref" href="#toc-entry-6">コードの質の担保について</a></h2>
<p>これまでのことは、ある前提の上に書かれている。それは、コードの質は、コードレビューで漏れてしまったとしても、たぶんリファクタリングで挽回できるということだ。
もちろん、コードレビューの段階で改善できたなら、それに越したことはない。けど、レビュイーを待たせずプロジェクト全体を円滑に回すという観点を重視すると、コードの質をどう位置付けるべきかが見えてくる。</p>
<p>リファクタリングは、後でじっくり1人でも取り組めることだけど、レビューを待たせるのはチーム全体に悪影響をおよぼす。
小さなプルリクエストを促すためには、手早いコードレビューがとても大切だ。
だから、コードの質の担保はコードレビューにおいて、優先度は低でいいと、ぼくは位置付けている。</p>
</div>
<div class="section" id="section-8">
<h2><a class="toc-backref" href="#toc-entry-7">レビュアーの負担を最小化すべき</a></h2>
<p>レビュイーとレビュアーでは、持っている情報量に大きな差がある。</p>
<p>レビュイーは、着手する前に、タスクの説明を読んで、その背景や理由を理解し、情報が不足していれば情報を持っている人に質問し、それらをどう実現するのがいいか何パターンか考え、良いと思った実装方針の落し穴に実装しだしてから気付いて、途中でやりかたを変えたり等等等、ひとつのプルリクエストを出すまでにもそれなりの時間をかけているはず。</p>
<p>コードだけから、それらの様々な経験に追い付くのは難しい。コードを一生懸命読んで意図を推測することもある程度はできるかもしれないが、コードを読み解くには時間がかかるし、第一それは思い込みで見当違いかもしれない。</p>
<p>レビュイーはすべて知識として持っているのだから、ちょっと手間をかけて、それらの情報を余さず文章に書き出したり、図表として表現しておく。そうすることで、チーム全体としての効率が良くって欲しい。これらを丁寧に記述しておけば後々の資産にもなる。ある変更がどういう理由で入ったかをgit blameなどから追跡するのは、日常の業務でよくあることだ。プルリクエストの説明が丁寧に書かれていれば、後から読んだ人はきっと感謝する。</p>
</div>
<div class="section" id="section-9">
<h2><a class="toc-backref" href="#toc-entry-8">丁寧なプルリクエストを起点にしてプルリクエストの回転数上昇を目指す</a></h2>
<p>理想的には、ひとつのプルリクエストは、単一の目的だけを含む必要最小限の大きさであるべきだ。
そのほうがレビューは楽だし、リバートもしやすくなる。</p>
<p>けれども、レビューが遅いとこれが守りづらくなる。
ある変更に依存した変更を別のブランチとして分割したいということはよくあるだろう。
このとき、レビューが短時間で返ってくると期待できるのであれば、別のプルリクエストとして、分けて出しやすい。
しかし、一旦プルリクエストを出すと最初の反応が返ってくるまで数日も待たされる、というようなことが常態化
しているとこうはいかない。リファクタリングだけ切り出すなどして、できればいくつかのブランチに分割するのが理想的。それなのに、この先何回も待たされることを考えると、まとめられるものはまとめて出してしまいたい、という誘惑にかられる。</p>
<p>こうなってしまうと、大きなプルリクエストが常態化し、大きなプルリクエストはレビューもめんどうなので、より時間がかかり回転数が下がる。悪循環。</p>
<p>そうではなく、プルリクエストの説明に時間をかけることで、そこを起点にして、以下のような好循環を作りたい。</p>
<img alt="理解しやすいプルリクエスト → 手早いレビュー →手早いマージ → 小さなプルリクエスト →(小さいので)理解しやすいプルリクエスト" class="align-center" src="https://blog.tai2.net/images/how_to_code_review/fine_cycle.jpg">
<p>こうすることで、結果的にコード変更の回転数が上がって欲しい。</p>
</div>
<div class="section" id="section-10">
<h2><a class="toc-backref" href="#toc-entry-9">まとめ</a></h2>
<ul class="simple">
<li>コードレビューがつまらないのは、その性質からして自然なこと</li>
<li>コードレビューは、チームの同期を取るための手段のひとつ</li>
<li>レビューの効率は、プルリクエストの質に依存する</li>
<li>フィードバックによって文化を浸透させることを目指す</li>
<li>コードの質はコードレビューに加えて、リファクタリングでもカバーできる</li>
<li>レビュイーとレビュアーは持っている情報量がまったく違うので、ギャップを埋めるためにレビュイーが努力すべき</li>
</ul>
</div>
<div class="section" id="section-11">
<h2><a class="toc-backref" href="#toc-entry-10">参考リンク</a></h2>
<div class="section" id="eng-practices-1">
<h3><a class="reference external" href="https://google.github.io/eng-practices/">eng-practices</a> (<a class="reference external" href="https://fujiharuka.github.io/google-eng-practices-ja/">日本語訳</a>)</h3>
<p>Googleのパッチの出しかた及びコードレビューのしかたについてのガイドライン。コードレビューまわりのあれこれについて網羅的に書かれている。必読。</p>
</div>
<div class="section" id="thoughtbot-s-code-review-guide-1">
<h3><a class="reference external" href="https://github.com/thoughtbot/guides/tree/main/code-review">thoughtbot's code review guide</a></h3>
<p>レビューでのやりとりについて良く言われる基本的なことが箇条書きで書かれている。この一文が好き "Remember that you are here to provide feedback, not to be a gatekeeper." 「あなたはフィードバックするためにレビューしているのであって、欠陥を阻止するのが目的ではないことを忘れないこと」</p>
</div>
<div class="section" id="railsconf-2015-implementing-a-strong-code-review-culture-1">
<h3><a class="reference external" href="https://www.youtube.com/watch?v=PJjmw9TRB7s">RailsConf 2015 - Implementing a Strong Code-Review Culture</a></h3>
<p>thoughtbotのDerek Priorさんによる講演。コードレビューが嫌いだったPriorさんが、いかにして好きになれたのか。コードレビューは何のためにあるのか。結果よりもプロセスが大切である。コードレビューは、コードを説明する場ではなく、コードについて議論する場である。コードレビューの質を上げるためには背景の説明が大切。等等等。</p>
</div>
<div class="section" id="how-to-make-a-perfect-pull-request-1">
<h3><a class="reference external" href="https://betterprogramming.pub/how-to-make-a-perfect-pull-request-3578fb4c112">How to Make a Perfect Pull Request</a></h3>
<p>どのようなプルリクエストが迅速にマージされるかという分析。Whyをコメントに書く、小さなプルリクエストを出す、プルリクエストの説明を明確に記述する、など。</p>
</div>
<div class="section" id="how-to-write-the-perfect-pull-request-1">
<h3><a class="reference external" href="https://github.blog/2015-01-21-how-to-write-the-perfect-pull-request/">How to write the perfect pull request</a></h3>
<p>GitHub公式ブログの記事。どのようにプルリクエストを行うべきについての簡潔な説明。</p>
</div>
<div class="section" id="how-to-write-a-git-commit-message-1">
<h3><a class="reference external" href="https://cbea.ms/git-commit/">How to Write a Git Commit Message</a></h3>
<p>コミットメッセージの書き方についての記事だけど、なぜWhyを書く必要があるのか説明されている。</p>
</div>
<div class="section" id="stop-nitpicking-in-code-reviews-1">
<h3><a class="reference external" href="https://blog.danlew.net/2021/02/23/stop-nitpicking-in-code-reviews/">Stop Nitpicking in Code Reviews</a></h3>
<p>完璧主義のコードレビューはいいことないという話</p>
</div>
<div class="section" id="on-commit-messages-1">
<h3><a class="reference external" href="http://who-t.blogspot.com/2009/12/on-commit-messages.html">On commit messages</a></h3>
<p>コミットメッセージの書き方指南だけど、プルリクエストにも適用できると思う。なぜこの変更が必要なのか。どのようにそれを実現するのか。この変更によってどんな効用が得られるのか。読み手に文脈を提供しないのは経済的な損失。</p>
</div>
<div class="section" id="code-review-decision-fatigue-1">
<h3><a class="reference external" href="https://tylercipriani.com/blog/2022/03/12/code-review-procrastination-and-clarity/">Code Review Decision Fatigue</a></h3>
<p>コードレビューが精神的にとても消耗する行為であり、それがレビューの先送りを招くことを指摘している。</p>
</div>
<div class="section" id="the-code-review-pyramid-1">
<h3><a class="reference external" href="https://www.morling.dev/blog/the-code-review-pyramid/">The Code Review Pyramid</a></h3>
<p>コードレビューでどこにより時間をかけるべきか。後々の変更が大変な部分に時間をかけて、後々簡単に変更できることには時間をかけない。</p>
</div>
<div class="section" id="how-to-make-your-code-reviewer-fall-in-love-with-you-1">
<h3><a class="reference external" href="https://mtlynch.io/code-review-love/">How to Make Your Code Reviewer Fall in Love with You</a></h3>
<p>レビュアーの時間を尊重せよ。レビュアーがレビューに割く時間は、彼・彼女らが自分のコードを書けたはずの時間だ。レビュアーのために書き手ができることが網羅されている。質の高いレビューをしてもらうためにレビュイーができることはたくさんある。</p>
</div>
<div class="section" id="my-favourite-git-commit-1">
<h3><a class="reference external" href="https://dhwthompson.com/2019/my-favourite-git-commit">My favourite Git commit</a></h3>
<p>コミットメッセージにおける背景の説明がなぜ重要なのか。背景の説明をどのように書けばいいのかを上手に説明してくれている。これらはもちろんプルリクエストについても同じことが言える。</p>
</div>
<div class="section" id="section-12">
<h3><a class="reference external" href="https://osak.hatenablog.jp/entry/code-review-objectives-and-howto">コードレビューの目的と考え方</a></h3>
<p>よくあるコードレビュー指南とくらべて、人間の弱さとか精神的な面にまで踏み込んでいる点ですばらしいエントリ。既存文献もよく踏まえた上でのまとめにもなっているので大変勉強になる。</p>
</div>
</div>
<ol class="simple-footnotes"><li id="sf-how-to-code-review-1">事前の提案ドキュメントを通じて行われる設計レビューとか <a href="#sf-how-to-code-review-1-back" class="simple-footnote-back">↩</a></li><li id="sf-how-to-code-review-2">効率よくレビューしつつ、セキュリティーをいかに担保するのかという点については、いまのところどうすればいいのかわからない。時間をかけずに効率よくレビューするという命題と、セキュリティー検査をしっかり行うという命題は相容れない気がする。ちなみにバグの発見はコードレビューの主目的ではないと思う。品質はQAで担保されるべきことなので。経験とか直感から、こういうバグがありそうなど、きな臭さを感じたときに重点的にチェックしたりするのは、もちろん良いことではある。 <a href="#sf-how-to-code-review-2-back" class="simple-footnote-back">↩</a></li><li id="sf-how-to-code-review-3">もちろん、特定のエッジケースでどう動作するか気になるなど、自分で動かして確かめたほうが手っ取り早いこともある。 <a href="#sf-how-to-code-review-3-back" class="simple-footnote-back">↩</a></li></ol>人月の神話2022-02-28T00:00:00+09:002022-02-28T00:00:00+09:00tai2tag:blog.tai2.net,2022-02-28:/the-mythical-man-month.html<p class="first last">人月の神話をひさしぶりに読んでみた</p>
<p><a class="reference external" href="https://www.amazon.co.jp/dp/4894716658/">人月の神話</a> をひさしぶりに読んでみた。</p>
<p>人月の神話は、フレデリック・ブルックスの超有名古典的エッセイ集で、ソフトウェアエンジニアリングに関する多岐にわたるトピック取り扱っている。その中でもとくに有名で、よく世間で言及されるのは、表題にもなってる「人月の神話」と「銀の弾などない」、それから「セカンドシステム症候群」あたりだろうか。</p>
<p>はじめて読んだのは20年くらい前。社会人になったばかりのころ、満員電車にゆられながら、「へー人を増やしても開発ってうまくいかないのねー」などとわかったような顔をしながら読んでいたのを覚えている。当時は職業プログラマとしての経験を積む前で、本を読んでも鵜呑みにすることしかできなかった。でも、熟練のプログラマとして経験を積んだいま読んだら、またなにか違った洞察を得られたりするかもしれない。読み返してみた動機はそんな感じ。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-2" id="toc-entry-1">現代のプログラマにとって有益か</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-2">やっぱり本質と偶有がわからない</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-3">自分の経験と照らし合わせての感想</a><ul>
<li><a class="reference internal" href="#section-5" id="toc-entry-4">タールの沼</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-5">人月の神話</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-6">貴族政治、民主政治、そしてシステムデザイン</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-7">セカンドシステム症候群</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-8">命令を伝える</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-9">バベルの塔は、なぜ失敗に終わったか</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-10">一つは捨て石にするつもりで</a></li>
<li><a class="reference internal" href="#section-12" id="toc-entry-11">破局を生み出すこと</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-13" id="toc-entry-12">まとめ</a></li>
</ul>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-1">現代のプログラマにとって有益か</a></h2>
<p>改めて読んでみて、とくべつすごい感動したとかはなかったんだけど、人月の神話とセカンドシステム症候群あたりは、いまでも危険な兆候を察知したり、当てはまりそうな状況のときに自説を補強する材料として使ってみてもいいのかもしれない。人数を増やせば単純に生産性があがるという直感から来る思い込みは根強いし、人月にまつわるコミュニケーションの諸問題が広く常識として定着することは永遠になさそう。</p>
<p>この本をまだ読んでいない人に薦めるかと言われれば、さすがに同様のトピックを扱っていてもっと現代的な書籍はぜったいあるはずなので、違う本を読んだ方がいい、気がする。ただ、具体的にどれと言われると知らない。ぼくが読んだことある本だと、 <a class="reference external" href="https://www.ohmsha.co.jp/book/9784274227943/">実践ソフトウェアエンジニアリング</a> とか、たぶんほとんどのトピックを扱ってそうだけど、いかんせん...すごく退屈で読んでもぜんぜんおもしろくない本なので、あんまりおすすめできない...。おすすめがあれば教えてください。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-2">やっぱり本質と偶有がわからない</a></h2>
<blockquote>
「技術においても管理手法においても、それだけで十年間に生産性や信頼性と容易性での飛躍的な改善を一つでも約束できるような開発は一つとしてない」</blockquote>
<p>この業界の人間なら聞いたことくらいはあるであろう銀の弾丸の議論、その元ネタがこの本に載っているエッセイのひとつ「銀の弾などない」です。このエッセイでは、ソフトウェア構築を「複雑な概念構造体を作り上げる」本質的作業と「抽象的な実在をプログラミング言語で表現」する偶有的作業に分ける。その上で、これまでのソフトウェア開発は、主に偶有的な作業にまつわる困難を取り除くことで進歩してきた、しかし、これ以上偶有的な困難を取り除いても「大きな収穫」は得られないとする(ブルックスの主張では、偶有的困難の占める割合は全体の半分以下)。さらに、今後10年(1986年〜1996年)に本質的な困難を飛躍的に改善する技術はひとつも現れないとブルックスは断言する。では飛躍的な改善とはいったいどれくらいなのか。これは「生産性で十倍の改善」であれば十分らしい。(ちなみに、現代に置いても偶有的作業の占める割合のほうが圧倒的に大きいぞコノヤローっていう <a class="reference external" href="https://danluu.com/essential-complexity/">反論</a> もある)</p>
<p>この本質的と偶有的という議論の核となる区別、考えれば考えるほどわからなくなってくる。</p>
<p>「難しさの本質とはソフトウェアの性質に固有な困難のこと」で、「偶有的難しさとは実現するとき派生するが本来備わっているものではない困難」なのだそうだ。そして、1986年までに偶有的困難を解決することで飛躍的な改善をもたらしてきた技術として「高水準言語」「タイムシェアリング」「UNIX」を挙げている。</p>
<p>ブルックスは言語を本質からは独立したものとして考えているようなふしがある。しかし、言語とそのランタイムは切り離して考えられないと思う。そして、言語で何が実現できるかという「本質的」なことにおいては、言語そのものよりもライブラリや動作モデルなど含めたランタイムがより重要な気がする。たとえば、サーバーにRubyを使うかNode.jsを使うかでは、ランタイムの動作モデルが根本的に違うので、どちらを使うかでシステムの設計・構成そのものが部分的にせよ変わる、ということはあり得ると思う(もっとも最近はRubyでもFiberが浸透してきてるのでNode.js的な非同期ベースなノリの設計ができるのかもしれないけど)。あるいは、マイクロサービスのコンポーネントをPHPで実装することもできはするだろうけど、現実的には多くの企業が、たぶんそのランタイムの利便性から、マイクロサービスの実装言語としてGolangとかを選択したりしているんじゃないだろうか。つまり、言語の選択というのは偶有的ではなく本質的なことに思える。</p>
<p>なにが偶有的作業で、なにが本質的作業なのか。よくわからないなりに云々うなりながら考えていたら、ひとつ使えそうな切り口を思い付いた。対象のプロダクトによって変わる作業、domain specificな作業というのが本質的で、開発対象によらず一定の作業、general purposeな作業というのが偶有的。こう考えるとうまく分類できるのではないか。この切り口で考えると、開発ツールが扱う問題は偶有的な問題ということになる。つまり、プログラミング言語、テキストエディタ、LSP、Coreutilsのような汎用性のあるユーティリティーなどは、すべて偶有的な解決策。ブルックスが「高水準言語」「タイムシェアリング」「UNIX」を挙げたのも当てはまる。逆に本質的な解決策というのは、特定の問題にしか役立たないもの。たとえば、音声を認識してテキストを抽出するという問題とか、インターネット上で人々が議論できる空間を実現するという問題とか、家計を管理するという問題とか。開発者ではなくエンドユーザーに価値を提供する解決策とも言ってもいいかもしれない。つまり、開発ツールがいくら進化しても、エンドユーザーに直接価値を届ける上で汎用的な解決策は存在しないよってこと? お、なんかそれっぽいかも? でもブルックスの言う本質的作業と偶有的作業からはすこし離れてしまったような気もする。やっぱりよくわからない。</p>
<p>それから、本質的攻略と新技術の違い。そして、生産性というのが何を意味しているのか。このあたりもよくわからない。ブルックスがIBMで開発をしていた時代から、いまに至るまで、新技術が途切れることはなかっただろうし、ソフトウェア領域での新技術が現れないなどとは考えていなかっただろうから、きっと新技術とブルックスの言う本質的攻略は別ものなんだろう。新技術は、これまでできなかったことをできるようにしたりするから、生産性やら信頼性やら容易性やらを飛躍的に向上させると思う。たとえば、OpenCVというライブラリが現れたことで手軽に画像認識アプリケーションを開発できるようになったし、Unityのような汎用ゲームエンジンの登場で3Dゲームの開発の生産性が飛躍的上がったり、Google Mapsによって地図ベースのアプリケーションが開発できるようになったりした。ブルックスが予言した時代の範疇で言えば、デスクトップとGUIライブラリによってユーザーにとっての使い勝手が飛躍的によくなったり。つまり、新しい技術で新しい価値が提供できるようになることは、飛躍的な生産性の向上とは言わない? 「生産」とはいったい。しかしまあ、こういった新技術がどの時代でもどんどん出てくるのはあたり前なので、そこから考えると本質的な攻略は、個別の特定分野を対象とした新技術ではなく、広範囲に適用できる技術でなければならないんだろう、きっと。</p>
<p>と、ここまで考えてきて、ブルックスが人月の神話の95年版を書いた後に、本質的な解決、銀の弾丸に限りなく近いものが登場したことに思い当たる。それは、インターネットの普及、そしてそれに付随して登場した、オープンソース、パッケージマネージャ、クラウドサービスあたりの台頭だ。単一ではなく複合的だという理由でつっぱねられる可能性はなきにしもあらずだけど、これらの組み合わせが、現在我々が生産できるソフトウェアのレベルを飛躍的に引き上げているのは事実だろう。実際、ブルックス自身、本質への有望な攻略のひとつとしてソフトウェア製品を挙げていて、これはその延長線上にあるものだと思う。</p>
<p>まあしかし、けっきょく銀の弾丸ってただの未来予測で、それが当たってたからと言って、だからなんなのという話ではある。本質と偶有に分けて考えることが、我々一介のプログラマになにか有用な示唆を与えてくれる気もあまりしないし。けっきょく我々がやることは、いま使える技術でシステムを作るだけなんだから、銀の弾丸はないと宣言されたところで、はいそうですかって話でしかないんだよな。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-3">自分の経験と照らし合わせての感想</a></h2>
<p>自分のプログラマとしての経験照らし合わせて、人月の神話の各トピックごとに語ってみる(とくに言いたいことがないトピックは省略)。</p>
<div class="section" id="section-5">
<h3><a class="toc-backref" href="#toc-entry-4">タールの沼</a></h3>
<p>システムの開発コストは、コンポーネントの足し算ではない…と言われても、そんなでかいシステム関わったことないから、よくわからない。システムのでかさのせいでコストが異常に増大してるパターン…うーん、あったかな。多くの場合、各コンポーネントができて、それを組み合わせれば、わりと素直に動いてた気がする。もちろん組み合わせる時に多少の想定外とかはあるけど、ちゃちゃっと修正して、問題洗い出すだけでふつうに対応できてた。システムの規模がさらに大きくなって何階層にもなってたら、組み合わせた時の問題が大きくなるというのは、なんとなくなら想像できる。
ただ、そんなに巨大なシステム(数十人から数百人のプログラマがかかわるシステム)ってなんなんだろうというのは、経験してこなかったし、たぶんこれからも経験しないで終わるのかもしれない。</p>
<p>そもそも現在においては、巨大なシステムを部分ごと分けて作って、それから一気にくっつけてテストするという作り方をすることは、たぶんあまり普通じゃないし、それをやる必要もない。現代では、最初から動くものを作って統合した状態で少しずつ育てていくというのがあたりまえなはず。</p>
<p>ブルックス本の時代と現代における大きな違いのひとつは「出荷」というものに対するスタンスの違いだと思う。ブルックスの作っていたのは、ハードウェア専用に作られた、ハードウェアと一緒に出荷することが前提のOSとかコンパイラその他といったものだ。ハードウェアとソフトウェア込みでのシステム一式として納品する先とかも決まったりしていて、スケジュールを通りに出荷することがものすごく大事だったんじゃないだろうか。</p>
<p>ぼくが普段扱っているような、すでに稼動しているウェブシステムだと、納期という概念があまりない。もちろん、いついつまでにこれこれをリリースするみたいなことをセールスに約束してたり、新規プロダクト立ち上げのときにビジネス状の都合で奮闘するといったこともまったくないことはないけど、基本的には動いているシステムを少しずつ改善していくという作業が主になる。すると、 <a class="reference external" href="https://messagepassing.github.io/018-deadline/">見積もりというものの重要性が相対的に薄れてくる。</a></p>
<p>ただ、一方で確かにタールの沼に足を取られるような経験をしたことも何度かはある。そのようなプロジェクトでは、ビジネス上の希望と、おそらく未熟な見積もりスキルの両方から決められたデッドラインがあり、チームは寄せ集めで未成熟、アーキテクトの不在、単純な技術力不足といったいくつもの要因が重なっていたように思う。単一の要因が欠けていただけで派手に転んだプロジェクトというのは見たことがない。どんなプロジェクトでもなにかしら足りないものというのはあるものだけど、ひとつやふたつなら、みんなのアイデアとか誰かのがんばりでどうにかカバーできる。ただ、破綻したプロジェクトでは、それらの数が多すぎたように思う。ひとつやふたつの問題を工夫やがんばりで解消したところで焼石に水。つまり、後から呼ばれた外野から見ると、それらは最初から成功する見込みがまったくなかったように見える。</p>
<p>にも関わらず、なぜそれらのプロジェクトは走り出してしまったのか。走り出す時点で、とても成功する見込みのないひどい状態にあることに気づけなかったのか。あるいは気づいていたのに止まることができなかった?これらプロジェクトの失敗理由をブルックスの理論で説明できるだろうか。ちょっと試してみよう。(ここでは、失敗=出荷までこぎつけられなかったプロジェクトという意味で使っています)</p>
</div>
<div class="section" id="section-6">
<h3><a class="toc-backref" href="#toc-entry-5">人月の神話</a></h3>
<p>まず、スケジュールを立てることの難しさを述べている「人月の神話」の話はたしかに当てはまる。事実として、それらの失敗プロジェクトにおいて、開始時に立てたスケジュールは、完全に間違っていた。ただ、付け加えるならスケジュールにさえ十分な余裕があれば問題なく完成していたかと言われると、ちょっと疑わしい。そもそもコミュニケーションはうまくいっておらず、コードと製品の品質は著しく低かった。もちろんタイトなスケジュールのために品質が下がったということもあるだろうけど、それ以前の根本的な問題があった気がしてならない。</p>
</div>
<div class="section" id="section-7">
<h3><a class="toc-backref" href="#toc-entry-6">貴族政治、民主政治、そしてシステムデザイン</a></h3>
<blockquote>
「コンセプトの完全性こそ、システムデザインにおいてもっとも重要な考慮点である」</blockquote>
<p>この主張は、プロジェクトの失敗についてなにか重要なことを言っている気がする。失敗したプロジェクトのいくつかでは、たしかにコンセプトの完全性がなかった。それは既存のなにかの焼き直しであり、継ぎはぎ細工であった。そういえば、すでに完成されたものの焼き直しで、ソースコードも流用できるから簡単だろうと下方に難易度が見積もられていたことも共通している。</p>
<blockquote>
「コンセプトの完全性を得るには、デザインが一人ないしは互いに意見が同じ少人数の頭脳グループで考え出されなければならない」</blockquote>
<p>これが実践できていれば、そして、その1人ないし少人数が十分に経験を詰んだプログラマであれば、プロジェクト成功の確率はかなり上がるんじゃないだろうか。継ぎはぎで成長して一貫性もくそもないシステムを1から刷新しようとしたことが、そもそも間違いだったようにも思える。それをわかっていれば、これは難易度がものすごく高いプロジェクトであることを着手前に認識できた...のかもしれない。</p>
</div>
<div class="section" id="section-8">
<h3><a class="toc-backref" href="#toc-entry-7">セカンドシステム症候群</a></h3>
<p>さきほど言ったように、いくつかのプロジェクトは、既存システムの焼き直しだった。これらは「セカンドシステム症候群」であった可能性がある。というのも、どちらも、既存システムにある機能すべてを持つことを最初から前提としてしまったからだ。最初に核となる最小の動くシステムとして作成し、そこからイテレーションを経て育てていくというのが現代のソフトウェア開発における成功パターンだと思う。ところが、失敗プロジェクトでは、既存システムで実現している盛りだくさんの機能をすべて実現することを前提としてしまった。それらは一度は実現した、はっきりと形の決まっている機能であるため、再構築は容易であると見立てた。そして短いスケジュールを組んだ。ただし、再構築を行うのはオリジナルのチームとはまったく無関係どころか、別の会社だった。1度目のシステム構築を行った開発者との連絡はまともに取れず、心もとないドキュメントとソースコードだけも頼りに再実装を行う。最初から多数の機能を急いで実現しようとしているため、一個一個のコンポーネントの仕上がりもろくに検証されず、線表にしたがって最低限の動作確認だけして表面上動いているように見えたら、できていることにされて、生煮えのまま次の機能に取りかかる。もちろんろくに検証されていないので実際には一個一個の機能やコンポーネントが多数の不具合を抱えている。隠れた、あるいは見なかったことにされた不具合はどんどん蓄積していきやがて...。焼き直しだから、既存のコードがあるから簡単にできる。これらの言葉が聞こえてきたら、警戒した方がいいかもしれない。きっと、2度目だろうが何度目だろうが、システムの作り方は変えるべきじゃないのだ。いつも最小限だが完璧に動作するシステムからはじめて、成長させていくのが1番の正攻法なんだと思う。ブルックスの言うセカンドシステム症候群は、2度目のシステムで1度目以上に機能を盛り込んでしまうことへの警句だったけど、ぼくの経験からすると、1度目と同等を最初から目指している時点で危険なんだと思う。</p>
</div>
<div class="section" id="section-9">
<h3><a class="toc-backref" href="#toc-entry-8">命令を伝える</a></h3>
<p>この章は、いかにしてコンセプトの完全性を達成するかについて述べている。失敗プロジェクトでは、ドキュメントがないことはなかったが、とても十分なドキュメントとは言えなかった。そしてドキュメントを書いた人とのコミュニケーションも困難だった。さらに、既存システムのコードがドキュメントの代替になると考えていた節がある。システム構築においてコードはドキュメントの代替にはならないと思う。ひとつには、コードに設計が内包されているのは事実だけど、コードは設計を伝えるには非効率的なんだと思う。コードの質が低い場合にはとくにそうだ。それから、コードを読む能力というのも人によってまちまちなのだから、ある程度の人海戦術的な局面を想定するのであれば、コード=ドキュメントという考えはますます成り立たないだろう。まともなドキュメントなしに多人数で開発するのは、スケールしないし、精度も効率も悪すぎる。</p>
</div>
<div class="section" id="section-10">
<h3><a class="toc-backref" href="#toc-entry-9">バベルの塔は、なぜ失敗に終わったか</a></h3>
<p>コミュニケーションの問題は、間違いなくあった。失敗プロジェクトにおいても、ブルックスの推奨するような進捗や問題共有のための定例会議や、日常的なチャットベースでのコミュニケーションはあった。それにも関わらず十分なコミュニケーションが行われてはいなかったように思われる。一つには、開発は会社をまたがって行われ、発注と受注という主従関係で分断されていた。互いの組織内では活発なコミュニケーションが行われていたかもしれないが、組織を横断してのコミュニケーションは限定される。また、開発側は、問題を認識していてもそれをそのまま共有する動機はなくギリギリまで組織内部で対処しようとするため、別の組織からはなかなか中で起きている問題が見えてこない。ブルックスも指摘するように、組織におけるコミュニケーションの構造は、(組織の権限構造が木構造であるのとは裏腹に)ネットワーク構造なのだけど、このネットワークのノードを繋ぐ「点線」が十分でなかったとは言えるのかもしれない。</p>
<p>この事例だけ見ると、複数の会社が分担して開発することがそもそもよくないとも取られかねないけど、実際には、複数の外注会社が協力して開発して、それでもきちんとシステムを作り上げられたプロジェクトももちろんある。だから、これは唯一の原因ではなく、あくまでプロジェクトが抱えていたたくさんの問題のうちのひとつなんだろうと思う。体験的には、正常にまわっていたプロジェクトでは、組織間の壁が薄く、カジュアルにやりとりが行われていたような気がする。それから、アーキテクトがきちんと全体的なタスクの状況と問題を把握できていることも重要に思える。</p>
</div>
<div class="section" id="section-11">
<h3><a class="toc-backref" href="#toc-entry-10">一つは捨て石にするつもりで</a></h3>
<p>変化に備えることの重要性を述べている。失敗プロジェクトでは、実現すべき仕様は最初から変化していないため、この章の内容はあたらない。変化が要求されなくともプロジェクトは失敗する。</p>
</div>
<div class="section" id="section-12">
<h3><a class="toc-backref" href="#toc-entry-11">破局を生み出すこと</a></h3>
<p>この章は、まさになにがプロジェクトの破綻を生み出すのか、どう回避すべきなのかについて説明している。失敗プロジェクトを振り返るのになにか重要な知見を与えてくれるはずだ。ブルックスはマイルストーンを持つことが必要だと言っている。もちろん失敗プロジェクトにおいてもマイルストーンは存在した。だけど、それはブルックスの言う「ナイフの刃のような鋭さをもって定義」されてはいなかった。明確なマイルストーンとは、「コーティングは90%完了した」というような感覚的なものではなく、誰でも測定できる事実であるべきということだ。だから、それはチェックリストとして、例えば「ユーザーはウェブとスマホの両方から複数の商品を出品・取消できる」とか「何万件のデータに対して1秒以内に検索が完了する」というような形で定義されるものの羅列になるんだと思う。明確でないと、遅れに気づくこともできず、マイルストーンが意味をなさなくなってしまう。また、ブルックスはクリティカル法を用いてスケジュール管理することを推奨している。失敗プロジェクトには、「ナイフのような」マイルストーンも、クリティカルパス図も存在しなかった。それらが存在していれば、スケジュールの遅延はきっともっと早くにあぶり出されていた………ん?いやいやいやそんなものは無くてもスケジュールの遅れは明明白白だった。これらの管理ツールはきちんと使えれば有効なものではあるだろうが、それだけでプロジェクトの破綻を防げたとは到底思えない。</p>
</div>
</div>
<div class="section" id="section-13">
<h2><a class="toc-backref" href="#toc-entry-12">まとめ</a></h2>
<ul class="simple">
<li>ブルックスの本は話が古くてわかりづらいので、現代のプログラマにはもっと適した本がきっとある</li>
<li>システム開発の本質と偶有を区別しても、そんなにメリットなさそう</li>
<li>失敗プロジェクトは、複数の原因が重なって失敗した</li>
<li>セカンドシステムは実際危険</li>
<li>ブルックスの言っていることは、どれもたぶん大事なことだけど、失敗プロジェクトは、なにかもっとはじまる前の段階で、すでに失敗がはじまっていた気がしてならない</li>
</ul>
</div>
Automatic note taking on Otter.ai2022-01-09T00:00:00+09:002022-01-09T00:00:00+09:00tai2tag:blog.tai2.net,2022-01-09:/how-to-use-otter-en.html<p class="first last">Otter.ai is a note-taking service, which generates text of speaking conversations through your audio devices in real-time.There are some ways to record meetings on Otter.ai. Some give you the best quality, and others require no extra cost depending on your facilities. I suggest three ways in this article.</p>
<p>Otter.ai is a note-taking service, which generates text of speaking conversations through your audio devices in real-time.</p>
<p>It greatly helps English learners like me to understand what others speak. You can read recorded text back to check what you missed during meetings. Of course, it's valuable enough to only take notes automatically as meeting logs.</p>
<p>Zoom's <a class="reference external" href="https://support.zoom.us/hc/en-us/articles/207279736-Managing-closed-captioning-and-live-transcription">live transcription</a> gives you a similar benefit, but you have to ask admins to enable it. On the other hand, you can use Otter.ai by yourself.</p>
<p>You can combine it with video conferencing tools like Zoom, Huddle, etc. I select Zoom as an example because I have recently used it. However, using other apps is not so different from Zoom.</p>
<p>There are some ways to record meetings on Otter.ai. Some give you the best quality, and others require no extra cost depending on your facilities. I suggest three ways in this article:</p>
<ul class="simple">
<li>Use no extra tools or devices</li>
<li>Use Loopback and BlackHole on a single computer(macOS)</li>
<li>Use another computer</li>
</ul>
<div class="section" id="use-no-extra-tools-or-devices">
<h2>Use no extra tools or devices</h2>
<div class="figure">
<img alt="The simplest form to record meetings on Otter.ai" src="https://blog.tai2.net/images/how-to-use-otter/simplest-form.jpg" />
<p class="caption">The simplest form to record meetings on Otter.ai</p>
</div>
<p>This is the fair simplest and maybe somewhat capable aproach than you think.</p>
<p>You use your standard microphone and speakers.
The speaking voices sounded from your speaker, including yours and other ones, come into your microphone, then they are put into Otter.ai. You don't use headphones to pick up all attendees' speaking.</p>
<div class="figure">
<img alt="Just use your usual microphone and speakers." src="https://blog.tai2.net/images/how-to-use-otter/speakers-and-mic-on-zoom.png" />
<p class="caption">Just use your usual mic and speakers. I use the mic of my USB cam due to its quality.</p>
</div>
<div class="figure">
<img alt="Set the same microphone to the system default" src="https://blog.tai2.net/images/how-to-use-otter/mic-for-browsers.png" />
<p class="caption">Set the same microphone to the system default so your browser can use it.</p>
</div>
<p>This method often works well, maybe because of <a class="reference external" href="https://support.zoom.us/hc/en-us/articles/115003279466-Using-and-preserving-original-sound-in-a-meeting">the echo cancellation</a> of Zoom. Still, sometimes you are possibly bothered with echo.</p>
<p>If you would like to completely get rid of echo issues, you can use the other ways below.</p>
</div>
<div class="section" id="use-loopback-and-blackhole-on-a-single-computer-macos">
<h2>Use Loopback and BlackHole on a single computer(macOS)</h2>
<div class="figure">
<img alt="Internal routing using Loopback and BlackHole to record on Otter.ai" src="https://blog.tai2.net/images/how-to-use-otter/loopback-and-blackhole.jpg" />
<p class="caption">Internal routing using Loopback and BlackHole to record on Otter.ai</p>
</div>
<p>You can route and mix any of your audio output streams into an audio input stream with the combination of Loopback and BlackHole. These two apps are provided only on macOS. You have to find similar software if you use other operating systems.</p>
<p><a class="reference external" href="https://github.com/ExistentialAudio/BlackHole">BlackHole</a> is a virtual audio device that converts audio output into audio input. BlackHole redirect sounds an app plays on BlackHole to another app using BlackHole as input.</p>
<div class="figure">
<img alt="Set BlackHole to the system default so your browser can process all required audio." src="https://blog.tai2.net/images/how-to-use-otter/blackhole-for-input.png" />
<p class="caption">Set BlackHole to the system default so your browser can process all required audio.</p>
</div>
<p><a class="reference external" href="https://rogueamoeba.com/loopback/">Loopback</a> is an audio routing software that can take multiple audio output streams, mix them, and split them into multiple output streams. It works as a virtual audio device as well as BlackHole.</p>
<div class="figure">
<img alt="Send your mic and Zoom output to BlackHole and monitor only Zoom with your headphones." src="https://blog.tai2.net/images/how-to-use-otter/loopback-config.png" />
<p class="caption">Send your mic and Zoom output to BlackHole and monitor only Zoom with your headphones.</p>
</div>
<p>Your voice flows to both Zoom and Loopback. Then the latter one is mixed with the output of Zoom on Loopback and ends up flowing to Otter.ai through BlackHole. The output of Zoom is also routed to your headphone for monitoring.</p>
<div class="figure">
<img alt="Send the output of Zoom to Loopback. Your microphone is directly routed to Zoom." src="https://blog.tai2.net/images/how-to-use-otter/specify-loopback-on-zoom.png" />
<p class="caption">Send the output of Zoom to Loopback. Your microphone is directly routed to Zoom.</p>
</div>
<p>You can accordingly record your voice from your mic and others' voices from Zoom on Otter.ai without bothering about echo. Otter.ai converts those sounds into text.</p>
<p>This is the way I currently use to take meeting notes.</p>
</div>
<div class="section" id="use-another-computer-and-blackhole">
<h2>Use another computer and BlackHole</h2>
<div class="figure">
<img alt="Use another computer to record meetings on Otter.ai" src="https://blog.tai2.net/images/how-to-use-otter/multiple-computers.jpg" />
<p class="caption">Use another computer to record meetings on Otter.ai</p>
</div>
<p>Using two computers is an option if you have another computer and can use another account on remote meeting services. You join a meeting with one computer while taking a meeting note on Otter.ai with another computer.</p>
<p>It simply and perfectly works regardless of whether recording your voice or not. All you need are to send the output of Zoom to Otter.ai through BlackHole and hear sound with headphones to avoid echo. A pitfall of this is it requires extra physical space on your desk.</p>
<p>I haven't tried this method myself, but I believe it would work.</p>
</div>
長年やってた自営業をやめてAutifyに転職した2021-09-19T00:00:00+09:002021-09-19T00:00:00+09:00tai2tag:blog.tai2.net,2021-09-19:/employee-programmer.html<p class="first last">フリーランスプログラマ雑感では、フリーランスプログラマの実態に興味がある会社員に向けて書いたけど、今回は逆に、会社員をやったことがないとか、ぼくのように会社員だったのが昔すぎて、どんなものだったのか忘れてしまったという方に向けて、会社員とはどういうものか説明する。</p>
<p>ひょんなことから、長年やっていたフリーランスプログラマをやめて、 <a class="reference external" href="https://autify.com/">Autify</a> というE2Eテスト自動化サービスを運営している会社の社員になり、一年が経った。</p>
<p><a class="reference external" href="https://blog.tai2.net/freelance-programmer.html">フリーランスプログラマ雑感</a> では、フリーランスプログラマ生活を振り返って、フリーランスプログラマの実態に興味がある会社員に向けて書いたけど、今回は逆に、会社員をやったことがないとか、ぼくのように、会社員だったのが昔すぎてどんなものだったのか忘れてしまったという方に向けて、会社員プログラマとはどういうものか説明する。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#autify-2" id="toc-entry-1">Autify入社のきっかけ・動機</a></li>
<li><a class="reference internal" href="#section-1" id="toc-entry-2">自営業→会社員</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-3">受託→自社事業</a></li>
<li><a class="reference internal" href="#autify-3" id="toc-entry-4">Autifyはどんな会社か</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-5">英語</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-6">技術周り</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-7">まとめ</a></li>
</ul>
</div>
<div class="section" id="autify-2">
<h2><a class="toc-backref" href="#toc-entry-1">Autify入社のきっかけ・動機</a></h2>
<p>Autifyの開発には、正式リリース前から業務委託のパートナーとして携わっていた。で、しばらく働くうちに社員になりませんかという誘いを受けた。</p>
<p>その時点で、Autifyは事務所も引き払って全社員フルリモート体制に移行していた。保育園の子供2人いるのもあって、社員になったからといって働き方を変えることはできないけど大丈夫か確認したら、成果さえ出してくれればなんでもいいとのことだった。</p>
<p>以前の記事で書いたように、フリーランス自体、なんとなく流れでなったのを惰性で続けていただけで、とくにフリーランスでなければならないという強いこだわりもなかったので、まあやってみるかということで軽率に入社した。</p>
</div>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-2">自営業→会社員</a></h2>
<p>自営業から会社員になったことで、実感する変化がいくつかあった。</p>
<p>業務委託として手伝う中で、slackに入って#engineeringチャンネルとかも見ていたし、外部の人間なりに、ちょっとしたチームの一員みたいな気分でいた。だけど中に入ってみて、わかった。外の立場でいたときには、整理された後の情報だけを受け取っていたということが。外と内だとだいぶ見えかたが違う。打ち合わせも、まったくないわけではないにしろ、ほんとうに必要最低限、結論が出た後の情報伝達だけで、フリーランスのときは、なんの割り込みもストレスもなく、ただひたすら集中して実装するという作業だけをやっていた。</p>
<p>社員になると、ほぼ毎日なんらかの打ち合わせがある。いまではだいぶ慣れたけど、入社した当時は、こんなに打ち合わせするんだと驚いていた(それでもぼくは打ち合わせ少ないほうだと思うけど)。</p>
<p>それから、半年毎にコミットメントシートと言って個人の目標を決めなければいけないし、360度評価というのもあって、自分以外の社員への評価も要求される。ぼくはシニアエンジニアとして採用プロセスにも関わっているので、入社希望者のコードの評価や面談もする。顧客から問題が上がってくれば、問題の重要度によっては、機能開発を中断して原因調査と修正をする。スプリント毎に取り組む課題を決めて、スプリントの終わりには振り返る。チーム外にチームとしての成果をどう見せるか考える。会社員ってこんなにいろんなことしてたんだっていうのは、ほんとにおどろいた。そうか、これが組織の一員になるということなのか。</p>
<p>一方で、なにもしてないのに給料が(かなり大幅に)上がったのにもおどろいた。いや、なにもしていないというと語弊があるんだけど、自営業の場合、自分から何らかのアクションを起こさない限り収入が増えることはけっしてない。正直、べつに給料上がらなくても、下がりさえしなければいいやくらいの気持ちでいたので、上げてと言ったわけでもないのに、なぜか急にもらえるお金が増えたというのは、すごく新鮮だった。</p>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-3">受託→自社事業</a></h2>
<p>ぼくは、これまでのプログラマとしてのキャリアを通してずっと、受託開発をやってきた。フリーになる前は会社員だったけど、その会社も受託開発の会社だった。だから、自社サービス一本でちゃんとまわしている会社というのは初めてだ。</p>
<p>受託開発は、一括請負と準委任で大きく分けられる。準委任の場合との比較で言えば、正直、プログラマとしてだとそこまで違いはない感じがしている。作るものは決まっているし、どういう優先順位でこなしていくかもプロダクトオーナーを中心にチームで決めている。</p>
<p>一括請負の場合は納期と仕様がわりとかっちり決まってることが多いので、だいぶ違う。受託開発は短距離走もあるけど、自社サービスの開発だと常に長距離走になる。</p>
<p>とくにAutifyについて言えば、業務委託の立場でも、中に入って自社事業としてやっていても、ほとんど同じ意識で開発してて、劇的な変化みたいなのは感じてない。ひたすら実装したり不具合を修正したりする。</p>
<p>ただ、踏み込もうと思えばいくらでも踏み込める立場にはなった。あと、わかりづらい問題の原因調査みたいなのは、フリーランスの受託の立場だとあまりまわってこないのかもしれない。やることが曖昧気味で、必要期間も読みづらく、タスクとして切り出しづらいなどの理由があると思う。結果的に、より広範囲の仕事をカバーするようにはなった。</p>
</div>
<div class="section" id="autify-3">
<h2><a class="toc-backref" href="#toc-entry-4">Autifyはどんな会社か</a></h2>
<p>2021年9月現在、組織として28人。うちエンジニアリングチームが12人。</p>
<p>社名と同名のE2Eテスト自動化サービスでグローバル市場を取ることを目指している。なので、公用語英語。エンジニアチームに関しては4割が非日本語話者。海外の顧客も徐々に獲得しつつある。</p>
<p>フロントエンドエンジニアとして入社したけど、とくにフロントエンドに限ることなく、アプリケーションプログラマとして幅広く開発できている。業務委託のころも含めて、長らくAutify for WebというWebサービス用のテストシステムを開発してきたけど、ここ半年くらいはチームを移って、モバイルアプリをテストするための <a class="reference external" href="https://autify.com/mobile">Autify for Mobile</a> の立ち上げをやっている。</p>
<p>経歴的に他の会社のことはよくわからないので比較はできないけど、エンジニアリングチームは、ふつうのWebエンジニアが知恵を出しあって日々開発している。GitHub上で開発して、CIがあって、AWS上で動いてて、RailsとReactで書かれてて、毎週イテレーション回して、KPTやって...。そんな感じで、ふつうのプラクティスをふつうにまわしている。</p>
<p><a class="reference external" href="https://autify.com/ja/careers">AutifyのValue</a> は、以下の三つ:</p>
<ul class="simple">
<li>Solve burning needs</li>
<li>Aim high, stay grounded</li>
<li>Ownership & collaboration</li>
</ul>
<p>とくに、CEO近澤さんが国内に広めたと言っても過言ではない <a class="reference external" href="https://chikathreesix.com/burning-needs">Burning needs</a> は、社内に浸透していると思う。次に取る選択肢が本質的な問題解決になるか、不要なこと・後回しにしても問題ないことに時間を割こうとしていないか、失敗の中に次の行動に活かせる点が含まれていないか。とくに意思決定層は、こういった思考を元に日々行動を選択しているのを感じる。</p>
<p>あと、Autifyは、CSチームがめちゃくちゃ強い会社な気がする。一時期、テクニカルサポーエンジニアの助っ人みたいなこともやってたのだけど、顧客の問題解決に真摯に向き合って取り組んでる姿が、まさにAutifyの顔って感じですごいなあと思った。同時に、いかに効率的に対応していくかみたいなとこも、いろいろ工夫してがんばってるように見えて、そこもすごい。</p>
<p>Autifyは、子育てしている人の率がわりと高くて、CEO、CTO含め、自分と似たような年齢の子供を育てている人が多い。だから、子供が熱を出して仕事ができなかったり、場合によっては子供の面倒を見ながら仕事をするといった状態も、当たり前のように理解してもらえる。驚くべきことに、子育て中の人は必要な分だけ育児に充てていいという制度まである。それから、通常の有給に加えて、子の看護休暇というのも5日間付与されている。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Autifyの就業規則改訂。子育てしやすすぎ(最高)なのでは。 <a href="https://t.co/jFLcbHSgRM">pic.twitter.com/jFLcbHSgRM</a></p>— 🔫武藤スナイパーカスタム (@__tai2__) <a href="https://twitter.com/__tai2__/status/1308649436529131521?ref_src=twsrc%5Etfw">September 23, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>制度面で言うと、エンジニア以外も含めて全社員が実質的に成果ベースで働けるように、裁量労働制ではなく、フレックスタイム制を採用している。ここらへんは、なんか <a class="reference external" href="https://logmi.jp/tech/articles/193745">日本の労働制度とかが関係していてめんどくさいらしい</a> んだけど、Autiyでは、全社員について、何時から何時まで、一日何時間働かなきゃいけないという縛りはない。</p>
<p>それから、フルリモートなので東京付近に住まなければいけないという決まりもなく、実際に、国外も含めいろんなところから働いている人がいる。</p>
<p>他の転職エントリ:</p>
<ul class="simple">
<li><a class="reference external" href="https://blog.a-know.me/entry/2021/03/15/214630">E2Eテスト自動化のAutify(オーティファイ)に入社しました & ご挨拶も兼ねて OSS を書きました</a></li>
<li><a class="reference external" href="https://note.com/hiroxyy/n/n905e8ae4bbd5">オーティファイに入社して驚いたこと</a></li>
</ul>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-5">英語</a></h2>
<p>前述のように公用語英語なので、すくなからず英語ができないといけない。これは、英語ができなければ世界は取れないというCEOの考えからそうなっている。</p>
<p>ぼくは、入社前、英会話がまったくできなかった。どのくらいできないかというと、英語でリクルーティングの電話が突然かかってきたときに(なぜかときどきかかってくる)、まず「やっべー英語だよどーしよ」となる。で、相手がしゃべり終わると、"I don't speak English"と言って即会話終了。街で外国人旅行者に電車の行き先を聞かれたときとかも、同様に断って、そのたびもうちょっと英語やらないとなーとか思ってた。そういうレベル。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">なぞの電話突然かかってきて、「hi, ムトーサン、ごにょごにょ(とてもゆっくりな英語)」とか言われて、困惑してたら、<br><br>Do you speak English?<br><br>A little<br><br>あー、英語話せませんか?<br><br>英語話せません<br><br>Okay, thank you(ガチャ<br><br>みたいな感じになって、なぜか圧倒的屈辱感だけが残ったんだが?</p>— 🔫武藤スナイパーカスタム (@__tai2__) <a href="https://twitter.com/__tai2__/status/1209661672199077888?ref_src=twsrc%5Etfw">December 25, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>業務委託のときは、基本的に日本人の特定の社員としかやりとりしていなかったので、英会話能力は不要だった。が、社員になってチームに入るなら、英語は避けられないと思った。</p>
<p>ということで、入社1ヶ月前くらいから、付け焼き刃でDMM英会話をはじめてみたりした。</p>
<p>Autifyでは、入社すると英会話能力のテストを受ける。その結果に応じて、会社から英語勉強の支援を受けられる。具体的には、週一回30分の英語セッションで、英語の疑問点について質問したり、直すべき点の指摘を受けたりする。それから、Slackに#english-learningっていうチャンネルがあって、ネイティブの講師が英語の疑問に答えてくれる。</p>
<p>それに加えて、年間6万円まで英語学習に使える予算が出る(これはレベルに依る)。ぼくは、いま現在は、 <a class="reference external" href="https://brighture.jp/japan/">My Brighture</a> っていうサービスを使っている。</p>
<p>そんな甲斐もあって、いまではそこまで英会話に怖気付くこともなくなった。たぶん街で道を聴かれたら教えてあげられるし、海外旅行にいってもどうにかコミュニケーションを取れるような気はしている。入社時に受けたテストではCEFR A2 mid判定だったけど、いまではCEFR B1 midまで伸びた。</p>
<p>とは言え、やっぱり同僚の言ってることは半分以上わからない。ぼくの場合、読み書きはそんなに苦がないんだけど、リスニングがとにかく弱く、ネイティブにふつうのスピードでしゃべられると、ほんとに聴き取れない。</p>
<p>なお、仕事では基本的にSlackやGitHubのテキストベースでのコミュニケーションだから、いまのところそこまで支障はない。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-6">技術周り</a></h2>
<p>技術的におもしろいところはいろいろある。</p>
<p>E2Eという性質上、実際のブラウザの上で顧客サービスを走らせるのが前提になる。クラウド上でクロスブラウザでテストスクリプトを走らせるワーカーと呼ばれる仕組みとか、簡単にテストシナリオを作成できるようにするためのレコーダーと呼ばれるブラウザ拡張とか。Autify for Webの開発をやってると必然的にブラウザのAPIとか仕様とかに詳しくなってくる。</p>
<p>Autify for Mobileは、ブラウザのマニアックな知識とかは要らないけど、今度はモバイルアプリの挙動とかAppiumというモバイルテスト用のプロトコルについても知らなくちゃいけないし、VM上で動作するモバイルアプリとブラウザごしにリアルタイムで対話するためのUIの実現とか、こっちはこっちで別のおもしろさがある。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-7">まとめ</a></h2>
<p>この記事では、フリーランスプログラマをやめて、Autifyの会社員になって感じたことをまとめた。
一開発者としての意識はそれほど変わらないものの、開発以外のやることの多さや、勝手に給料が上がることの驚き、Autifyという会社に持っている印象、入社して英語力が上がったことや、技術的なおもしろさなどにふれた。</p>
</div>
Benefits That Help Attract And Retain Top Talent2021-05-15T00:00:00+09:002021-05-15T00:00:00+09:00Artur Meystertag:blog.tai2.net,2021-05-15:/benefits-that-help-attract-and-retain-top-talent-en.html<p class="first last">The recruitment market is more competitive each year, so offering the right benefits will increase the company’s chances to attract top talent. Gone are the days where employees were happy with a good salary and decent health insurance; now they expect more after going through university or years of experience.</p>
<p>The recruitment market is more competitive each year, so offering the right benefits will increase the company’s chances to attract top talent. Gone are the days where employees were happy with a good salary and decent health insurance; now they expect more after going through university or years of experience.</p>
<p>It also depends on the industry. The tech industry has some of the most competitive and creative benefits of all; something like pet-friendly offices is common in tech companies. But these are some benefits that are more common and will help you attract and retain top talent.</p>
<div class="section" id="flexible-culture">
<h2>Flexible Culture</h2>
<p>One of the main benefits a potential employee looks for is a company with a flexible culture, according to an <a class="reference external" href="https://www.businessnewsdaily.com/10108-employee-flexibility-recruiting.html">article</a> on Business News Daily. This means anything from flexible working hours to flexible dress code. The days where everyone went to the office in suits are in the past. This is still true in some industries, but each year it becomes less frequent.</p>
<p>Also, employees now want to be more autonomous, according to an <a class="reference external" href="https://www.entrepreneur.com/article/254030">article</a> published by Entrepreneur Asia Pacific. They want to be able to work at the hours they are most productive or have the chance to decide to maybe work harder four days a week and have Friday off. So try to offer flexible culture as much as you can if you want to attract and retain top young talent.</p>
</div>
<div class="section" id="remote-opportunities">
<h2>Remote Opportunities</h2>
<p>People choose to work remotely for many reasons. Maybe they want to spend more time with their families, want the comfort of working from their own home, or just want the freedom of working from anywhere in the world. Either way, remote work is becoming more common each year; more so now with the Covid-19 pandemic.</p>
<p>Make sure you offer remote working opportunities to your employees. As suggested by Business News Daily, it doesn’t have to be full-time remote, but maybe once a week or once a month. Plus, remote work brings a lot of benefits like lower overall costs.</p>
<p>It also depends on your profession. A doctor can hardly operate a patient remotely, or at least we are not there yet. But a <a class="reference external" href="https://careerkarma.com/careers/web-development/">Web Developer</a> can work remotely efficiently. So, you can have part of the team remote and the rest at the office, depending on your needs.</p>
</div>
<div class="section" id="family-benefits">
<h2>Family Benefits</h2>
<p>Mindsets are changing with the new generations. Now, it is more common for people to prioritize their mental health and family life over work, as suggested in <a class="reference external" href="https://sprigghr.com/blog/performance-culture/the-importance-of-work-life-balance/">this article</a> published by Sprigghr. Many companies offer benefits like four to 12 months of family leave for new parents, even for adoption and foster care.</p>
<p>Other less common benefits are egg freezing assistance and reimbursements for fertility treatments. Either way, something like offering health insurance for all direct family members can make a huge difference for someone to stay at a company.</p>
</div>
<div class="section" id="student-loan-assistance">
<h2>Student Loan Assistance</h2>
<p>Most Americans who reach professional levels do it with student loan debt. Some of them reach middle age still in debt, according to an <a class="reference external" href="https://www.cnbc.com/2021/04/06/student-loans-affected-older-millennials-homes-families-careers.html">article</a> on CNBC. So, another benefit that has become really attractive for top workers is student loan assistance. It varies from yearly bonuses to help pay the debt to monthly allowances. This will not only attract employees, but it will give the company tax breaks.</p>
</div>
<div class="section" id="career-development">
<h2>Career Development</h2>
<p>Another benefit that everyone looks for is career development opportunities. People generally want to grow in their careers, but studies and specializations are expensive, so having an employer that will pay for it is a huge draw. You can offer to pay for courses to further employees’ skills, and they don’t have to be super expensive.</p>
<p>Many <a class="reference external" href="https://onlinedegreehero.com/">online courses</a> are quite affordable and will give you great results. The company can also offer training programs and platforms made by its employees. And, at last, they can create mentorship programs where new employees work with more experienced ones.</p>
</div>
<div class="section" id="pto">
<h2>PTO</h2>
<p>PTO stands for “paid time off.” Generous vacations are the <a class="reference external" href="https://www.shrm.org/resourcesandtools/hr-topics/employee-relations/pages/workers-taking-more-vacation-.aspx">second most important benefit</a> for employees. Some companies are starting even to get unlimited PTO, according to Business News Daily. You don’t have to offer this same benefit, but you should offer generous PTO to attract top talent.</p>
<p>Workers that don’t take vacations regularly can suffer from burn-out, which brings poor performance to the company. Some companies even believe that holidays should be mandatory as shown in <a class="reference external" href="https://www.forbes.com/sites/amberjohnson-jimludema/2018/06/05/three-reasons-your-company-should-make-vacation-mandatory/?sh=4938509b38ec">this article</a> by Forbes; employees will come back rested and refreshed.</p>
</div>
<div class="section" id="in-summary">
<h2>In Summary</h2>
<p>The recruitment industry is a process of trial and error, but there are some things you can do from the start to be successful. By offering benefits that you know will attract the right candidates, you are starting off on the right foot.</p>
<p>Employees nowadays look for companies that offer flexible schedules, remote working options, family benefits, career development, and generous PTO. Try to add some of these to your own team and your employees will be happier.</p>
</div>
ikinari-modules: package.json不要でunpkg等からimportしてバンドルできるCLI2020-12-20T00:00:00+09:002020-12-20T00:00:00+09:00tai2tag:blog.tai2.net,2020-12-20:/ikinari-modules.html<p>この記事は <a class="reference external" href="https://qiita.com/advent-calendar/2020/nodejs">Node.js Advent Calendar 2020</a> の20日目の記事です。</p>
<p>先日、</p>
<div class="highlight"><pre><span></span><span class="nb">echo</span><span class="w"> </span><span class="s2">"import stringLength from 'https://unpkg.com/string-length'; \</span>
<span class="s2">console.log(stringLength('🐴'))"</span><span class="w"> </span><span class="se">\</span>
<span class="p">|</span><span class="w"> </span>somethingUsefulCommand
</pre></div>
<p>みたいな感じで、 <a class="reference external" href="https://unpkg.com/">unpkg</a> からのstatic importを解決し …</p><p>この記事は <a class="reference external" href="https://qiita.com/advent-calendar/2020/nodejs">Node.js Advent Calendar 2020</a> の20日目の記事です。</p>
<p>先日、</p>
<div class="highlight"><pre><span></span><span class="nb">echo</span><span class="w"> </span><span class="s2">"import stringLength from 'https://unpkg.com/string-length'; \</span>
<span class="s2">console.log(stringLength('🐴'))"</span><span class="w"> </span><span class="se">\</span>
<span class="p">|</span><span class="w"> </span>somethingUsefulCommand
</pre></div>
<p>みたいな感じで、 <a class="reference external" href="https://unpkg.com/">unpkg</a> からのstatic importを解決してバンドル化した上で標準出力してくれるツールがあればいいのに、と思うことがあった。</p>
<p>調べたところ、そのような機能を持った既存コマンドは見つけられなかったのでこれを作成した。</p>
<p><a class="reference external" href="https://github.com/tai2/ikinari-modules">https://github.com/tai2/ikinari-modules</a></p>
<p>これを使えば、以下のようにpackage.jsonを作らずともバンドル化されたJSファイルを得られる</p>
<div class="highlight"><pre><span></span><span class="nb">echo</span><span class="w"> </span><span class="s2">"import stringLength from 'https://unpkg.com/string-length'; \</span>
<span class="s2">console.log(stringLength('🐴'))"</span><span class="w"> </span><span class="se">\</span>
<span class="p">|</span><span class="w"> </span>ikinari<span class="w"> </span>-i<span class="w"> </span>-
</pre></div>
<p>作るまでの過程で、非常にふわっとした理解しか持っていなかったES modulesについても調べて理解したので、そのあたりのことや、既存ツールの状況などについて、この記事でまとめる。</p>
<div class="section" id="section-1">
<h2>どのバンドラーをベースにするか</h2>
<p>既存でぴったりマッチするツールがなくても、webpackなどモジュールバンドラーのAPIを使うなりなんなりすれば、たぶんサクッと作れるだろうという直感はあった。ただ、URLを直接importするということが、最近のブラウザでできるのは知っていたものの、各種バンドラーがその機能を持っているのかはまったく知らなかったので調べた。</p>
<ul class="simple">
<li><a class="reference external" href="https://webpack.js.org/">webpack</a> : webpack 5で、 <a class="reference external" href="https://webpack.js.org/blog/2020-10-10-webpack-5-release/#uris">httpsからフェッチするプラグイン</a> が追加されたらしい 。ただし、現状まだexperimental。</li>
<li><a class="reference external" href="https://rollupjs.org/">rollup</a> : いくつかプラグインがあるが、 <a class="reference external" href="https://github.com/mjackson/rollup-plugin-url-resolve">rollup-plugin-url-resolve</a> を使えば動作することが確認できた。</li>
<li><a class="reference external" href="https://parceljs.org/">parcel</a> : コミュニティープラグインを含めて探したが、それらしいものは見つけられなかった。</li>
<li><a class="reference external" href="https://www.snowpack.dev/">snowpack</a> : 今回調べるまで知らなかったが、snowpack自体にはモジュールのバンドリング機能はないらしい。あくまで開発時のビルド体験高速化が目的のツール。なので対象外。</li>
</ul>
<p>今回、個人的な趣味で、入力ファイルは標準入力からも受け付けられるようにしたかった。webpack cliは標準入力を取れず、rollupのcliは取れる。なので、webpackにしようかrollupにしようかすこし迷ったものの、rollup cliをラップすることにした。</p>
</div>
<div class="section" id="skypackunpkg">
<h2>skypackとunpkg</h2>
<p>npmのモジュールを配布しているCDN自体、いくつもある。</p>
<p>rollup-plugin-url-resolveを動かして試した結果、skypackからのimportなら成功するが、unpkgからではできないものがあることがわかった。</p>
<p>skypackとunpkgには、前者がパッケージにある変換をかけて配布しているのに対して、unpkgはnpmに上がっているものをそのまま配布しているだけという違いがある。(実はunpkgにも?moduleをいうパラメータを付ければ変換済みの結果にアクセスできる機能があるが、不完全であり、一部のパッケージでしか動作しないことを確認した) skypackがなにをやっているかというと、まずCommonJSのファイルをES Moduleに変換している。それから、package.jsonを見て依存関係を解決した上でimport指定(specifier)を変換している。つまり、import指定に@v4.0.1みたいなバージョン指定を付与している。</p>
<p>だから、skypackならブラウザから直接importできる。</p>
<p>rollupでunpkgからのimportができないのは、skypackがやっている処理の一部分しかできないからだ。つまり、 <a class="reference external" href="https://github.com/rollup/plugins/tree/master/packages/commonjs/#readme">@rollup/plugin-commonjs</a> プラグインなどでCommonJS → ES変換はできるものの、rollup-plugin-url-resolveは単純なフェッチ機能のみで、依存関係解決機能がない。</p>
<p>とりあえず、skypack限定っていう形なら目標が実現できることは、この時点で確定した。実用的にはこれでも十分なんだけど、それだけだとつまらないので、もうちょっと深堀してunpkgからのimportをあらためてゴールに設定した。たぶん、rollup-plugin-url-resolveをちょこっと修正するなり、補完するプラグインを作るなりすればイケるだろう。</p>
</div>
<div class="section" id="section-2">
<h2>依存解決の問題</h2>
<p>そもそもブラウザでのimportとNodeでのimport、CommonJSのrequireとの間にはどのような違いがあるのか、なにが問題なのかを見定めるために、このあたりをきっちり理解しておく必要がある。</p>
<p>まず、ブラウザでのimportだけど、これは現状、与えられたURLを単純にそのままフェッチするだけで、依存関係の解決機能はブラウザには一切ない。これがNodeとの大きな違いだと思う。それから、ブラウザでは、bare specifierと呼ばれる指定ができない。これは、Nodeでいちばん普通のユースケースである、パッケージ名だけの指定。 <code>import 'string-length'</code> みたいなやつ。bare specifier自体は、 <a class="reference external" href="https://github.com/WICG/import-maps">import-maps</a> という機能 が実装されれば利用できるようになる模様。パッケージ名をどのように解決するかをJSONファイルで補う仕様のようだ。これができると、npmで依存を解決してnode_modulesにパッケージをまとめた上で、それをそのままブラウザから使うことができるようになる。つまり、ブラウザとnpmが直接コラボできるようになるということと理解した。</p>
<p>一方、Nodeでの依存解決はどうなっているのか。以前 <a class="reference external" href="https://blog.tai2.net/node-quiz-about-npm-install.html">別の記事</a> にまとめたけど、Nodeの依存解決というのは、実は2フェーズから成り立っている。npm installとrequire/importだ。npm installが、package.jsonを見て依存パッケージとバージョンを把握した上で、node_modulesフォルダを適切に構成する。そして、require/importが、その中から <a class="reference external" href="https://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders">規定されたアルゴリズム</a> に従って、実際のパッケージを検索する。この2つが合わさって、最終的に取り込むモジュールが確定される。</p>
<p>Nodeでのrequireとimportについては、細かい挙動の違いはあるものの、 <a class="reference external" href="https://nodejs.org/api/esm.html#esm_resolver_algorithm_specification">検索アルゴリズム</a> 自体は同じものだと思っている。けど違ったら教えてください。</p>
</div>
<div class="section" id="import">
<h2>リモートimportで依存解決を実装する</h2>
<p>上記で見たように、Nodeでは、import実行時に1から依存解決をするわけではない。npm installで、パッケージのフェッチを含む大半の依存解決が住んでいることが前提のアルゴリズムになっている。今回やりたいことは、パッケージがまったく手元にないことが前提になるので、通常のブラウザやNodeのimportとはまったく違うやりかたをしなければならない。</p>
<p>具体的には、importer(importを実行している元ファイル)がリモートURLであった場合には、自分自信も同一のリモートサイトに置かれていると見なして、まずpackage.jsonを読んだ上で実際の依存バージョンを特定し、その上でフェッチする。幸い、upnkgには、package.json準拠の <a class="reference external" href="https://docs.npmjs.com/about-semantic-versioning#using-semantic-versioning-to-specify-update-types-your-package-can-accept">バージョン表記</a> を理解した上で、適切なバージョンにリダイレクトしてくれたり、bear specifierから、インポートすべきモジュールにリダイレクトしてくれたりする機能はあるので、そのあたりはすこし楽をできる。</p>
<p>ということで、上記を実現するために不足している機能をrollup-plugin-url-resolveに追加した。</p>
<p><a class="reference external" href="https://github.com/mjackson/rollup-plugin-url-resolve/pull/9">https://github.com/mjackson/rollup-plugin-url-resolve/pull/9</a></p>
<p>これで、当初の目標が実現できた。</p>
</div>
<div class="section" id="section-3">
<h2>参考文献</h2>
<ul class="simple">
<li>JavaScript modules <a class="reference external" href="https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/">https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/</a> ES Modulesについて一歩踏み込んだ理解ができるので、おすすめ。</li>
<li>JavaScript modules <a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules</a></li>
<li>JavaScript modules <a class="reference external" href="https://v8.dev/features/modules">https://v8.dev/features/modules</a></li>
<li>Modules: ECMAScript modules <a class="reference external" href="https://nodejs.org/api/esm.html">https://nodejs.org/api/esm.html</a></li>
<li>WICG/import-maps <a class="reference external" href="https://github.com/WICG/import-maps">https://github.com/WICG/import-maps</a></li>
</ul>
</div>
コロナ時代、リモートワークがハイテク業界の給与に与える影響2020-10-09T00:00:00+09:002020-10-09T00:00:00+09:00Artur Meystertag:blog.tai2.net,2020-10-09:/remote-work-covid19-ja.html<p class="first last">コロナウィルスによるパンデミックで、我々はまわりのビルを一望できるオフィスとパーティションで区切られた区画を使わなくなり、ベッドルーム上の作業環境、あるいは仕事用になったダイニングテーブルがそのかわりとなった。リモートワークは、雇用する側される側どちらからも、ますます支持されつつある状況だ。リモートワークは、コロナ以前の働き方と違うものとも言えるし、同じものとも言える。業務内容自体は変わらないが、コミュニケーションや会議のやり方が変わったのだ。</p>
<p>コロナウィルスによるパンデミックで、我々はまわりのビルを一望できるオフィスとパーティションで区切られた区画を使わなくなり、ベッドルーム上の作業環境、あるいは仕事用になったダイニングテーブルがそのかわりとなった。リモートワークは、雇用する側される側どちらからも、ますます支持されつつある状況だ。リモートワークは、コロナ以前の働き方と違うものとも言えるし、同じものとも言える。業務内容自体は変わらないが、コミュニケーションや会議のやり方が変わったのだ。</p>
<p>リモートワークが成功したことによって、いま住んでいる街を離れようと考えて、もっと生活費が安い地域内を探したり、地元に帰ろうとしている会社員もいる。会社は、リモートワーク下でどう賃金を処理するべきか四苦八苦している。Facebookは地域に応じた賃金に転換することを <a class="reference external" href="https://techcrunch.com/2020/05/26/disparate-pay/">表明した</a> が、他の会社がどうするつもりなのか、外野からはよくわからない。あるいは、どう進めるかまだ決断していない。</p>
<p>主要なハイテク拠点は、アメリカでも <a class="reference external" href="https://www.mercurynews.com/2020/07/22/coronavirus-economy-bay-area-boasts-nations-highest-wages-tech-job-google-facebook-apple-amazon-netflix/">屈指の高級</a> を誇っている。もっとも高給な三つの郡は、サンタクララ、サンマテオ、サンフランシスコという隣接した群だった。これらシリコンバレーの群に、ニューヨーク、ボストンといった郡が続く。こちらも技術職の拠点だ。パンデミック以前、ハイテク企業はいい人材を獲得するためにしのぎを削り、これが給料の高騰を牽引した。たが、パンデミックが大量の失業と大規模な株の売却を引き起こした。</p>
<p>企業は、業務を遂行するための生産的な手段として、リモートワークをただちに採用した。Twitter、Square、およびFacebookは、自宅に留まりたい社員に向けて、恒久的なリモートワークを即座に公表した。Facebookは、ベイエリアから離れる社員に対して居住地域を報告するよう求めるつもりだ。これは、移住先の生活費に応じて給与を調整するのが目的だ。不安定な経済と、リモートワーク移行の狭間で、ハイテク業界の給与予測はより難しくなるだろう。</p>
<div class="section" id="section-2">
<h2>技術職の給与は、依然として平均以上</h2>
<p>ハイテク産業は、景気後退を他の業界よりもうまく乗り切っている。最近では、S&P 500が、テック企業の力によりQ2とQ3に最高値を叩き出した。パンデミック以前と同様の給与を払ってはいないにせよ、ハイテク企業はこれまでずっと高給だった。直近においても、売却を克服し、利益を出し続けられたことから、技術職の給与はほかよりも高い水準を維持し続けるだろう。</p>
<p>未経験者向けの職に興味がある人に対しても、技術職求人まだまだあり、それは今後も続くだろう。ハイテク企業や他の小売がeコマースの成長を目の当たりにするからだ。給与は、Covid-19以前ほど高くはないかもしれないが、全国平均を上回る見込がある。コードを学びたいと思ったことがあるなら、<a class="reference external" href="https://careerkarma.com/schools/thinkful/">Thinkful</a> のようなコーディングブートキャンプに参加したり、計算機科学の学位を取得する価値はいまでもあるだろう。ハイテク企業は非ハイテク企業よりも迅速に雇用を戻しつつあるし、やはり成長は固い。</p>
</div>
<div class="section" id="section-3">
<h2>技術職はなくならない</h2>
<p>どの既存産業の会社も、ハイテク労働者と最新技術を製品に取り込む方法を求めている。計算機科学のバックグラウンドを持つプロフェッショナルは、起業家や実業家のアイデアを実現するだろう。芝刈りから個人向け製品まで、企業は、技術革新で産業を破壊する方法探している。企業は、これまでとは違ったものの売り方を掘り起こすために技術を使っている。健康ドリンクのDirty Lemonはテキストメッセージ専売の企業だ。企業は、顧客に新機軸の製品やサービスを届けるために技術を使っている。</p>
<p>アパレル企業とスーパーマーケットは、データサイエンスを活用して消費動向や購入したプロダクトを追跡している。Xを買った人はYも買うということをひとたび知れば、企業は営業やプロダクトを増強して、そこで収益を得ようとする。データは企業にとってかげがえのないものだ、とくに経済が細っているこのご時世、消費者が消費に対して意識的になっているようなときには。データサイエンティストは、アルゴリズムの実行や、売上や効率を改善するためのデータ収拾をまかされる。<a class="reference external" href="https://careerkarma.com/wiki/how-to-become-data-scientist-no-degree">学位がなくてもデータサイエンティストになる</a> ことは可能だ。</p>
</div>
<div class="section" id="section-4">
<h2>リモートワークと給与</h2>
<p>Facebookは従業員の給与を考慮するときに、居住地の生活費も同様に考慮すると表明した。とはいえ、リモートワークをしているからと言って、オフィスから通勤可能な距離に住むことを人々が望まないわけではない。<a class="reference external" href="https://economictimes.indiatimes.com/magazines/panache/get-ready-to-say-goodbye-to-5-day-work-week-post-covid-future-will-be-split-between-office-and-home/articleshow/76762564.cms?from=mdr">リモート従業員に対する最近の調査</a> によれば、従業員たちはリモートワークを謳歌する一方、週1回程度オフィスに通いたいと考えている。シリコンバレーからより安い群への大規模な離脱があったとしても、全員が、リモートワークをオフィスから何千キロも離れたところで働くことだと思っているわけではない。リモートで働いているが同じ地域に住み続ける人の給与が変わることはない、たとえほとんどの仕事をリモートで行っていたとしても。</p>
</div>
How Remote Work Is Changing Tech Salaries In The Era Of Coronavirus2020-09-27T00:00:00+09:002020-09-27T00:00:00+09:00Artur Meystertag:blog.tai2.net,2020-09-27:/remote-work-covid19-en.html<p class="first last">The coronavirus pandemic caused us to trade corner offices and cubicles for bedroom offices or repurposed kitchen tables. Remote work is gaining traction among employees and employers. Remote work is both different and the same as work pre-Covid-19. The work we are doing hasn't changed, but our methods of communication and meeting have.</p>
<p>The coronavirus pandemic caused us to trade corner offices and cubicles for bedroom offices or repurposed kitchen tables. Remote work is gaining traction among employees and employers. Remote work is both different and the same as work pre-Covid-19. The work we are doing hasn't changed, but our methods of communication and meeting have.</p>
<p>Due to the success of remote work, some employees are considering leaving their current cities searching for locations with lower costs of living or are going back to their hometown. Companies are struggling with how to approach pay for remote working situations. <a class="reference external" href="https://techcrunch.com/2020/05/26/disparate-pay/">Facebook has said</a> it will switch to location-based pay while others haven't been transparent or haven't decided how to proceed.</p>
<p>Major tech hubs boast some of the <a class="reference external" href="https://www.mercurynews.com/2020/07/22/coronavirus-economy-bay-area-boasts-nations-highest-wages-tech-job-google-facebook-apple-amazon-netflix/">highest salaries</a> in the country. The three counties with the highest wages were the bordering counties of Santa Clara, San Mateo, and San Francisco. These Silicon Valley counties are followed by the counties containing New York City and Boston, which are also hubs for tech jobs. Before the pandemic, tech companies were scrambling to hire the right talent, which drove salaries up, but the pandemic caused massive job loss and major stock sell-offs.</p>
<p>Companies quickly adopted remote work as a productive approach to getting work done. Twitter, Square, and Facebook were quick to announce permanent work from home for employees who wished to remain home. Facebook will require employees who move away from the Bay Area to report where they live to have their salary adjusted for the cost of living in their new location. It will be trickier to predict tech salaries between the volatile economy and the switch to remote work.</p>
<div class="section" id="tech-salaries-will-still-be-higher-than-average">
<h2>Tech Salaries Will Still Be Higher Than Average</h2>
<p>The tech industry is surviving the downturn of the economy better than most industries. The S&P 500 recently hit a record high with tech companies' help in Q2 and Q3. Even if companies don't offer the same salaries as before the pandemic, tech firms have a history of paying well. Their recent success beating sell-offs in March and continuing to make profits will continue to keep salaries above non-tech jobs.</p>
<p>For those concerned about entry-level jobs, tech jobs are still in demand and will continue to be as tech companies, and other retailers, see rises in e-commerce. Salaries may not be as competitive as they were before Covid-19, but they are likely to be above the average for the country. If you have been curious about learning to code, it will still be worth your efforts to attend a coding bootcamp, such as <a class="reference external" href="https://careerkarma.com/schools/thinkful">Thinkful</a>, or pursue a degree in computer science. Tech companies are hiring back workers faster than non-tech firms and still show the promise of growth.</p>
</div>
<div class="section" id="tech-jobs-wont-go-away">
<h2>Tech Jobs Won’t Go Away</h2>
<p>Companies in any given industry are looking for tech workers and ways to implement the newest technologies into their products. Professionals with computer science backgrounds will implement the ideas of entrepreneurs and business people. From lawn care to personal products, companies are looking to disrupt industries through technological innovations. Companies are using tech to approach sales in different ways. The wellness drink company Dirty Lemon is exclusively sold through text messages. Companies are using tech to reach customers in different ways.</p>
<p>Clothing companies and grocery stores use data science to track consumer spending and the products they buy. Companies apply their earnings through sales or product placement once they know a customer who purchases X also buys Y. Data is precious to companies, especially as consumers are more conscious of spending during lean economic times. Data scientists are responsible for running algorithms and collecting appropriate data to improve sales or increase efficiency. You can become a <a class="reference external" href="https://careerkarma.com/wiki/how-to-become-data-scientist-no-degree">data scientist with no degree.</a></p>
</div>
<div class="section" id="remote-work-and-salary">
<h2>Remote Work And Salary</h2>
<p>While Facebook has said they will equate employees' salaries with the cost of living in their location, remote work doesn't mean you won't live a commutable distance from your office. <a class="reference external" href="https://economictimes.indiatimes.com/magazines/panache/get-ready-to-say-goodbye-to-5-day-work-week-post-covid-future-will-be-split-between-office-and-home/articleshow/76762564.cms?from=mdr">Surveys of recent remote employees</a> have shown that while they enjoy working remotely, they expect to go into the office about once a week. Even though there has been a large exodus from Silicon Valley to cheaper parts of the country, not everyone sees remote work as working thousands of miles away from the office. Those who work remotely but stay in the same area won't see a change in their salaries, even if most work is done remotely.</p>
</div>
フリーランスプログラマ雑感2020-08-04T00:00:00+09:002020-08-04T00:00:00+09:00tai2tag:blog.tai2.net,2020-08-04:/freelance-programmer.html<p class="first last">ぼくにとって、ほかの業種、ほかの立場の人の職業生活がどういうもんなのかわからないのと同程度に、ほかの人にとってもフリーランスプログラマがどういうものか、きっとイメージがあまりわかないんだろうと思う。そこで、フリーランスプログラマ生活を振り返って、それがどのようなものだったのかを振り返ってみたい。</p>
<p>フリーランスプログラマになって、かれこれ10年近く経ってしまった。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">昨日をもって退職しました。今日から(しばらくは)フリーランスとしてがんばります。</p>— 武藤スナイパーカスタム🔫 (@__tai2__) <a href="https://twitter.com/__tai2__/status/9698729204383745?ref_src=twsrc%5Etfw">November 30, 2010</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>会社を辞めて、とくに深い考えもなくなんとなくフリーランスになった。しばらくすればどこかの会社に就職するのかなあ、きっとそうなんだろうなあ、とかぼんやりと思ってたことを考えると、そのまま10年近くも続けてしまったのは感慨深い。</p>
<p>ぼくにとって、ほかの業種、ほかの立場の人の職業生活がどういうもんなのかわからないのと同程度に、ほかの人にとってもフリーランスプログラマがどういうものか、きっとイメージがあまりわかないんだろう。そこで、フリーランスプログラマ生活を振り返って、それがどのようなものだったのかを思いつくままに語ってみたい。フリーランスプログラマという語は、フリーランスとプログラマという2つの要素からなっている。この2つをとくに明確に分けて考えるつもりもないので、フリーランスとプログラマ、両方のトピックが綯い交ぜになった文章になるんだろうと思う。いちおう、これからフリーランスプログラマになろうと検討している人に、すこしでも参考になるようなことを書きたいという気持ちで書いてはいる。</p>
<p>書いてみたら長くなったので、つまみ食いしやすいように見だしをつけた。ぜんぶ読むのはたいへんだろうから、興味のありそうなトピックにジャンプしてください。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-2" id="toc-entry-1">フリーランスプログラマになった経緯</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-2">フリーランスプログラマ=受託開発プログラマ</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-3">確定申告について</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-4">どうやって仕事を取る?</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-5">やりたいことは言っておいたほうがいい</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-6">知名度はいらない</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-7">単価</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-8">契約形態: 一括請負契約と準委任契約</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-9">働かない自由</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-10">いのちだいじに</a></li>
<li><a class="reference internal" href="#section-12" id="toc-entry-11">フリーランスプログラマは高リスク?</a></li>
<li><a class="reference internal" href="#section-13" id="toc-entry-12">フリーランスでは受けられない仕事</a></li>
<li><a class="reference internal" href="#section-14" id="toc-entry-13">案件のかけもちは2つが限界</a></li>
<li><a class="reference internal" href="#section-15" id="toc-entry-14">フリーランス40歳の壁</a></li>
<li><a class="reference internal" href="#section-16" id="toc-entry-15">フリーランスプログラマの仕事は受け身</a></li>
<li><a class="reference internal" href="#section-17" id="toc-entry-16">便利につかえるのがフリーランスプログラマ</a></li>
<li><a class="reference internal" href="#section-18" id="toc-entry-17">プロダクトの保守をしたことがない</a></li>
<li><a class="reference internal" href="#section-19" id="toc-entry-18">やっておいて良かったこと</a></li>
<li><a class="reference internal" href="#section-20" id="toc-entry-19">あるフリーランスプログラマの一日</a></li>
<li><a class="reference internal" href="#section-21" id="toc-entry-20">フリーランスプログラマに向いてる人</a></li>
</ul>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-1">フリーランスプログラマになった経緯</a></h2>
<p>まずは、どうやってなったのか、ことの経緯からはじめる。フリーランスプログラマになるまえは、会社員として、おもに受託開発をやっているとてもちいさな会社で、会社員受託開発プログラマをやっていた。いま思えば、当時リーマンショックの影響もあって、会社の業績がかんばしくなかったんだろう。上司や同僚などいっしょに仕事をしていた人がつぎつぎと会社をはなれていった。で、最終的に社員2人と社長1人という状況にまでなった。社長は仕事を取ってきて、それ以降のお客さんとのやりとりから納品まで、すべて社員に丸投げという感じのフロー。こちらとしては、生活に必要なお金がもらえてプログラムが書けてさえいれば、まあ満足だったので、そのまま続けていた。しかし、給料支払いの遅延や案件トラブルにつづく減給といったことに起因する、社長との感情的な軋轢から、しだいに辞めたいなという気持ちが生じてくる。</p>
<p>さきに辞めた職場の仲間は、フリーランスプログラマとして、会社員時代とおなじ取引先と仕事をしたりしてた。フリーランスプログラマになったら仕事を斡旋してもらえるというような会話はなんとなくしたりしていたので、まあ辞めてもなんとかなるだろうと思っていた。また、会社員として最後に納品した仕事(依頼経緯からなにから社長はまったく関与してしていなかった)も、フェーズ2以降がきまっている案件ではあったので、お客さんに相談してみた。そういうことなら今後はきみ個人に直接たのもうか、ということになった。</p>
<p>そんなこんなで、無事退職し、晴れてフリーランスプログラマとなった。社長からは、きみは個人ではやっていけないよというようなことを言われたのを覚えている(そんなのわかるもんかよと思った)。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-2">フリーランスプログラマ=受託開発プログラマ</a></h2>
<p>ちなみに、世の中のたいはんのフリーランスプログラマは、いわゆる受託開発で生計を立てていると思う。つまり、お客さんからなにかをつくって欲しいと依頼を受けて、それをつくり、納品するという仕事だ。さいきんは技術顧問などコンサルタント的な仕事も増えてきているようだけど、大部分はじぶんで手を動かす開発だと思う。個人受託開発プログラマと言いかえてもいい。もともと会社でも受託開発をメインにやっていたし、上司がいなくなってからは見積もりから検収まで、お客さんとのやり取りもほぼすべてじぶんでやっていた(請求だけは社長がやっていた)ため、フリーランスプログラマになっても、やっていること自体はほとんど会社員時代と変わらなかった。変わったことと言えば、業務時間や作業場所にしばられず、自宅で自由に仕事をするようになったことくらいだ。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-3">確定申告について</a></h2>
<p>独立するにあたり、今まで会社まかせだった確定申告と所得税の納税というのをじぶんでやらないといけないらしい。まずそのあたりを調べなければと思い、Amazonかなにかで適当にしらべたら評価の高かった、きたみりゅうじさんの <a class="reference external" href="https://www.amazon.co.jp/dp/4534040016/">「フリーランスを代表して 申告と節税について教わってきました。」</a> という本を読んだ。個人レベルなら、この本で身につけた知識だけで、10年間じゅうぶんにやってこられた。税理士を頼ったことはない(というか、一度くらいプロに相談しようと思いつつ、めんどくさくてけっきょくやらずじまいだった)。もちろん、プロの税理士に相談すればこまかい節税の最適化テクニックなどはあるんだろうけど、ともかく、この本のおかげであるていどの自信をもって、じぶん自身で確定申告をこなすことができた。独立してから知り合ったフリーランスのなかにも、この本で税の勉強をしたという人は何人もいる。さらに、さいきんは、 <a class="reference external" href="https://www.freee.co.jp/">freee</a> など便利な帳簿管理サービスもある。ぼくはfreeeユーザーだけど、これをつかえば極限までかんたんに経理と確定申告ができる。フリーランスプログラマの場合、クレジットカードのつかいわけなど、経費管理の方法をくふうすれば、年間で必要な経費管理の時間は数時間から半日程度で済むと思う。こういうサービスはつかったほうがいい。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-4">どうやって仕事を取る?</a></h2>
<p>フリーランスと会社員のいちばんの違いは、プロジェクトごとの契約をお客さんとじぶんで直接交わさなきゃいけないってこと。会社員の直接の契約相手は会社で、収入を確保して社員に給料を支払う責任が雇用者にある。いっぽう、フリーランスプログラマは、仕事を取れなければそのまま収入がなくなってしまう。仕事とお金の心配をじぶんでしなくちゃいけない。ぼく自身は、さっきも言ったように、辞めるまえからあるていど仕事の算段がついていた。そして、10年間をふりかえって、仕事がなくてこまったということは一度もない。逆に、いそがしいときには、もしつぎ仕事がこなかったら、旅行にいったり、何ヶ月も趣味の勉強や開発に没頭しようという妄想をふくらませてばかりいた。でも、じっさいに何ヶ月も仕事がとぎれるというようなことは、あえて仕事をことわって自由時間をつくる努力をしないかぎり、一度もなかった。感覚としては、年に何回か、思い出したように、こういう仕事があるのだが、てつだってもらえないかという連絡が知り合いからくるかんじ。連絡手段は、電話やメール、facebookメッセージとかが多い。別にぼくがとくべつすごいって言いたいわけじゃなくて(まあ、みんなから頼られるプログラマではあるんだぜと自慢したい気持ちもそりゃなくはないけど)、身近にいるたくさんのフリーランスプログラマたちは、例外なく、みんないつもいそがしそうにしている。人手が必要で仕事の相談をすると、ちょっといそがしくて、すぐにはむずかしいと言われてしまうことも多い。つまり、世の中には、アプリやシステムを、けっして安くないお金を出してでもつくりたいと思っている人や会社がたくさんあって、フリーランスプログラマ市場には、じゅうぶんな需要がある。だから、仕事はそこらじゅうにある。いまは、仕事にあぶれるということは、あまりないんじゃないかなあ。選り好みさえしなければ。</p>
<p>とはいえ、やはり仕事がなかったらつらいという不安は、ほんどのフリーランスが共通して抱えている思いなんじゃないかと思う。ぼくも、そういう不安がふと頭をよぎることはある。ただ、仕事をきちんと誠実にこなしてさえいれば、一つのプロジェクトを終えるごとに、信頼というのは着実に積みかさねられていく。まあこんなのよく言われることではあるんだけど、仕事をしていくうえで、信頼はほんとうにだいじだと実際ぼくも思う。ただ、それはべつに大袈裟にかまえるようなことでもなくて、ただ誠実に仕事をやってさえいれば、かってについてくるもの。なんにも難しいことじゃない。いまのところソフトウェア開発業界では、とくべつに秀でた才能なんてなくても、プログラムをつくれるたしかな技術力さえあればいい。それだけで、ふつうにやっているだけで生きていけると思う。それほど社会はソフトウェア開発スキルを必要としているという感覚がある。</p>
<p>ぼくの場合は、会社員時代に受託開発をやっていたから、仕事をもらえるコネクションなど、じゅうぶんな下地ができていた。じゃあ、そういう下地なしに、いきなりフリーランスプログラマになるのはむずかしいんだろうか。</p>
<p>さいきんでは、エージェントがフリーランスプログラマに案件を紹介してくれるようなサービスがいくつもある。そういうサービスを活用すれば、仕事をもらう直接のつてがなくても、個人で仕事をはじめることは、じゅうぶんに可能な気がする(実際やったわけではないから気がするってだけだけど)。どういう仕事があるのか興味があったのと、仕事の選択肢を増やすつもりもあって、じぶんでもいくつか登録してみた。そのうちの一つで、 <a class="reference external" href="https://flxy.jp/categories/freelance">flexy</a> というサービスの <a class="reference external" href="https://www.facebook.com/yegu.qin">エージェント野谷さん</a> からは、定期的(年1、2回くらい?)に案件を紹介していただいた。野谷さんは、じぶんの興味分野や得意スキルなどもしっかりと把握してくれたうえで適切な案件を紹介してくれるかたで、個人的に信用している。タイミングがなかなかあわず、相談をうけてもじっさい契約にいたったことは、いまのところないんだけど。この種のサービスは、案件成立時に契約額の何割かを、仲介会社が報酬として受け取るというようなビジネスモデルになってるんではないかと思う(聞いたわけじゃないから想像だけど)。そういったサービスにたいして、ほんらい受け取れる報酬を横取りされているように感じて嫌悪感を持っているフリーランスプログラマもいるようだ。個人的には、ほんらいなら何もなかったところに取引が生じているわけだから、立派に付加価値になっていると思う。ゼロだった売り上げがゼロでなくなっているわけじゃん?(仕事をもらう伝手がまったくない状況を想定していることに注意ね)。それに、報酬にかんして言えば、じぶんが満足する額をもらえているかどうかだけが重要なことであって、他の人があいだに入っていくら取っているかとかどうでもいいことなんじゃないのかね。たりないと感じるなら、額を増やして要求すればいいだけのことじゃない?(あたりまえだけど、いくらでも好きなだけお金がもらえるということではない。相場感みたいなものは必要。単価についてはのちほど語る)</p>
<p>ほかに、伝手がない人に役立ちそうなものとして、 <a class="reference external" href="https://freelancenow.discussionpartners.net/">FreelanceNow</a> みたいなコミュニティーもある。こちらもタイミングの問題などがあり実際に契約にいたったことはない。だけど、のぞき見している感じだと、仕事はたくさんあるっぽい。クラウドワークスとかランサーズとかの、いわゆるクラウドソーシングと呼ばれるようなサービスもある。クラウドソーシングついて言うと、ソフトウェア開発系にかんしては、検索してでてくる案件がちょっと引いてしまうようなめちゃくちゃな内容のものが多く目につくので、一度もつかったことがない。</p>
</div>
<div class="section" id="section-6">
<h2><a class="toc-backref" href="#toc-entry-5">やりたいことは言っておいたほうがいい</a></h2>
<p>ぼくの場合、基本的に、依頼のきた仕事はスケジュールと金額さえあえばなんでも受ける。内容で仕事を断わったことはこれまで一度もない。ほとんど毎回、なにかしらつかったことのないプログラミング言語やフレームワークでつくってくれという指定があるので、多くの案件は、まず言語やフレームワークのつかいかたを覚えることからはじまる。モバイルプログラミングの依頼があればiOSやAndroidのプログラミングガイドを読むところからはじめるし、かなりマイナーなプログラミング環境の仕事もやったことがある。技術資料を読むのが趣味みたいなところがあって、ドキュメントを読むのはまったく苦痛じゃないので、こういう仕事スタイルが性分にあっているんだと思う。なので、仕事でつかったことのある言語やフレームワークの数だけで言えば、けっこうな数になる。もちろん、どれを取っても極めるというほど深くは知らないんだけど。それでも、案件の性質によらずなにかとつかう機会の多いJavaScriptとかは、自然とけっこうくわしくなってきた。</p>
<p>よく、なんでもできるっていうのは、なんにもできないのと同じことだみたいな言葉を耳にすることがある。それは一理あって、なんでもやりますって人だと、なにか具体的な仕事があるときに、その仕事ならあの人にたのもうということにはなりにくいんじゃないかと思う。だから、やりたい技術とかつかってみたい言語とかがあるなら、常日頃からじぶんがそれに興味があることをまわりにチラつかせておいたほうがいい。それが記憶のフックになって、あの人に相談してみようかってことになるかもしれないから。実際にそのやりたい技術で、なにかしらのデモ的なものでもつくって見せられればなおいい。ぼくはそういうことはいっさいやってないので、まあじぶんの興味あること(コンピューターグラフィクスとか)とかんけいなく来た仕事来た仕事受けてたら、すっかり器用貧乏系プログラマになってしまった。</p>
<p>ただ、どんな仕事でも、そのなかで、あるていどじぶん自身がたのしめる方向に仕事を持っていく余地はあるんじゃないかと思う。言語やフレームワークが決まっていないなら、じぶんがやってみたい言語を提案するとか、じぶんが試してみたいと思っているアイデアを差し込んでみるとか。仕事を完遂さえすれば、過程や方法はなんでもいいはず。目をくばると、その余白にじぶんのやりたいことをやる自由が見つかったりする。これは、ある先輩フリーランスプログラマから影響を受けた仕事にたいする姿勢だったりする。</p>
</div>
<div class="section" id="section-7">
<h2><a class="toc-backref" href="#toc-entry-6">知名度はいらない</a></h2>
<p>仕事をもらう相手についていうと、最初のころはおなじお客さんからもらうことが多かった。そして、年数を重ねるうちに、だんだんあたらしいお客さんからの仕事も増えていった。逆に、昔はよく仕事をもらっていたけど、さいきんではほとんど付き合いがなくなってしまったお客さんもいる。増えていったというか、変わっていったというほうが適切かも。なぜ声をかけてもらえなくなってしまったのかはよくわからない。知りたい気もするけど、あまり聞きたくない気もする。あたらしく仕事をもらうようになったお客さんとの出会いは、どういう経路があるか。ぼくの場合、広い意味でのコミュニティーで知り合ったつきあいが多い。コワーキングスペースに通ううちに知り合った人もいるし、勉強会の懇親会で知り合いになった人もいる。そういう人たちから、あるときふと仕事の相談を受ける。mizchiさんみたいな顔の広い有名人で、最新トレンドを追っているような人であれば、TwitterのDMで案件の相談を受けたりするみたいだけど、ぼくの場合は10年やっていてそういったことはほとんどない。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ちょっと前まで似たような状況だったけど、エンジニアはブログ書いてれば「うちでもこれやりたいんですけど」って仕事のオファーが継続的に来るので困ったことなかった <a href="https://t.co/71KJHbEdAC">https://t.co/71KJHbEdAC</a></p>— @mizchi (@mizchi) <a href="https://twitter.com/mizchi/status/1288373806340304896?ref_src=twsrc%5Etfw">July 29, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>リアルでの勉強会など、なんらかの場を通じて知りあった人から相談を受けることがほとんどだった。フリーランスプログラマになるために、インターネットでの知名度が必要かというと、これは断言するけど、まったくそんなことはない。知り合いにフリーランスは何十人もいるけど、ツイッターでフォロワーが何千人もいるような有名人なんて1人もいない。そして、みんな安定して生計を立てている。ソフトウェア開発業界なら、知名度がなくても、ふつうに仕事をして、しっかりと信頼をつみかさねていけば、じゅうぶんにやっていけるはず。</p>
</div>
<div class="section" id="section-8">
<h2><a class="toc-backref" href="#toc-entry-7">単価</a></h2>
<p>さて、フリーランスプログラマとして開業し、めでたく仕事の打診がきた。つぎは単価を決めないといけない。ぼくの場合は、会社で仕事を受けていたときの単価を基準として、個人なのでそれよりすこし低めという設定をした。昨今はさまざまな人がじぶんの単価を公開してくれていて、探せばいろいろでてくる。まあ、世間のフリーランスプログラマがいくらくらいの単価にしてるのか、どのように決めているのかはふつうに気になるだろうから、いくつか参考リンクを書いておく:</p>
<blockquote>
<ul class="simple">
<li><a class="reference external" href="https://gist.github.com/mizzy/2c09a8d5399b670640f24c5d969b2c12">フリーランス参考情報</a></li>
<li><a class="reference external" href="https://note.com/shu223/m/mf9db39f3c77d">フリーランスの収益公開シリーズ全部入りパック</a></li>
<li><a class="reference external" href="https://kirimin.hatenablog.com/entry/2018/12/20/212925">エンジニアとして就職してフリーランスになった7年間の収入を公開するよ</a></li>
<li><a class="reference external" href="https://u1tnk.github.io/blog/2018/09/09/my-price/">フリーランスとしての自己紹介と仕事の条件</a></li>
<li><a class="reference external" href="http://terurou.hateblo.jp/entry/2019/01/13/162332">顧客企業の求人情報から受託システム開発契約の単価を決める話(フリーランス・零細企業向け)</a></li>
<li><a class="reference external" href="https://qiita.com/KazukiTanaka/items/130a2c477847b24e35ce">フリーランスエンジニアの単価を決める</a></li>
<li><a class="reference external" href="https://www.lifehacker.jp/2014/12/141205freelance_rate.html">適切なフリーランス料金を決めるための鉄則は、クライアントの思考プロセスを理解すること</a></li>
</ul>
</blockquote>
<p>あとは、まわりにフリーランスプログラマの知りあいがいるなら、きっと質問すれば多くの人は教えてくれるんではないだろうか。ちなみに、ぼくの場合、現在は人月120万円だ。さっきも書いたけど、単価についてはじぶんが満足できる額を設定するということがいちばん重要なことだと思う。あんまり人の話と比較しすぎても、しあわせになれない気がする。</p>
</div>
<div class="section" id="section-9">
<h2><a class="toc-backref" href="#toc-entry-8">契約形態: 一括請負契約と準委任契約</a></h2>
<p>契約形態の話も大事。ソフトウェア受託開発において、契約形態は大別して二種類、一括請負契約と準委任契約っていうのがある。一括請負は、じぶんの経験だと一番よくある形態。最初にお客さんからの要件提示があって、その要件を実現するならどのくらいの期間と費用がかかるかっていう見積もりをする。で、出した見積もりで合意が取れればその見積額で契約を交わして、計画したスケジュールに則って納期までにプログラムをつくり、納品するという形。準委任契約は、契約上、なにをいつまでにつくるということを定めず、一定時間お客さんのために労働するということだけを決める。そして、労働したなら、成果が出たかどうかにかかわらず報酬をもらえるっていうやつ。成果をあらかじめ約束するか、しないかが大きな違い。準委任契約については、さらに2つのタイプに分けられる。じっさいの稼働時間にかかわらず固定の報酬が支払われる、いわゆる月額定額制と言われるタイプの契約と、稼働時間を記録しておいて、稼働時間の実績におうじて報酬が支払われるタイプの契約。時給いくらで毎月お金をもらうので、感覚としてはアルバイトに近い。</p>
<p>ソフトウェア開発なんて計画どおりにいくわけがないんだから準委任契約が合理的だ。という考えが、アジャイルとかスクラムの文脈で増えつつあるというのが昨今の流れのような気がする。ただ、これは成果にたいする責任を受けるがわが持つか、発注するがわが持つかというのが本質のように思える。受ける側からすれば、成果物に責任を負わない準委任契約のほうがリスクがないのはたしか。いっぽう、一括請負でやる場合、当然リスク込みで受注しなければならないので、必然的に見積額が肥大化する。ふつうに見積もった額にたいして、バッファーとして2倍3倍をかけてお客さんに提出するのは当たり前だし、実際そうしておおきめのバッファーを持たないと、こちらがおおきな損をしてしまう可能性がある。これは、作業の実質と報酬(費用)がかけ離れてしまいがちという意味で、双方にとってあまりよくない状況だと思う。だから、ソフトウェア開発は準委任契約(月額定額制、納品のない受託開発)を基本とすべきだというのが、 <a class="reference external" href="https://www.sonicgarden.jp/32">ソニックガーデン倉貫さんなどの主張</a> だ。ただ、契約のきめかたについては、基本的にこちらに決定権がない場合がほとんどなので、提示された条件で受けるかどうかを決めるしかないのが実情なんじゃないだろうか(なかには、契約形態から相談に乗ってくれる奇特なお客さんもいるにはいるけど)。</p>
<p>開業からしばらくは、一括請負の仕事しかなかった。けど、ここ数年、なんでか知らないがもっぱら準委任契約の仕事ばかりで、たまに一括請負の仕事がはいってくる程度という感じになっている。10年やってわかったことがある。じぶんにいちばん合っているのは、稼働時間におうじて報酬をもらうタイプの契約だということ。それは、こんな感じの働きかた。経験上、スクラム的なプロジェクト運用と組み合わされることが多いので、スプリントごとにゆるく目標を立て、そこに向かって作業をする。スプリントは無理のない範囲で計画を立てるし、万が一なんらかの理由で目標達成できないことがあったとしても、小さなずれだから大きな問題にはならない。これが一括請負+ウォーターフォールだと納期にまにあわないのは大ごとになってしまう。納期まぎわに徹夜でがんばったり、言いわけを考えてお客さんと交渉をしたりといろいろ面倒なことになる。準委任契約+稼働時間報酬は、他の契約とは体感のプレッシャーがだいぶちがう。目標達成へのゆるいプレッシャーはあるものの、稼働時間におうじた報酬なので、稼働しなかったとしても、じぶんのもらえるお金が減るだけだし、まあいいかと気楽にかまえていられる。思わず外に出かけたくなるような気持ちのいい日に、突発的に仕事を休んでピクニックにでかけても、そんなにうしろめたい気分にならない。働いたら働いたぶんだけお金を請求できるので、生活にこまるということもない。なんとも具合のいい契約じゃないですか。</p>
<p>逆に、つらかったのは月額定額の契約だ。こちらは、稼働時間にかんけいなく、つねに一定の収入が保証される安定性がメリットと言われる。うらを返せば、金銭が発生している以上、一定の稼働時間が期待される。というか、ぼくはそのように感じてしまう。だから、スプリントの作業において、はやめにある程度成果を達成したとしても、なんとなくもっと働かないといけないような気分になる。結果として、つねに働ける一杯働いてしまう。そうしないと、報酬分働いていないような気がして、なんだか後ろめたい。どうもこの働き方は、じぶんにはあまり合っていないらしい。</p>
<p>一括請負は、見積もりがあまかったり諸々の都合で納期間際にバタバタしてしまいがちではあるけど、要は期日までに必要な成果物ができていれば、あとはどうでもいいわけで、非常にフリーランス的であり、そんなに嫌いではない。最終目標が最初の段階で明確に決まってるのがいい。それさえクリアしちゃえば、あとはなにもしなくていいわけだし。一日の決まった時間毎日働くことを要請されるよりははるかにマシだ。とりあえずバッファーをあるていど多めにとってさえおけば・・・と言って、まあスマートに納品できないことはざらなんだけど、さりとてまったく要求を満たせず契約不履行となってしまったことも、いちおうはない。</p>
</div>
<div class="section" id="section-10">
<h2><a class="toc-backref" href="#toc-entry-9">働かない自由</a></h2>
<p>働きかたと言えば、フリーランスプログラマは、経済的な事情がゆるすかぎり、休むのも自由だ(もちろん契約した案件をきっちり終えることは前提)。顧客と契約しなければ、何ヶ月だって自由に休んでいられる。そして、好きなタイミングで仕事を再開できる。じっさい、子供が生まれるときには、何ヶ月か育休期間として休んだし、独身時代には、働きすぎてつかれたときにしばらく旅行に出たりもした。働かない自由もまたフリーランスプログラマの醍醐味だと思う。とはいえ、働いてお金を稼ぎつづけないと生きていけないので、稼がないとなあという気持ちがつねにつきまとうのも事実。労働者階級の悲しい宿命か。</p>
</div>
<div class="section" id="section-11">
<h2><a class="toc-backref" href="#toc-entry-10">いのちだいじに</a></h2>
<p>働きすぎと言えば、一度、ひどいプロジェクトがあった。マネジメントがだめなのか、あるいはそもそも体制がめちゃくちゃなのかで、参加していること自体がとてもつらく、しまいには、一刻もはやく抜けだしたいとしか考えられなくなってしまった。比較的おおきめのプロジェクトで、末端の一実装者であるじぶん1人の力ではどうにもならなかった。その案件は、一括請負ではなく、準委任契約で稼働時間におうじた報酬の案件だった。システムのリリースは遅延につぐ遅延。きちんとうごく状態までもっていくことも絶望的なように思えた。プロジェクトとしては、混沌としたまま続いていこうとしている状態だったんだけど、あまりにもつらいので、じぶんの担当部分を形のうえだけでも、どうにかこうにかでっちあげた。で、逃げるように契約を解除させてもらった。逃げたければいつでも逃げ出せるのもフリーランスプログラマのいいところと言えるかもしれない。責任論というのもあるんだろうけど、こっちとしてはメンタルの健康がかかっていることなので、大袈裟に言えば命がけですよ。信用のために頑張ることも大事だけど、それよりもじぶんが健康でいることのほうがとうぜん大事。</p>
<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">でもやべーと思った瞬間に契約切って真っ先にトンズラできる身軽さこそフリーランスの最も重要な長所なんだし、やべーと思いながらズルズル居続けるのはその立場なら自殺行為だとおもいますよ。</p>— 7594591200220899443 (@shyouhei) <a href="https://twitter.com/shyouhei/status/1128465039654973441?ref_src=twsrc%5Etfw">May 15, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>と言っても、一括請負契約だと、さだめられた成果物を納品することが契約書に書かれてしまっているので、なかなか途中で投げ出すのは難しいかもしれない。最悪、民事請求なんてこともあり得るんだろうし。すくなくとも法律のうえでは。ただ、長年受託をやってきた経験から言えば、お客さんとの良好な関係を築いてさえいれば、万が一なにか問題が起きても、そんなに無茶なことは言われないんじゃないかと思う。これは受託開発にかぎらないことだけど、なにかヤバい気配とか、想定とちがった事態とかが生じたら、とにかくその時点でだれかに相談したほうがいい。1人で抱え込んで知らせるのが遅くなればなるほど、リカバリーは難しくなる。あたりまえだけど、お客さんは、べつに敵対関係にある相手ではなく、プロジェクトを無事にまるくおさめるという共通の利害をもったパートナーだ。だから、こまったことがあったら、どうすれば切り抜けられるか一緒に解決方法をかんがえてくれるはず。まあ、その結果、なんとかここまでは無理してでもやってくださいなんて、けっきょく丸投げに近いことを言われる結果になってしまうこともなくはないけど、この機能は最悪なくてもいいですとか、この部分は納期が過ぎたあとでもいいですとか、誰か助けてくれる人を探しましょうとか、なにかしら言ってくれることが多かった。無い袖は振れないわけだし。</p>
</div>
<div class="section" id="section-12">
<h2><a class="toc-backref" href="#toc-entry-11">フリーランスプログラマは高リスク?</a></h2>
<p>責任論について言えば、個人は無限責任だけど、会社は有限責任なので、フリーランスプログラマはたいへん危険だという話もときどき聞く。まわりで賠償問題までこじれたという話を聞いたことはないんだけど、たぶん事実なんだろう。ぼくも含めて、まわりのフリーランスプログラマはのんきな人が多いのか、そういったことにあまり深刻にはなっていないみたい。</p>
</div>
<div class="section" id="section-13">
<h2><a class="toc-backref" href="#toc-entry-12">フリーランスでは受けられない仕事</a></h2>
<p>それから、会社によっては個人には仕事を出さないらしい。じぶんは個人に仕事を出してくれる会社としか直接のつきあいがないのでわからないけど(あたりまえ)、そういう話はよく聞く。だから、フリーランスプログラマである時点で、受けられる仕事の種類・市場は限定されている。ぼくの場合も、大きい会社と直接の取引をすることはなく、大きい会社の仕事は、知り合いの会社が仕事を受けて、知り合いの会社から個人として発注を受けるという形でずっとやってきた。</p>
</div>
<div class="section" id="section-14">
<h2><a class="toc-backref" href="#toc-entry-13">案件のかけもちは2つが限界</a></h2>
<p>フリーランスプログラマは、複数の会社と契約して、複数のプロジェクトを同時平行で進行していることが多いと思う。よく言われるのは、ずっとおなじ会社と契約して、ひとつのプロジェクトばかり続けるのは、社員のように保証のないフリーランスプログラマにとってリスクが高い、だから複数の会社と契約するようにしたほうがいい、という話。まあ根拠があるのか無いのかよくわからない話ではあるけど、ほうっておいても勝手にそのような感じにはなっていた。ただ、同時並行のプロジェクトをいくつまで許容するかという問題はある。仕事はあるのでことわららなければ数を増やせるんだけど、多ければいいってもんでもない。並行するプロジェクトが増えれば増えるほど、複数プロジェクトでのやりかたや内容の違いに対応するコストが大きくなって、しんどくなってくる。個人的には、コンスタントに毎週成果を出すなら、2プロジェクトが限界かなという感触を持っている。1プロジェクトなら取り組んでいることに集中できて、もっと楽に成果をだせる。次の仕事がすぐに見つかるなら、べつにそれでもいいのかもしれないような気もする。もちろん、案件の内容とか、技術顧問ではいるなどプロジェクトとのかかわりかたとかで、ここらへんの話はぜんぜん変わってくるとは思う。</p>
</div>
<div class="section" id="section-15">
<h2><a class="toc-backref" href="#toc-entry-14">フリーランス40歳の壁</a></h2>
<p>フリーランスプログラマなんてずっと続けていて、歳をとったらどうするつもりなの? キャリアプランは?みたいなことをたまに言われる。 <a class="reference external" href="https://www.amazon.co.jp/dp/4478065721">フリーランス40歳の壁</a> なんて話もある(ぼくも来年ちょうど40だ)。まあ、正直なにもかんがえてない。いまのところ仕事がなくなるような兆候はないし、歳をとったからといってプログラミング能力が衰えるとも思えない(老眼の問題は確実にあるだろうから気をつけないと...)。おじいちゃんになるまでひたすらプログラミングして暮らしてけばいいんじゃないのぐらいに思っている。まわりのフリーランスプログラマ仲間では、40を過ぎた人がちょいちょい出てきている感じだけど、知り合いで50代の人はたぶんいない。べつに40になったからなにかが変わったという話もいまのところは聞いてない。ソフトウェア開発の需要はあるし、リモート作業で年齢とかもあまりかんけいないし(ふだん仕事しててチームメンバーの年齢なんて気にしないよね?)、なんとかなるんじゃないの?</p>
<p>と言いつつ、以前、サービスの立ち上げを手伝ったスタートアップ起業家(当時20代)に、50代のプログラマに仕事をたのみたいと思うか聞いてみたら、あまり頼みたいとは思わないという返答だったこともいちおう書いておく。</p>
<p>あと、言うなら、べつに会社員だって、小さい会社の場合とくにだけど、いつ潰れるかなんてわからない。というか、じぶんがおじいちゃんになるまで会社が存続してる可能性のほうが低いだろう、たぶん。だったら、フリーランスとたいしてかわらないよね。大企業だって、昨今いつ業績が悪化してリストラされるかわかったもんじゃない。会社員プログラマがフリーランスプログラマにくらべて安定しているというのは、しょうじきぼくにはよくわからない話ですね。</p>
</div>
<div class="section" id="section-16">
<h2><a class="toc-backref" href="#toc-entry-15">フリーランスプログラマの仕事は受け身</a></h2>
<p>つぎはフリーランスプログラマの仕事のすすめかた、フリーランスプログラマの仕事とはどのようなものなのかについて話す。冒頭にも書いたように、フリーランスプログラマは、受託開発を生業とする人が多い。受託開発をやらないとすると、サービスなりアプリなりをじぶんで企画し、つくって、それを売ってお金を稼ぐということになると思うけど、はたしてそれはフリーランスプログラマと言えるのか。いずれにしろ、受託開発とくらべて、桁違いに難易度がはねあがるので、みんながみんなできることじゃないだろう。受託開発は、ぶっちゃけ、ソフトウェア開発能力がありさえすればノーリスクでだれにでもできる仕事。</p>
<p>基本的には、お客さんが、こちらがやるべき仕事の内容を定義するので、その内容にしたがってプログラムを書いて納品する。タスク管理システムのようなものでチケットを管理している場合は、お客さんがそこに要件を書き出して、こちらはそれを淡々と消化して閉じていく。どのていどの詳細までお客さんが決めるか、タスクをじっさいに起票するのがだれなのかなどはケースバイケースだろうけど、なにをつくるのか、つくったものがOKなのかを判断するのはこちらではなくお客さん。これは受託開発の根本というか、まあ定義みたいなもんだよね。もちろん、こっちからなにかを提案してはいけないってことはない。もっとこうしたほうがプロジェクトが成功に近づくっていうアイデアがあれば、提案するのは自由。だけど、それを最終的に判断する責任がお客さんにあるっていう事実にはかわりない。つまり、フリーランスプログラマというのは、基本的に受け身の職業だってこと。ぼくはずっと受託開発をやってきたので、プログラマとしてのキャリアをつうじて、ある意味、ずっと受け身の姿勢でやってきた。プログラムを実装して期待された動作を実現させるという磨き上げた己の能力を、金銭を対価としてお客さんに提供するっていうのが、フリーランスプログラマの本質だとぼくは思っている。</p>
</div>
<div class="section" id="section-17">
<h2><a class="toc-backref" href="#toc-entry-16">便利につかえるのがフリーランスプログラマ</a></h2>
<p>これまでフリーランスプログラマの視点から語ってきけど、雇う側は、なんでフリーランスプログラマをつかうんだろうか。社員じゃだめなのか。直接聞いたことはないから想像でしかないけど、経営者からすれば、社員を雇うとなるとリスクも高くハードルもだいぶあがる。立ちあげようとしているサービスが軌道にのるかどうかもわからない状況では、なおさらだろう。そういうときに、長期的な関係をもつことなく、収入の保障をする必要もないフリーランスプログラマはべんりな存在なんだと思う。ちょっと契約してみて、期待する仕事ができなさそうであれば、単発の仕事で関係を終えることも、フリーランスプログラマなら簡単。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">会社からするとフリーランスが使い勝手がいいのはすぐ解雇出来るから。すぐ切れるというのは付加価値。日本の法律で解雇がしにくい状況が続く限りはフリーランスは価値がある、解雇しやすい法律が出来たらそりゃ就職するわ</p>— さぼ@EBILAB 👨💻☕️🎹🎧🐈 (@saboyutaka) <a href="https://twitter.com/saboyutaka/status/1149161900967464960?ref_src=twsrc%5Etfw">July 11, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>これまでの仕事歴をかんがみると、まだ開発のためにあたらしく社員を雇用する余裕がないとき、経営の安定していない状況で新規プロダクトを立ちあげたいとき、とりあえずお試しでコンセプトを検証したいプロトタイピングなどを目的として仕事を依頼されることが圧倒的に多かった。あるいは、正規の社員が見つかるまでの繋ぎか。ガッと作って納品して、ちゃんと動いてますね、ではさようならという仕事ばかりで、すでに軌道に乗っているサービスの運用フェーズや改修にかかわったことはほとんどない。サービスが軌道にのっていて、安定した売りあげがあるなら、社員にやってもらえばいいわけだから、それはそうだと思う。逆に、いろんな会社のさまざまなプロジェクトで、0を1にする仕事をできるのがフリーランスプログラマの醍醐味と言えるかもしれない。</p>
</div>
<div class="section" id="section-18">
<h2><a class="toc-backref" href="#toc-entry-17">プロダクトの保守をしたことがない</a></h2>
<p>こういったプロジェクトとのかかわりかたが多いため、書いたコードの長期間にわたる保守をほとんどしたことがない。書きあげて、お客さんにわたしたら終わり。あとはそれをお客さんが好きにつかうだけ。どうつかわれるかはまったく関知しない。場合によっては、しばらく経ってからこまかい改修や機能追加の依頼がくることもある。けど、それはまた別の仕事なので、プロダクトを継続的にまわしているという感覚ではない。つまり、じぶんの書いたコードのお守りを長期的にすることが基本的にない。だから、メンテナンス性の高いコードを書く動機がない。もちろん、かならずしもめちゃくちゃなコードを書くということではなく、ぼくの知り合いの範囲では、どちらかというとちゃんとしたコードを書く人が多いと思うけど、構造的にしっかりと書く理由がないという話。とくに、自動テストなんかは、どこまでやるかは完全に個々人の裁量で、まあぶっちゃけ書かれないことも多いんじゃないかと思う。さいきんはだんだんテストコードというものが根付いてきているとはいえ… だから、たんに機能としてできているかどうか以上に、いわゆる内部品質というか、コードの保守性だのテストだのまでふくめた形でのソースコードが欲しいのであれば、依頼するときに条件として指定する必要があると思う。お客さんから、コンポーネントごとにテストもちゃんと書いてねと言われれば、それはもちろんちゃんと書く。</p>
</div>
<div class="section" id="section-19">
<h2><a class="toc-backref" href="#toc-entry-18">やっておいて良かったこと</a></h2>
<p>だんだん語ることもなくなってきた。あとは、フリーランスプログラマとしてやっていくにあたって、やっておいて良かったと思うことについても言っておくか。</p>
<p>まず、さっきも書いたけど、きたみりゅうじさんの「フリーランスを代表して 申告と節税について教わってきました。」は読んでおいてよかった。べつにこの本でなくてもいいけど、じぶんで確定申告するなら、最低限の税の知識は必須だと思う。それから、これもじぶんで申告するならだけど、なんらかの会計ソフト的なものもあったほうがいい。世の中には、エクセルでつけてSQLで帳簿管理する変人もいるようだけど、まあ専用のソフトウェアをつかったほうが楽だ。freeeとかをつかうと会計知識的な部分もだいぶ隠蔽してくれるし、電子申告までワンストップでやってくれるのでたいへんスムーズ。</p>
<p>それから小規模企業共済。これは、掛け金そのまま(最大月7万円)所得控除で、受けとるときも税制的に優遇されるのでかなりお得。ぼくは調べるのめんどうで加入が遅れたけど、もっとはやくやっておけばよかったなと思っている。インフレ時にお得じゃないのではみたいな話もあるようだけど、実際どうなるのかよくわからない。いちおう金利連動の仕組み自体はあるんだけど、ここ20年以上ずっとデフレなので機能してない。あとは、国保のかわりに文美に入るとお得だっていう話もよく聞く。国保は実際かなり取られるからねー。ゲーム系の人だといけたりするらしいんだけど、純粋なITエンジニアだと加入条件満たさないようなので、ぼくは未加入。</p>
<p>あと、フリーランスプログラマ云々とはあまり関係ないかもしれけど、togglっていう稼働時間をメモっておけるウェブサービス。案件によってはお客さんに要求されたりするらしい。ここからダイレクトに報酬を計算できる。お客さんに要求されなかったとしても、じぶんのパフォーマンスを監視するためのツールとしてつかっておくといい。ぼくは、基本的に平日日中の行動はすべてこれで記録している。どのタスクにどれくらいかかったかとか、今日は何時間仕事したかとか、インターネットどれくらい見てたかとか、まるわかりになる。そしてそれをグラフ表示して見られる。プログラマなら、こういうデータをみるだけでもたのしいんじゃないだろうか。見積にたいして、実績が随分乖離してしまったからもっと多めに見積もらないとなあとか、さいきん働き過ぎてるなあとかいったことが客観的にわかるので、ふりかえりをするときに大活躍。</p>
</div>
<div class="section" id="section-20">
<h2><a class="toc-backref" href="#toc-entry-19">あるフリーランスプログラマの一日</a></h2>
<p>時間について言うと、ここ数年、ぼくの典型的な一日中のタイムスケジュールは、おおむねこんな感じになっている。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">典型的な1日のスケジュール <a href="https://t.co/CH5UUy8hLt">pic.twitter.com/CH5UUy8hLt</a></p>— 武藤スナイパーカスタム🔫 (@__tai2__) <a href="https://twitter.com/__tai2__/status/1280765918780338182?ref_src=twsrc%5Etfw">July 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>ざっくり毎月100時間とかその程度働いて暮らしている感じ。時間給なので、人月単価の額よりはすくないお金しか稼いでないけど、そんなにいそがしくなく、ワークライフバランス的にもちょうどいい感じで日々を送れている。じぶんがどのていどはたらくか、仕事以外のことにどの程度時間を割くかといったことを、完全に自己のコントロール下に置いて、ライフステージにあわせて柔軟に変えられるのはフリーランスプログラマの強みだと思う。独身でひまなら好きなだけ働けばいいし、子供がちいさいくて手がかかるあいだは今のぼくのように仕事をある程度おさえめにすることもできる。また、もうちょっと子供が大きくなって手がかからなくなってきたら、はたらく量を増やしてお金を稼ぐこともできる。もしそうしたければ。</p>
</div>
<div class="section" id="section-21">
<h2><a class="toc-backref" href="#toc-entry-20">フリーランスプログラマに向いてる人</a></h2>
<p>ぼくのフリーランスプログラマ観はこのツイートにまとめられる。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">長年フリーランスやってるけど、フリーランスの一番のメリットは自由な生き方ができるところ。だから、自由を強く求める性分の人にフリーランスは合ってる。</p>— 武藤スナイパーカスタム🔫 (@__tai2__) <a href="https://twitter.com/__tai2__/status/1202931752886366209?ref_src=twsrc%5Etfw">December 6, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>あたえられたタスクを淡々とこなすことができる人、人からあたえられたお題でプログラミングをずっとやっていることが苦痛でない人は、フリーランスプログラマに向いている。それから、将来の見通しが立たなくて夜も寝られないようではこまるので、まあなんとかなるだろうと考えられる、あるていど楽観的な性格も必要だとは思う。</p>
</div>
同じファイルを2度読み込むと速くなる2018-12-21T00:00:00+09:002018-12-21T00:00:00+09:00tai2tag:blog.tai2.net,2018-12-21:/buffer_cache_experiment.html<p>この記事は <a class="reference external" href="https://qiita.com/advent-calendar/2018/murasame-lab-2018">ムラサメ研究所 学会 Advent Calendar 2018</a> の21日目の記事です。</p>
<p>現代的なOSであれば、ディスクから読み込んだデータを …</p><p>この記事は <a class="reference external" href="https://qiita.com/advent-calendar/2018/murasame-lab-2018">ムラサメ研究所 学会 Advent Calendar 2018</a> の21日目の記事です。</p>
<p>現代的なOSであれば、ディスクから読み込んだデータをバッファーキャッシュ(UNIX系)あるいはファイルキャッシュ(Windows)などと呼ばれる
RAM上の領域にキャッシュしておく機能があります(以降バッファーキャッシュで統一)。
したがって、同じファイルを2回以上読みこむと、2回目以降のほうが高速に読み込める見込みが高いです。<sup id="sf-buffer_cache_experiment-1-back"><a href="#sf-buffer_cache_experiment-1" class="simple-footnote" title="1回目アクセスの時点でキャッシュがないと仮定">1</a></sup></p>
<p>本記事では、このことを実験的に確認し、その事実がアプリケーションプログラミングに与える影響を考察します。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-2" id="toc-entry-1">バッファーキャッシュとは</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-2">検証方法</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-3">検証用プログラム</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-4">検証結果</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-5">アプリケーションプログラミングへの影響</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-6">まとめ</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-7">参考リンク</a></li>
</ul>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-1">バッファーキャッシュとは</a></h2>
<p>バッファーキャッシュは、カーネルの管理しているメモリ空間に配置され、アプリのアクセスできるユーザー空間とは隔離されています。
これはカーネルによって透過的に管理されているため、ふだんアプリケーションプログラミングをしている分には、あまり意識することがありません。</p>
<div class="figure">
<img alt="Buffer Cache" src="https://blog.tai2.net/images/buffer-cache-experiment/disk_cache.png">
<p class="caption">バッファーキャッシュ</p>
</div>
<p>アプリがファイルからデータを読み込むためにシステムコールを発行すると、まずバッファーキャッシュに該当するデータが存在するか確認されます。
キャッシュ上にデータが存在する場合、そのデータがユーザー空間にコピーされます。ディスクアクセスが発生しないため高速です。</p>
<p>キャッシュ上にない場合は、ディスクからデータが読み込まれ、バッファーキャッシュにデータが充填されます。
ディスクアクセスが発生するため低速ですが、次回以降の同一データへのアクセスは高速化されることが期待できます。
ちなみに、1MBのデータをメインメモリから読み込むのとディスクから読み込むのでは <a class="reference external" href="http://highscalability.com/numbers-everyone-should-know">10倍くらい開きがある</a> ようです。</p>
<p>バッファーキャッシュの利用は、あくまでカーネル管理下で行われる裏方の処理なので、
アプリケーションから明示的にキャッシュを操作したり解放したりすることは、基本的にはできませんし、
以前アクセスしたからといって、次回アクセス時にキャッシュが残っているという保証もありません。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-2">検証方法</a></h2>
<p>どうすればバッファーキャッシュの効果を確認できるのか考えます。
基本的な考えかたとしては、ファイルからのデータ読み込み時間を計測し、初回アクセスと2回目のアクセスでは、後者のほうが速くなることを確かめれば良さそうです。バッファーキャッシュの増減やディスクアクセスなども観測できると、なお良いでしょう。</p>
<p>筆者が普段使っているのはmacOS(Darwin)なので、検証もmacOSで行います。CPUはCore i7 3.1GHz、RAM 16GB、SSDという環境です。
macOSでは、ファイルディスクリプタに対して、fcntlシステムコールでF_NOCACHEフラグを指定することで、
当該ファイルディスクリプタ経由でのアクセス時にキャッシュ充填をしないようにできます。</p>
<p>ただし、すでにバッファーキャッシュが存在する場合はそちらからデータを取ってきてしまうため、
実験のためにはキャッシュされていないことを保証する必要があります。
これにはpurgeコマンドが使えます。このコマンドを使うとバッファーキャッシュがクリアされます。</p>
<p>LinuxではO_DIRECTフラグ、WindowsではFILE_FLAG_NO_BUFFERINGフラグを使えば同様の挙動が実現できます。
というか、これらはキャッシュがあっても直接ディスクを読みにいくので、macOS以外のほうが実験しやすいかもしれません。</p>
<p>ディスクアクセスは、OS付属のfs_usageコマンドで確認できます。
バッファーキャッシュの量は、確認する方法がわかりませんでした。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-3">検証用プログラム</a></h2>
<p>検証用に以下のスクリプトを作成しました。
ファイルを繰り返し読み込み、読み込みにかかった時間を計測するプログラムです。</p>
<div class="highlight"><pre><span></span><span class="c1"># read_file_test.py</span>
<span class="kn">import</span> <span class="nn">fcntl</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="k">class</span> <span class="nc">measure_time</span><span class="p">():</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">label</span> <span class="o">=</span> <span class="s1">''</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">label</span> <span class="o">=</span> <span class="n">label</span>
<span class="k">def</span> <span class="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">t0</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
<span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">type</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">traceback</span><span class="p">):</span>
<span class="n">t1</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'</span><span class="si">{}</span><span class="s1">: </span><span class="si">{:.6f}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">label</span><span class="p">,</span> <span class="n">t1</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">t0</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">read</span><span class="p">(</span><span class="n">static</span><span class="p">,</span> <span class="n">no_cache</span><span class="p">,</span> <span class="n">parse</span><span class="p">):</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">static</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="k">if</span> <span class="n">no_cache</span><span class="p">:</span>
<span class="n">fcntl</span><span class="o">.</span><span class="n">fcntl</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">fileno</span><span class="p">(),</span> <span class="n">fcntl</span><span class="o">.</span><span class="n">F_NOCACHE</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">if</span> <span class="n">parse</span><span class="p">:</span>
<span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">parse_args</span><span class="p">():</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s1">'File read test'</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'input'</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s1">'?'</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">'file to read'</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'-c'</span><span class="p">,</span> <span class="s1">'--count'</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">'number of repetition'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'-n'</span><span class="p">,</span> <span class="s1">'--no-cache'</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s1">'no_cache'</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">'store_true'</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'-s'</span><span class="p">,</span> <span class="s1">'--sleep'</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">'sleep time'</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'-p'</span><span class="p">,</span> <span class="s1">'--parse'</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">'store_true'</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">'parse data as json'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parse_args</span><span class="p">()</span>
<span class="k">with</span> <span class="n">measure_time</span><span class="p">(</span><span class="s1">'total'</span><span class="p">):</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">count</span><span class="p">):</span>
<span class="k">with</span> <span class="n">measure_time</span><span class="p">(</span><span class="n">i</span><span class="p">):</span>
<span class="n">read</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">input</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">no_cache</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">parse</span><span class="p">)</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">sleep</span><span class="p">:</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">sleep</span><span class="p">)</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>読み込み回数、F_NOCACHEフラグの有無、繰り返し毎のsleepなどが指定できます。
また、実際のプログラムでは読み込んだデータに対してなんらかの処理をするはずなので、
典型的なタスクとして、読み込んだデータをJSONとしてパースすることもできます。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-4">検証結果</a></h2>
<p>まずは、1MiBのデータを作成します。</p>
<div class="highlight"><pre><span></span>$ dd if=/dev/random of=1MB_data count=1024 bs=1024
</pre></div>
<p>キャッシュOFFで100回,1MBのデータを読み込んでみます。
最初にpurgeコマンドを実行してキャッシュクリアしていることに注意してください。</p>
<div class="highlight"><pre><span></span>$ sudo purge && python3 read_file_test.py 1MB_data --count=100 --no-cache
中略
97: 0.001974
98: 0.002611
99: 0.001890
total: 0.186595
</pre></div>
<p>1回の読み込みに平均1.87ミリ秒程度かかりました。</p>
<p>今度はキャッシュONで読み込んでみます。
まずは一回読み込んでキャッシュを充填させます。</p>
<div class="highlight"><pre><span></span>$ python3 read_file_test.py 1MB_data --count=1
</pre></div>
<p>この状態で実行すれば、ディスクアクセスは発生しないはずです。</p>
<div class="highlight"><pre><span></span>$ python3 read_file_test.py 1MB_data --count=100
中略
97: 0.000339
98: 0.000557
99: 0.000364
total: 0.025158
</pre></div>
<p>平均は0.25ミリ秒程度まで縮まりました。</p>
<p>キャッシュなしだと、数倍〜十数倍程度は遅くなるようです。
おおむね期待通りの結果になりました。</p>
<p>次は、キャッシュON/OFFで実際にディスクアクセスパターンが変化しているのか確認します。
ディスクアクセスをリアルタイムに監視するために1秒のsleepを入れて、キャッシュOFFでスクリプトを実行します。</p>
<div class="highlight"><pre><span></span>$ python3 read_file_test.py 1MB_data --sleep=1 --no-cache
</pre></div>
<p>実行中にfs_usageコマンドを使うことでディスク読み込みが発生しているか確認します。</p>
<div class="highlight"><pre><span></span>$ sudo fs_usage -f diskio `pgrep -f read_file_test.py`
Password:
23:19:05 RdData[AN] 1MB_data 0.001928 W Python
23:19:06 RdData[AN] 1MB_data 0.002141 W Python
23:19:07 RdData[AN] 1MB_data 0.002193 W Python
23:19:08 RdData[AN] 1MB_data 0.002167 W Python
23:19:09 RdData[AN] 1MB_data 0.002226 W Python
23:19:10 RdData[AN] 1MB_data 0.001808 W Python
23:19:11 RdData[AN] 1MB_data 0.002109 W Python
23:19:12 RdData[AN] 1MB_data 0.002303 W Python
23:19:13 RdData[AN] 1MB_data 0.001472 W Python
23:19:14 RdData[AN] 1MB_data 0.001120 W Python
23:19:15 RdData[AN] 1MB_data 0.002314 W Python
^C
</pre></div>
<p>たしかに、1秒ごとにディスク読み込みが発生が発生しています。
今度は、キャッシュONで実行してみると、</p>
<div class="highlight"><pre><span></span>$ sudo purge && python3 read_file_test.py 1MB_data --sleep=1
</pre></div>
<p>同様にfs_usageで確認します。</p>
<div class="highlight"><pre><span></span>$ sudo fs_usage -f diskio `pgrep -f read_file_test.py`
^C
</pre></div>
<p>出力がなにもありません。
キャッシュONのときには、たしかにディスクアクセスが発生していません。
なお、バッファーキャッシュの増減もvm_statコマンドなどで監視できそうな気がしたのですが、
結果をどう解釈して良いかわからなかったので省略します。</p>
<p>では、読み込むファイルサイズを変えると、結果は変わるでしょうか?
次はこの疑問を確かめてみましょう。</p>
<p>さきほど作成した1MiBに加えて、1KiB,1GiBのデータを作成します。</p>
<div class="highlight"><pre><span></span>$ dd if=/dev/random of=1KB_data count=1 bs=1024
$ dd if=/dev/random of=1GB_data count=1048576 bs=1024
</pre></div>
<p>1KiBキャッシュあり</p>
<div class="highlight"><pre><span></span>$ python3 read_file_test.py 1KB_data --count=1 && python3 read_file_test.py 1KB_data --count=100
中略
total: 0.008201
</pre></div>
<p>1KiBキャッシュなし</p>
<div class="highlight"><pre><span></span>$ sudo purge && python3 read_file_test.py 1KB_data --count=100 --no-cache
中略
total: 0.025077
</pre></div>
<p>1MiBキャッシュあり</p>
<div class="highlight"><pre><span></span>$ python3 read_file_test.py 1MB_data --count=1 && python3 read_file_test.py 1MB_data --count=100
中略
total: 0.023998
</pre></div>
<p>1MiBキャッシュなし</p>
<div class="highlight"><pre><span></span>$ sudo purge && python3 read_file_test.py 1MB_data --count=100 --no-cache
中略
total: 0.174918
</pre></div>
<p>1GiBキャッシュあり</p>
<div class="highlight"><pre><span></span>$ python3 read_file_test.py 1GB_data --count=1 && python3 read_file_test.py 1GB_data --count=100
中略
total: 72.426231
</pre></div>
<p>1GiBキャッシュなし</p>
<div class="highlight"><pre><span></span>$ sudo purge && python3 read_file_test.py 1GB_data --count=100 --no-cache
中略
total: 78.807513
</pre></div>
<p>1回の平均読み込み時間(ミリ秒)</p>
<table border="1" class="docutils">
<colgroup>
<col width="16%">
<col width="42%">
<col width="42%">
</colgroup>
<thead valign="bottom">
<tr><th class="head"> </th>
<th class="head">キャッシュあり</th>
<th class="head">キャッシュなし</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>1KiB</td>
<td>0.08</td>
<td>0.25</td>
</tr>
<tr><td>1MiB</td>
<td>0.24</td>
<td>1.75</td>
</tr>
<tr><td>1GiB</td>
<td>724.26</td>
<td>788.08</td>
</tr>
</tbody>
</table>
<p>1GiBのときのみ、キャッシュなしにも関わらず、2回目以降の読み込みが速くなるという不思議な現象が見られました。
また、1GiBになると、若干の速度向上は見られるものの、それまで見られていた数倍レベルの速度向上が見られなくなりました。
この速度低下がなにに起因するものなのか、筆者には確認する方法が思い付きません。
ちなみに、1GiBの場合でもディスクからの読み込み自体は発生していません。
全データキャッシュに乗ってはいるようです。</p>
</div>
<div class="section" id="section-6">
<h2><a class="toc-backref" href="#toc-entry-5">アプリケーションプログラミングへの影響</a></h2>
<p>ここまでで確認できた、バッファーキャッシュによって2回目以降のファイル読み込みが高速化されるという事実は、
アプリケーションプログラミングにたいして何か影響を及ぼし得るでしょうか?</p>
<p>ひとつのアプリ内において、コード的に離れた部分で、同一のファイルに対して何度も処理を行うケースが考えられます。
同一のファイルを何度もオープンして読み込むのは無駄が多いような気がしますし、
全体の処理速度がそのために遅くなりはしないか、不安な気持ちが湧くかもしれません。</p>
<p>何度も読み込むよりは、読み込んだデータをアプリのメモリ内にキャッシュしておいて、再利用したくなってきます。
ですが、このようなキャッシュ処理を追加すると余分なコードが発生し、すこしコードが汚れてしまうかもしれません。
逐次必要に応じてファイルを読み込む設計ならば、対象のファイル名だけが各部に行き渡っていれば十分です。</p>
<p>このような迷いが生じたときに、OSのバッファーキャッシュにキャッシュを任せることによって、
アプリのコードをシンプルに保つ望みが持てるかもしれません。</p>
<p>ところで、このようなシチュエーションでは、ファイルを読み込むだけではなく、
実際に読み込んだデータに対してなんらかの処理を加えるはずです。
ですから、単にファイル読み込みの時間を計測するだけなく、データ処理の時間も合わせて測らなければ片手落ちです。
ここでは、よくある処理の例として、読み込んだデータをJSONとしてパースしてみます。
<sup id="sf-buffer_cache_experiment-2-back"><a href="#sf-buffer_cache_experiment-2" class="simple-footnote" title="ここまで書いて思いましたが、アプリケーションレイヤーならば、読み込み+データ処理結果も含めてキャッシュを検討するケースのほうが多そうですね…">2</a></sup></p>
<p>比較用に、純粋にJSONパースの時間だけを計測するためのスクリプトも作成しました。</p>
<div class="highlight"><pre><span></span><span class="c1"># parse_json_test.py</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="k">class</span> <span class="nc">measure_time</span><span class="p">():</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">label</span> <span class="o">=</span> <span class="s1">''</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">label</span> <span class="o">=</span> <span class="n">label</span>
<span class="k">def</span> <span class="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">t0</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
<span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">type</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">traceback</span><span class="p">):</span>
<span class="n">t1</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'</span><span class="si">{}</span><span class="s1">: </span><span class="si">{:.6f}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">label</span><span class="p">,</span> <span class="n">t1</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">t0</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">parse_json</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">parse_args</span><span class="p">():</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s1">'JSON parse test'</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'input'</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="s1">'?'</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">'file to read'</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'-c'</span><span class="p">,</span> <span class="s1">'--count'</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">'number of repetition'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parse_args</span><span class="p">()</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">input</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">,</span> <span class="n">measure_time</span><span class="p">(</span><span class="s1">'total'</span><span class="p">):</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">count</span><span class="p">):</span>
<span class="k">with</span> <span class="n">measure_time</span><span class="p">(</span><span class="n">i</span><span class="p">):</span>
<span class="n">parse_json</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>JSONデータは、 <a class="reference external" href="https://next.json-generator.com/EJKoXD-xU">JSON Generator</a> というサイトで生成した <a class="reference external" href="https://tai2.net/misc/sample.json">145KiB程度のデータ</a> です。</p>
<p>パースあり、キャッシュあり</p>
<div class="highlight"><pre><span></span>$ python3 read_file_test.py sample.json --count=1 && python3 read_file_test.py sample.json --count=100 --parse
中略
total: 0.159036
</pre></div>
<p>パースなし、キャッシュあり</p>
<div class="highlight"><pre><span></span>$ python3 read_file_test.py sample.json --count=1 && python3 read_file_test.py sample.json --count=100
中略
total: 0.007628
</pre></div>
<p>パースあり、キャッシュなし</p>
<div class="highlight"><pre><span></span>sudo purge && python3 read_file_test.py sample.json --count=100 --parse --no-cache
中略
total: 0.248836
</pre></div>
<p>パースなし、キャッシュなし</p>
<div class="highlight"><pre><span></span>sudo purge && python3 read_file_test.py sample.json --count=100 --no-cache
中略
total: 0.053962
</pre></div>
<p>パースのみ(データ読み込みなし)</p>
<div class="highlight"><pre><span></span>$ python3 parse_json_test.py sample.json --count=100
中略
total: 0.151621
</pre></div>
<p>1回の平均処理時間(ミリ秒)</p>
<table border="1" class="docutils">
<colgroup>
<col width="45%">
<col width="27%">
<col width="27%">
</colgroup>
<thead valign="bottom">
<tr><th class="head"> </th>
<th class="head">パースあり</th>
<th class="head">パースなし</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>キャッシュあり</td>
<td>1.59</td>
<td>0.08</td>
</tr>
<tr><td>キャッシュなし</td>
<td>2.49</td>
<td>0.54</td>
</tr>
<tr><td>データ読み込みなし</td>
<td>1.52</td>
<td> </td>
</tr>
</tbody>
</table>
<p>この結果からわかるのは、実際のデータ処理に比べれば、ファイルの読み込み時間は比較的割り合いが小さい、ということです。
ファイル読み込み時間の占める比率が小さいのであれば、そもそもキャッシュがどうこうを気にする意味すらありません。
ただ、この計測結果はPythonで行ったもので、CやC++でJSONのパースを行えば簡単に10倍くらいは差が付くため、
C/C++アプリではファイル読み込みの締める比重が大きくなり、バッファーキャッシュの重要性が相対的に増すということは、十分に考えられます。</p>
</div>
<div class="section" id="section-7">
<h2><a class="toc-backref" href="#toc-entry-6">まとめ</a></h2>
<p>OSのバッファーキャッシュが有効に働くため、1MiB程度のファイル読み込みならば高速化されることが確認できました。
1GiBだとなぜか高速化されませんでしたが、これは原因がよくわかりません。
また、純粋なデータ読み込みよりもJSONパースのほうがはるかに時間がかかることもわかりました。</p>
<p>したがって、アプリケーションプログラミングにおいて、純粋なファイル読み込みの時間というのは、あまり気にしないで良さそうです。</p>
</div>
<div class="section" id="section-8">
<h2><a class="toc-backref" href="#toc-entry-7">参考リンク</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/PerformanceOverview/PerformanceTools/PerformanceTools.html">Performance Tools</a> macOS付属のパフォーマンス計測用ツール集</li>
<li><a class="reference external" href="https://github.com/axboe/fio/issues/48">OSX fcntl(fd, F_NOCACHE, 1) not equivalent to O_DIRECT on Linux #48</a> F_NOCACHEの挙動(キャッシュがあると見にいっちゃう)について</li>
<li><a class="reference external" href="https://www.cnet.com/news/purge-the-os-x-disk-cache-to-analyze-memory-usage/">Purge the OS X disk cache to analyze memory usage</a> purgeコマンドの解説</li>
<li><a class="reference external" href="http://highscalability.com/numbers-everyone-should-know">Numbers Everyone Should Know</a> ディスクアクセスやメモリアクセスなど各種速度まとめ</li>
<li><a class="reference external" href="https://www.reddit.com/r/programming/comments/3pojrz/the_fastest_json_parser_in_the_world/">The fastest JSON parser in the world?</a> 各種言語でのJSONパースベンチマーク</li>
<li><a class="reference external" href="https://docs.microsoft.com/en-us/windows/desktop/fileio/file-caching">File Caching</a> Windowsのファイルキャッシュについて</li>
<li><a class="reference external" href="https://www.tldp.org/LDP/sag/html/memory-management.html">Chapter 6. Memory Management</a> Linux仮想メモリの概要</li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-buffer_cache_experiment-1">1回目アクセスの時点でキャッシュがないと仮定 <a href="#sf-buffer_cache_experiment-1-back" class="simple-footnote-back">↩</a></li><li id="sf-buffer_cache_experiment-2">ここまで書いて思いましたが、アプリケーションレイヤーならば、読み込み+データ処理結果も含めてキャッシュを検討するケースのほうが多そうですね… <a href="#sf-buffer_cache_experiment-2-back" class="simple-footnote-back">↩</a></li></ol>良いモジュールの探しかたとモジュール哲学(browserify-handbookから抜粋翻訳)2018-11-05T00:00:00+09:002018-11-05T00:00:00+09:00tai2tag:blog.tai2.net,2018-11-05:/node_module_philosophy.html<p><a class="reference external" href="https://github.com/browserify/browserify-handbook#finding-good-modules">browserify-handbook</a> からの抜粋翻訳です。
ライセンスはCC BY 3.0 です。
書いたのは、かつてNode.js界で人気のライブラリをたくさん作って …</p><p><a class="reference external" href="https://github.com/browserify/browserify-handbook#finding-good-modules">browserify-handbook</a> からの抜粋翻訳です。
ライセンスはCC BY 3.0 です。
書いたのは、かつてNode.js界で人気のライブラリをたくさん作っていた <a class="reference external" href="https://github.com/substack">substack</a> 。</p>
<p><a class="reference external" href="https://blog.tai2.net/rails_is_omakase.html">DHHのおまかせ思想</a> と対比するとおもしろいです。</p>
<p>変更点:</p>
<ul class="simple">
<li>原文でrequire()となっていた部分を「requireあるいはimport」としました。</li>
</ul>
<hr class="docutils" />
<div class="section" id="section-1">
<h2>良いモジュールの探しかた</h2>
<p>ブラウザで動く良いnpmモジュールを探すにあたっての役に経つ経験則をいくつか書いておく:</p>
<ul class="simple">
<li>npmでインストールできる</li>
<li>READMEのコードスニペットで <code>require()</code> あるいは <code>import</code> を使っている ― パッと見で、現在書いているコードにどうやって組み込むのかがわかるべきだ</li>
<li>スコープと目的にたいして、とても明快で限定されたアイデアを持っている</li>
<li>いつ、他のライブラリに委譲すべきかわかっている ― それ自身であまりにもたくさんのことをこなそうとしない</li>
<li>ソフトウェアのスコープ、モジュラリティー、インターフェイスについて、自分が同意できるような意見を持った作者によって書かれている、あるいはメンテされている(しばしば、コード/ドキュメントを詳細に読むよりも手っ取り早いショートカットになる)</li>
<li>どのモジュールが、評価中のライブラリに依存しているのか調べる ― これは、npmに公開されたモジュールのためのパッケージページに焼き込まれている</li>
</ul>
<p>githubのスター数や、プロジェクトアクティビティー、見掛け倒しのランディングページのような他の指標は、これらほどには信頼できない</p>
<div class="section" id="section-2">
<h3>モジュール哲学</h3>
<p>人々は、かつて、お手軽なユーティリティースタイルのあれこれをまとめてエクスポートするのが、プログラマがコードを利用する主な方法になると考えた、なぜなら、それがnpm以外のほとんどのプラットフォームにおいてコードをエクスポートやインポートする主要な方法だからだ。実のところ、npmにおいてさえもいまだにそのようなやりかたは続いている。</p>
<p>しかしながら、この <a class="reference external" href="https://github.com/substack/node-mkdirp/issues/17">典型的なメンタリティー</a> (テーマ的に関連しているが分離可能な機能をひとつのパッケージに含める方を向いている)は、前github、前npm時代における公開と探索の困難を解決するための遺物であるように思われる。</p>
<p>利便性の庇護の元に機能を一箇所にまとめてエクスポートしようとするモジュールには、二つ大きな問題がある: 境界線を決めるための縄張り争いと、どのモジュールが何をしているのかの探索だ。</p>
<p>機能がごちゃ混ぜになったパッケージは、どの新機能が入り、どれがそうでないのかの <a class="reference external" href="https://github.com/jashkenas/underscore/search?q=%22special-case%22&ref=cmdform&type=Issues">境界を管理するのに多大な時間を浪費させる</a> 。この種のパッケージの問題領域において、スコープが何なのかについて明快で自然な境界はない、すべては <a class="reference external" href="https://blog.tai2.net/rails_is_omakase.html">どこかのだれかの独り善がりな見解だ</a> 。</p>
<p>Node, npm, そしてbrowserifyは、そうではない。これらは、公式にアラカルト、参加型であり、見解の相違や、雨後の竹の子のように新しいアイデア・アプローチが出てくることをむしろ祝福し、こういったものを一致、標準、あるいは「ベストプラクティス」の名の元に押さえ込むことを良しとしない。</p>
<p>ガウシアンぼかしが必要な人で、「うーむ、どのモジュールがガウシアンぼかしを含んでいるか知るためには、まず、一般的な数学、統計学、画像処理、それから便利ライブラリのチェックからはじめる必要があるだろうな。stat2か、image-pack-utilsか、maths-extra、あるいはunderscoreに入っているということもありえるか?」などと考える人はいない。ない。こんなやりかたありえない。やめるんだ。ガウシアンぼかしが必要な人は、npmをgaussinで検索し、直ちに <a class="reference external" href="https://github.com/browserify/browserify-handbook#finding-good-modules">ndarray-gaussian-filter</a> が目に入り、それはまさしくかれらの欲しいものである。そして、だれかの巨大で便利だが放置された領地を前に呆然とするかわりに、かれらの実際の問題に戻る。</p>
</div>
</div>
Railsはおまかせ(Rails is omakase翻訳)2018-11-05T00:00:00+09:002018-11-05T00:00:00+09:00tai2tag:blog.tai2.net,2018-11-05:/rails_is_omakase.html<p>作者DHHによる <a class="reference external" href="http://david.heinemeierhansson.com/2012/rails-is-omakase.html">Railsの思想を語ったエッセイ</a> の翻訳です。
DHHから許可を得て公開しています(ライセンス不明)。</p>
<p><a class="reference external" href="https://blog.tai2.net/node_module_philosophy.html">substackのモジュ …</a></p><p>作者DHHによる <a class="reference external" href="http://david.heinemeierhansson.com/2012/rails-is-omakase.html">Railsの思想を語ったエッセイ</a> の翻訳です。
DHHから許可を得て公開しています(ライセンス不明)。</p>
<p><a class="reference external" href="https://blog.tai2.net/node_module_philosophy.html">substackのモジュール哲学</a> と対比するとおもしろいです。</p>
<hr class="docutils" />
<p>世の中には、アラカルトなソフトウェア環境がたくさんある。ものを食べに来た場所で、まずしなくちゃならないのは、きみがまさに欲しているものを注文するために、選択肢のメニューを注意深く見渡すことだ。ORMにはこれを、テンプレート言語にはこれを、そしてこのルーティングライブラリを使っておしまい。もちろん、きみは自分がなにが欲しいのかわかっていなければならないだろう、そして、もしいつでも同じものを注文するのなら、知識の限界を引き延ばすための勉強をすることはめったにないだろう。まあ、あるんだけど。これは、ソフトウェアを使うときの、とても一般的なやりかただ。</p>
<p>Railsはそうではない。Railsはおまかせだ。料理人のチームが素材を選び、APIを設計し、きみのかわりに食べる順番を決める。それらは、なにがおいしいフルスタックフレームワークの足しになるかという、かれらの考えにもとづいている。メニューは個人的であると同時に、風変わりなものであり得る。どこのだれにでも魅力的になるよう意図されたものではないんだ。</p>
<p>だからと言って、お客がメニューにないことを表現できないわけじゃない。取り替えることは許される、理由があるなら。test/unitが好きじゃない? 問題ない、自分でrspecを持ってくればいい。CoffeeScriptがお気に召さない? Gemfileから一行削除しよう。</p>
<p>もっと言えば、メニューに提案することだってできる。良いアイデアは、だれが考えたかに関係なく良いアイデアだ。けれども、親切な提案と喧嘩腰のディナーの間には微妙な違いがある。その違いは、たいていの場合、提案が断られたときにあきらかになる。「もうしわけないけど、ホットドッグはうちの寿司にはあまり合わないんだ。それから、きみはうなぎが好きじゃないかもしれないけれど、うちでは理由があって取り入れてる。けど、提案してくれたのはありがとうな!」メニューについてのほとんどの議論がこれで終わりならどれだけよかったことか。</p>
<p>でも、これでは終わらない、よね? ふつうは、そのまま続ける。 <strong>だけど、おれはウナギが嫌いなんだ!!!デフォルトのコース設定にそんなのがあると、舌がおかしくなっちまうよ兄弟!!はずしてくれよおおお!!</strong> オーケイ兄弟、座って酒でも飲んでなよ。</p>
<p>おれらが、ときにおれが、Railsというおまかせ体験の料理長として、皿に盛るものを決めるとき、たいていの場合、考え抜かれた味と嗜好を基準にしている。おれは、この決まったやりかたで10年間やってきた。1万時間以上をRailsに注ぎ込んできた。だからと言って、おれの味覚がきみに合うようになるわけではない、けど、確実に、よくまとまっているということは言えるわけだ。</p>
<p>なにが好ましい機能かについて意見が合わないことはあり得るけど、結論は、「食べるのはやめておこう」ってことになる可能性が高い。きみが一見さんならなおさらそうだ。おれらのやりかたに使った時間がすくないほど、調理場で手伝う立場からは遠のき、意見の重みは減る。とくに、大声で攻撃的なやりかたで表現されている意見は。</p>
<p>意外なことに、こういう意見に気分を害する人たちがときどきいる。「しかし、ぼくの意見は、きみのと同じくらい妥当だ!」いいや、ぜんぜんそんなことはない。きみの意見は、きみにとって妥当だが、このレストランでおれがデザインしたメニューには、ほとんど確実に当てはまらない。きみにはいつでも行使できる力がある、それは店に通わないことで自分の意見を表明することだ。メニューのほとんどのものが気に食わないなら、いったいどうしてまだテーブルに着いてるんだい? ドアはすぐそこだ、出るときはできるだけお静かに。</p>
<p>もし長期的な影響をRailsのメニューの設計に対して持ちたいなら、それができる立場まで登り詰めなければならないだろう。おれらは、さらに3人をRailsコアグループに今週追加した。実際にそれをなしとげた連中だ。かれらは、「どんな手助けができる?」命令や、「ここにぼくがデザインした一皿がある、味見してみてよ」をたずさえてやってきた。コードで貢献した人だけがRaisの方向性に重大な影響を与えられるのであって、ただGithubのコメントスレッドに現れてCoffeeScriptの害悪について文句を言うだけで影響力を持った人は1人もいない。</p>
<p>この振舞いが無意味だとして、なぜ人々がそのような行為を選択するのか、じっくり考えるのも悪くはないだろう。おれのお気に入り理論は、それがその時点においては意味のある行動に感じられるから、というものだ。かれらは、日々の活動において、ものごとがどのように行われているのかにまったく注意を払っていないのだけど、いくらかの印を塗りたくってバリケードによじ登るチャンスが目の前にある。そう、おれは役目を果たしたぞ!立場を明確にしてやった! あんなもんは要らん…えーっとTurbolinksだっけ? 今週おれらが反対したのはCoffeeScriptだったか? いやBundlerだっけか? とにかく <strong>なんかか</strong> 気に食わん! そうだ!!!</p>
<p>締め括りとして、大局的な観点から見てみるのは悪くないだろう。おれは、この決まったやりかたで提供されているものが、おおむね気に入っているだろうか? 自分の見解の違いを、例外あるいは代替として、好みに合わないくつかのものに対して表現できるか? もしそうなら、調理場の料理人たちに、かれらがほんとうに思ったことに取り組むのが、最良の選択なのだと考えられるようにすることで、助けてあげるべきかもしれない。それは、おれがなにもかもを好きでいなければならないということではないが、世界中にうなぎを愛させようという、病的な冗談めいた陰謀なのだという考えを止められる。</p>
.gitignoreに.DS_Storeなど個人環境依存のファイルを含めても良いのではないか2018-05-25T00:00:00+09:002018-05-25T00:00:00+09:00tai2tag:blog.tai2.net,2018-05-25:/gitignore.html<p>Gitには、リポジトリに含めたくないファイルを指定できる .gitignore というファイルがあります。
通常は、これ自体リポジトリ …</p><p>Gitには、リポジトリに含めたくないファイルを指定できる .gitignore というファイルがあります。
通常は、これ自体リポジトリにコミットされ、チームで共有されます。</p>
<p>この.gitignoreファイルに、 .DS_Store(Mac環境で自動的にOSが生成するファイル)や、.swp(vimが一時的に生成するスワップファイル)など個人環境に依存するファイルは指定すべきではない、という話をツイッターで見かけて気になりました。</p>
<p>筆者は、これまで、こういったファイルを積極的に指定するようにしていたからです。</p>
<div class="section" id="ds-store">
<h2>.DS_Storeなどを入れるべきでない理由</h2>
<p>.gitignoreに.DS_Storeやなどを指定すべきでない理由として上げられているのは、vimのユーザーが参加したら.swpを、 VS Codeユーザーが参加したら.vscode、Windowsユーザーが参加したらThumbs.dbといったように、開発者個々の事情で必要な項目を足していったら際限がない、というものです。</p>
</div>
<div class="section" id="ds-store-1">
<h2>.DS_Storeを入れたい動機</h2>
<p>筆者が.DS_Storeなどのファイルを.gitignoreに指定したいと思う理由は、チーム開発において、これらの設定をリポジトリレベルで共有できれば、個々のユーザー環境設定に依存せずに済むからです。これにより、.DS_Storeのようなゴミファイルがリポジトリに混入してしまうことを確実に防げます。</p>
<p>入れないとなると、こういったファイルがリポジトリに入れられるたびに、開発者にそれらをグローバルな設定として無視するように頼む、事前にそれらの設定をしておくようにドキュメントを準備する、などの手間が生じます。</p>
<p>開発者個々の事情で項目が増えていくという懸念に関しては、筆者はまったく心配しておらず、必要な人が自分に必要な設定をどんどん追加して、それをチーム全体で共有すればいいという考えです。Windowsユーザーが入ったらThumbs.dbを追加すればいいですし、VS Codeユーザーが入ったら.vscodeも追加すればいいのです。</p>
</div>
<div class="section" id="gitignore">
<h2>gitignoreの公式ドキュメント</h2>
<p><a class="reference external" href="https://git-scm.com/docs/gitignore">公式ドキュメント</a> には、.gitignoreを含むgitの無視ファイルの設定について、以下のようにユースケースが挙げられています。</p>
<blockquote>
<p>Patterns which should be version-controlled and distributed to other repositories via clone (i.e., files that all developers will want to ignore) should go into a .gitignore file.</p>
<p>Patterns which are specific to a particular repository but which do not need to be shared with other related repositories (e.g., auxiliary files that live inside the repository but are specific to one user’s workflow) should go into the $GIT_DIR/info/exclude file.</p>
<p>Patterns which a user wants Git to ignore in all situations (e.g., backup or temporary files generated by the user’s editor of choice) generally go into a file specified by core.excludesFile in the user’s ~/.gitconfig. Its default value is $XDG_CONFIG_HOME/git/ignore. If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/ignore is used instead.</p>
<p>バージョン管理されて、他のリポジトリにclone経由で配布されるべきパターン(例えば、すべての開発者が無視したいであろうファイル)は、.gitignoreに含めるべき。</p>
<p>特定のリポジトリに固有だが、他の関連付けられたリポジトリと共有する必要のないパターン(たとえば、リポジトリ内に存在する補助的なファイルだが、ユーザーのワークフローに固有のもの)は、 $GIT_DIR/info/excludeファイルに含めるべき。</p>
<p>ユーザーが、いつでもGitに無視させたいパターン(たとえば、 ユーザーが選んだエディタによって生成されたバックアップや一時ファイル)は、一般に、ユーザーの ~/.gitconfig内にあるcore.excludesFileで指定されたファイルに含める。デフォルト値は $XDG_CONFIG_HOME/git/ignore。もし $XDG_CONFIG_HOME がセットされていないか空の場合、 $HOME/.config/git/ignore が代わりに使われる。</p>
<p>(日本語訳は筆者による)</p>
</blockquote>
<p>これを素直に解釈すると、.DS_Storeは、.gitignoreに指定せず、3番目のcore.excludesFileに入れるのが、マニュアルで推奨される使いかたのようです。ただ、3番目の説明については、 should(すべき)という表現を使っていない点は注目したいところです。ユーザーがいつでもGitに無視させたいパターンをcore.excludeFile以外で指定することを許容しているようにも読めます。</p>
</div>
<div class="section" id="section-1">
<h2>一般的なリポジトリではどうしているか</h2>
<p>世間のプロジェクトではどのように運用しているのか気になったので、いくつかのプロジェクトをランダムにピックアップして調べました。</p>
<table border="1" class="docutils">
<colgroup>
<col width="13%" />
<col width="87%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">プロジェクト</th>
<th class="head">.DS_Storeなどを含めている</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>git</td>
<td><a class="reference external" href="https://github.com/git/git/blob/e144d126d74f5d2702870ca9423743102eec6fcd/.gitignore">No</a></td>
</tr>
<tr><td>Rails</td>
<td><a class="reference external" href="https://github.com/rails/rails/blob/cd4a88123dc17462d5c2ff29172b0366bca14e0e/.gitignore">No</a></td>
</tr>
<tr><td>Linux</td>
<td><a class="reference external" href="https://github.com/torvalds/linux/blob/b50694381cfc22dce3a60a291cdae294a5e5777c/.gitignore">No</a></td>
</tr>
<tr><td>vim</td>
<td><a class="reference external" href="https://github.com/vim/vim/blob/833093bfb0e4a7f89b5adc66babcfa8ac09cfda9/.gitignore">No</a></td>
</tr>
<tr><td>express</td>
<td><a class="reference external" href="https://github.com/expressjs/express/blob/3ed5090ca91f6a387e66370d57ead94d886275e1/.gitignore">Yes</a></td>
</tr>
<tr><td>flow</td>
<td><a class="reference external" href="https://github.com/facebook/flow/blob/b07e579a0ce30ff52a237bd12192f3b04f87d16b/.gitignore">Yes</a></td>
</tr>
<tr><td>React</td>
<td><a class="reference external" href="https://github.com/facebook/react/blob/76e07071a11cd6e4796ad846bc835a18c8f49647/.gitignore">Yes</a></td>
</tr>
<tr><td>docker/cli</td>
<td><a class="reference external" href="https://github.com/docker/cli/blob/0089f172b781ab07dd1410c385da0896e30ca660/.gitignore">Yes</a></td>
</tr>
<tr><td>docker/compose</td>
<td><a class="reference external" href="https://github.com/docker/compose/blob/706164accd1eb8f12782dc12bb84c64aea742807/.gitignore">Yes</a></td>
</tr>
<tr><td>sinatra</td>
<td><a class="reference external" href="https://github.com/sinatra/sinatra/blob/5149dc9e0b0e281231b91223c6a414c905ad3a96/.gitignore">Yes</a></td>
</tr>
<tr><td>rust</td>
<td><a class="reference external" href="https://github.com/rust-lang/rust/blob/9823cb99c5779c0910a0d0a232966b37dfda73fd/.gitignore">Yes</a></td>
</tr>
<tr><td>chromium</td>
<td><a class="reference external" href="https://github.com/chromium/chromium/blob/2fd549b213e513551f15f193bfee46319cd48920/.gitignore">Yes</a></td>
</tr>
<tr><td>gechko</td>
<td><a class="reference external" href="https://github.com/mozilla/gecko-dev/blob/597ad02dc79ede5fde68cafab874bf000e47f50b/.gitignore">Yes</a></td>
</tr>
</tbody>
</table>
<p>Railsでは、</p>
<div class="highlight"><pre><span></span># Don't put *.swp, *.bak, etc here; those belong in a global .gitignore.
# Check out https://help.github.com/articles/ignoring-files for how to set that up.
</pre></div>
<p>のように明示的に禁止されています。</p>
<p>Linuxでは、cscope,ctags,gtagsといった補助ツールのものや、 <code>*~</code> といった(おそらくviの)バックアップファイルも含まれています。これは、これらのツールがプロジェクトの標準的な開発ツールとして認められているからではないかと思います。</p>
<p>同様に、vimでは.swp(vim固有のファイル)が含まれていますが、これもvimの開発者はvimを使う(つまり個人環境に依存しない)と想定されるためでしょう。</p>
<p>しかし、上記の簡単な調査結果を見る限り、.DS_Storeを.gitignoreに含めるのが少数派とまでは言えないのではないでしょうか。</p>
</div>
<div class="section" id="section-2">
<h2>結論</h2>
<p>公式ドキュメントの典型的なユースケースとは違っていることを承知の上で、
.DS_Storeや.swpなどの個人環境に依存する設定を.gitignoreに指定しても良いと思います。</p>
<p>それは、</p>
<ul class="simple">
<li>余計なやりとりを防げるという合理的なメリットがある</li>
<li>世間のプロジェクトを見回しても、.DS_Storeを含めるのはそれほど異常なことではない</li>
</ul>
<p>からです。</p>
</div>
Node.jsクイズ第58問 ./node_modules直下にはどのパッケージが入る?2018-05-01T00:00:00+09:002018-05-01T00:00:00+09:00tai2tag:blog.tai2.net,2018-05-01:/node-quiz-about-npm-install.html<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-1" id="toc-entry-1">バッケージの集合をまとめるためのパッケージを作りたい</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-2">問題</a></li>
<li><a class="reference internal" href="#node" id="toc-entry-3">Nodeモジュールの検索アルゴリズム</a></li>
<li><a class="reference internal" href="#npm-install" id="toc-entry-4">npm installのアル …</a></li></ul></div><div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-1" id="toc-entry-1">バッケージの集合をまとめるためのパッケージを作りたい</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-2">問題</a></li>
<li><a class="reference internal" href="#node" id="toc-entry-3">Nodeモジュールの検索アルゴリズム</a></li>
<li><a class="reference internal" href="#npm-install" id="toc-entry-4">npm installのアルゴリズム</a></li>
<li><a class="reference internal" href="#yarnnpminstall" id="toc-entry-5">yarnとnpmはinstallアルゴリズムが異なる</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-6">解答</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-7">まとめ</a></li>
</ul>
</div>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-1">バッケージの集合をまとめるためのパッケージを作りたい</a></h2>
<p>アプリに必要なプラグイン群への依存を別パッケージにまとめて記述しておいて、アプリはそのパッケージに依存するようにすれば便利ではないでしょうか?</p>
<div class="figure">
<img alt="Meta package" src="https://blog.tai2.net/images/node_quiz_about_npm_install/meta-package.png">
<p class="caption">npmでこのようなメタパッケージを実現したい</p>
</div>
<p>パッケージの依存関係をまとめるだけの、メタパッケージのようなものです。
そうすれば、アプリ自体のpackage.jsonは短くなるし、メタパッケージに必要なプラグインの選定を <a class="reference external" href="http://david.heinemeierhansson.com/2012/rails-is-omakase.html">オマカセ</a> できます。</p>
<p>しかし、Node.jsのパッケージシステム(npmとyarn)で、このようなことをやろうとすべきではありません。
このことは、以下の問題を考えることで理解できます。</p>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-2">問題</a></h2>
<p>以下のようなパッケージの依存関係があるとします。</p>
<div class="figure">
<img alt="Package dependencies" src="https://blog.tai2.net/images/node_quiz_about_npm_install/dependencies.png">
<p class="caption">パッケージの依存関係</p>
</div>
<p>モジュールA, B, C, D@1.0, D@2.0があり、</p>
<ul class="simple">
<li>A -> B (devDependencies)</li>
<li>B -> D@2.0 (dependencies)</li>
<li>A -> C (dependencies)</li>
<li>C -> D@1.0 (dependencies)</li>
</ul>
<p>という依存関係です。</p>
<p>このとき、 Aのpackage.jsonがあるディレクトリで <code>npm install</code> を実行すると、ローカルのnode_modules直下にはどのパッケージが配置されるでしょうか。
また、 <code>npm install --production</code> ではどうでしょうか?
(ただし、Aへのモジュール追加操作は、まずBを追加して、次にCの追加が行われるものとします。)</p>
<p>これを知るためには、Nodo.jsのモジュール検索アルゴリズムと、npm(yarn)のインストールアルゴリズムを理解する必要があります。</p>
</div>
<div class="section" id="node">
<h2><a class="toc-backref" href="#toc-entry-3">Nodeモジュールの検索アルゴリズム</a></h2>
<p>/Users/tai2/my-node-app/node_modules/some-node-module/foo.js から <code>require('bar')</code> したときには、
以下のような順番で対象モジュールの検索が行われます</p>
<ol class="arabic simple">
<li>/Users/tai2/my-node-app/node_modules/some-node-module/node_modules/bar</li>
<li>/Users/tai2/my-node-app/node_modules/bar</li>
<li>/Users/tai2/node_modules/bar</li>
<li>/Users/node_modules/bar</li>
<li>/node_modules/bar</li>
</ol>
<div class="figure">
<img alt="Node's module search algorithm" src="https://blog.tai2.net/images/node_quiz_about_npm_install/require-algorithm.png">
<p class="caption">Node.jsのモジュール検索アルゴリズム</p>
</div>
<p>このように、requireしているファイルのあるディレクトリから、ルートディレクトリまで順番に駆け上がっていく形で、node_modules内にあるモジュールを探します。ただし、ディレクトリ名がnode_modulesの場合は、その下のnode_modules(つまりnode_modules/node_modules)は検索対象になりません。</p>
<p>参考: <a class="reference external" href="https://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders">Loading from node_modules Folders</a></p>
</div>
<div class="section" id="npm-install">
<h2><a class="toc-backref" href="#toc-entry-4">npm installのアルゴリズム</a></h2>
<p>npm installでnode_modulesにパッケージを配置するときのアルゴリズムは、上記の検索アルゴリズムが前提になっています。
node_modules下の依存パッケージ内に、さらにnode_modulesディレクトリが配置され、その中に依存の依存が配置され、
さらにその下にもnode_modulesと依存の依存の依存が配置され・・・というようなツリー構造になります。</p>
<p>ただし、それを素朴にやると重複するパッケージが配置されてディスク容量が無駄なってしまうため、基本的には、なるべく上の階層にパッケージを配置します。
さきほど説明したように、requireの検索アルゴリズムは、ルートに向かって駆け上がってくれるので、上の階層に置くことで自然と共通化できます。</p>
<p>依存パッケージのインストール時に、すでに同名のパッケージの別バージョンがnode_modules内にある場合には、その下の階層のnode_modulesにインストールします。
その下の階層にもインストールできない場合にはさらに下というふうに、最終的には、依存パッケージ自身のプライベートなnode_modulesまで下る可能性があります。</p>
<p>参考: <a class="reference external" href="https://docs.npmjs.com/cli/install#algorithm">npm-install</a> , <a class="reference external" href="https://docs.npmjs.com/files/folders#cycles-conflicts-and-folder-parsimony">npm-folders</a></p>
</div>
<div class="section" id="yarnnpminstall">
<h2><a class="toc-backref" href="#toc-entry-5">yarnとnpmはinstallアルゴリズムが異なる</a></h2>
<p>yarnとnpmではアルゴリズムが微妙に異なります。
npmでは、 <code>npm install <package-name></code> を実行した時点でのnode_modulesツリーの状態を見て、インストール先を決定します。
つまり、 <code>npm install <package-name></code> を実行する順番によってレイアウトが変わるのです!</p>
<p>yarnでは、現在のディレクトリツリーに関係なく常に同じレイアウトになります。パッケージを追加する順序に依存しません。
言いかえると、yarnでは、 <code>yarn add</code> を実行するごとに、node_modules内でサブツリーが上位階層に移動したり、別のサブツリーに付け替えられたりします。</p>
<p>参考: <a class="reference external" href="https://code.facebook.com/posts/1840075619545360">Yarn: A new package manager for JavaScript</a></p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-6">解答</a></h2>
<p>以上を踏まえると、 <code>npm install</code> を実行したときのA/node_modules内のレイアウトは以下のようになります。</p>
<div class="highlight"><pre><span></span>$ tree node_modules/
node_modules/
├── B
│ └── package.json
├── C
│ ├── node_modules
│ │ └── D
│ │ └── package.json
│ └── package.json
└── D
└── package.json
</pre></div>
<p>Dのバージョン違いが2つあり、CのサブディレクトリにあるほうがD@1.0、上位にあるほうがD@2.0です。
これは、まずAにBを追加して、その結果A/node_modules/DにD@2.0が配置され、次にAにCが追加されるときには、
すでにDのバージョン違いがあるため、Cのプライベートなnode_modulesにD@1.0が配置されるためです。
ちなみに、yarnを使った場合は、これとは逆の順番になるようです。</p>
<p>プロダクション環境用に <code>npm install --production</code> でインストールした場合は、devDependencies(B)が無視されるため、以下のようになります。</p>
<div class="highlight"><pre><span></span>$ tree node_modules/
node_modules/
└── C
├── node_modules
│ └── D
│ └── package.json
└── package.json
</pre></div>
<p>A/node_modulesからDが消えました。つまり、Aパッケージからrequireを実行してDに到達することはできなくなりました。</p>
<p>参考までに、これを実験したときのモジュールを <a class="reference external" href="https://github.com/tai2/node_modules_layout_experiment">GitHub</a> に上げておきます。
このような実験を行うときには、ローカル環境にnpmレジストリを立てられる <a class="reference external" href="https://github.com/rlidwka/sinopia">sinopia</a> が便利です。</p>
<p>元々やりたかったのは、プラグイン(パッケージ)群への依存をまとめたメタパッケージのようなものを実現したいということでした。
ここまで見てきた事実で、なぜこのようなことをしてはいけないのかがわかります。
A,B,C,Dを具体的な例に置き換えてみます。</p>
<ul class="simple">
<li>A: アプリ</li>
<li>B: メタパッケージ</li>
<li>C: Bとは無関係にプラグインに依存したパッケージ</li>
<li>D: プラグイン</li>
</ul>
<p>開発時には、アプリからプラグインが使えていたのに、プロダクション環境では、プラグインが使えなくなってしまうという状況になってしまっています。
これは、アプリが、明示的に依存関係を指定していない(つまりpackage.jsonに記述していない)パッケージを、直接利用しようとしたことから生じています。</p>
<p>このようなバカなことを実際にするわけがないと思われるかもしれませんが、実際にこれをやっているwebpackerというパッケージがあります。
筆者は、これが原因でトラブルに見舞われました。A,B,C,Dを実在のパッケージに置き換えて依存関係を表すと以下の通りです。</p>
<ul class="simple">
<li>App -> Storybook (devDependencies)</li>
<li>Storybook -> file-loader@1.1 (dependencies)</li>
<li>App -> Webpacker (dependencies)</li>
<li>Webpacker -> file-loader@0.11 (dependencies)</li>
</ul>
<p>このときは、Appのpackage.jsonにfile-loader@1.1への依存を追加することで問題を回避しました。
<sup id="sf-node-quiz-about-npm-install-1-back"><a href="#sf-node-quiz-about-npm-install-1" class="simple-footnote" title="最新のWebpacker 4では、file-loaderへの依存が(たまたま)Storybookと同じ1.1になっているため、この問題は起きないと思います">1</a></sup>
webpackベースで似たような機能を提供する、create-react-app(react-scripts)やpoiではどうなっているか調べたところ、
これらは、アプリからプラグインを直接利用させるような設計にはなっていないため、問題なさそうでした。
webpackerは、ビルド機能そのものを提供するのではなくwebpackの設定ファイルのみを提供する(ビルドそのものはアプリ側で行う)、というコンセプトの違いが問題の根底にありそうです。</p>
<p>参考: <a class="reference external" href="https://stackoverflow.com/questions/18875674/whats-the-difference-between-dependencies-devdependencies-and-peerdependencies">What's the difference between dependencies, devDependencies and peerDependencies in npm package.json file?</a></p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-7">まとめ</a></h2>
<ul class="simple">
<li>複数のパッケージをまとめるメタパッケージのようなことをnpmの仕組みでやろうとするのは、やめたほうがいい</li>
<li>package.jsonで指定していないパッケージを直接利用すべきではない</li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-node-quiz-about-npm-install-1">最新のWebpacker 4では、file-loaderへの依存が(たまたま)Storybookと同じ1.1になっているため、この問題は起きないと思います <a href="#sf-node-quiz-about-npm-install-1-back" class="simple-footnote-back">↩</a></li></ol>「それ、もっとスマートに書けるよ」批判2018-04-20T00:00:00+09:002018-04-20T00:00:00+09:00tai2tag:blog.tai2.net,2018-04-20:/anti-smart-code.html<p>昨年末に、 <a class="reference external" href="https://speakerdeck.com/wakamsha/sore-motutosumatonishu-keruyo-javascript-kodowomotutoduan-ku-motutosinpurunishu-ku-tips-4xuan">それ、もっとスマートに書けるよ - JavaScript コードをもっと短く、もっとシンプルに書く Tips 4選</a> というスライドがツ …</p><p>昨年末に、 <a class="reference external" href="https://speakerdeck.com/wakamsha/sore-motutosumatonishu-keruyo-javascript-kodowomotutoduan-ku-motutosinpurunishu-ku-tips-4xuan">それ、もっとスマートに書けるよ - JavaScript コードをもっと短く、もっとシンプルに書く Tips 4選</a> というスライドがツイッターなどで話題に登りました。</p>
<script async class="speakerdeck-embed" data-id="535d2cacf7ac485b914db4786f907e21" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script><p>JavaScriptにおいてスマートなコードを書くための、4つのTipsを紹介するという内容です。
スライドでは、まず「改善前」のコードが示され、それらをどのように「改善」できるかが示されます。
しかし、4つのうち2つの例は、改善されているとは思えないものでした。元のコードのほうが良く見えるのです。<sup id="sf-anti-smart-code-1-back"><a href="#sf-anti-smart-code-1" class="simple-footnote" title="2番目の console.log はデバッグ時の一時的な処置なので問題なし。4番目の reduce に直す例は、よくあるふつうの書き方で、改善されていると思います">1</a></sup>
他の人々の反応を見ても、同様の否定的な見解が多いようですが、中には感銘を受けているようなコメントもいくつか見られました。</p>
<p>この記事では、なぜそれらのコードが良くないのかと考えることで、
チームにおけるコーディングはどのようにあるべきか、という問いを追求します。</p>
<p>それでは、2つのTipsを見ていきましょう。</p>
<div class="section" id="arrya-prototype-indexof">
<h2>1. Arrya.prototype.indexOf()</h2>
<p>最初のTipは、ユーザーエージェントの判定を例にしています。
元のコードはこちら。<sup id="sf-anti-smart-code-2-back"><a href="#sf-anti-smart-code-2" class="simple-footnote" title=" indexOf で、対象が含まれないかどうかの判定は、筆者なら > ではなく != を使います。存在しないことを表す特殊な値「ではない」ことを確認する、という意図を明確に表現したいからです。ここでやりたいのは、数値の大小比較ではありません。">2</a></sup></p>
<div class="highlight"><pre><span></span><span class="kd">const</span><span class="w"> </span><span class="nx">ua</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">navigator</span><span class="p">.</span><span class="nx">userAgent</span><span class="p">;</span>
<span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">ua</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s1">'iPhone'</span><span class="p">)</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="o">-</span><span class="mf">1</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">ua</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s1">'iPod'</span><span class="p">)</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="o">-</span><span class="mf">1</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">ua</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s1">'iPad'</span><span class="p">)</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="o">-</span><span class="mf">1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s1">'ios'</span><span class="p">;</span>
<span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s1">'other'</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>これを「スマート」にすると、このようになります。
(以後、本記事では、発表資料で挙げられている「改善」後のコードを「スマート」なコードと呼ぶことにします。)</p>
<div class="highlight"><pre><span></span><span class="kd">const</span><span class="w"> </span><span class="nx">ua</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">navigator</span><span class="p">.</span><span class="nx">userAgent</span><span class="p">;</span>
<span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">~</span><span class="nx">ua</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s1">'iPhone'</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="o">~</span><span class="nx">ua</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s1">'iPod'</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="o">~</span><span class="nx">ua</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s1">'iPad'</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s1">'ios'</span><span class="p">;</span>
<span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s1">'other'</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>2の補数表現において、-1はすべてのビットが立ちます。
ビット反転演算子を使うことで、-1は、すべてのビットがゼロで表現される数、すなわち0になるという事実を利用したトリックです。</p>
<p>ちなみに、発表者のwakamshaさんは、「整数に対して実行すると符号が反転して-1した値となる」と発言していることから、整数にチルダ演算子を適用することの意味を理解せずに、このトリックを使っているようです。-1以外の負数に対して試してみればわかりますが、単純に符号反転するわけではありません。</p>
</div>
<div class="section" id="let-const">
<h2>2. let / const</h2>
<p>次の例は、 <code>const</code> 変数を初期化する値を分岐させるというものです。</p>
<p>元のコードはこちら。</p>
<div class="highlight"><pre><span></span><span class="kd">let</span><span class="w"> </span><span class="nx">foo</span><span class="p">;</span>
<span class="kd">let</span><span class="w"> </span><span class="nx">bar</span><span class="p">;</span>
<span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="ow">new</span><span class="w"> </span><span class="nb">Date</span><span class="p">().</span><span class="nx">getHours</span><span class="p">()</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="mf">12</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">foo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'forenoon'</span><span class="p">;</span>
<span class="w"> </span><span class="nx">bar</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'am'</span><span class="p">;</span>
<span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">foo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'afternoon'</span><span class="p">;</span>
<span class="w"> </span><span class="nx">bar</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'pm'</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>「スマート」にするとこうなります。</p>
<div class="highlight"><pre><span></span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="nx">foo</span><span class="p">,</span><span class="w"> </span><span class="nx">bar</span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(()</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="ow">new</span><span class="w"> </span><span class="nb">Date</span><span class="p">().</span><span class="nx">getHours</span><span class="p">()</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="mf">12</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">foo</span><span class="o">:</span><span class="w"> </span><span class="s1">'forenoon'</span><span class="p">,</span>
<span class="w"> </span><span class="nx">bar</span><span class="o">:</span><span class="w"> </span><span class="s1">'am'</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">foo</span><span class="o">:</span><span class="w"> </span><span class="s1">'afternoon'</span><span class="p">,</span>
<span class="w"> </span><span class="nx">bar</span><span class="o">:</span><span class="w"> </span><span class="s1">'pm'</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">})();</span>
</pre></div>
<p>即時関数(IIFE)を使うことで、分岐を式として扱うトリックです。</p>
<p>最初のif文を使って書く方法は読み易いし、これで十分だと思います。どうしても <code>const</code> で済ませたければ、このように三項演算子で書けます。</p>
<div class="highlight"><pre><span></span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">foo</span><span class="p">,</span><span class="w"> </span><span class="nx">bar</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nb">Date</span><span class="p">().</span><span class="nx">getHours</span><span class="p">()</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="mf">12</span>
<span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">foo</span><span class="o">:</span><span class="w"> </span><span class="s1">'forenoon'</span><span class="p">,</span><span class="w"> </span><span class="nx">bar</span><span class="o">:</span><span class="w"> </span><span class="s1">'am'</span><span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">foo</span><span class="o">:</span><span class="w"> </span><span class="s1">'afternoon'</span><span class="p">,</span><span class="w"> </span><span class="nx">bar</span><span class="o">:</span><span class="w"> </span><span class="s1">'pm'</span><span class="w"> </span><span class="p">}</span>
</pre></div>
<p>分岐内に複数のステートメントを書きたい場合は、やはり <code>let</code> と <code>if</code> でいいと思います。
あるいは、即時関数ではなく、ちゃんと名前をつけて別の関数にしてそれを呼び出せば素直なコードになります。</p>
</div>
<div class="section" id="section-2">
<h2>チームにおけるコードはどうあるべきか</h2>
<p>チーム開発におけるコードは、リーダブルであるべきです。
それも、ただ読み辛くなければいいというだけではなく、全力で読み易さを追求するというのが、あるべき姿であると筆者は考えます。
例え、局所的に、ほんの少し実行効率や空間効率が悪くなったとしても、リーダブルなコードを書くことのほうが大事です。</p>
<p>なぜリーダブルなコードが大事なのでしょうか。</p>
<p>実際の開発におけるコードというのは、基本的に書く回数よりも読む回数のほうが多いものだから、というのがひとつの理由です。
一度書かれたコードは、開発に参加する何人もの人に読まれますし、自分自身も何度も読み返します。
半年後の自分は他人である、というのはよく言われることです。
ですから、なるべく読んですぐに理解できるコード、読解するために考えたり調べたりして、ストレスを感じることのないコードが理想です。</p>
</div>
<div class="section" id="section-3">
<h2>リーダブルなコードはどういうものか</h2>
<p>ところで、リーダブルなコードとはどういったものでしょうか。</p>
<p>おそらく、wakamshaさんにとっては、「スマート」なコードは、十分に読めるコードなのだと思います。</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">先日の勉強会資料が急に はてブ ホッテントリ入りしてたので何事かと思ったら、随分とネガティブなコメントが集まってて「まぁ…」と驚いてます。<br><br>トリッキーなのは認識してますがそんなに解りづらいかな?</p>— wakamsha / Naoki.YAMADA (@wakamsha) <a href="https://twitter.com/wakamsha/status/916835322050183173?ref_src=twsrc%5Etfw">October 8, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>人によって、どういうコードを読み易い、あるいは読み辛いと感じるかは、千差万別です。
万人に通じる絶対的に読み易いコードというのはないのかもしれません。</p>
<p>読み易さにとって重要な基準となるのが、それがふつうの書き方であるかどうかということです。
なにをもって「ふつうでない」とするかも、個別に議論し出すと難しくなってはくるのですが、基本的には、いろいろな人のコードを読んで経験を詰めば感覚としてわかってくると思います。</p>
<p>「スマート」なコードとして書かれている上記の2例は、どちらもふつうの書き方ではないと思います。
いままであのような書き方をしているコードを見たことがありません。
ですから、ひどく違和感を覚えるのです。</p>
<p>もちろん、それを補ってあまりある合理的なメリットがあるのであれば、ふつうでないコードを書いても良いと思います(ただし、その場合には、なぜふつうでない書き方をしているのかをコメントに書いておく必要があります)。
「スマート」なコードに、ふつうさを犠牲にする合理性があるでしょうか。そうは思えません。</p>
<p>ふつうさと並んで読み易さに関係してくるのが、その機能が持っている本来の意図と、使い方が一致しているか、ということです。
チルダ演算子は、オペランドが-1かどうかを判定するための演算子ではないですし、IIFEは、ES5以前にレキシカルスコープを模倣するために発明されたテクニックです。
本来の目的と使い方が一致していないために読むものに驚きを与えてしまっているという面はあると思います。</p>
<p>本来の機能と使用法の一致に関しては、但し書きが必要です。
たとえば、 <a class="reference external" href="https://blog.tai2.net/automatic_semilocon_insertion.html">JavaScriptのASI(自動セミコロン挿入)</a> は、元々はセミコロンを書き忘れても動くことを意図した機能ですが、
今では、JavaScriptをセミコロン不要な言語とするためにASIを積極的に利用することが、コミュニティーにおいてある程度の市民権を得ています。</p>
<p>本来の機能と使用法が一致しているべきというのは、絶対的な規則ではありません。
多くの人に受け入れられているのであれば、たとえ機能の本来の意図と一致していなくても許されます。つまり、ふつうのコードであるということです。
もしもチルダ演算子で-1かどうかを判定するテクニックが多くの人に受け入れられ使われている書き方であったなら、筆者もそれを受け入れます。
ただし、相応の合理的なメリットがなければ、多くの人に受け入れられることはないでしょう。</p>
</div>
<div class="section" id="section-4">
<h2>問題点</h2>
<p>以上の議論をふまえて、紹介されている2つのテクニックはどこが良くなかったのでしょうか。</p>
<p>まず両方に共通して言えることとして、どちらもふつうの書き方ではありません。
ふつうの書き方だったら、わざわざ勉強会で発表したりはしないと思いますし。
また、ふつうでない書き方をするに足る合理的なメリットもないと筆者は考えます。</p>
<p>1のコードを目にしたプログラマーの多くは、なぜ唐突にビット反転演算子が使われているのか、なにか特別な意味があるのか、
また、ビットを反転することによって実行結果がどうなるのか、頭を抱えることでしょう。</p>
<p>2については、すこし補足が必要かもしれません。
このテクニックは、たしかに、 <code>let</code> を排除するという機能的なメリットを提供しています。
基本的に <code>const</code> を使うという方針は正しいです(文字数が長くなるくらいしかデメリットがないので)。
かといって、それが絶対的に厳守しなければならないルールかというと、そうではないと思います。</p>
<p><code>const</code> を厳守するために無理な書き方をするくらいなら、筆者は <code>let</code> を使います。
<code>let</code> ならば <code>var</code> と違ってスコープも宣言以降に限定されていますし、スコープがよほど長くならなければ(100行以上に跨るとかだと厳しいかもしれません)、目視で十分に確認できます。問題ありません。</p>
<p>IIFEはそもそも読み易い書き方ではありません。
トリッキーな書き方ではありますが、それを補うほどにグローバルスコープを排除するということは重要でした。
ですから、モジュールの概念もブロックスコープ変数もなかった時代に、他にやりかたがないのでしかたがなく使っていたのです。
たかだか関数内での <code>let</code> を排除するという小さな目的のために持ち出すようなものではないと思います。</p>
<p>なにが良いコードで、なにが良くないのかについて合意を形成するのは、各人が持っている経験やバックグラウンドが異なる実際の開発において、相当困難であるということは、筆者も身を持ってわかっているつもりです。
ですから、コードの書き方については、ある程度の個性を許容するということは、チーム作業においてどうしても必要になってくると思います。</p>
<p>それでも、上記のような「スマート」なコードは、できることなら書かないで欲しいと願います。</p>
</div>
<div class="section" id="section-5">
<h2>まとめ</h2>
<p>愚直なコードを書くのは悪いことではありません。
小手先の「スマート」な書き方にほんとうにメリットがあるのか、立ち止まって考えてみましょう。</p>
</div>
<ol class="simple-footnotes"><li id="sf-anti-smart-code-1">2番目の <code>console.log</code> はデバッグ時の一時的な処置なので問題なし。4番目の <code>reduce</code> に直す例は、よくあるふつうの書き方で、改善されていると思います <a href="#sf-anti-smart-code-1-back" class="simple-footnote-back">↩</a></li><li id="sf-anti-smart-code-2"> <code>indexOf</code> で、対象が含まれないかどうかの判定は、筆者なら <code>></code> ではなく <code>!=</code> を使います。存在しないことを表す特殊な値「ではない」ことを確認する、という意図を明確に表現したいからです。ここでやりたいのは、数値の大小比較ではありません。 <a href="#sf-anti-smart-code-2-back" class="simple-footnote-back">↩</a></li></ol>Puppeteerで記事タイトルからog:imageを生成する2017-11-18T00:00:00+09:002017-11-18T00:00:00+09:00tai2tag:blog.tai2.net,2017-11-18:/puppeteer-ogimage.html<p>最近話題の <a class="reference external" href="https://dev.to">dev.to</a> で、og:imageを記事タイトルから生成しているのが良かったので、
このブログでも <a class="reference external" href="https://github.com/tai2/blog/commit/3a433584b62598878e5b17d552675b5369eea9aa">記事タイトルからog:image …</a></p><p>最近話題の <a class="reference external" href="https://dev.to">dev.to</a> で、og:imageを記事タイトルから生成しているのが良かったので、
このブログでも <a class="reference external" href="https://github.com/tai2/blog/commit/3a433584b62598878e5b17d552675b5369eea9aa">記事タイトルからog:imageを生成するようにしました</a> 。</p>
<p>dev.toのog:imageは、 <a class="reference external" href="https://cloudinary.com/features#manipulation">Cloudinary</a> という、
URLのクエリ文字列で画像処理をできるSaaSを使って動的に生成していますが、本記事ではこれとは別のアプローチを取ります。
<a class="reference external" href="https://github.com/GoogleChrome/puppeteer">Pupeteer</a> を使って自前で生成するというやりかたです。</p>
<div class="section" id="puppeteer">
<h2>Puppeteerとは</h2>
<p><a class="reference external" href="https://developers.google.com/web/updates/2017/04/headless-chrome">Headless Chrome</a> を操作するためのNode.js用ライブラリです。<a class="reference external" href="http://www.seleniumhq.org/">Selenium WebDriver</a> と同じようなものですが、Chromeに特化していて、シンプルなAPIを持っているのが特徴です。npm installするだけでChromium<sup id="sf-puppeteer-ogimage-1-back"><a href="#sf-puppeteer-ogimage-1" class="simple-footnote" title="Chromeのオープンソース版">1</a></sup>もいっしょにダウンロードしてくれるので、お手軽に使いはじめられます。</p>
</div>
<div class="section" id="section-1">
<h2>方法</h2>
<p><a class="reference external" href="https://github.com/tai2/blog/blob/d1cef7ddd6c8b1bfee089e207393b183fb5fcac2/ogimage.html">og:image用に組んだHTML</a> をPuppeteerでレンダリングして、画像として保存します。</p>
<p>og:imageの生成とキャプチャは、以下のような非常に短かい関数で実現できます。
<cite>exposeFunction</cite> でChrome側に関数をエクスポートできるので、これを使って、HTML側に記事タイトルを注入します。</p>
<div class="highlight"><pre><span></span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">capture</span><span class="p">(</span><span class="nx">article</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">viewport</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">width</span><span class="o">:</span><span class="w"> </span><span class="mf">1000</span><span class="p">,</span>
<span class="w"> </span><span class="nx">height</span><span class="o">:</span><span class="w"> </span><span class="mf">500</span><span class="p">,</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">injectedProps</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">title</span><span class="o">:</span><span class="w"> </span><span class="nx">article</span><span class="p">.</span><span class="nx">title</span><span class="p">,</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">basename</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nx">basename</span><span class="p">(</span><span class="nx">article</span><span class="p">.</span><span class="nx">path</span><span class="p">,</span><span class="w"> </span><span class="s1">'.rst'</span><span class="p">)</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">browser</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">puppeteer</span><span class="p">.</span><span class="nx">launch</span><span class="p">()</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">page</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">()</span>
<span class="w"> </span><span class="nx">page</span><span class="p">.</span><span class="nx">setViewport</span><span class="p">(</span><span class="nx">viewport</span><span class="p">)</span>
<span class="w"> </span><span class="c1">// getInjectedPropsという関数を注入してプロパティーをHTMLに引き渡す</span>
<span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">page</span><span class="p">.</span><span class="nx">exposeFunction</span><span class="p">(</span><span class="s1">'getInjectedProps'</span><span class="p">,</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="nx">injectedProps</span><span class="p">)</span>
<span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s1">'file://'</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s1">'ogimage.html'</span><span class="p">))</span>
<span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">page</span><span class="p">.</span><span class="nx">screenshot</span><span class="p">({</span><span class="nx">path</span><span class="o">:</span><span class="w"> </span><span class="sb">`content/images/og/</span><span class="si">${</span><span class="nx">basename</span><span class="si">}</span><span class="sb">.png`</span><span class="p">})</span>
<span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">browser</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span>
<span class="p">}</span>
</pre></div>
<p>HTML側では、レンダリング結果が画像サイズをはみ出ないように、1ピクセルずつ小さくしながら最適なフォントサイズを探索します。</p>
<div class="highlight"><pre><span></span><span class="kd">let</span><span class="w"> </span><span class="nx">fontSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">100</span><span class="w"> </span><span class="c1">// px dimension</span>
<span class="k">for</span><span class="w"> </span><span class="p">(;;)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">title</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">fontSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">fontSize</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">'px'</span>
<span class="w"> </span><span class="c1">// レンダリング結果の高さがwindow.innerHeight(viewportの高さ)をはみ出さなければ探索停止</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">clientHeight</span><span class="w"> </span><span class="o"><=</span><span class="w"> </span><span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">break</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="nx">fontSize</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="mf">1</span>
<span class="p">}</span>
</pre></div>
<p>なお、実装は、いったんPuppeteerとは切り離して単体のHTMLとしてデザインを完成させた上で、
あとからPuppeteerを組込むという工程で進めました。</p>
</div>
<div class="section" id="section-2">
<h2>評価</h2>
<p>HTMLとCSSでレイアウトができるため、とても柔軟な表現が可能です。
Cloudinaryについては詳しくありませんが、おそらくは、それに勝る表現の柔軟性があるのではないでしょうか。</p>
<p>一方、処理時間については、 筆者の環境(MacBook Pro Core i7 3.1GHz)で画像1枚生成するのに、Nodeプロセスの起動から終了までで2秒程度かかります。
Puppeteerの起動から完了まででも1.5秒、スクリーンショットの保存だけで0.5秒といった感じです。</p>
<p>このブログは、 <a class="reference external" href="https://blog.getpelican.com/">Pelican</a> という静的サイトジェネレータで管理しています。
このツールでは、記事変換時に、常に全記事一気に変換されるため、記事変換にog:imageの生成を付随させると、
publishにかかる時間がかなり長くなってしまいます。
そのため、自動的なog:imageの生成は断念して、新規記事追加ごとに手動で画像生成する運用でいくことにしました。</p>
<p>実際のサービスなどに応用することを考えると、生成のタイミングは工夫する必要があるかもしれません。</p>
</div>
<ol class="simple-footnotes"><li id="sf-puppeteer-ogimage-1">Chromeのオープンソース版 <a href="#sf-puppeteer-ogimage-1-back" class="simple-footnote-back">↩</a></li></ol>Webpacker 3ではじめるRailsエンジニアのためのモダンフロントエンド入門 〜Sprocketsを使わないRailsプロジェクト試案〜2017-10-21T00:00:00+09:002017-10-21T00:00:00+09:00tai2tag:blog.tai2.net,2017-10-21:/webpacker3.html<p class="first last">本記事には2つの目的があります: RailsのAsset pipeline(Sprockets)をまったく使用しなくてもRails開発が可能であることを実証すること、モダンフロントエンド未体験のRailsエンジニアに向けて、実際のコードをまじえつつ、モダンフロントエンド開発の雰囲気を伝えること。また、Webpackerの概要を知りたいRailsエンジニアへの機能と使い方紹介にもなっています。</p>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-1">はじめに</a></h2>
<blockquote>
<p>Webpack、ES6風味のJavaScript、そして他すべてのモダンなクライアント側開発体験の進歩をまだ試していないなら、Webpacker 3.0は、はじめるのに絶好の機会だ。</p>
<p class="attribution">—DHH</p>
</blockquote>
<p><a class="reference external" href="http://weblog.rubyonrails.org/2017/4/27/Rails-5-1-final/">Rails 5.1</a> で <a class="reference external" href="https://github.com/rails/webpacker">Webpacker</a> が導入され、Railsでもモダンなフロントエンド開発が簡単にできるようになりました。
Webpackerはリリースからどんどん進化しており、 <a class="reference external" href="https://gist.github.com/tai2/9a72ed78a6227c9bbe046e08f72cdd95">3.0でさらに使いやすくなりました。</a></p>
<p>本記事には2つの目的があります:</p>
<ul class="simple">
<li>RailsのAsset pipeline(Sprockets)をまったく使用しなくてもRails開発が可能であることを実証すること</li>
<li>モダンフロントエンド未体験のRailsエンジニアに向けて、実際のコードをまじえつつ、モダンフロントエンド開発の雰囲気を伝えること</li>
</ul>
<p>また、Webpackerの概要を知りたいRailsエンジニアへの機能と使い方紹介にもなっています。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-1" id="toc-entry-1">はじめに</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-2">対象読者</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-3">サンプルコード</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-4">この記事で扱わないもの</a><ul>
<li><a class="reference internal" href="#section-5" id="toc-entry-5">参考リンク</a></li>
</ul>
</li>
<li><a class="reference internal" href="#webpack" id="toc-entry-6">Webpackとは</a><ul>
<li><a class="reference internal" href="#section-6" id="toc-entry-7">参考リンク</a></li>
</ul>
</li>
<li><a class="reference internal" href="#webpacker-1" id="toc-entry-8">Webpackerとは</a><ul>
<li><a class="reference internal" href="#sprockets" id="toc-entry-9">なぜSprocketsを避けるのか</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-10">ディレクトリ構成</a></li>
<li><a class="reference internal" href="#view" id="toc-entry-11">Viewヘルパー</a></li>
<li><a class="reference internal" href="#webpack-2" id="toc-entry-12">デフォルトWebpack設定とそのカスタマイズ</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-13">ジェネレータ</a></li>
<li><a class="reference internal" href="#webpacker-3" id="toc-entry-14">Webpackerでアセットを管理する</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-15">参考リンク</a></li>
</ul>
</li>
<li><a class="reference internal" href="#rails-ujs" id="toc-entry-16">rails-ujs</a><ul>
<li><a class="reference internal" href="#csrf" id="toc-entry-17">CSRFトークンの取得</a></li>
</ul>
</li>
<li><a class="reference internal" href="#react" id="toc-entry-18">React</a><ul>
<li><a class="reference internal" href="#section-10" id="toc-entry-19">参考リンク</a></li>
</ul>
</li>
<li><a class="reference internal" href="#fluxredux" id="toc-entry-20">FluxアーキテクチャとRedux</a><ul>
<li><a class="reference internal" href="#section-11" id="toc-entry-21">非同期の扱い</a></li>
<li><a class="reference internal" href="#railsredux" id="toc-entry-22">RailsからReduxへのデータ受け渡し</a></li>
<li><a class="reference internal" href="#section-12" id="toc-entry-23">参考リンク</a></li>
</ul>
</li>
<li><a class="reference internal" href="#css-modules" id="toc-entry-24">CSS Modules</a><ul>
<li><a class="reference internal" href="#bootstrap" id="toc-entry-25">グローバルなクラスとBootstrap</a></li>
<li><a class="reference internal" href="#css-modulescsswebpack" id="toc-entry-26">CSS ModulesとグローバルCSSを両立するためのWebpack設定</a></li>
<li><a class="reference internal" href="#section-13" id="toc-entry-27">参考リンク</a></li>
</ul>
</li>
<li><a class="reference internal" href="#typescript-1" id="toc-entry-28">TypeScript</a><ul>
<li><a class="reference internal" href="#javascript-sprinkles" id="toc-entry-29">小粒なJavaScript(Sprinkles)</a></li>
<li><a class="reference internal" href="#section-14" id="toc-entry-30">参考リンク</a></li>
</ul>
</li>
<li><a class="reference internal" href="#babeles2015" id="toc-entry-31">BabelとES2015+</a><ul>
<li><a class="reference internal" href="#section-15" id="toc-entry-32">参考リンク</a></li>
</ul>
</li>
<li><a class="reference internal" href="#storybook" id="toc-entry-33">Storybook</a></li>
<li><a class="reference internal" href="#section-16" id="toc-entry-34">ユニットテスト</a><ul>
<li><a class="reference internal" href="#power-assert" id="toc-entry-35">power-assert</a></li>
<li><a class="reference internal" href="#enzyme" id="toc-entry-36">Enzyme</a></li>
<li><a class="reference internal" href="#section-17" id="toc-entry-37">参考リンク</a></li>
</ul>
</li>
<li><a class="reference internal" href="#prettier" id="toc-entry-38">Prettier</a></li>
<li><a class="reference internal" href="#tslint" id="toc-entry-39">TSLint</a></li>
<li><a class="reference internal" href="#webpack-bundle-analyzer" id="toc-entry-40">webpack-bundle-analyzer</a></li>
<li><a class="reference internal" href="#section-18" id="toc-entry-41">まとめ</a></li>
</ul>
</div>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-2">対象読者</a></h2>
<p>Rails自体の基本的な使い方は習得済みのエンジニアのために書きました。
とは言え、Webpacker以外についての説明は、Railsとは無関係に独立して読めるので、たんにモダンフロントエンドに興味がある人にとっても参考になると思います。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-3">サンプルコード</a></h2>
<p>本記事では、以下のサンプルコードを元に解説していきます。<sup id="sf-webpacker3-1-back"><a href="#sf-webpacker3-1" class="simple-footnote" title="記事内で引用しているコードは、型アノテーションを省略するなど、適宜省略した形で抜粋しています。">1</a></sup></p>
<p><a class="reference external" href="https://github.com/tai2/webpacker-react-example">https://github.com/tai2/webpacker-react-example</a></p>
<p>Rails 5.1とWebpacker 3を使った簡単なTodoアプリです。
従来通りのRails MVC<sup id="sf-webpacker3-2-back"><a href="#sf-webpacker3-2" class="simple-footnote" title="ほぼscaffoldingが生成したものそのままです">2</a></sup>とReactの2通りの方法で、同じ機能を実装しているので、
Railsだけで書いていたコードをモダンフロントエンドで実装するとどのようになるのか、比較し易いと思います。</p>
<p>あくまでサンプルコードではありますが、筆者が実際の案件で使うための検証も兼ねており、ほぼこのままの形でプロダクションに投入する予定のコードでもあります。</p>
<p>このサンプルでは以下のことが実現されています:</p>
<ul class="simple">
<li>Sprocketsを使用せず、webpackerのみでアセットを管理する。</li>
<li>Reactのクライアントアプリと通常のRails MVCを同一サービスで併用する(いわゆるSPAではない)。</li>
<li>RailsのViewで使う小粒なJavaScript(いわゆるJS Sprinkles)もwebpackで管理する。</li>
<li>サーバーAPIリクエストにCSRFプロテクションをかける。</li>
<li>静的ファイルのファイル名末尾にハッシュを付与する(いわゆるキャッシュバスター)。</li>
<li>通常のViewとReactアプリ両方でBootstrapを使う。</li>
<li>Reactアプリのスタイル定義は、CSS Modulesで行う。</li>
<li>JavaScriptは、すべてTypeScriptで記述し、静的型チェックを行う。</li>
<li>クライアントアプリは、React ReduxによるFluxアーキテクチャで構成する。</li>
<li>StorybookによるUIコンポーネントの開発。</li>
<li>power-assertによるシンプルなアサーションAPIでテストを記述する。</li>
<li>lodashを使うが、tree shakingにより実際に使用している関数のみバンドルする。</li>
<li>babel-preset-envによる必要最低限のpolyfill。<sup id="sf-webpacker3-3-back"><a href="#sf-webpacker3-3" class="simple-footnote" title="ただし、現状のUglifyJSでは、ES2015+をサポートしていないため実質的に無効化される。将来的にはUglifyJSが改善されて有効化される見込み。">3</a></sup></li>
</ul>
<p>環境構築というのは、個々の要素間の相性などにより、得てして問題が発生し、正しく動作させるために試行錯誤が必要になります。
ですから、これらの要素をすべて詰め込んで、動作する組み合わせを選定し、実際に動作検証をしたというだけで、ひとつの成果と言って過言ではありません。</p>
<p>以降、上記の要素について個々に解説していきます。
ただし、ひとつの記事で、すべてを詳細に解説することは難しいので、できる限りコードを添えつつ簡単な概要と参考リンクを紹介するに留めます。あくまで、コードの雰囲気と便利なツールの紹介が目的です。
また、JavaScriptのエコシステムというのは非常に多用で選択肢が豊富であり、これが正解というのものはありません。
ここで紹介するものも、あくまでひとつの例に過ぎないことに注意してください。<sup id="sf-webpacker3-4-back"><a href="#sf-webpacker3-4" class="simple-footnote" title="とは言え、紹介しているライブラリ・ツールはどれもJavaScript界で一定の評価を得ているポピュラーなものばかりです。">4</a></sup></p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-4">この記事で扱わないもの</a></h2>
<p>昨今のシングルページアプリケーション(SPA)と呼ばれる、JavaScriptのみでUIが構成されるWebアプリでは、
しばしばサーバーサイドレンダリングを行います。
これは、JavaScriptが走る前に、あらかじめサーバー側でHTMLを生成してレスポンスに含めておくことで、
初期表示までの時間を短縮する技術です。
SPAでは、最終的なJavaScriptのサイズが数MB以上になることも珍しくないため、
シビアなパフォーマンスが要求されるサービスでは、このような施策が要求されます。
また、サーバーサイドレンダリングを行うとSEO上も有利になると言われています。
筆者はサーバーサイドレンダリングの経験がないため、この記事では扱いません。</p>
<p>また、アクセスされたURLに応じて、クライアント側で表示内容を変更する、
クライアントサイドルーティングも扱いません。<sup id="sf-webpacker3-5-back"><a href="#sf-webpacker3-5" class="simple-footnote" title="筆者の案件では使わないため">5</a></sup></p>
<div class="section" id="section-5">
<h3><a class="toc-backref" href="#toc-entry-5">参考リンク</a></h3>
<ul class="simple">
<li><a class="reference external" href="https://havelog.ayumusato.com/develop/javascript/e675-spa_and_server_rendering_with_fluxible.html">yahoo/fluxible による SPA + Server Rendering の概観</a></li>
<li><a class="reference external" href="https://speakerdeck.com/yosuke_furukawa/you-need-to-know-ssr">You Need to know SSR</a></li>
<li><a class="reference external" href="http://blog.bitjourney.com/entry/2017/09/29/183826">TypeScript+webpack+Hypernova on RailsでSSRするときの設定ファイル</a></li>
</ul>
</div>
</div>
<div class="section" id="webpack">
<h2><a class="toc-backref" href="#toc-entry-6">Webpackとは</a></h2>
<p>ごくごく最近まで、ブラウザ上のJavaScriptには、モジュール分割のための機能がありませんでした。
そのため、ランタイムに頼らずにモジュール化を行うための手法がいくつも発明されてきました。
その中のひとつが、トランスパイルとバンドル化です。</p>
<p>この手法では、CommonJSやESModuleなどの本来はブラウザ上で使えない(あるいは使えなかった)
仕様を解釈しつつ、それをブラウザが解釈できるソースコードに変換(トランスパイル)します。
また、モジュール機能のないブラウザ上で実行するために、変換したソースコードはすべて
結合してひとつのソースコードにします(バンドル化)。</p>
<div class="figure">
<img alt="Webpack" src="https://blog.tai2.net/images/webpacker3/webpack.png">
<p class="caption">WebpackはJSアプリのアセットをひとまとめにする</p>
</div>
<p>JavaScriptのバンドルツールとしては、 <a class="reference external" href="https://rollupjs.org/">Rollup</a> や <a class="reference external" href="http://fuse-box.org/">Fusebox</a> など、いくつものプログラムがありますが、現在もっともポピュラーなのが <a class="reference external" href="https://webpack.js.org/">Webpack</a> です。</p>
<p>また、バンドル化するときには、同時に、ECMAScript 2017(ES2017)などの最新のJavaScript仕様から、
より広範囲のブラウザで実行できるECMAScript 5(ES5)などにトランスパイルします。
これにより、現在のフロントエンドプログラミングでは、最新の便利な言語機能を使って、以前よりも快適に開発ができます。</p>
<p>Webpackはあくまでバンドル化だけを行うツールであり、トランスパイルは別のツールが行います。
JavaScriptのトランスパイラでもっともポピュラーなのが <a class="reference external" href="https://babeljs.io/">Babel</a> です。
WebpackとBabel、およびそれらのプラグインを組み合わせることで、単にトランスパイル&バンドル化を行うだけでなく、さまざまなことが行えます。</p>
<p>トランスパイラとしてもうひとつメジャーなのが、 <a class="reference external" href="https://www.typescriptlang.org/index.html">TypeScript</a> です。こちらは、名前からも分かる通り、静的型チェックの機能を備えつつ、ECMAScriptとほぼ互換性のある文法を持ったべつの言語になっています。</p>
<div class="section" id="section-6">
<h3><a class="toc-backref" href="#toc-entry-7">参考リンク</a></h3>
<ul class="simple">
<li><a class="reference external" href="https://ics.media/entry/12140">最新版で学ぶwebpack 3入門 – JavaScript開発で人気のバンドルツール</a></li>
</ul>
</div>
</div>
<div class="section" id="webpacker-1">
<h2><a class="toc-backref" href="#toc-entry-8">Webpackerとは</a></h2>
<p><a class="reference external" href="https://github.com/rails/webpacker">Webpacker</a> は、RailsとWebpackを統合するためのgemおよび、Nodeモジュールです。
これには、以下のような機能が含まれます。</p>
<ul class="simple">
<li>WebpackでビルドしたアセットをRailsのViewから使用するためのヘルパー</li>
<li>Webpackのデフォルト設定</li>
<li>オンデマンドビルド(Railsへのリクエスト時にWebpackを起動)</li>
<li>React,Vue,Angular,Elmの雛型ジェネレータ</li>
</ul>
<div class="section" id="sprockets">
<h3><a class="toc-backref" href="#toc-entry-9">なぜSprocketsを避けるのか</a></h3>
<p>Railsには、もともとSprocketsという、CoffeeScriptやSASSのトランスパイルができる機能が含まれています。
Railsとしては、JavaScriptのコンパイルのみをWebpackerで行い、スタイルシートやその他アセットは従来通りSprockets
から利用するというのが当面の方針のようです。</p>
<p>しかしながら、筆者の見るところ、実はWebpackerにはSprocketsがなくてもそれだけで完結できる十分な機能が備わっています。
<sup id="sf-webpacker3-6-back"><a href="#sf-webpacker3-6" class="simple-footnote" title="以前であれば、Sprocketsを使わずにWebpackのみでアセットを管理するためには、 ヘルパなどを自前で実装する必要がありましたが、 いまではWebpackerのみで事足ります。">6</a></sup></p>
<p>だとすると、同一機能を持ったものが2つ存在しているのはDRYではありません。</p>
<p>また、Rails 5.1では、 <a class="reference external" href="https://qiita.com/ryohashimoto/items/aa2a7065abc6a4dcedd1">Sprockets経由でES2015+の構文を使用することが可能であり、</a> <sup id="sf-webpacker3-7-back"><a href="#sf-webpacker3-7" class="simple-footnote" title="ECMAScriptの仕様は、2015以降毎年更新されています。それらを総称して2015+と呼んだりもします。また、2015以前の仕様であるES5と対比する意味で、ES6,ES7,ES8などと呼ばれたりもします。">7</a></sup>npm<sup id="sf-webpacker3-8-back"><a href="#sf-webpacker3-8" class="simple-footnote" title="Node.jsのパッケージ管理ツール、及びその中央リポジトリ。Rubyで言うところのRubyGems。JavaScriptのエコシステムはnpmを要として発展しています。">8</a></sup>からインストールしたモジュールを使用することさえも可能です。しかし、Sprocketsでは、現状、ESModuleのimportステートメントやCommonJSのrequire関数を解釈することができないため、実際には、利用できるNodeモジュールがかなり制限されています。</p>
<p>ですから、Nodeエコシステムの恩恵をフルに受けられるWebpacker一本でいくほうが良いと判断し、それを実証するためにこの記事を書きました。</p>
<p>デメリットとして、Sprocketsが前提になっているようなgem(Mountable Engineなど)は、利用できなくなると思われます。</p>
</div>
<div class="section" id="section-7">
<h3><a class="toc-backref" href="#toc-entry-10">ディレクトリ構成</a></h3>
<p>Webpackerのデフォルトでは、</p>
<ul class="simple">
<li><code>app/javascript/packs/</code> 変換元のファイル</li>
<li><code>public/packs/</code> 変換後のファイル</li>
</ul>
<p>というディレクトリ構成になっています。
<code>app/javascript/packs</code> 内に置かれたすべてのファイルは、自動的に、Webpackのエントリポイントとして扱われます。
つまり、このディレクトリ内にあるファイルはすべて、<code>public/packs/</code> に変換後のファイルが出力されるということです。
Webpackerでは、この個々のエントリーポイントをpackと呼びます。</p>
<p>同時に、従来の <code>app/assets/javascripts</code> はそのまま残されています。<sup id="sf-webpacker3-9-back"><a href="#sf-webpacker3-9" class="simple-footnote" title="Webpackerのディレクトリが app/javascript/ であることには、明確な意図 が込められているようです。">9</a></sup>
Railsのデフォルトでは、 <code>app/assets</code> はアセットパイプラインの管轄であり、ここに置かれているJavaScriptはSprocketsによってビルドされます。</p>
<p>RailsとWebpackerを併用するRailsのデフォルトであれば、この設定で適切なのですが、
我々は、いまSprocketsを捨ててすべてのアセット管理をWebpackerにまかせようとしています。
JavaScript以外のスタイルシート、画像ファイルといったアセット一般をそこに置くとなった場合に、
<code>app/javascript/</code> というパスは不適切に思えます。
幸い、この部分は設定ファイルの <code>source_path</code> で変更できるため、<code>app/assets/</code> に変更します。
pack用のディレクトリは、 <code>app/assets/packs</code> で、それ以外は従来のRailsと同じ形になります。
アセットパイプラインの責務をWebpackerに置き換えるので、これがしっくり来ます。</p>
</div>
<div class="section" id="view">
<h3><a class="toc-backref" href="#toc-entry-11">Viewヘルパー</a></h3>
<p>Webpackerでは3つのViewヘルパーが追加されます。</p>
<div class="highlight"><pre><span></span><span class="cp"><%=</span><span class="w"> </span><span class="n">javascript_pack_tag</span><span class="w"> </span><span class="s1">'todos'</span><span class="w"> </span><span class="cp">%></span>
</pre></div>
<p><code>javascript_pack_tag</code> で、packでバンドルされたJavaScriptファイルを出力できます。</p>
<div class="highlight"><pre><span></span><span class="cp"><%=</span><span class="w"> </span><span class="n">stylesheet_pack_tag</span><span class="w"> </span><span class="s1">'todos'</span><span class="w"> </span><span class="cp">%></span>
</pre></div>
<p><code>stylesheet_pack_tag</code> で、packでバンドルされたCSSファイルを出力できます。
スタイルシートのバンドルについては後程説明します。</p>
<div class="highlight"><pre><span></span><span class="x"><img class="logo" src="</span><span class="cp"><%=</span><span class="w"> </span><span class="n">asset_pack_path</span><span class="w"> </span><span class="s1">'images/rails.svg'</span><span class="w"> </span><span class="cp">%></span><span class="x">" /></span>
</pre></div>
<p><code>asset_pack_path</code> で、packに含まれるすべてのアセットへのパスを出力できます。
packに画像などのファイルを含める方法については後程説明します。</p>
<p>これらのヘルパーを使用すると、プロダクション環境では、ファイル名に自動的にダイジェストが付加されます。</p>
</div>
<div class="section" id="webpack-2">
<h3><a class="toc-backref" href="#toc-entry-12">デフォルトWebpack設定とそのカスタマイズ</a></h3>
<p>Webpackの設定ファイルは、それ自身がJavaScriptモジュールであり、設定が記述されたオブジェクトをエクスポートしています。
そしてWebpackerのnpmモジュールは、ReactやSCSSなどの変換が使えるように設定されたWebpackの設定を提供するオブジェクトです。
ただし、厳密にはWebpack設定そのものではなく、カスタマイズがしやすいインターフェイスを備えた独自のオブジェクトになっています。</p>
<div class="highlight"><pre><span></span><span class="c1">// Webpackerの設定オブジェクトをインポートする</span>
<span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">environment</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">'@rails/webpacker'</span><span class="p">)</span>
<span class="c1">// ここで設定をカスタマイズする</span>
<span class="c1">// Webpackerの独自オブジェクトからWebpack設定オブジェクトに変換しエクスポートする</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">environment</span><span class="p">.</span><span class="nx">toWebpackConfig</span><span class="p">()</span>
</pre></div>
<p>Webpackerのオブジェクトで設定できるのは、ローダーとプラグインのみに制限されています。
ローダーは、拡張子ごとの変換を定義し、プラグインは、それ以外の一般的な拡張、たとえばminifyや(トランスパイル対象のコードへの)環境変数の注入などです。この範囲に収まらないカスタマイズがどうしても必要な場合は、 <code>toWebpackConfig()</code> した後の生のWebpack設定オブジェクトをいじる必要があります。</p>
</div>
<div class="section" id="section-8">
<h3><a class="toc-backref" href="#toc-entry-13">ジェネレータ</a></h3>
<p>Rails 5.1以降では、プロジェクト生成時にReact、Vue.js、Angular、Elmのどれかを選択して雛型を生成することができます。
Reactの場合は以下のようにします。</p>
<div class="highlight"><pre><span></span>rails<span class="w"> </span>new<span class="w"> </span>myapp<span class="w"> </span>--webpack<span class="o">=</span>react
</pre></div>
<p>あるいは、既存のプロジェクト(5.1にアップグレード済みかつwebpacker gem追加済みとする)にwebpacker関連の設定を追加するには、
以下のようにします。</p>
<div class="highlight"><pre><span></span>./bin/rails<span class="w"> </span>webpacker:install
./bin/rails<span class="w"> </span>webpacker:install<span class="o">=</span>react
</pre></div>
<p>ちなみに、この記事のサンプルプロジェクトは以下のコマンドで生成しました。</p>
<div class="highlight"><pre><span></span>rails<span class="w"> </span>new<span class="w"> </span>webpacker-react-example<span class="w"> </span>--webpack<span class="o">=</span>react<span class="w"> </span>--skip-turbolinks<span class="w"> </span>--skip-coffee<span class="w"> </span>--skip-sprockets
</pre></div>
</div>
<div class="section" id="webpacker-3">
<h3><a class="toc-backref" href="#toc-entry-14">Webpackerでアセットを管理する</a></h3>
<p>Webpackでは、JavaScript以外の一般のアセット、たとえばPNGやSVGなどのファイルをバンドルに入れるこのが可能です。
そのためには、JavaScriptのソースから、アセットをモジュールとしてインポートする必要があります。</p>
<div class="highlight"><pre><span></span><span class="c1">// react.svgをバンドルに追加する</span>
<span class="k">import</span><span class="w"> </span><span class="nx">reactIcon</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">'images/react.svg'</span>
</pre></div>
<p>このようにすると <code>images/react.svg</code> が最終的な生成物に含まれることになります。
<code>reactIcon</code> には、デプロイ後の環境で画像を参照するためのパスが入ります。
<code>import</code> の構文自体はES2015+のものですが、画像ファイルなどを対象としてインポートできる機能は、Webpackの独自拡張になります。
こうして、バンドルが依存しているアセットをコードで明示的に表現するのがWebpack流のやりかたです。</p>
<p>本記事では、Railsアプリで使用するすべてのアセットをWebpackerで管理するため、通常のViewから参照する画像などについても、JavaScriptで依存関係を表明しておく必要があります。RailsのViewから使用するすべての画像について個別にインポートするのは現実的ではないので、Webpackの <code>require.context</code> という関数を使います。</p>
<div class="highlight"><pre><span></span><span class="nx">require</span><span class="p">.</span><span class="nx">context</span><span class="p">(</span><span class="s1">'images'</span><span class="p">,</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="sr">/\.(png|jpg|jpeg|svg)$/</span><span class="p">)</span>
</pre></div>
<p>本サンプルプログラムでは、Webpackerの <code>source_path</code> を <code>app/assets</code> に変更しているため、このディレクトリにあるファイルは相対パスを使わないで参照ができます。2番目の引数は再帰的に検索することを意味します。従って、上記のコードで、<code>app/assets/images</code> 以下のすべての画像ファイルをpackに追加することを意味します。</p>
</div>
<div class="section" id="section-9">
<h3><a class="toc-backref" href="#toc-entry-15">参考リンク</a></h3>
<ul class="simple">
<li><a class="reference external" href="https://gist.github.com/tai2/bf284bd00039eabf405049ad42275bd1">Webpacker 2 → Webpacker 3 移行ログ</a></li>
</ul>
</div>
</div>
<div class="section" id="rails-ujs">
<h2><a class="toc-backref" href="#toc-entry-16">rails-ujs</a></h2>
<p>以前jquery-ujsと呼ばれていたものが、いまではjQuery依存を取り除かれ、 <a class="reference external" href="https://github.com/rails/rails/tree/master/actionview/app/assets/javascripts">rails-ujs</a> になりました。
Sprocketsを使用する場合はとくになにも設定しなくても有効化されていますが、本記事では、アセットパイプラインを使用しないため、JavaScriptのエントリポイントから明示的に <code>import</code> する必要があります。</p>
<div class="highlight"><pre><span></span><span class="k">import</span><span class="w"> </span><span class="nx">Rails</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">'rails-ujs'</span>
<span class="nx">Rails</span><span class="p">.</span><span class="nx">start</span><span class="p">()</span>
</pre></div>
<p>これでフォーム処理中のsubmitボタン自動無効化などが有効になります。</p>
<div class="section" id="csrf">
<h3><a class="toc-backref" href="#toc-entry-17">CSRFトークンの取得</a></h3>
<p>JavaScriptのCSRF保護機能を使うには、rails-ujsでCSRFトークンを取得して、リクエストヘッダに設定します。</p>
<div class="highlight"><pre><span></span><span class="k">import</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">csrfToken</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">'rails-ujs'</span>
<span class="nx">request</span>
<span class="w"> </span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s1">'/todos.json'</span><span class="p">)</span>
<span class="w"> </span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s1">'X-CSRF-Token'</span><span class="p">,</span><span class="w"> </span><span class="nx">csrfToken</span><span class="p">())</span>
</pre></div>
<p>上記コードでは、XHR APIをラップした <a class="reference external" href="https://github.com/visionmedia/superagent">superagent</a> を使っています。</p>
<p>なお、RailsのViewに <code>csrf_meta_tags</code> を挿入しておく必要があります。</p>
<div class="highlight"><pre><span></span><span class="cp"><%=</span><span class="w"> </span><span class="n">csrf_meta_tags</span><span class="w"> </span><span class="cp">%></span>
</pre></div>
</div>
</div>
<div class="section" id="react">
<h2><a class="toc-backref" href="#toc-entry-18">React</a></h2>
<p>昨今のフロントエンド開発では、Virtual DOMなどの仕組みに基いた、宣言的にHTMLを記述できるViewライブラリ群が隆盛を極めています。
その中でもとりわけ人気があるのがfacebookの開発している <a class="reference external" href="https://reactjs.org/">React</a> です。React自体はただのViewライブラリであり、APIも非常にシンプルでなにも難しいことはありません。</p>
<p>以下に、サンプルコードから、Reactのコンポーネント<sup id="sf-webpacker3-10-back"><a href="#sf-webpacker3-10" class="simple-footnote" title="ReactではViewの構成単位をコンポーネントと呼びます。">10</a></sup>定義の一例を挙げます。</p>
<div class="highlight"><pre><span></span>import TodoItem from '../TodoItem'
function TodoList(props) {
return (
<table className="table">
<thead>
<tr>
<th>Content</th>
<th>Due date</th>
<th />
</tr>
</thead>
<tbody>{props.todos.map(id => <TodoItem key={id} id={id} />)}</tbody>
</table>
)
}
</pre></div>
<p>Reactのコンポーネント描画関数では、Propsと呼ばれる入力パラメータを表すオブジェクトを外部から受け取って、JSXというXMLライクな記法で記述される要素を返します。上記では、todosという配列を受け取って、その各要素を別のコンポーネントに変換しています。
JSXは、HTMLとほぼ互換性があるので、HTMLの知識がそのまま流用できます。<sup id="sf-webpacker3-11-back"><a href="#sf-webpacker3-11" class="simple-footnote" title="例にもある用に、一部、 class のようなJavaScriptの予約語は使えないため、 className のように別のキーワードに置き換えられています。">11</a></sup>
与えられるPropsが変化すれば、それに応じて表示内容も変化します。</p>
<p>上記のコンポーネントでは、 <code><TodoItem></code> という大文字で始まる見慣れないタグが使われています。<sup id="sf-webpacker3-12-back"><a href="#sf-webpacker3-12" class="simple-footnote" title="DOM標準以外のコンポーネントは大文字で始まる必要があります。">12</a></sup>これは、アプリケーションで独自に定義したコンポーネントです。HTML標準以外のファイル外部で定義されたタグは、必ずJavaScriptモジュールとして <code>import</code> する必要があります。Reactプログラミングでは、こうして独自に定義したコンポーネントを組み合わせてViewツリーを構築していきます。
また、見ての通りただのJavaScriptの関数なので、<code>if</code> や <code>for</code> などすべてのJavaScript構文を使えます。</p>
<p>エントリポイントは、以下のようになります。</p>
<div class="highlight"><pre><span></span>// #todo-appの要素を検索し、その子要素としてAppコンポーネントをレンダリングする
ReactDOM.render(
<App />,
document.getElementById('todo-app')
)
</pre></div>
<p>これだけ見ると、どこからもPropsを注入していないし、Propsが与えられたとしても変化する余地がないのでインタラクティブなアプリを作れないと思われるかもしれません。どのように状態を扱うかについては、次節で説明します。</p>
<p>乱暴に言ってしまえば、Reactはただのテンプレートです。ただし、Virtual DOMのおかげで、複雑なViewをリアルタイムに書き換えても高速に描画されます。これとよく比較されるものとして、jQueryのようなユーティリティーでDOMの部分部分を手続き的に書き換える旧来の手法があります。
Reactベースのアプリ開発では、宣言的な言語で平易に記述できることや、モジュールシステムのおかげで依存関係が明確化されることで、格段にメンテナンス性が高まります。</p>
<div class="section" id="section-10">
<h3><a class="toc-backref" href="#toc-entry-19">参考リンク</a></h3>
<ul class="simple">
<li><a class="reference external" href="https://qiita.com/ossan-engineer/items/66feec268f9c4e582bb6">Reactアハ体験</a> Reactへの理解を深められます</li>
</ul>
</div>
</div>
<div class="section" id="fluxredux">
<h2><a class="toc-backref" href="#toc-entry-20">FluxアーキテクチャとRedux</a></h2>
<p>前節では紹介しませんでしたが、Reactにも状態を扱う機能はあります。これを使って、クリックなどのイベントに応じてなどインタラクティブに状態を変化させることも可能です。しかし、コンポーネント間でのデータの受け渡し方法は(基本的には)Propsしかないため、アプリの複雑な状態管理をこれだけで行うのは、不可能とは言わないまでも少々心許無いところです。</p>
<p>Reactの世界では、状態を管理するための手法として、 <a class="reference external" href="https://facebook.github.io/flux/docs/overview.html">Flux</a> というアーキテクチャが発展してきました。
Fluxライブラリにおいても、例によって激しい競争が行われましたが、これを生き残って今現在一強状態にあるのが <a class="reference external" href="http://redux.js.org/">Redux</a> です。</p>
<p>Reduxでは、アプリのほぼすべての状態<sup id="sf-webpacker3-13-back"><a href="#sf-webpacker3-13" class="simple-footnote" title="コンポーネント自身が状態を管理することもあるが、基本はストアに格納する">13</a></sup>をストアに格納します。
ストアは、言わば巨大なグローバル変数であり、それは基本的にはJSONシリアライズ可能なJavaScriptのオブジェクトです。<sup id="sf-webpacker3-14-back"><a href="#sf-webpacker3-14" class="simple-footnote" title="実際にはJSONシリアライズできないオブジェクトを格納することも可能で、それが必要な場合もあるが(FileやBlobなど)、そうするといくつかのReduxの恩恵を受けられなくなる">14</a></sup>
Reduxでのデータの流れは次の図のようになります。</p>
<div class="figure">
<img alt="Redux" src="https://blog.tai2.net/images/webpacker3/redux.png">
<p class="caption">Reduxにおけるデータの流れ</p>
</div>
<p>アプリ内でのユーザーの操作は、アクションと呼ばれるプレーンなJavaScriptオブジェクトで表現されます。
アクションがコンポーネントから送出されると、Reducerと呼ばれる関数が呼ばれます。
これは、現在のストア状態とアクションを受けて、次のストア状態を返す関数です。
ストアの状態変更は、必ずこのReducerを経由します。
ストアと接続されたコンポーネントはその状態を監視しているので、Reducerによって変更された状態は、コンポーネントに通知されます。
このように、データの流れが一方向に循環することから、Fluxは、一方向データフローであると言われます。</p>
<div class="highlight"><pre><span></span><span class="c1">// Reducerは、受け取ったアクションに応じて、新しいストア状態を返す。</span>
<span class="c1">// これによりストアが更新される。</span>
<span class="kd">function</span><span class="w"> </span><span class="nx">appReducer</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span><span class="w"> </span><span class="nx">action</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="nx">actions</span><span class="p">.</span><span class="nx">SELECT_ORDER</span><span class="o">:</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">...</span><span class="nx">state</span><span class="p">,</span>
<span class="w"> </span><span class="nx">sortBy</span><span class="o">:</span><span class="w"> </span><span class="nx">action</span><span class="p">.</span><span class="nx">payload</span><span class="p">.</span><span class="nx">sortBy</span><span class="p">,</span>
<span class="w"> </span><span class="nx">sortOrder</span><span class="o">:</span><span class="w"> </span><span class="nx">action</span><span class="p">.</span><span class="nx">payload</span><span class="p">.</span><span class="nx">sortOrder</span><span class="p">,</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="nx">actions</span><span class="p">.</span><span class="nx">TOGGLE_DONE_FILTER</span><span class="o">:</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">...</span><span class="nx">state</span><span class="p">,</span>
<span class="w"> </span><span class="nx">doneFilter</span><span class="o">:</span><span class="w"> </span><span class="o">!</span><span class="nx">state</span><span class="p">.</span><span class="nx">doneFilter</span><span class="p">,</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="c1">// ... 中略</span>
<span class="w"> </span><span class="k">default</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">state</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="c1">// react-reduxのconnect関数によって、コンポーネントとストアが接続される。</span>
<span class="nx">connect</span><span class="p">(</span>
<span class="w"> </span><span class="c1">// 1番目の引数でコンポーネントにストアの状態を渡し、</span>
<span class="w"> </span><span class="p">(</span><span class="nx">state</span><span class="p">)</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">({</span>
<span class="w"> </span><span class="nx">sortBy</span><span class="o">:</span><span class="w"> </span><span class="nx">state</span><span class="p">.</span><span class="nx">app</span><span class="p">.</span><span class="nx">sortBy</span><span class="p">,</span>
<span class="w"> </span><span class="nx">sortOrder</span><span class="o">:</span><span class="w"> </span><span class="nx">state</span><span class="p">.</span><span class="nx">app</span><span class="p">.</span><span class="nx">sortOrder</span><span class="p">,</span>
<span class="w"> </span><span class="nx">doneFilter</span><span class="o">:</span><span class="w"> </span><span class="nx">state</span><span class="p">.</span><span class="nx">app</span><span class="p">.</span><span class="nx">doneFilter</span><span class="p">,</span>
<span class="w"> </span><span class="p">}),</span>
<span class="w"> </span><span class="c1">// 2番目の引数でコンポーネントのコールバックを定義し、そこでアクションを送出する</span>
<span class="w"> </span><span class="p">(</span><span class="nx">dispatch</span><span class="p">)</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">({</span>
<span class="w"> </span><span class="nx">onOrderChange</span><span class="p">(</span><span class="nx">ev</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">[</span><span class="nx">prop</span><span class="p">,</span><span class="w"> </span><span class="nx">order</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">ev</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s1">'-'</span><span class="p">)</span>
<span class="w"> </span><span class="nx">dispatch</span><span class="p">({</span><span class="w"> </span><span class="nx">type</span><span class="o">:</span><span class="w"> </span><span class="nx">SELECT_ORDER</span><span class="p">,</span><span class="w"> </span><span class="nx">payload</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">sortBy</span><span class="p">,</span><span class="w"> </span><span class="nx">sortOrder</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">})</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="nx">onDoneFilterChange</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">dispatch</span><span class="p">({</span><span class="w"> </span><span class="nx">type</span><span class="o">:</span><span class="w"> </span><span class="nx">TOGGLE_DONE_FILTER</span><span class="w"> </span><span class="p">})</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="p">})</span>
<span class="p">)(</span><span class="nx">TodoConditions</span><span class="p">)</span>
</pre></div>
<p>Reduxを使用することで次のようなメリットが得られます。</p>
<ul class="simple">
<li>状態を必要とするコンポーネントがストアと接続されることで、<ul>
<li>コンポーネント自身が状態を持つ必要がなくなり、</li>
<li>Propsのバケツリレーも不要になる。</li>
</ul>
</li>
<li>react-reduxの提供する最適化機能により、不要なレンダリングを回避できる(パフォーマンス向上)</li>
</ul>
<p>また、アプリ内で起きたイベントが、Actionのシーケンスとして表現されるため、DX向上にも活用できます。
たとえば、 <a class="reference external" href="https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en">Chrome拡張</a> を導入すれば、次のスクリーンショットのようにアクションのログをブラウザ内で見ることができます。</p>
<div class="figure">
<img alt="redux-devtools-extention" src="https://blog.tai2.net/images/webpacker3/redux-devtools.png">
<p class="caption">Chrome拡張でアクションログが確認できる</p>
</div>
<div class="section" id="section-11">
<h3><a class="toc-backref" href="#toc-entry-21">非同期の扱い</a></h3>
<p>Reducerもコンポーネントもなんらかの値を受け取って、即時に値を返すただの関数であるため、
実は、前節までに紹介した枠組みのままでは、HTTPリクエストのような非同期な処理を実行する余地がありません。</p>
<p>Reduxでは、ミドルウェアと呼ばれる拡張機構が用意されており、非同期な処理はここで取り扱います。
非同期処理を扱うミドルウェアは、 <a class="reference external" href="https://github.com/gaearon/redux-thunk">redux-thunk</a> 、 <a class="reference external" href="https://github.com/acdlite/redux-promise">redux-promise</a> 、 <a class="reference external" href="https://github.com/agraboso/redux-api-middleware">redux-api-middleware</a> などさまざまな
ものがあり、しばしば論争の種になったりもしますが、筆者は <a class="reference external" href="https://redux-saga.js.org/">redux-saga</a> というライブラリを使用しています。</p>
<div class="figure">
<img alt="Redux with async" src="https://blog.tai2.net/images/webpacker3/redux-with-async.png">
<p class="caption">Reduxにおけるデータの流れ(非同期版)</p>
</div>
<p>redux-sagaでは、sagaと呼ばれる、reduxの通常の枠組みとは別の一種の外部環境を設け、
その中でアプリに関するすべての非同期な処理を扱います。
これは、Actionを受け取り、非同期処理を実行した結果として別のActionを送出する、
Aciton-Action変換として解釈できます。</p>
<p>Reduxの通常の枠組みをそのまま残しつつ、<sup id="sf-webpacker3-15-back"><a href="#sf-webpacker3-15" class="simple-footnote" title="redux-thunkやredux-promiseでは、アクションの定義を拡張します。これらを使った場合、Actionはもはや、ただのオブジェクトではありません。">15</a></sup>自然に非同期を取り入れることができるというのが、
筆者がredux-sagaを採用する理由です。
redux-sagaに非常に高機能な非同期処理のためのユーティリティー群が含まれますが、典型的なユースケースではその中のごく一部があれば十分です。</p>
<div class="highlight"><pre><span></span><span class="c1">// Todo項目追加時のsaga</span>
<span class="c1">// ADD_TODO_REQUESTEDアクションを受け取り、APiを呼び出して、</span>
<span class="c1">// ADD_TODO_RECEIVEDアクションを送出する</span>
<span class="kd">function</span><span class="o">*</span><span class="w"> </span><span class="nx">addTodoRequested</span><span class="p">(</span><span class="nx">action</span><span class="o">:</span><span class="w"> </span><span class="nx">actions</span><span class="p">.</span><span class="nx">AddTodoRequested</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">requestId</span><span class="p">,</span><span class="w"> </span><span class="nx">item</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">content</span><span class="p">,</span><span class="w"> </span><span class="nx">dueDate</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">action</span><span class="p">.</span><span class="nx">payload</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">item</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">yield</span><span class="w"> </span><span class="nx">call</span><span class="p">(</span><span class="nx">webApi</span><span class="p">.</span><span class="nx">addTodo</span><span class="p">,</span><span class="w"> </span><span class="nx">content</span><span class="p">,</span><span class="w"> </span><span class="nx">dueDate</span><span class="p">,</span><span class="w"> </span><span class="kc">false</span><span class="p">)</span>
<span class="w"> </span><span class="k">yield</span><span class="w"> </span><span class="nx">put</span><span class="p">(</span><span class="nx">actions</span><span class="p">.</span><span class="nx">addTodoReceived</span><span class="p">({</span><span class="w"> </span><span class="nx">requestId</span><span class="p">,</span><span class="w"> </span><span class="nx">item</span><span class="w"> </span><span class="p">}))</span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">yield</span><span class="w"> </span><span class="nx">put</span><span class="p">(</span>
<span class="w"> </span><span class="nx">actions</span><span class="p">.</span><span class="nx">addTodoReceived</span><span class="p">(</span>
<span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">IdentifiableError</span><span class="p">(</span><span class="nx">SINGLETON_ID</span><span class="p">,</span><span class="w"> </span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">)</span>
<span class="w"> </span><span class="p">)</span>
<span class="w"> </span><span class="p">)</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>sagaはジェネレータ関数で定義されるため非同期処理を逐次処理のように記述できます。
これもsagaの大きな魅力です。</p>
</div>
<div class="section" id="railsredux">
<h3><a class="toc-backref" href="#toc-entry-22">RailsからReduxへのデータ受け渡し</a></h3>
<p>RailsからReact Reduxアプリにデータを受け渡すには、Viewの中でデータ格納用の要素を用意し、属性としてJSON化した文字列を格納します。</p>
<div class="highlight"><pre><span></span><span class="cp"><%=</span><span class="w"> </span><span class="n">content_tag</span><span class="w"> </span><span class="ss">:div</span><span class="p">,</span>
<span class="w"> </span><span class="nb">id</span><span class="p">:</span><span class="w"> </span><span class="s1">'todos-data'</span><span class="p">,</span>
<span class="w"> </span><span class="ss">data</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="ss">todos</span><span class="p">:</span><span class="w"> </span><span class="vi">@todos</span>
<span class="w"> </span><span class="p">}</span><span class="o">.</span><span class="n">to_json</span><span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="cp">%></span>
<span class="cp"><%</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">%></span>
</pre></div>
<p>クライアント側からは、この文字列を取り出してパースした上で使用します。
Reduxの <a class="reference external" href="http://redux.js.org/docs/api/createStore.html">ストア作成関数</a> には、2番目の引数として初期値を指定できるため、これで、
アプリの初期状態をサーバー側から制御できます。</p>
<div class="highlight"><pre><span></span><span class="kd">function</span><span class="w"> </span><span class="nx">getPreloadedState</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">node</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'todos-data'</span><span class="p">)</span><span class="o">!</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">convert</span><span class="p">(</span><span class="nb">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="s1">'data'</span><span class="p">)))</span>
<span class="p">}</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'DOMContentLoaded'</span><span class="p">,</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// createAppStoreは、ストア作成用にアプリ内で定義しているヘルパー</span>
<span class="w"> </span><span class="nx">store</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">createAppStore</span><span class="p">(</span><span class="nx">getPreloadedState</span><span class="p">())</span>
<span class="w"> </span><span class="nx">render</span><span class="p">(</span><span class="nx">App</span><span class="p">)</span>
<span class="p">})</span>
</pre></div>
</div>
<div class="section" id="section-12">
<h3><a class="toc-backref" href="#toc-entry-23">参考リンク</a></h3>
<ul class="simple">
<li><a class="reference external" href="https://github.com/Microsoft/TypeScript-React-Starter">Microsoft/TypeScript-React-Starter</a> TypeScriptでReact Reduxを型付けをしているサンプルコード</li>
<li><a class="reference external" href="https://spin.atomicobject.com/2017/04/20/typesafe-container-components/">Typesafe Container Components with React-Redux’s Connect and TypeScript</a> TypeScriptでReact Reduxを型付けするやりかた</li>
<li><a class="reference external" href="https://medium.com/@martin_hotell/redux-typescript-typed-actions-with-less-keystrokes-d984063901d">Redux & Typescript typed Actions with less keystrokes</a> ReduxのアクションをTypeScriptでスマートに型付けする方法</li>
<li><a class="reference external" href="https://qiita.com/kuy/items/716affc808ebb3e1e8ac">redux-sagaで非同期処理と戦う</a> redux-sagaの日本一詳しい説明</li>
<li><a class="reference external" href="https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f">Using Redux DevTools in production</a> Redux DevToolsをプロダクション環境で使うことのメリット</li>
</ul>
</div>
</div>
<div class="section" id="css-modules">
<h2><a class="toc-backref" href="#toc-entry-24">CSS Modules</a></h2>
<p>コンポーネント指向の昨今のフロントエンドアプリ開発においては、CSSにも変革が起きています。
そのひとつが、 <a class="reference external" href="https://github.com/css-modules/css-modules">CSS Modules</a> です。
CSSでは、しばしばセレクタの詳細度が問題になり、
スタイル設計において問題を起こさないための技法として、 <a class="reference external" href="http://getbem.com/">BEM</a> のような技法が発展してきました。</p>
<p>CSS Modulesでは、コンポーネントごとに専用のCSSファイルを定義します。
そこでは、ファイル(モジュール)が固有の名前空間を持つため、クラス名の衝突が原理的に発生しません。
そのため、BEMのような技法を使わずとも自然と詳細度が1になります。</p>
<div class="highlight"><pre><span></span><span class="c">/* styles.scss */</span>
<span class="p">.</span><span class="nc">logo</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">width</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="kt">px</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<div class="highlight"><pre><span></span>import styles from './styles.scss'
function App({ todos }) {
return (
<div>
<img className={styles.logo} src={reactIcon} alt="react icon" />
<TodoConditions />
<TodoList todos={todos} />
<TodoAddForm />
</div>
)
}
</pre></div>
<p>スタイルシートがコンポーネントに属すことは <code>import</code> によってコードで明示
されます。
例えば、上記コードの <code>.logo</code> クラスは、CSS Modulesでなければ、<code>.App .logo</code> のように入れ子のセレクタになっていたかもしれません。
しかし、これでは詳細度が2に上がってしまい柔軟性が下がります。BEMライクであれば、 <code>.App__logo</code> のようになるのでしょうが、やや煩雑です。
我々にはCSS Modulesがあるので、いまやクラス名は、安全なままに、短く明確です。</p>
<p>なお、CSS Modulesとは別に、 <a class="reference external" href="http://cssinjs.org/">CSSinJS</a> という、スタイルをJavaScriptのコードで直接記述するアプローチもあります。</p>
<div class="section" id="bootstrap">
<h3><a class="toc-backref" href="#toc-entry-25">グローバルなクラスとBootstrap</a></h3>
<p>一方で、通常のRailsのViewからCSS Modulesを利用することはできません。
そのためReactコンポーネントと一対一で定義するスタイルシート以外に、グローバルなスタイルシートが必要になります。</p>
<p>サンプルコードでは、Rails View用のpackをひとつ用意し、そこからグローバルに使用するスタイルシートを取り込んでいます。</p>
<div class="highlight"><pre><span></span><span class="p">@</span><span class="k">import</span><span class="w"> </span><span class="s1">'~bootstrap/dist/css/bootstrap'</span><span class="p">;</span>
<span class="p">@</span><span class="k">import</span><span class="w"> </span><span class="s1">'~bootstrap/dist/css/bootstrap-theme'</span><span class="p">;</span>
<span class="p">@</span><span class="k">import</span><span class="w"> </span><span class="s1">'~stylesheets/scaffold'</span><span class="p">;</span>
<span class="p">@</span><span class="k">import</span><span class="w"> </span><span class="s1">'~stylesheets/react-datetime'</span><span class="p">;</span>
<span class="p">.</span><span class="nc">check</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">width</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">em</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">.</span><span class="nc">logo</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">width</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="kt">px</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>このようなスタイルシートを含むpackをレイアウトファイルで取り込んでいます。
Bootstrapもインポートしているため、アプリケーション全体で利用できます。</p>
<div class="highlight"><pre><span></span><span class="x"><!DOCTYPE html></span>
<span class="x"><html></span>
<span class="x"> <head></span>
<span class="x"> <title>WebpakcerReactExampl</title></span>
<span class="x"> </span><span class="cp"><%=</span><span class="w"> </span><span class="n">csrf_meta_tags</span><span class="w"> </span><span class="cp">%></span>
<span class="x"> </span><span class="cp"><%#</span><span class="c"> 'app'は グローバルアセットのためのpack </span><span class="cp">%></span>
<span class="x"> </span><span class="cp"><%=</span><span class="w"> </span><span class="n">javascript_pack_tag</span><span class="w"> </span><span class="s1">'app'</span><span class="w"> </span><span class="cp">%></span>
<span class="x"> </span><span class="cp"><%=</span><span class="w"> </span><span class="n">stylesheet_pack_tag</span><span class="w"> </span><span class="s1">'app'</span><span class="w"> </span><span class="cp">%></span>
<span class="x"> </span><span class="cp"><%=</span><span class="w"> </span><span class="k">yield</span><span class="w"> </span><span class="ss">:head</span><span class="w"> </span><span class="cp">%></span>
<span class="x"> </head></span>
<span class="x"> <body></span>
<span class="x"> <div class="container"></span>
<span class="x"> </span><span class="cp"><%=</span><span class="w"> </span><span class="k">yield</span><span class="w"> </span><span class="cp">%></span>
<span class="x"> </div></span>
<span class="x"> </body></span>
<span class="x"></html></span>
</pre></div>
<p>グローバルなアセットはクライアントアプリとも共有されるため、
Reactアプリからも同様にBootstrapのクラスを利用できます。</p>
<div class="highlight"><pre><span></span>function EditButton({ className = '', disabled = false, onClick }) {
return (
<button
type="button"
className={classNames('btn btn-default btn-xs', className)}
disabled={disabled}
aria-label="Edit"
onClick={onClick}
>
<span className="glyphicon glyphicon-edit" aria-hidden="true" />
</button>
)
}
</pre></div>
</div>
<div class="section" id="css-modulescsswebpack">
<h3><a class="toc-backref" href="#toc-entry-26">CSS ModulesとグローバルCSSを両立するためのWebpack設定</a></h3>
<p>Webpackerの提供するデフォルトの設定では、CSS Modulesは有効にはなっていません。
<a class="reference external" href="https://github.com/rails/webpacker/blob/master/docs/webpack.md#overriding-loader-options-in-webpack-3-for-css-modules-etc">ドキュメント</a> でCSS Modulesを有効化する方法は紹介されていますが、<code>.scss</code> 拡張子に対するローダーは1つしかなく、その設定を変更してしまっているため、今度はグローバルなCSSが使えなくなります。</p>
<p>本記事のサンプルアプリでは、スタイルシート用のローダを2つ用意した上で、<code>node_modules</code> と <code>app/assets/stylesheets</code> に置かれているスタイルシートはグローバル、それ以外はCSS Modulesとして、ディレクトリによって設定を分けています。</p>
<div class="highlight"><pre><span></span><span class="kd">const</span><span class="w"> </span><span class="nx">globalStylePaths</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="nx">resolve</span><span class="p">(</span><span class="s1">'app/assets/stylesheets'</span><span class="p">),</span>
<span class="w"> </span><span class="nx">resolve</span><span class="p">(</span><span class="s1">'node_modules'</span><span class="p">)</span>
<span class="p">]</span>
<span class="kd">function</span><span class="w"> </span><span class="nx">enableCssModules</span><span class="p">(</span><span class="nx">cssLoader</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">cssModuleOptions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">modules</span><span class="o">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nx">sourceMap</span><span class="o">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nx">localIdentName</span><span class="o">:</span><span class="w"> </span><span class="s1">'[name]__[local]___[hash:base64:5]'</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="nx">cssLoader</span><span class="p">.</span><span class="nx">options</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">merge</span><span class="p">(</span><span class="nx">cssLoader</span><span class="p">.</span><span class="nx">options</span><span class="p">,</span><span class="w"> </span><span class="nx">cssModuleOptions</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// デフォルトのstyleローダーは、app/assets/stylesheetsとnode_modulesに限定</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">styleLoader</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">environment</span><span class="p">.</span><span class="nx">loaders</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'style'</span><span class="p">)</span>
<span class="nx">styleLoader</span><span class="p">.</span><span class="nx">include</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">globalStylePaths</span>
<span class="c1">// styleローダーをコピーしつつ、上記で限定された以外のパスは、CSS Modulesを有効化</span>
<span class="ow">delete</span><span class="w"> </span><span class="nx">require</span><span class="p">.</span><span class="nx">cache</span><span class="p">[</span><span class="nx">require</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s1">'@rails/webpacker/package/loaders/style'</span><span class="p">)]</span>
<span class="kd">const</span><span class="w"> </span><span class="nx">moduleStyleLoader</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">'@rails/webpacker/package/loaders/style'</span><span class="p">)</span>
<span class="nx">moduleStyleLoader</span><span class="p">.</span><span class="nx">exclude</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">globalStylePaths</span>
<span class="nx">enableCssModules</span><span class="p">(</span><span class="nx">moduleStyleLoader</span><span class="p">.</span><span class="nx">use</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">el</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="nx">el</span><span class="p">.</span><span class="nx">loader</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s1">'css-loader'</span><span class="p">))</span>
<span class="nx">environment</span><span class="p">.</span><span class="nx">loaders</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s1">'moduleStyle'</span><span class="p">,</span><span class="w"> </span><span class="nx">moduleStyleLoader</span><span class="p">)</span>
</pre></div>
</div>
<div class="section" id="section-13">
<h3><a class="toc-backref" href="#toc-entry-27">参考リンク</a></h3>
<ul class="simple">
<li><a class="reference external" href="http://postd.cc/css-modules/">CSSモジュール ― 明るい未来へようこそ</a> CSS Modulesの魅力がわかりやすく書いてある記事</li>
</ul>
</div>
</div>
<div class="section" id="typescript-1">
<h2><a class="toc-backref" href="#toc-entry-28">TypeScript</a></h2>
<p>近年のJavaScript界では、静的型チェックの実施がますます普通のことになってきています。
取り得る選択肢は2つ、TypeScriptと <a class="reference external" href="https://flow.org/">flowtype</a> です。
どちらも素のJavaScriptとほぼ機能的な互換を保ちつつ、静的型付けのために文法を拡張しています。
型システム自体もかかなり似ており、どちらも構造的部分型がベースになっています。</p>
<p>TypeScriptは、ES5などの下位バージョンへのトランスパイラも兼ねていますが、
flowtypeは、純粋に型付けのためのツールという立ち位置になっています。
また、どちらも第三者が作った型付けされていないモジュールに、後付けで型を定義できる仕組みを持っており、
そのための中央リポジトリを持っている点も同じです。</p>
<p>TypeScriptとflowtypeどちらにするかは、非常に悩ましい選択なのですが、
redux-sagaなどの依存しているライブラリが、公式に型定義を提供しているという理由から、
TypeScriptを選択しました。<sup id="sf-webpacker3-16-back"><a href="#sf-webpacker3-16" class="simple-footnote" title="redux-sagaの型定義も中央リポジトリにあるにはあるのですが、バージョンアップに追随できていないのが現状です。また、redux-sagaとflowtypeについての筆者の理解度が低いために、自分で型定義を書くことはあきらめました。">16</a></sup></p>
<p>型チェックから受けられる恩恵は非常に大きなもので、コードが満たすべき性質を記述することで、かなりのプログラミングエラーを未然に防いでくれます。個人的に、型チェックなしの環境でプログラミングしていると、Reducerでのプログラミングエラーがしばしば発生し、デバッグに時間を取られていたのですが、これがかなり改善されたと思います。以下は型付けされたReducerの抜粋です。</p>
<div class="highlight"><pre><span></span><span class="kr">interface</span><span class="w"> </span><span class="nx">TodoMap</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">readonly</span><span class="w"> </span><span class="p">[</span><span class="nx">id</span><span class="o">:</span><span class="w"> </span><span class="nx">number</span><span class="p">]</span><span class="o">:</span><span class="w"> </span><span class="nx">Readonly</span><span class="o"><</span><span class="nx">Todo</span><span class="o">></span>
<span class="p">}</span>
<span class="kr">interface</span><span class="w"> </span><span class="nx">TodosState</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">readonly</span><span class="w"> </span><span class="nx">byId</span><span class="o">:</span><span class="w"> </span><span class="nx">TodoMap</span>
<span class="w"> </span><span class="nx">readonly</span><span class="w"> </span><span class="nx">ids</span><span class="o">:</span><span class="w"> </span><span class="nx">number</span><span class="p">[]</span>
<span class="p">}</span>
<span class="kd">function</span><span class="w"> </span><span class="nx">addTodoReceived</span><span class="p">(</span><span class="nx">state</span><span class="o">:</span><span class="w"> </span><span class="nx">TodosState</span><span class="p">,</span><span class="w"> </span><span class="nx">action</span><span class="o">:</span><span class="w"> </span><span class="nx">actions</span><span class="p">.</span><span class="nx">AddTodoReceived</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">payload</span><span class="w"> </span><span class="ow">instanceof</span><span class="w"> </span><span class="ne">Error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">state</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">newTodo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">action</span><span class="p">.</span><span class="nx">payload</span><span class="p">.</span><span class="nx">item</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">...</span><span class="nx">state</span><span class="p">,</span>
<span class="w"> </span><span class="nx">byId</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">...</span><span class="nx">state</span><span class="p">.</span><span class="nx">byId</span><span class="p">,</span>
<span class="w"> </span><span class="p">[</span><span class="nx">newTodo</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span><span class="o">:</span><span class="w"> </span><span class="nx">newTodo</span><span class="p">,</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="nx">ids</span><span class="o">:</span><span class="w"> </span><span class="p">[...</span><span class="nx">state</span><span class="p">.</span><span class="nx">ids</span><span class="p">,</span><span class="w"> </span><span class="nx">newTodo</span><span class="p">.</span><span class="nx">id</span><span class="p">],</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span><span class="w"> </span><span class="nx">todosReducer</span><span class="p">(</span>
<span class="w"> </span><span class="nx">state</span><span class="o">:</span><span class="w"> </span><span class="nx">TodosState</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">initialTodosState</span><span class="p">,</span>
<span class="w"> </span><span class="nx">action</span><span class="o">:</span><span class="w"> </span><span class="nx">actions</span><span class="p">.</span><span class="nx">Action</span>
<span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nx">TodosState</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="nx">actions</span><span class="p">.</span><span class="nx">ADD_TODO_RECEIVED</span><span class="o">:</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">addTodoReceived</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span><span class="w"> </span><span class="nx">action</span><span class="p">)</span>
<span class="w"> </span><span class="c1">// ... 中略</span>
<span class="w"> </span><span class="k">default</span><span class="o">:</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">state</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>引数に型指定(<code>:</code> の右側)が付いている点に注意してください。
これにより、たとえば <code>state</code> に存在しないプロパティーを参照しようとすると、コンパイルエラーになります。</p>
<p>また、<code>TodoState</code> でストアの形が型で定義されているため、
あらかじめ定義されたストアの形状と異なる状態を作ってしまうことが原理的に発生しなくなります。</p>
<p>ReduxのReducerは純粋関数である必要があります。もし、新しい状態オブジェクトを返すのではなく、<code>state</code> 引数を直接書き換えて返してしまうと、コンポーネントの描画が更新されないという不具合が起きます。
上記定義では、ストアのプロパティーに <code>readonly</code> 修飾子が付いているため、そもそも書き換えることができません。</p>
<div class="section" id="javascript-sprinkles">
<h3><a class="toc-backref" href="#toc-entry-29">小粒なJavaScript(Sprinkles)</a></h3>
<p>現状、JavaScriptをSprocketsでビルドする場合、Webpackを通らないため、<code>import</code> ステートメントは使えませんし、TypeScriptを使うこともできません。
本記事の方式であれば、すべてのJavaScriptはWebpackを通るため、Viewの中で使用するいわゆる小粒なJavaScriptでさえも、モダン環境の恩恵をフルに受けられます。以下はその例です。TypeScriptで記述され、<code>import</code> ステートメントを使用しています。</p>
<div class="highlight"><pre><span></span><span class="c1">// セレクトボックスの状態に応じてページURLを変更する小粒なJavaScript</span>
<span class="k">import</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="kr">as</span><span class="w"> </span><span class="nx">queryString</span><span class="w"> </span><span class="kr">from</span><span class="w"> </span><span class="s1">'query-string'</span>
<span class="kd">function</span><span class="w"> </span><span class="nx">prepereSelectElems</span><span class="p">()</span><span class="o">:</span><span class="w"> </span><span class="ow">void</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">doms</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span>
<span class="w"> </span><span class="s1">'select[data-change-query]'</span>
<span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="kr">as</span><span class="w"> </span><span class="nx">NodeListOf</span><span class="o"><</span><span class="nx">HTMLSelectElement</span><span class="o">></span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">query</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">queryString</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">)</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="kd">const</span><span class="w"> </span><span class="nx">select</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nx">doms</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">query</span><span class="p">.</span><span class="nx">sort_by</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">select</span><span class="p">.</span><span class="nx">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">query</span><span class="p">.</span><span class="nx">sort_by</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="nx">select</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'change'</span><span class="p">,</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">query</span><span class="p">.</span><span class="nx">sort_by</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">select</span><span class="p">.</span><span class="nx">value</span>
<span class="w"> </span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sb">`?</span><span class="si">${</span><span class="nx">queryString</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">query</span><span class="p">)</span><span class="si">}</span><span class="sb">`</span>
<span class="w"> </span><span class="p">})</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'DOMContentLoaded'</span><span class="p">,</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">prepereSelectElems</span><span class="p">()</span>
<span class="p">})</span>
</pre></div>
</div>
<div class="section" id="section-14">
<h3><a class="toc-backref" href="#toc-entry-30">参考リンク</a></h3>
<ul class="simple">
<li><a class="reference external" href="http://typescript.ninja/typescript-in-definitelyland/">Revised Revised 型の国のTypeScript</a> 日本語で書かれたTypeScriptの解説書。</li>
<li><a class="reference external" href="https://webpack.js.org/guides/typescript/">TypeScript(Webpack公式ページ)</a> TypeScriptのためのWebpack設定。アセット用の型定義についても言及されている。</li>
<li><a class="reference external" href="https://github.com/blacksonic/typescript-webpack-tree-shaking">Tree-shaking example with Typescript and Webpack</a> TypeScript + WebpackでのTree-shakingのサンプル。</li>
<li><a class="reference external" href="https://medium.com/@martin_hotell/tree-shake-lodash-with-webpack-jest-and-typescript-2734fa13b5cd">Tree shake Lodash with Webpack, Jest and Typescript</a> TypeScript + Webpackでlodashをtree shakingする方法</li>
</ul>
</div>
</div>
<div class="section" id="babeles2015">
<h2><a class="toc-backref" href="#toc-entry-31">BabelとES2015+</a></h2>
<p>ES5へのトランスパイルにTypeScriptを使用しない場合の選択肢が、Babelを使ったトランスパイルです。
こちらはプラグイン形式になっており、TypeScriptよりも幅広いカスタマイズが可能です。</p>
<p>筆者は、TypeScriptをES5へのトランスパイラとしては使わず単なる型チェッカーとして使っています。
そのため、TypeScriptにはECMAScriptの最新仕様でコードを出力させ、そこからさらにBabelでのトランスパイルを行います。</p>
<p>構成の複雑さが増すというデメリットがある一方、このようにすることで、次のようなメリットが受けられます:</p>
<ul class="simple">
<li><a class="reference external" href="https://github.com/babel/babel-preset-env">babel-preset-env</a> によって、現在のブラウザの実装状況に応じて必要最低限のpolyfill<sup id="sf-webpacker3-17-back"><a href="#sf-webpacker3-17" class="simple-footnote" title="ブラウザ実装の足りない部分を補うために、その実装を自分のコードに含めること">17</a></sup>と変換を行うことができる。</li>
<li>Webpackのtree shaking<sup id="sf-webpacker3-18-back"><a href="#sf-webpacker3-18" class="simple-footnote" title="未使用のコードを除去してバンドルサイズを縮小すること">18</a></sup>で、classも除去できる(Babelを通さない場合、classはそのまま残る)</li>
<li><a class="reference external" href="https://github.com/lodash/babel-plugin-lodash">babel-plugin-lodash</a> <sup id="sf-webpacker3-19-back"><a href="#sf-webpacker3-19" class="simple-footnote" title="lodashは、JavaScript使いに人気のあるユーティリティーライブラリです。すべての関数をバンドルに入れると、かなりのサイズになります。">19</a></sup>で、めんどうな手続きなしに、最低限の <a class="reference external" href="https://lodash.com/">lodash</a> 関数だけをバンドルに入れることができる。<sup id="sf-webpacker3-20-back"><a href="#sf-webpacker3-20" class="simple-footnote" title="ただし、Webpack 4からはtree shakingが強化されてTypeScriptでも同様の効果を得られる模様">20</a></sup></li>
<li>React Componentの <a class="reference external" href="https://github.com/gaearon/react-hot-loader">Hot Loading</a> が、Babelを使わない場合よりもすこし機能アップする。</li>
</ul>
<p>Babelを通すかどうかについては、非常に迷ったのですが、将来的にもBabelを通したほうが多くのメリットを得られるであろうと考え、こちらに賭けることにしました。ただし、実際には設定の変更だけでソースコードはそのままでいいはずなので、後でBabelをはずすのは、おそらく簡単なはずです。</p>
<p>なお、Webpackerのデフォルト設定では、 <a class="reference external" href='import"babel-polyfill"'>babel-polyfillが組込まれていない</a> ため、エントリーポイントで、</p>
<div class="highlight"><pre><span></span><span class="k">import</span><span class="w"> </span><span class="s1">'babel-polyfill'</span>
</pre></div>
<p>する必要があります。</p>
<div class="section" id="section-15">
<h3><a class="toc-backref" href="#toc-entry-32">参考リンク</a></h3>
<ul class="simple">
<li><a class="reference external" href="http://es6-features.org/">ECMAScript 6 — New Features: Overview & Comparison</a> ES6の新機能が短いコード例で紹介されており、短時間でキャッチアップできます。</li>
<li><a class="reference external" href="https://github.com/mbeaudru/modern-js-cheatsheet">Modern JavaScript Cheatsheet</a></li>
</ul>
</div>
</div>
<div class="section" id="storybook">
<h2><a class="toc-backref" href="#toc-entry-33">Storybook</a></h2>
<p><a class="reference external" href="https://storybook.js.org/">Storybook</a> は、プロトタイピング、ビジュアルTDD、デザイナーとの協業など、さまざまな可能性を秘めたツールです。
これをを使うと、Reactコンポーネントを状態ごとにカタログとして一覧表示できます。</p>
<div class="figure">
<img alt="Storybook" src="https://blog.tai2.net/images/webpacker3/storybook.png">
<p class="caption">Storybookによるコンポーネントの表示</p>
</div>
<p>Storybookでは、コンポーネントごとにstoriesと呼ばれる一連の状態定義を行います。
これは、以下のようにさながらユニットテストのような見た目をした<sup id="sf-webpacker3-21-back"><a href="#sf-webpacker3-21" class="simple-footnote" title="入力値のパターンを列挙しつつ対象コードを実行するという意味で">21</a></sup>コードになっています。</p>
<div class="highlight"><pre><span></span>storiesOf('TodoAddForm', module)
.add('typical', () => (
<TodoAddForm
addTodoRequest={succeededRequest}
onAddTodo={action('added')}
/>
))
.add('while adding', () => (
<TodoAddForm addTodoRequest={loadingRequest} onAddTodo={action('added')} />
))
.add('adding error', () => (
<TodoAddForm addTodoRequest={errorRequest} onAddTodo={action('added')} />
))
</pre></div>
</div>
<div class="section" id="section-16">
<h2><a class="toc-backref" href="#toc-entry-34">ユニットテスト</a></h2>
<p>JavaScriptでのテストフレームワークは、<a class="reference external" href="https://mochajs.org/">Mocha</a> 、<a class="reference external" href="https://jasmine.github.io/">Jasmine</a> 、<a class="reference external" href="https://github.com/avajs/ava-docs/blob/master/ja_JP/readme.md">Ava</a> 、<a class="reference external" href="http://facebook.github.io/jest/ja/">Jest</a> などの選択肢があります。
<sup id="sf-webpacker3-22-back"><a href="#sf-webpacker3-22" class="simple-footnote" title="筆者もあまり詳しいわけではないので、たぶん他にもいろいろあると思います。">22</a></sup></p>
<p>個人的には、アーキテクチャが洗練されていて並列実行に対応していたり、後述のpower-assertベース
でアサーションAPIが簡単なAvaを使いたかったのですが、
残念ながら現段階では <a class="reference external" href="https://github.com/avajs/ava/blob/master/docs/specs/001%20-%20Improving%20language%20support.md#typescript-projects">トランスパイラのサポートがまだ弱く、</a> TypeScriptのコードをテストするのは厳しそうだっったため、
Mochaを選択しました。</p>
<div class="section" id="power-assert">
<h3><a class="toc-backref" href="#toc-entry-35">power-assert</a></h3>
<p><a class="reference external" href="https://github.com/power-assert-js/power-assert">power-assert</a> を使うと、Node.jsの標準アサーションAPIを使いつつ、テスト失敗時に結果をわかりやすく表示できます。
アサーションAPIは厳選されており、多数の細分化されたアサーションAPIの使い分けに頭を悩ますことなく、テスト対象という本質にフォーカスできます。</p>
<div class="figure">
<img alt="power-assert" src="https://blog.tai2.net/images/webpacker3/power-assert.png">
<p class="caption">power-assertを使えば式のどこが期待と異なるのか一目瞭然</p>
</div>
<p>サンプルコードでは、Mocha上でTypeScriptのコードをテストするために、 <a class="reference external" href="https://github.com/power-assert-js/espower-typescript">espower-typescript</a> を使いました。
これを使うと、テスト時にテストコードとテスト対象両方の自動的なトランスパイルが可能になります。
なお、ブラウザ向けビルドと異なり、テストコード自体は、Babelを通さずに直接実行されるため、
TypeScriptの設定ファイルをターゲットに応じて分けています。</p>
</div>
<div class="section" id="enzyme">
<h3><a class="toc-backref" href="#toc-entry-36">Enzyme</a></h3>
<p>Reactコンポーネントのユニットテストには <a class="reference external" href="http://airbnb.io/enzyme/">Enzyme</a> を使用します。</p>
<div class="highlight"><pre><span></span>describe('<TodoAddForm />', () => {
describe('display errors', () => {
context('when request failed', () => {
it('should render error message', () => {
const request = { requesting: false, error: new Error('error') }
const wrapper = enzyme.shallow(
<TodoAddForm addTodoRequest={request} onAddTodo={_.noop} />
)
assert(wrapper.find('.error').exists())
})
})
})
})
</pre></div>
<p>このようにテスト内にJSXでコンポーネントを直接記述する形になります。
Viewのテストは壊れやすくなりがちで難しい面がありますが、ReactでTDDを実践したい人などには
便利かもしれません。</p>
</div>
<div class="section" id="section-17">
<h3><a class="toc-backref" href="#toc-entry-37">参考リンク</a></h3>
<ul class="simple">
<li><a class="reference external" href="https://journal.artfuldev.com/unit-testing-node-applications-with-typescript-using-mocha-and-chai-384ef05f32b2">Unit testing node applications with TypeScript — using mocha and chai</a> TypeScriptのコードをMochaでテストする方法。</li>
</ul>
</div>
</div>
<div class="section" id="prettier">
<h2><a class="toc-backref" href="#toc-entry-38">Prettier</a></h2>
<p><a class="reference external" href="https://github.com/prettier/prettier">Prettier</a> はコードの自動フォーマッタです。TypeScriptやSCSSにも対応しています。
自動的にコーディングにある程度の一貫性が得られるのでたいへんありがたいです。</p>
</div>
<div class="section" id="tslint">
<h2><a class="toc-backref" href="#toc-entry-39">TSLint</a></h2>
<p><a class="reference external" href="https://palantir.github.io/tslint/">TSLint</a> は、TypeScript用の静的解析ツールです。
Prettierと重複する部分がありますが、こちらはコーディングスタイル以外にもコーディングエラーを発見してくれたりします。
TypeScriptとPretteirを導入している環境だと相対的な重要度は低いと言えますが、コストゼロでメリットが得られるので導入しています。</p>
</div>
<div class="section" id="webpack-bundle-analyzer">
<h2><a class="toc-backref" href="#toc-entry-40">webpack-bundle-analyzer</a></h2>
<p>冒頭でも書きましたが、webpackを通してバンドル化すると、ちょっとしたアプリでもすぐにJavaScriptが数MBを越えます。
ファイルサイズは、アプリのロード時間に直結するため重要です。
<a class="reference external" href="https://github.com/webpack-contrib/webpack-bundle-analyzer">webpacker-bundle-analyzer</a> というプラグインを使えば、
以下の画像のようにバンドルサイズの内訳をグラフィカルに表示できるので、最適化のための方針が立てやすくなります。</p>
<div class="figure">
<img alt="webpacker-bundle-analyzer" src="https://blog.tai2.net/images/webpacker3/webpacker-bundle-analyzer.png">
<p class="caption">webpacker-bundle-analyzerによる解析結果</p>
</div>
</div>
<div class="section" id="section-18">
<h2><a class="toc-backref" href="#toc-entry-41">まとめ</a></h2>
<p>この記事では、まず、Webpacker 3を使って構築したサンプルアプリを元にしつつ、Webpackerの基本的な機能と使い方を説明しました。
同時に、Sprocketsを使用しなくともRailsアプリが成立することを説明し、そのための設定を紹介しました。</p>
<p>後半では、React Reduxをはじめとして、モダンフロントエンド開発で使用されている便利なライブラリやツールを簡単に紹介しました。
また、CSS Modulesの組込についてはとくに注意が必要なため、設定方法を紹介しました。Babelの節では、TypeScriptとBabelを併用することで得られるささいなメリットについても説明しました。</p>
</div>
<ol class="simple-footnotes"><li id="sf-webpacker3-1">記事内で引用しているコードは、型アノテーションを省略するなど、適宜省略した形で抜粋しています。 <a href="#sf-webpacker3-1-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-2">ほぼscaffoldingが生成したものそのままです <a href="#sf-webpacker3-2-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-3">ただし、現状のUglifyJSでは、ES2015+をサポートしていないため実質的に無効化される。将来的にはUglifyJSが改善されて有効化される見込み。 <a href="#sf-webpacker3-3-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-4">とは言え、紹介しているライブラリ・ツールはどれもJavaScript界で一定の評価を得ているポピュラーなものばかりです。 <a href="#sf-webpacker3-4-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-5">筆者の案件では使わないため <a href="#sf-webpacker3-5-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-6">以前であれば、Sprocketsを使わずにWebpackのみでアセットを管理するためには、 <a class="reference external" href="http://engineer.crowdworks.jp/entry/2016/05/24/174511">ヘルパなどを自前で実装する必要がありましたが、</a> いまではWebpackerのみで事足ります。 <a href="#sf-webpacker3-6-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-7">ECMAScriptの仕様は、2015以降毎年更新されています。それらを総称して2015+と呼んだりもします。また、2015以前の仕様であるES5と対比する意味で、ES6,ES7,ES8などと呼ばれたりもします。 <a href="#sf-webpacker3-7-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-8">Node.jsのパッケージ管理ツール、及びその中央リポジトリ。Rubyで言うところのRubyGems。JavaScriptのエコシステムはnpmを要として発展しています。 <a href="#sf-webpacker3-8-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-9">Webpackerのディレクトリが <code>app/javascript/</code> であることには、<a class="reference external" href="https://github.com/rails/webpacker/pull/2#issuecomment-265611291">明確な意図</a> が込められているようです。 <a href="#sf-webpacker3-9-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-10">ReactではViewの構成単位をコンポーネントと呼びます。 <a href="#sf-webpacker3-10-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-11">例にもある用に、一部、 <code>class</code> のようなJavaScriptの予約語は使えないため、 <code>className</code> のように別のキーワードに置き換えられています。 <a href="#sf-webpacker3-11-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-12">DOM標準以外のコンポーネントは大文字で始まる必要があります。 <a href="#sf-webpacker3-12-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-13">コンポーネント自身が状態を管理することもあるが、基本はストアに格納する <a href="#sf-webpacker3-13-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-14">実際にはJSONシリアライズできないオブジェクトを格納することも可能で、それが必要な場合もあるが(FileやBlobなど)、そうするといくつかのReduxの恩恵を受けられなくなる <a href="#sf-webpacker3-14-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-15">redux-thunkやredux-promiseでは、アクションの定義を拡張します。これらを使った場合、Actionはもはや、ただのオブジェクトではありません。 <a href="#sf-webpacker3-15-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-16">redux-sagaの型定義も中央リポジトリにあるにはあるのですが、バージョンアップに追随できていないのが現状です。また、redux-sagaとflowtypeについての筆者の理解度が低いために、自分で型定義を書くことはあきらめました。 <a href="#sf-webpacker3-16-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-17">ブラウザ実装の足りない部分を補うために、その実装を自分のコードに含めること <a href="#sf-webpacker3-17-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-18">未使用のコードを除去してバンドルサイズを縮小すること <a href="#sf-webpacker3-18-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-19">lodashは、JavaScript使いに人気のあるユーティリティーライブラリです。すべての関数をバンドルに入れると、かなりのサイズになります。 <a href="#sf-webpacker3-19-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-20">ただし、Webpack 4からはtree shakingが強化されてTypeScriptでも同様の効果を得られる模様 <a href="#sf-webpacker3-20-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-21">入力値のパターンを列挙しつつ対象コードを実行するという意味で <a href="#sf-webpacker3-21-back" class="simple-footnote-back">↩</a></li><li id="sf-webpacker3-22">筆者もあまり詳しいわけではないので、たぶん他にもいろいろあると思います。 <a href="#sf-webpacker3-22-back" class="simple-footnote-back">↩</a></li></ol>Flux Standard Actionで失敗したアクションを識別する場合は、カスタムエラーオブジェクトを作れば良い2017-09-26T00:00:00+09:002017-09-26T00:00:00+09:00tai2tag:blog.tai2.net,2017-09-26:/fsa-error.html<p class="first last">ReduxなどのFluxアプリケーションの実装では、アクションのフォーマットとして、Flux Standard Action(FSA) を採用しているプロジェクトも多いと思います。 FSAを使って非同期アクションの結果を表現しようとしているときに、以下のような疑問を抱いたことはないでしょうか?</p>
<div class="section" id="section-1">
<h2>問題</h2>
<p>ReduxなどのFluxアプリケーションの実装では、アクションのフォーマットとして、 <a class="reference external" href="https://github.com/acdlite/flux-standard-action">Flux Standard Action(FSA)</a> を採用しているプロジェクトも多いと思います。
FSAを使って非同期アクションの結果を表現しようとしているときに、以下のような疑問を抱いたことはないでしょうか?</p>
<p>たとえば、TODOアイテムの更新をするアクションとして、<code>UPDATE_TODO:REQUESTED</code>, <code>UPDATE_TODO:RECEIVED</code> という2つのアクションがあるとします。これらは、それぞれHTTPリクエストとレスポンスに対応します。</p>
<div class="highlight"><pre><span></span><span class="c1">// リクエスト</span>
<span class="p">{</span>
<span class="w"> </span><span class="nx">type</span><span class="o">:</span><span class="w"> </span><span class="s1">'UPDATE_TODO:REQUESTED'</span><span class="p">,</span>
<span class="w"> </span><span class="nx">payload</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">id</span><span class="o">:</span><span class="w"> </span><span class="mf">100</span><span class="p">,</span>
<span class="w"> </span><span class="nx">text</span><span class="o">:</span><span class="w"> </span><span class="s1">'Do something!'</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="c1">// レスポンス</span>
<span class="p">{</span>
<span class="w"> </span><span class="nx">type</span><span class="o">:</span><span class="w"> </span><span class="s1">'ADD_TODO:RECEIVED'</span><span class="p">,</span>
<span class="w"> </span><span class="nx">payload</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">id</span><span class="o">:</span><span class="w"> </span><span class="mf">100</span><span class="p">,</span>
<span class="w"> </span><span class="nx">text</span><span class="o">:</span><span class="w"> </span><span class="s1">'Do something!'</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>リクエスト成功時は、これで問題ありません。</p>
<p>では、エラーレスポンスが返ってきたときはどうなるでしょうか?
FSAにおいては、<code>error</code> プロパティーが <code>true</code> の場合、<code>payload</code> はエラーオブジェクトであるべきと定められています。
JavaScriptのエラーオブジェクトは、 <a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error">Errorコンストラクタ</a> で表現されます。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="w"> </span><span class="nx">type</span><span class="o">:</span><span class="w"> </span><span class="s1">'ADD_TODO:RECEIVED'</span><span class="p">,</span>
<span class="w"> </span><span class="nx">payload</span><span class="o">:</span><span class="w"> </span><span class="nx">error</span><span class="p">,</span><span class="w"> </span><span class="c1">// error instanceof Error === true</span>
<span class="w"> </span><span class="nx">error</span><span class="o">:</span><span class="w"> </span><span class="kc">true</span>
<span class="p">}</span>
</pre></div>
<p>問題は、複数のTODOの更新操作が並行して走る場合です。リクエストエラー時には、ユーザーに対して操作が完了しなかったことを通知したいでしょう。複数の更新操作が同時に走るのであれば、どのアイテムに対する操作が失敗したのかも示したほうが親切かもしれません。</p>
<p>しかし、この場合、<code>payload</code> は <code>Error</code> オブジェクトに占有されてしまっています。エラー時に、複数の操作を識別するためには、どうすれば良いでしょうか?</p>
</div>
<div class="section" id="section-2">
<h2>解法: カスタムエラーオブジェクトを作成する</h2>
<p>ひとつの方法として、カスタムエラーオブジェクトを作成することが考えられます。</p>
<div class="highlight"><pre><span></span><span class="kd">class</span><span class="w"> </span><span class="nx">IdentifiableError</span><span class="w"> </span><span class="k">extends</span><span class="w"> </span><span class="ne">Error</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kr">constructor</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="p">...</span><span class="nx">args</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">super</span><span class="p">(...</span><span class="nx">args</span><span class="p">)</span>
<span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'IdentifiableError'</span>
<span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">targetId</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">id</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>このように専用のエラークラスを定義し、リクエストエラー時には、このオブジェクトを <code>payload</code> に格納します。
エラーオブジェクトの <code>targetId</code> プロパティーを参照すれば、どの項目についての操作が失敗したのかを識別できます。
また、静的型環境での型付けも問題ありません。</p>
</div>
<div class="section" id="section-3">
<h2>別の解法1: エラー時専用のアクションを用意する</h2>
<p>アクションとして、<code>UPDATE_TODO:REQUESTED</code> 、<code>UPDATE_TODO:RECEIVED</code> の2種類ではなく、<code>UPDATE_TODO:REQUESTED</code> 、<code>UPDATE_TODO:SUCCEEDED</code> , <code>UPDATE_TODO:FAILED</code> の3種類にします。
そして、FAILEDの場合には、<code>error !== true</code> の通常のアクションとして、<code>payload</code> に非エラーオブジェクトを乗せます。</p>
</div>
<div class="section" id="meta">
<h2>別の解法2: metaに情報を持たせる</h2>
<p>FSAでは、<code>type</code> 、 <code>payload</code> 、 <code>error</code> 以外の第4のフィールドとして、<code>meta</code> プロパティーを持つことが許されています。
<code>meta</code> プロパティーにも <code>payload</code> と同様に任意のオブジェクトを乗せられるため、これを使って解決することも可能です。
ただ、いまここで乗せたい識別情報は、<code>meta</code> に乗せるような情報なのかは、判断が難しいところです。
更新対象のIDというのは、どちらかというと、ペイロードそのもののようにも思えます。</p>
<p>ミドルウェアで、アクションごとのユニークなIDを発行して、それをmetaに乗せて、TODOアイテムと関連付けた上でリクエストの状態を管理するというような、よりシステマチックで大掛りな方法も考えられるかもしれません。</p>
</div>
<div class="section" id="section-4">
<h2>参考リンク</h2>
<ul class="simple">
<li><a class="reference external" href="https://github.com/acdlite/flux-standard-action/issues/17">What is the reason error property is boolean? #17</a> FSAのリポジトリでこの問題について議論されています。</li>
<li><a class="reference external" href="https://medium.com/@xjamundx/custom-javascript-errors-in-es6-aa891b173f87">Custom JavaScript Errors in ES6</a> カスタムエラーオブジェクトの作成方法が解説されています。</li>
</ul>
</div>
React Redux Real World Examples 〜先人から学ぶReact Reduxの知恵〜2017-02-22T00:00:00+09:002017-02-22T00:00:00+09:00tai2tag:blog.tai2.net,2017-02-22:/real-world-redux.html<p class="first last">この記事は、実際のReact Reduxプロダクトのソースコードを調査することで、筆者がふだんReact Reduxで開発をしていて感じる疑問への答えを探る試みです。</p>
<p>React Reduxを使ってプロダクトを作りはじめて、かれこれ半年くらい経ちます。
しかし、どうもうまく書けていない気がすることがときどきあり、悩んでいたところ、ツイッターで次のような助言をもらいました。</p>
<blockquote class="twitter-tweet" data-lang="en-gb"><p lang="ja" dir="ltr"><a href="https://twitter.com/__tai2__">@__tai2__</a> 達人かどうかは微妙なところがありますが、ある程度の規模のコードはここにリンク集あります <a href="https://t.co/B79B5s1DTe">https://t.co/B79B5s1DTe</a></p>— Yuki Kodama (@kuy) <a href="https://twitter.com/kuy/status/806651108793851904">8 December 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script><p>この記事は、上記のリンク集でまとめられている実際のReact Reduxプロダクトのソースコードを調査することで、筆者がふだんReact Reduxで開発をしていて感じる疑問への答えを探る試みです。</p>
<p>筆者が答えを得たいと思っている疑問は次の3つです
<sup id="sf-real-world-redux-1-back"><a href="#sf-real-world-redux-1" class="simple-footnote" title="いろいろわかった今から見れば、単に筆者の無知から来る疑問だったものもあります。実際のところ、これらの疑問の多くは 公式ドキュメント を隅から隅まで読めば、答えやヒントが言及されています)。ちなみに、副作用については、筆者の中では現状 redux-saga で対応するという答えで決着しており、とくに疑問に思う部分もないため扱いません。">1</a></sup></p>
<ul class="simple">
<li>Storeはどんな具合に階層化すべきか</li>
<li>Store初期化(hydration)用データの定義はどうすべきか</li>
<li>Componentはどう整理すべきか</li>
</ul>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-1" id="toc-entry-1">事例</a><ul>
<li><a class="reference internal" href="#project-tofino" id="toc-entry-2">Project Tofino</a></li>
<li><a class="reference internal" href="#wp-calypso" id="toc-entry-3">wp-calypso</a></li>
</ul>
</li>
<li><a class="reference internal" href="#ducks" id="toc-entry-4">[コラム]Ducksパターン</a></li>
<li><a class="reference internal" href="#store" id="toc-entry-5">Storeはどんな具合に構成すべきか</a></li>
<li><a class="reference internal" href="#store-1" id="toc-entry-6">Store構成についての見解</a></li>
<li><a class="reference internal" href="#store-hydration" id="toc-entry-7">Store初期化(hydration)用データの定義はどうすべきか</a><ul>
<li><a class="reference internal" href="#store-2" id="toc-entry-8">Store初期化用のデータについての見解</a></li>
</ul>
</li>
<li><a class="reference internal" href="#component" id="toc-entry-9">Componentはどう整理すべきか</a><ul>
<li><a class="reference internal" href="#component-1" id="toc-entry-10">Componentについての見解</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-2" id="toc-entry-11">まとめ</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-12">参考リンク</a></li>
</ul>
</div>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-1">事例</a></h2>
<p>冒頭で上げたReact Reduxアプリリンク集には、実に32個ものサンプルがありました(2016年12月時点)。
ひととおり手元にダウンロードして動かしてみたり、コードがどのように書かれているかを簡単に確認しましたが、ここですべてに触れることはもちろんできません。本記事では、規模の大きさやコードの綺麗さなどから参考になりそうな2つのアプリをとくに詳しく見ます。</p>
<div class="section" id="project-tofino">
<h3><a class="toc-backref" href="#toc-entry-2">Project Tofino</a></h3>
<p><a class="reference external" href="https://github.com/mozilla/tofino">https://github.com/mozilla/tofino</a></p>
<div class="figure">
<img alt="Project Tofino" src="https://blog.tai2.net/images/real-world-redux/tofino.png">
<p class="caption">Tofinoのスクリーンショット</p>
</div>
<p>MozillaによるブラウザUI実験用のElectronアプリで、UI部分がReact Reduxで実装されています。
小〜中規模プロジェクトでありそうなコード量(UI部分だけで12000行程度)であることと、Reducerのコードが非常に読みやすく整理されているため取り上げることにします。Immutable.js使用。</p>
<p>redux部分は、 <a class="reference external" href="https://github.com/mozilla/tofino/tree/7fd8ff0f9a17159893ea4edd613bb90fbc791a29/app/ui">/app/ui/にまとまっています。</a></p>
</div>
<div class="section" id="wp-calypso">
<h3><a class="toc-backref" href="#toc-entry-3">wp-calypso</a></h3>
<p><a class="reference external" href="https://github.com/Automattic/wp-calypso">https://github.com/Automattic/wp-calypso</a></p>
<div class="figure">
<img alt="wp-calypso" src="https://blog.tai2.net/images/real-world-redux/wp-calypso.png">
<p class="caption">wp-calypsoのスクリーンショット</p>
</div>
<p>Automatic社による、React Reduxを(部分的に)使ったWordPress.comの新しい実装です。
それなりの規模があるコードベース(クライアントサイド35万行以上)で、どのようにコードを整理しているのかの実例として、取り上げます。
Node.jsによるSSR込みの本格的なUniversal JavaScriptの実例でもあります。
DucksというReduxの状態を関心ごとに分離するパターンに近い形で構成されています。</p>
<p>かなり大規模で、すでにある程度の歴史を経ている(Initial commitは2015年11月)だけあって、
createClassとextends Componentが同居してたり、fluxとreduxが同居してたり、Object.assignとImmutable.jsが同居してたりする、という意味でもリアルです。</p>
</div>
</div>
<hr class="docutils">
<div class="section" id="ducks">
<h2><a class="toc-backref" href="#toc-entry-4">[コラム]Ducksパターン</a></h2>
<p><a class="reference external" href="https://github.com/erikras/ducks-modular-redux">Ducks</a> は、Action Type、Action Creator、Reducerを関心ごとにひとまとめにしてパッケージ化するためのパターンです。
本来、ActionはReducerとは直交する概念であり、任意のReducerが任意のAction Typeを扱うことができます。
しかし、実際のアプリでは、ReducerとAction Typeの間に偏りがあり、あるActionは、特定のReducerによってしか処理されないことが多いです。
たとえば、<code>CREATE_USER</code> , <code>UPDATE_USER</code> , <code>DELETE_USER</code> というAction Typeは、<code>user</code> Reducerにしか影響しない、といった具合です。</p>
<p>Ducskの提案では、どのようなルールでパッケージを定義するかというところを厳密に定義していますが、大事なのは関心ごとにこれらをまとめることで、アプリをサブシステムに分解することができ、管理がしやすくなるということです。たとえば、上記に加えて <a class="reference external" href="https://twitter.com/dan_abramov/status/664581975764766721">Selectorもパッケージに含めてもいいと思います。</a>
wp-calypsoでは、Ducsk風の関心の分離を行うことで、分割統治を行っています。大規模になるほど、このパターンの有効性は増してくると思われます。</p>
</div>
<hr class="docutils">
<div class="section" id="store">
<h2><a class="toc-backref" href="#toc-entry-5">Storeはどんな具合に構成すべきか</a></h2>
<p>Storeの形(Shape)は、Reducerの返すデータによって規定されます。言いかたを変えると、ReducerはStoreがそうあるべき形状に合致したデータを返さなければなりません。Storeをどのように構成するかによって、Reducerの可読性が決まるといっても過言ではないでしょう。</p>
<p>Reducerは、 <code>(state, action) -> state</code> とい形式の純粋関数です。
純粋関数という制約を持つがゆえに、書くときにじゃっかん特殊なテクニックが要求されます。</p>
<p>あるとき、筆者はReducerのあるケース節がとんでもなく可読性の低いコードになっていることに気付きました。
階層化されたデータを直接変更することなく新しい値を得るために、無数のmap、アロー関数、スプレッド記法、Object.assignなどが詰め込まれた、解読に時間を要するようなコードです。このようなコードの例として、例えば、 <a class="reference external" href="https://github.com/nicksenger/JSchematic">JSchematic</a> というアプリの <a class="reference external" href="https://github.com/nicksenger/JSchematic/blob/29b841e7ec94c0730f0af277a6aa51554390ad14/src/js/reducers/reducerManageElements.js#L12">Reducerの一部</a> が挙げられます。これは、なかなか読みづらいと思います。</p>
<p>また、こんなこともありました。Reducerの実装では、Redux標準のcombineReducersという高階関数を利用することで、複数のスライス(サブツリー)に分割して、関心の分離を実現できます。
これはReduxアプリでの基本テクニックですが、スライスされたReducerから、別のスライスに分離されたサブツリーを参照したくなりました。
combineReducersによる分離では、各スライスが、あたかも独立したツリーであるかのようになり、お互いに見えなくなります。</p>
<div class="highlight"><pre><span></span><span class="kd">function</span><span class="w"> </span><span class="nx">a</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span><span class="w"> </span><span class="nx">action</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// スライスaを変更する処理</span>
<span class="p">}</span>
<span class="kd">function</span><span class="w"> </span><span class="nx">b</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span><span class="w"> </span><span class="nx">action</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// スライスbを変更する処理</span>
<span class="p">}</span>
<span class="kd">function</span><span class="w"> </span><span class="nx">c</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span><span class="w"> </span><span class="nx">action</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// スライスcを変更する処理</span>
<span class="p">}</span>
<span class="k">export</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="nx">combineReducers</span><span class="p">({</span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="p">,</span><span class="w"> </span><span class="nx">c</span><span class="p">});</span>
<span class="c1">// combineReducersで状態を分割した場合、お互いの状態を知らない「スライス」となる。</span>
<span class="c1">// スライスcからスライスa,bの値が欲しくなっても参照することはできない。</span>
</pre></div>
<p>どうすればいいかしばらく悩んだあげく、しかたがないので欲しい値を計算する処理を、見える範囲にもうひとつ書いてしのぎました。つまり、重複する値の計算処理をスライスごとに1個ずつ書いたのです。たしかに、これで動きはしましたが、正しいことをしている感覚はありませんでした。</p>
<p>そこで、次のような疑問が浮かびます:</p>
<ul class="simple">
<li>combineReducersの勘所はどのようなものか。</li>
<li>Reducerの可読性を高めるStoreの構成法はどんなものか。</li>
<li>ツリーは、どの程度深くなるか、あるいは深くすべきではないか。</li>
<li>スライス間で共有したいデータがある場合、どうすべきか。</li>
</ul>
</div>
<div class="section" id="store-1">
<h2><a class="toc-backref" href="#toc-entry-6">Store構成についての見解</a></h2>
<p>Reducerの構成方法については、実は <a class="reference external" href="http://redux.js.org/docs/recipes/StructuringReducers.html">公式のStructuring Reducersというドキュメント</a> にかなり丁寧に書いてあって、これを読めばだいたい勘所が掴めます。この中では、</p>
<ul class="simple">
<li>ルートReducer: 大本のReducer</li>
<li>スライスReducer: combineReducersで分割されたReducer</li>
<li>ケース関数: Reducerの中のひとつのcase節に相等する関数</li>
<li>高階Reducer: Reducerを受け取って別のReducerを返す関数</li>
</ul>
<p>といった概念を導入し、それに沿ってReducerを読み易くするための工夫を説明しています。
とくに、 <a class="reference external" href="http://redux.js.org/docs/recipes/reducers/RefactoringReducersExample.html">Refactoring Reducer Logic Using Functional Decomposition and Reducer Composition</a> などは参考になると思います。適切に関数を分割して、ユーティリティー関数を導入することで、可読性が向上していく様が見て取れるからです。</p>
<p>TofinoのReducerは、redux-ecosystem-linksに載っているアプリの中でも、とくに見易い印象を受けました。
たとえば、 <a class="reference external" href="https://github.com/mozilla/tofino/blob/7fd8ff0f9a17159893ea4edd613bb90fbc791a29/app/ui/browser-modern/reducers/pages.js">pagesというReducer</a> は、Tofinoの中でももっとも複雑なスライスReducerですが、それでも十分な読み易さを保っていると思います。
ポイントは、case節内の具体的な処理をすべてケース関数に抜き出しているところです。筆者の経験では、一定以上の長さのReducerでは、これをするだけで、ずいぶん見通しが良くなって印象が変わります。</p>
<p>個々のケース関数に関しては、Immutable.jsの恩恵によって可読性が向上している面があります。Immutable.jsでは、withMutationsを使えば不変データ構造の更新を破壊的に記述することができます。よって、ふつうの手続き型プログラミングと変わらない感覚でReducerが記述できます。</p>
<p>例えば、ページを新規追加するときのケース関数は以下のようになっています。</p>
<div class="highlight"><pre><span></span><span class="kd">function</span><span class="w"> </span><span class="nx">createPage</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">UIConstants</span><span class="p">.</span><span class="nx">HOME_PAGE</span><span class="p">,</span><span class="w"> </span><span class="nx">options</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">selected</span><span class="o">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nx">index</span><span class="o">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
<span class="p">})</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">state</span><span class="p">.</span><span class="nx">withMutations</span><span class="p">(</span><span class="nx">mut</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">page</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">Page</span><span class="p">({</span><span class="w"> </span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">location</span><span class="w"> </span><span class="p">});</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">pageIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">options</span><span class="p">.</span><span class="nx">index</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">null</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="nx">options</span><span class="p">.</span><span class="nx">index</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="nx">state</span><span class="p">.</span><span class="nx">displayOrder</span><span class="p">.</span><span class="nx">size</span><span class="p">;</span>
<span class="w"> </span><span class="nx">mut</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="s1">'displayOrder'</span><span class="p">,</span><span class="w"> </span><span class="nx">l</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="nx">l</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="nx">pageIndex</span><span class="p">,</span><span class="w"> </span><span class="nx">page</span><span class="p">.</span><span class="nx">id</span><span class="p">));</span>
<span class="w"> </span><span class="nx">mut</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="s1">'ids'</span><span class="p">,</span><span class="w"> </span><span class="nx">s</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">id</span><span class="p">));</span>
<span class="w"> </span><span class="nx">mut</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="s1">'map'</span><span class="p">,</span><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="p">=></span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">page</span><span class="p">));</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">selected</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">mut</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s1">'selectedId'</span><span class="p">,</span><span class="w"> </span><span class="nx">page</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">});</span>
<span class="p">}</span>
</pre></div>
<p>ためしに、これをImmutable.jsを使わずに <a class="reference external" href="http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html">Immutable Update Patterns</a> にならってES2015+の記法のみで書くと、おそらくこのような形になるでしょう。</p>
<div class="highlight"><pre><span></span><span class="kd">function</span><span class="w"> </span><span class="nx">createPage</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">UIConstants</span><span class="p">.</span><span class="nx">HOME_PAGE</span><span class="p">,</span><span class="w"> </span><span class="nx">options</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">selected</span><span class="o">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nx">index</span><span class="o">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
<span class="p">})</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">page</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">Page</span><span class="p">({</span><span class="w"> </span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">location</span><span class="w"> </span><span class="p">});</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">pageIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">options</span><span class="p">.</span><span class="nx">index</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">null</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="nx">options</span><span class="p">.</span><span class="nx">index</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="nx">state</span><span class="p">.</span><span class="nx">displayOrder</span><span class="p">.</span><span class="nx">size</span><span class="p">;</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">displayOrder</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">...</span><span class="nx">state</span><span class="p">.</span><span class="nx">displayOrder</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mf">0</span><span class="p">,</span><span class="w"> </span><span class="nx">pageIndex</span><span class="p">),</span>
<span class="w"> </span><span class="nx">page</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="w"> </span><span class="p">...</span><span class="nx">state</span><span class="p">.</span><span class="nx">displayOrder</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">pageIndex</span><span class="p">),</span>
<span class="w"> </span><span class="p">];</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">ids</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[...</span><span class="nx">state</span><span class="p">.</span><span class="nx">ids</span><span class="p">,</span><span class="w"> </span><span class="nx">page</span><span class="p">.</span><span class="nx">id</span><span class="p">];</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">newPage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">[</span><span class="nx">page</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span><span class="o">:</span><span class="w"> </span><span class="nx">page</span><span class="w"> </span><span class="p">};</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">map</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">...</span><span class="nx">state</span><span class="p">.</span><span class="nx">map</span><span class="p">,</span><span class="w"> </span><span class="p">...</span><span class="nx">newPage</span><span class="w"> </span><span class="p">};</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">newState</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">...</span><span class="nx">state</span><span class="p">,</span>
<span class="w"> </span><span class="nx">displayOrder</span><span class="p">,</span>
<span class="w"> </span><span class="nx">ids</span><span class="p">,</span>
<span class="w"> </span><span class="nx">map</span><span class="p">,</span>
<span class="w"> </span><span class="p">};</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">selected</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">newState</span><span class="p">.</span><span class="nx">selectedId</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">page</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">newState</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>Immutable.jsを使うのと使わないのでは、コードを理解するために要する時間がまったく違います。
ただ、筆者の所感としては、Immutable.jsは絶対に必要というわけではなく、適宜定型的な処理をユーティリティー関数として抽出したり、場合によっては、 <a class="reference external" href="https://github.com/debitoor/dot-prop-immutable">dot-prop-immutable</a> のようなモジュールを利用して補うことで、十分に可読性を保てると思っています。</p>
<p>wp-calypsoでは、Ducksライクに <a class="reference external" href="https://github.com/Automattic/wp-calypso/tree/6153f05db236cfadad8bc166edf99088974b493f/client/state">状態を関心ごとに分離しています。</a> 各ディレクトリごとにREADMEが配置されていて、設計論のようなものが記述されていたりするのがおもしろいです。Storeの階層はそれなりに深くなっています。扱う状態が深くなるほど、Reducerの可読性は低くなる傾向にあるように思いますが、combineReducersによって適切に状態をスライスすることで、個々のReducerはそれほど読みづらくはなっていない印象です。<sup id="sf-real-world-redux-2-back"><a href="#sf-real-world-redux-2" class="simple-footnote" title="もちろん、全Reducerに目を通したわけではありませんが…">2</a></sup>。スライスのスライスのスライスのような、3重にcombineRecucersされたReducerも見られることからも、このwp-calypsoの大規模さがうかがえます。</p>
<p>こうした例から、Storeの構成法、ツリーの深さといったことについて得た結論は、こうです:
基本的には、 <a class="reference external" href="http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html">正規化</a> を適切に施せば、そもそもツリーはそれほど深くならないはずだが、大規模アプリなど、管理の都合上どうしてもツリーが深くってしまう場合であっても、combineReducersによって適切にツリーをスライスすることで、Reducerの可読性を保つことができる。</p>
<p>さて、combineReducersによって状態をスライスしたときに、そのメリットのコインの裏返しとして現れてくるのが、前述した、他のスライスが見えなくなるという問題です。これも基本的には、 <a class="reference external" href="http://redux.js.org/docs/recipes/reducers/BeyondCombineReducers.html#sharing-data-between-slice-reducers">公式のBeyond combineReducersというドキュメント</a> で解決策がいくつか提示されてますが、ここでは、そのひとつとして、 <a class="reference external" href="https://github.com/acdlite/reduce-reducers">reduce-reducers</a> を取り上げます。reduce-reducersを使うと、Reducerの過程を2パス(あるいはそれ以上)に分割することができます。つまり、reduce-reducersを利用して、1パス目で、他のスライスに依存しない通常のReducerを実行、その後、2パス目で、他のスライスに依存するReducerを改めて実行、というふうにするのです。例えば、次のコードを見てください。</p>
<div class="highlight"><pre><span></span><span class="kd">function</span><span class="w"> </span><span class="nx">a</span><span class="p">(</span><span class="nx">state</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1</span><span class="p">,</span><span class="w"> </span><span class="nx">action</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="s1">'FOO'</span><span class="o">:</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">state</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mf">1</span><span class="p">;</span>
<span class="w"> </span><span class="k">default</span><span class="o">:</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">state</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span><span class="w"> </span><span class="nx">b</span><span class="p">(</span><span class="nx">state</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1</span><span class="p">,</span><span class="w"> </span><span class="nx">action</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="s1">'FOO'</span><span class="o">:</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">state</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mf">2</span><span class="p">;</span>
<span class="w"> </span><span class="k">default</span><span class="o">:</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">state</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="c1">// assuming state.a and state.b already exists</span>
<span class="kd">function</span><span class="w"> </span><span class="nx">c</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span><span class="w"> </span><span class="nx">action</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="s1">'FOO'</span><span class="o">:</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">...</span><span class="nx">state</span><span class="p">,</span>
<span class="w"> </span><span class="nx">c</span><span class="o">:</span><span class="w"> </span><span class="nx">state</span><span class="p">.</span><span class="nx">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">state</span><span class="p">.</span><span class="nx">b</span><span class="p">,</span>
<span class="w"> </span><span class="p">};</span>
<span class="w"> </span><span class="k">default</span><span class="o">:</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">state</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="nx">reduceReducers</span><span class="p">(</span><span class="nx">combineReducers</span><span class="p">({</span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="p">}),</span><span class="w"> </span><span class="nx">c</span><span class="p">);</span>
</pre></div>
<p>a,bは通常のスライスで、cはa,bの計算結果に依存します。このようにreduce-reducersを利用することで、適切な分割を保ちつつ、スライス間でデータを共有できない問題が解消できます。</p>
</div>
<div class="section" id="store-hydration">
<h2><a class="toc-backref" href="#toc-entry-7">Store初期化(hydration)用データの定義はどうすべきか</a></h2>
<p>Reduxでは、Store作成時に初期化(hydration)用のデータを与えることができます。</p>
<div class="highlight"><pre><span></span><span class="nx">createStore</span><span class="p">(</span><span class="nx">reducers</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="nx">x</span><span class="o">:</span><span class="w"> </span><span class="mf">1</span><span class="p">,</span><span class="w"> </span><span class="nx">y</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="mf">3</span><span class="p">,</span><span class="mf">4</span><span class="p">],</span><span class="w"> </span><span class="nx">z</span><span class="o">:</span><span class="w"> </span><span class="s1">'foo'</span><span class="p">});</span>
</pre></div>
<p>ここで与えるデータの形は、Reducerによって規定されるデータの形と一致している必要があります。
つまり、Storeの形状はあらかじめ定められており、初期値を与える側と、Reducer側が協調して動作する必要があります。
上の例で言うと、reducerの返す状態は、xという数値、yという配列、zという文字列をプロパティとして持っているという暗黙の知識が前提になっています。
しかし、ある程度の規模のアプリでStoreが複雑になってきたときに、初期値として与えているデータと、Reducerの期待するデータの形状が一致していると、どうすれば確信できるでしょうか?なにかひとつの対象を二重管理しているような気がして、若干の不安を感じます。</p>
<div class="section" id="store-2">
<h3><a class="toc-backref" href="#toc-entry-8">Store初期化用のデータについての見解</a></h3>
<p>これには、いくつかの緩和、あるいは解決策があります。</p>
<p>まず、 <a class="reference external" href="http://redux.js.org/docs/recipes/reducers/InitializingState.html#combined-reducers">combineReducersの動作</a> を知ることで、これはそれほど問題にはならないことがわかります。なぜなら、combineReducersによってスライスすることで、初期化時にすべてのデータを与える必要はなくなるからです。アプリの実装において、ルートReducerは、combineReducersによって分離されたスライスの集合になっている場合が多いと思います。</p>
<div class="highlight"><pre><span></span><span class="kd">const</span><span class="w"> </span><span class="nx">rootReducer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">combineReducer</span><span class="p">({</span><span class="nx">x</span><span class="p">,</span><span class="w"> </span><span class="nx">y</span><span class="p">,</span><span class="w"> </span><span class="nx">z</span><span class="p">});</span>
</pre></div>
<p>このようにしたときに、createStoreに与えるデータは、x,y,zそれぞれを個別に指定できます。undefinedのプロパティが存在した場合、そのプロパティは、スライスごとに定義された初期値(通常、Reducerの第一引数のデフォルト値)が使われます。Storeの形について必要な知識も部分的になるため、問題が緩和されたと言えるでしょう。</p>
<p>Tofinoで興味深いのは、Immutable.jsの <a class="reference external" href="https://facebook.github.io/immutable-js/docs/#/Record">Record</a> を利用して、 <a class="reference external" href="https://github.com/mozilla/tofino/blob/7fd8ff0f9a17159893ea4edd613bb90fbc791a29/app/ui/browser-modern/model/index.js">Storeの形状を型として表現している</a> ことです。Tofinoでは、 <a class="reference external" href="https://github.com/mozilla/tofino/tree/7fd8ff0f9a17159893ea4edd613bb90fbc791a29/app/ui/browser-modern/model">model/ディレクトリ</a> にデータ構造が型としてまとめられています。これによって、Storeの形がコードで明示されることになるため、だいぶ安心できます。ただし、Recordの挙動は、プリミティブレベルまで含めたデータ型を定義して厳密なチェックを行うわけではなく、キーが存在しない場合はデフォルト値で初期化、型に定義されていないキーがコンストラクタに渡された場合は無視、存在しないキーに後からsetした場合ランタイムエラーというものなので、バリデーション用途で使うには、心許無いかもしれません。</p>
<p>データ型の定義という点において、wp-calypsoではまた違ったアプローチを取っています。このアプリでは、 <a class="reference external" href="https://github.com/mafintosh/is-my-json-valid">is-my-json-valid</a> というJSONバリデータを使って定義したスキーマによって、動的に型チェックを行います。
このスキーマ定義は、初期化時にローカル保存しておいたStoreの状態が、動作しているプログラムが期待するデータ構造と一致するかをチェックすることを目的としたものです。wp-calypsoでは、SERIALIZEアクションによって構築される永続化用の状態を、 <a class="reference external" href="https://github.com/Automattic/wp-calypso/blob/f8ea145698153ffcc69579362b264d945483d030/client/state/initial-state.js#L70">定期的にローカル保存します。</a> アプリのバージョンが異なれば永続化されるデータ構造も異なる可能性があるため、このようなバリデーションが必要になってくるのです。</p>
<p>あるいは、 <a class="reference external" href="https://flowtype.org/">flowtype</a> を使ってStoreの状態全体の型を定義すれば、Storeの形状は暗黙の知識ではなくコードで明示されたものになるため、問題を完全に解消できます。ただし、flowtypeによるチェックはあくまで静的なものであるため、ローカルに保存しておいたデータをStoreに流し込んだときに、動的にデータの整合性をチェックするような使い方はできません。<sup id="sf-real-world-redux-3-back"><a href="#sf-real-world-redux-3" class="simple-footnote" title=" flow-runtime を利用することで、解決するかもしれません。">3</a></sup></p>
<p>余談ですが、Server Side Renderingを行う際のRedux Storeへのデータの受け渡し・初期化方法についても、 <a class="reference external" href="http://redux.js.org/docs/recipes/ServerRendering.html">Server Rendering</a> というドキュメントが用意されています。 <a class="reference external" href="https://github.com/relax/relax">Relax</a> というCMSが、このドキュメント <a class="reference external" href="https://github.com/relax/relax/blob/cf18abcd28fbabd593bdccfc61721c9b64935750/lib/server/shared/helpers/render-html.js">ほぼそのままのやりかた</a> で実装しており、ReduxでSSRをやるときには参考になると思います。</p>
</div>
</div>
<div class="section" id="component">
<h2><a class="toc-backref" href="#toc-entry-9">Componentはどう整理すべきか</a></h2>
<p>JSXによって再利用性の高いComponentを宣言的に記述できることが、Reactの特徴のひとつです。
Componentは実際のところただのJavaScriptの関数もしくはクラスなので、容易に括り出して共通化などができます。
このComponentを細分化する粒度について、なにか指針はあるでしょうか?</p>
<p>Reduxでは、 <a class="reference external" href="https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options">connect関数</a> を用いてStoreとComponentを接続します。
接続されたComponentはStoreに変更があると自動的にプロパティが更新されます。
Reduxでは、接続されたComponentを <a class="reference external" href="https://medium.com/@learnreact/container-components-c0e67432e005#.e5fgnyfic">Container component</a> 、接続されていないComponentをPresentational componentなどと言って区別します。
Container componentは、Componentツリーのルートに限らず、どのノードに差し込んでもかまいません。
Container componentを挿入する位置について、なにか指針はあるでしょうか?</p>
<p>また、Componentの作りかたに関連して、以下のような疑問もあります。</p>
<ul class="simple">
<li>componentの純粋さにどこまでこだわるべきか。</li>
<li><code>containers/</code> と <code>components/</code> は分けるべきか。</li>
<li>階層が深くなったときに、<code>propName={propsName}</code> のような、プロパティを上から下に流すだけで記述が冗長になる問題には、どう対処すべきか。</li>
</ul>
<div class="section" id="component-1">
<h3><a class="toc-backref" href="#toc-entry-10">Componentについての見解</a></h3>
<p>Componentの整理方法については、redux-ecosystem-linksに載っているアプリを見た限り、実に様々です。</p>
<p>Componentの純粋さにどこまでこだわるべきか、UIに関する状態をStoreに掃き出すか否かに関する切り分けはどうするか、といったことに関して、
無理なく記述できるのであれば、Componentにはstateを持たせず純粋関数にしておいたほうが良い、という一般論以上の指針は得られませんでした。 <a class="reference external" href="http://redux.js.org/docs/recipes/reducers/BasicReducerStructure.html">公式のBasic Reducer Structure and State Shape</a> というドキュメントでは、アプリケーションの処理・表示対象となるドメインデータ、現在選択中のアイテムなどを示すアプリ状態、それからUIの状態という3つに分類しています。このうちドメインデータとアプリ状態については、Storeに格納すれば良いと迷いなく判断できるのですが、UI状態については、わざわざStoreに格納する意味が薄くComponentに状態を持たせたほうが合理的な気がして、判断に悩む部分があります。ケースバイケースで判断するしかないかもしれません。</p>
<p>Reduxに含まれるサンプルプログラムをはじめとして、多くのアプリでは、 <code>containers/</code> と <code>components/</code> という形で <a class="reference external" href="https://github.com/reactjs/redux/tree/master/examples/real-world/src">ディレクトリを分けています。</a>
しかし、このディレクトリ構成にどれほど意味があるのか筆者は疑問を感じています。
主な理由としては、PresentationalとContainerの区別というのは、 <a class="reference external" href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.7smj0zmty">それほど明確ではなく、</a> しばしばPresentational ComponentであったものがContainer Componentに昇格したりします。また、Presentationalの世界にContainerはいっさい現れることなく閉じているのであればともかく、実際には、PresentationalとContainerが入り乱れてビューツリーを構築します。 また、しばらくこのやりかたで開発をしてみて、大きなメリットを感じたこともありません。むしろ、ディレクトリおよびクラスが明確に分かれていることに煩雑さを感じます。それなりに実際的なコードであるTofinoでもwp-calypsoでも、ディレクトリを分けて明確に区別することはしていませんし、国内における大規模なReact Reduxの適用事例のひとつであるアメブロでも、やはり <a class="reference external" href="https://developers.cyberagent.co.jp/blog/archives/636/">区別はしていない</a> ようです。</p>
<p>かわりに筆者が使っているディレクトリ構成は次のようなものです。</p>
<ul class="simple">
<li>Presentational,Containerの区別なく、Componentはすべて <code>components/</code> ディレクトリに格納する。</li>
<li>すべてのComponentは、各々ディレクトリと <code>index.jsx</code> と <code>styles.pcss</code> を持つ(CSS Modulesが前提)。</li>
<li>Container componentは、 <code>compoennts/</code> 直下に置く。</li>
<li>あるContainer component専用のComponentは、そのContainer componentのディレクトリ配下に置く。</li>
<li>複数のComponentから利用される再利用可能なPresentational Componentは、<code>components/shared</code> に置く。</li>
</ul>
<p>言葉だけではわかりづらいので、この構成の例を次に挙げます。</p>
<div class="highlight"><pre><span></span>components
├── ContainerA
│ ├── Sub1
│ │ ├── index.jsx
│ │ └── styles.pcss
│ ├── Sub2
│ │ ├── index.jsx
│ │ └── styles.pcss
│ ├── index.jsx
│ └── styles.pcss
├── ContainerB
│ ├── index.jsx
│ └── styles.pcss
└── shared
└── Button
├── index.jsx
└── styles.pcss
</pre></div>
<p>さて、次に <code>propName={propName}</code> のような、上から下へのプロパティの受け渡しが増殖して煩雑になってしまう問題です。
実例として、Relaxというアプリの <a class="reference external" href="https://github.com/relax/relax/blob/cf18abcd28fbabd593bdccfc61721c9b64935750/lib/shared/screens/admin/shared/components/page-builder-menu/tabs/link/linking.jsx#L94">LinkingというComponent</a> を見てみます。</p>
<div class="highlight"><pre><span></span><span class="nx">renderProperty</span><span class="w"> </span><span class="p">(</span><span class="nx">prefix</span><span class="p">,</span><span class="w"> </span><span class="nx">property</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">links</span><span class="p">,</span>
<span class="w"> </span><span class="nx">addSchemaLink</span><span class="p">,</span>
<span class="w"> </span><span class="nx">changeLinkAction</span><span class="p">,</span>
<span class="w"> </span><span class="nx">removeLink</span><span class="p">,</span>
<span class="w"> </span><span class="nx">overLink</span><span class="p">,</span>
<span class="w"> </span><span class="nx">outLink</span><span class="p">,</span>
<span class="w"> </span><span class="nx">context</span><span class="p">,</span>
<span class="w"> </span><span class="nx">goal</span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">;</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">(</span>
<span class="w"> </span><span class="o"><</span><span class="nx">Property</span>
<span class="w"> </span><span class="nx">key</span><span class="o">=</span><span class="p">{</span><span class="nx">property</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span>
<span class="w"> </span><span class="nx">property</span><span class="o">=</span><span class="p">{</span><span class="nx">property</span><span class="p">}</span>
<span class="w"> </span><span class="nx">prefix</span><span class="o">=</span><span class="p">{</span><span class="nx">prefix</span><span class="p">}</span>
<span class="w"> </span><span class="nx">links</span><span class="o">=</span><span class="p">{</span><span class="nx">links</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="nx">links</span><span class="p">[</span><span class="nx">prefix</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">property</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="p">[]}</span>
<span class="w"> </span><span class="nx">addSchemaLink</span><span class="o">=</span><span class="p">{</span><span class="nx">addSchemaLink</span><span class="p">}</span>
<span class="w"> </span><span class="nx">changeLinkAction</span><span class="o">=</span><span class="p">{</span><span class="nx">changeLinkAction</span><span class="p">}</span>
<span class="w"> </span><span class="nx">removeLink</span><span class="o">=</span><span class="p">{</span><span class="nx">removeLink</span><span class="p">}</span>
<span class="w"> </span><span class="nx">overLink</span><span class="o">=</span><span class="p">{</span><span class="nx">overLink</span><span class="p">}</span>
<span class="w"> </span><span class="nx">outLink</span><span class="o">=</span><span class="p">{</span><span class="nx">outLink</span><span class="p">}</span>
<span class="w"> </span><span class="nx">context</span><span class="o">=</span><span class="p">{</span><span class="nx">context</span><span class="p">}</span>
<span class="w"> </span><span class="nx">goal</span><span class="o">=</span><span class="p">{</span><span class="nx">goal</span><span class="p">}</span>
<span class="w"> </span><span class="o">/></span>
<span class="w"> </span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>このComponentではいくつかのプロパティを受け取っていますが、ほとんどは、そのまま次の <code>Property</code> Componentに流しているだけです。
Reactアプリを開発していて、このような状況に遭遇したことのある方も多いのではないでしょうか。</p>
<p>この状況を解消する手段はいくつか考えられます。</p>
<p>まず、connectをしてStoreと直接繋ぐことで、プロパティの受け渡しをなくすことです。
Redux作者のdan_abramovも、プロパティを使わずに次のComponentに送っていることに気付いたら、新しいContainer Componentを導入する良いタイミングであると <a class="reference external" href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.a4ezrej5l">述べています。</a></p>
<p>それから、多くのプロパティを次に委譲しているだけであれば、Reactの <a class="reference external" href="https://zhenyong.github.io/react/docs/jsx-spread.html">spread演算子</a> を使うことで、コードを大幅に短縮することもできます。
例えば、wp-calypsoの <a class="reference external" href="https://github.com/Automattic/wp-calypso/blob/7475c744b951cbe4b44525c2aa93d2708adaeae0/client/components/card/index.jsx">Card</a> というComponentは、共通のプロパティを持つバリエーションである <a class="reference external" href="https://github.com/Automattic/wp-calypso/blob/7475c744b951cbe4b44525c2aa93d2708adaeae0/client/components/card/compact.jsx">CompactCard</a> を持っています。これはReactにおけるspread演算子の典型的な利用例です。</p>
<p>Tofinoにもspread演算子に関するおもしろいテクニックが見られます。
PropTypesを定義する際に、子Componentに渡したくないプロパティを別に分けておき、lodashの <code>omit</code> と <code>Object.keys</code> を利用することで、プロパティの <strong>消費</strong> を <a class="reference external" href="https://github.com/mozilla/tofino/blob/7fd8ff0f9a17159893ea4edd613bb90fbc791a29/app/ui/shared/widgets/dropdown-menu-btn.jsx#L132">賢く表現しています。</a> ただし、このテクニックは、Componentのプロパティ定義をflowtypeで行っている場合には残念ながら使えません。flowtypeの型情報は実行時には除去されてしまうためです。</p>
<p>もう一つ考えられるのは、renderが大きくなってきてコードを整理する際に、別Componentに分けるのではなく、別メソッドにrenderFooのようなメソッドを設けて分離することです。同じクラス内のメソッドであれば、Componentのプロパティには <code>this</code> 経由でどこからでもアクセスできるため、そもそもプロパティの受け渡しは不要です。</p>
</div>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-11">まとめ</a></h2>
<p>この記事では、Redux Real World Example、Project Tofino、wp-calypsoといった実際のアプリのソースコードを参考に、React Redux開発において生じる疑問へのヒントを探りました。</p>
<p>Storeの構成は、概ね公式ドキュメントをよく読んでその通りにやれば可読性を担保でき、大規模な場合にはDucksパターンを使う、スライス間での状態共有が必要な場合にはreduce-reducersを使う、といったテクニックを見ました。</p>
<p>Storeの初期化データ定義については、Immutable.jsのRecordを利用する方法があることや、ローカルに保存された状態を動的にバリデーションする必要がある場合があることを見ました。また、flowtypeを利用することで問題が解消することについても触れました。</p>
<p>最後に、Componentの整理について、多数のReact Reduxアプリを見た中で行き着いたファイル構成のスタイルを提案し、プロパティの受け渡しが冗長になってしまう場合の対策をいくつか紹介しました。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-12">参考リンク</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://github.com/markerikson/redux-ecosystem-links/blob/master/apps-and-examples.md#applications">Applications and Examples</a></li>
<li><a class="reference external" href="https://medium.com/@learnreact/container-components-c0e67432e005#.lo4csvl0g">Container Components</a></li>
<li><a class="reference external" href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.wot8t890i">Presentational and Container Components</a></li>
<li><a class="reference external" href="https://github.com/erikras/ducks-modular-redux">Ducks: Redux Reducer Bundles</a></li>
<li><a class="reference external" href="https://speakerdeck.com/chrisui/real-world-redux">Real World Redux</a></li>
<li><a class="reference external" href="https://developers.cyberagent.co.jp/blog/archives/636/">アメブロ2016 ~ React/ReduxでつくるIsomorphic web app ~</a></li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-real-world-redux-1">いろいろわかった今から見れば、単に筆者の無知から来る疑問だったものもあります。実際のところ、これらの疑問の多くは <a class="reference external" href="http://redux.js.org/">公式ドキュメント</a> を隅から隅まで読めば、答えやヒントが言及されています)。ちなみに、副作用については、筆者の中では現状 <a class="reference external" href="https://github.com/redux-saga/redux-saga">redux-saga</a> で対応するという答えで決着しており、とくに疑問に思う部分もないため扱いません。 <a href="#sf-real-world-redux-1-back" class="simple-footnote-back">↩</a></li><li id="sf-real-world-redux-2">もちろん、全Reducerに目を通したわけではありませんが… <a href="#sf-real-world-redux-2-back" class="simple-footnote-back">↩</a></li><li id="sf-real-world-redux-3"> <a class="reference external" href="https://codemix.github.io/flow-runtime/#/">flow-runtime</a> を利用することで、解決するかもしれません。 <a href="#sf-real-world-redux-3-back" class="simple-footnote-back">↩</a></li></ol>正規表現等によるメールアドレスの形式確認をサーバーサイドで行う必要はない2017-01-29T00:00:00+09:002017-01-29T00:00:00+09:00tai2tag:blog.tai2.net,2017-01-29:/email_validation.html<p class="first last">サーバーサイドでのメールアドレスチェックは、入力有無や長さ確認など、メールアドレス以外のテキストフィールドと同様の処理を行えば十分であり、ユーザーインターフェイスの改善を目的とした形式チェックはクライアントサイドのみで行えば十分です。</p>
<p>先日、SNSで次のエントリを見かけました。</p>
<p><a class="reference external" href="https://blog.ohgaki.net/redos-must-review-mail-address-validation">正規表現でのメールアドレスチェックは見直すべき – ReDoS</a></p>
<p>ある種の正規表現をサーバー上で実行したときに、DoS攻撃に繋がるおそれがあるという指摘です。
ところで、そもそもメールアドレスの形式チェックはなんのために行うのでしょうか?</p>
<p>ユーザーの正しいメールアドレスが <code>muto@example.com</code> のケースを考えます。</p>
<p>もしもユーザーが入力したメールアドレスが、<code>mutoexample.com</code> のように@マークが含まれないなど不正な形式であった場合、当然ながらユーザーのもとにメールは届きません。このようなミスは、たしかに正規表現等による簡単な形式チェックで防げるでしょう。</p>
<p>一方、たとえば、 <code>nuto@example.com</code> と入力してしまうような入力ミスは、形式的に不正ではないため正規表現では防げません。つまり、正規表現では、ユーザーの入力ミスによってメールが届かないケースを完全に防ぐことはできません。</p>
<p>しかしながら、メールアドレスをユーザーIDとして使用するようなサービスであれば、ユーザーが入力を間違えたままにはなることはありません。一般的に、登録されたメールアドレスに対してトークン付きのURLが記載されたメールを送信し、ユーザーがそのURLにアクセスすることで、はじめて登録完了となるからです。<sup id="sf-email_validation-1-back"><a href="#sf-email_validation-1" class="simple-footnote" title="ユーザーIDとしては使用せず、連絡用などとして到達確認を必須としないケースでは、誤ったメールアドレスが登録されたままになることもありえます。">1</a></sup></p>
<p>いずれにせよ誤ったメールアドレスが入力され得るのであれば、メールアドレスの形式チェックは不要なのでしょうか?必ずしもそうとは言えません。ユーザーの元にメールが <strong>到達しないまで</strong> 何のフィードバックも与えないよりは、誤りが確認できた段階でフィードバックを返すほうがユーザーインターフェイスとして優れているからです。</p>
<p>しかし、ユーザーインターフェイスの改善を目的としたメールアドレスのチェックであれば、サーバーサイドよりも適した場所があります。クライアントサイドです。HTML5であれば、 <code>input</code> タグに <code>type=email</code> として指定すれば形式チェックが行われますし、正規表現で簡単な形式確認を行っても良いでしょう。クライアントサイドであれば、例え重い正規表現を実行してもDoS攻撃になることはありません。ユーザーにもより速くフィードバックを返すことができユーザーインターフェイスとしても優れています。</p>
<p>もちろん、直接HTTPリクエストを送信するなどして、あえて形式チェックを通さずにリクエストを送信することは可能ですが、通常サービス側で用意したユーザーインターフェイスを使用していれば起きないことなので、このケースは無視して良いでしょう。</p>
<p>したがって、サーバーサイドでのメールアドレスチェックは、入力有無や長さ確認など、メールアドレス以外のテキストフィールドと同様の処理を行えば十分であり、ユーザーインターフェイスの改善を目的とした形式チェックはクライアントサイドのみで行えば十分です。</p>
<ol class="simple-footnotes"><li id="sf-email_validation-1">ユーザーIDとしては使用せず、連絡用などとして到達確認を必須としないケースでは、誤ったメールアドレスが登録されたままになることもありえます。 <a href="#sf-email_validation-1-back" class="simple-footnote-back">↩</a></li></ol>「雇用関係によらない働き方」に関する研究会の内容確認2016-12-24T00:00:00+09:002016-12-24T00:00:00+09:00tai2tag:blog.tai2.net,2016-12-24:/abenomics-freelance.html<p class="first last">去る10月、安倍内閣の働き方改革の一貫として、フリーランス促進をしていく方針であることが報道 されました。 働き方改革は、アベノミクスの 3本の矢(構造改革)の柱 として位置付けられている政策です。11月と12月には、この政策に関する研究会が実施され、それぞれ資料が公開されています。そこで、我々フリーランスにとって関心の高い、この政策の動向を占うものとなるであろう研究会の内容について見ていきます。</p>
<p>この記事は、 <a class="reference external" href="http://www.adventar.org/calendars/1405">フリーランス Advent Calendar 2016</a> の24日目です。</p>
<p>去る10月、安倍内閣の <a class="reference external" href="http://www.kantei.go.jp/jp/singi/hatarakikata/index.html">働き方改革</a> の一貫として、フリーランス促進をしていく方針であることが <a class="reference external" href="http://news.tv-asahi.co.jp/news_economy/articles/000085993.html">報道</a> されました。
働き方改革は、 <a class="reference external" href="http://www.kantei.go.jp/jp/headline/seicho_senryaku2013.html">アベノミクス</a> の <a class="reference external" href="http://www.nikkei.com/article/DGXLASFL28HFW_Y6A920C1000000/">第3の矢(構造改革)の柱</a> として位置付けられている政策です。11月と12月には、この政策に関する研究会が実施され、それぞれ資料が公開されています。</p>
<p>そこで、我々フリーランスにとって関心の高い、この政策の動向を占うものとなるであろう研究会の内容について見ていきます。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-2" id="toc-entry-1">第1回 「雇用関係によらない働き方」の現状及び人材育成・教育訓練のあり方について</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-2">第2回 「雇用関係によらない働き方」の働き手にとって円滑に働くための環境整備のあり方について</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-3">「雇用関係によらない働き方」に関する研究会参加者</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-4">「雇用関係によらない働き方」政策について我々のできること</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-5">所感</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-6">参考リンク</a></li>
</ul>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-1">第1回 「雇用関係によらない働き方」の現状及び人材育成・教育訓練のあり方について</a></h2>
<ul class="simple">
<li><a class="reference external" href="http://www.meti.go.jp/press/2016/11/20161117005/20161117005.html">第1回「雇用関係によらない働き方」に関する研究会を開催しました</a></li>
</ul>
<p>この研究会の趣旨は、「兼業・副業」や「フリーランサー」のような、「時間・場所・契約にとらわれない、柔軟な働き方」の実態や課題を把握し、政策の方向を検討する場を設けることです。</p>
<p>第1回研究会では、これまで経産省や厚生労働省といった省庁によって行われてきた <a class="reference external" href="http://www.meti.go.jp/committee/sankoushin/shin_sangyoukouzou/pdf/008_05_01.pdf">新産業構造ビジョン</a> 、 <a class="reference external" href="http://www.mhlw.go.jp/file/06-Seisakujouhou-12600000-Seisakutoukatsukan/0000133449.pdf">働き方の未来2035</a> などの成果に基いて、「雇用関係によらない働き方」の <a class="reference external" href="http://www.meti.go.jp/press/2016/11/20161117005/20161117005-d.pdf">現状と課題についての整理</a> が行われました。また、海外におけるフリーランスの状況が紹介されました。</p>
<p>発表資料の中にわかりやすい図があったため抜粋します。</p>
<div class="figure">
<img alt="abenomics freelance model" src="https://blog.tai2.net/images/abenomics-freelance/env.png" />
<p class="caption">雇用関係によらない働き方の模式図</p>
</div>
<p>上の図における社会環境、つまり国の提供する制度・サービスをどのように変えるべきかというのが、この研究会の主な検討内容になるはずです。</p>
<p>この研究会における論点として挙げられていたのは、次の4つです。</p>
<ul class="simple">
<li>人材育成</li>
<li>労働法制</li>
<li>社会保障制度</li>
<li>企業・業界としての取組、政策的支援</li>
</ul>
<p>第1回のテーマである人材育成についても <a class="reference external" href="http://www.meti.go.jp/press/2016/11/20161117005/20161117005-f.pdf">検討が行われました</a> 。こちらも、国内の現状の取組と、海外事例が合わせて紹介されました。そこで、今後検討すべき課題として挙げられたのが次の4つです。</p>
<ul class="simple">
<li>働き手のスキル意識</li>
<li>人材育成・教育訓練の提供主体・内容</li>
<li>人材育成・教育訓練に係るコスト</li>
<li>スキル向上の評価方法</li>
</ul>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-2">第2回 「雇用関係によらない働き方」の働き手にとって円滑に働くための環境整備のあり方について</a></h2>
<ul class="simple">
<li><a class="reference external" href="http://www.meti.go.jp/press/2016/12/20161219004/20161219004.html">第2回「雇用関係によらない働き方」に関する研究会を開催しました</a></li>
</ul>
<p>第2回研究会では、 <a class="reference external" href="http://www.meti.go.jp/press/2016/12/20161219004/20161219004-6.pdf">労働法制と社会保証制度についての検討が行われ</a> 、
現状の国内法制・社会保障制度および、諸外国の法制・制度の整理が行われました。また、 <a class="reference external" href="http://www.meti.go.jp/press/2016/12/20161219004/20161219004-8.pdf">法学者からの発表</a> もありました。
労働法の対象が今後自営業にも広がっていくという指摘が興味深かったです。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-3">「雇用関係によらない働き方」に関する研究会参加者</a></h2>
<p>これまでのところ、この研究会に参加したのは以下のような属性の人達です。</p>
<p>委員:</p>
<ul class="simple">
<li>キャリアコンサルタント</li>
<li>ジャーナリスト</li>
<li>労働政策研究者</li>
<li>クラウドソーシング協会事務局長</li>
</ul>
<p>第一回ゲスト:</p>
<ul class="simple">
<li>パソナテック取締役</li>
<li>KAIZEN PLATFORM CEO</li>
<li>ランサーズ取締役</li>
<li>Waris代表取締役</li>
<li>フリーランス3名</li>
</ul>
<p>第二回ゲスト:</p>
<ul class="simple">
<li>パソナテック取締役</li>
<li>KAIZEN PLATFORM CEO</li>
<li>ランサーズ取締役</li>
<li>クラウドワークス執行役員</li>
<li>Waris代表取締役</li>
<li>法学者</li>
<li>フリーランス3名</li>
</ul>
<p>個人的には、事業者に比べてフリーランスが少ないように感じました。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-4">「雇用関係によらない働き方」政策について我々のできること</a></h2>
<p>基本的には、最終的に政策(法律)としてどういう形で反映されるのかをチェックしていくということになると思います。</p>
<p><a class="reference external" href="http://www.meti.go.jp/press/2016/11/20161117005/20161117005-e.pdf">Webアンケート</a> が実施されるようなので、アンケートを通じてフリーランスの実態を政府に伝えることができます。</p>
<p>あとは、経産省宛てに <a class="reference external" href="https://www.e-gov.go.jp/policy/servlet/Propose">意見を送る</a> なども考えられますが、効果があるのかは定かではありません。</p>
</div>
<div class="section" id="section-6">
<h2><a class="toc-backref" href="#toc-entry-5">所感</a></h2>
<p>「独⽴志向」・「上昇志向」の両⽅ある⼈が起業を⽬指すのに対し、「独⽴志向」はあるけど「上昇志向」ではない⼈がフリーランスを志向する、という分析があり、それはその通りだなと思いました。
筆者自身、まったく上昇指向はなく、とりあえず家族が生活していければ十分です。
その他、おおむね書いてあることに違和感はなく、目指している方向がズレていると感じることもありませんでした。
あとは、その実現手段が適切かどうかということだと思います。</p>
<p>研究会のレポートを見ると、どうもこの政策は、クラウドソーシング事業者を中心に据えて展開していくことを想定しているようにも見えます。
筆者はいまのところクラウドソーシングを利用せずに、主に人脈を通じて仕事をしています。
人脈だけで商売ができている現状はたいへんありがたいですが、一方で脆さもあります。
もし人脈に頼らずに安定した働き方ができたら、独立した個人として、より強くなれるはずです。
しかし、現状でそれをすぐに実現することはできないため、手段として事業者を使うことが近道になるかもしれません。
その場合にも、クラウドソーシングのような形ではなく、どちらかということ、自分のパーソナリティーやスキルを把握した上で、
自分のかわりに足で仕事を取ってきてくれるエージェントのような形のほうが、個人的には期待が持てます。</p>
<p>それから、人材育成の一環で、スキルアップの「見える化」と題して、<a class="reference external" href="https://crowd-kentei.or.jp/about_test/about_web_writing_proficiency_exam/">WEBライティング技能検定講座</a> を民間資格標準の取組として取り上げていましたが、Webライティングについては、キュレーションメディアの問題に端を発して、 <a class="reference external" href="http://nlab.itmedia.co.jp/nl/articles/1612/13/news140.html">問題を指摘されています</a> 。ライティング自体もクラウドソーシング事例として取り上げられていました。しかし、Webにおけるライティングのクラウドソーシングがいくつかの問題を抱えていることは、いまとなっては散々指摘された事実なので、公的に取り上げるトピックではない気がします。</p>
<p>「雇用関係によらない働き方」政策における主役は、事業者ではなく、あくまで個々の働き手であるべきです。
極論を言えば、顧客とフリーランスの間の問題、働き手のライフスタイルの問題であって、中間事業者はなくてもかまわないわけです。
事業者に助成金をバラまくだけで、フリーランスの働き手にあまり恩恵がないというようなことでは、意味がありません。</p>
<p>フリーランスの普及を目指しているようですが、無理に数を増やす必要はないと思います。
ただ、フリーランスという生き方を選択しようとする人がいたときに、無用な障害がないように足場を整備してくれてさえいれば、十分です。
第二回研究会でも指摘されているように、現状、会社員は、フリーランスに比べて税制・社会保障上で優遇されているので、フリーランスの扱いも会社員と同程度に引き上げられるべきです。
もしいまの時代や社会にフリーランスがフィットするのであれば、自然と増えていくのではないでしょうか。</p>
</div>
<div class="section" id="section-7">
<h2><a class="toc-backref" href="#toc-entry-6">参考リンク</a></h2>
<ul class="simple">
<li><a class="reference external" href="http://www.meti.go.jp/press/2016/11/20161117005/20161117005.html">第1回「雇用関係によらない働き方」に関する研究会を開催しました</a></li>
<li><a class="reference external" href="http://www.meti.go.jp/press/2016/12/20161219004/20161219004.html">第2回「雇用関係によらない働き方」に関する研究会を開催しました</a></li>
<li><a class="reference external" href="http://www.kantei.go.jp/jp/singi/hatarakikata/index.html">働き方改革実現会議</a></li>
<li><a class="reference external" href="http://news.tv-asahi.co.jp/news_economy/articles/000085993.html">「もっとフリーランスを!」 働き方改革で経産省</a></li>
<li><a class="reference external" href="http://www.mhlw.go.jp/file/06-Seisakujouhou-12600000-Seisakutoukatsukan/0000133449.pdf">「働き方の未来2035」〜一人ひとりが輝くために〜【報告書】</a></li>
<li><a class="reference external" href="http://www.chusho.meti.go.jp/pamflet/hakusyo/H27/PDF/shokibo/06sHakusyo_part1_chap3_web.pdf">小規模事業者の未来</a></li>
<li><a class="reference external" href="http://www.meti.go.jp/committee/sankoushin/shin_sangyoukouzou/pdf/008_05_01.pdf">新産業構造ビジョン ~第4次産業革命をリードする日本の戦略~ 産業構造審議会 中間整理</a></li>
<li><a class="reference external" href="http://www.stat.go.jp/data/shugyou/2012/index.htm">平成24年就業構造基本調査</a></li>
<li><a class="reference external" href="http://www.meti.go.jp/meti_lib/report/2015fy/000607.pdf">平成26年度 小規模企業等の事業活動に関する調査 報告書</a></li>
<li><a class="reference external" href="http://www.meti.go.jp/meti_lib/report/2016fy/000334.pdf">平成27年度 小規模事業者等の事業活動に関する調査に係る委託事業 報告書</a></li>
<li><a class="reference external" href="http://www.lancers.jp/magazine/25809">フリーランス実態調査2016年度版を発表! 日本のフリーランス事情</a></li>
<li><a class="reference external" href="http://homeworkers.mhlw.go.jp/report-kentoukai/">今後の在宅就業施策の在り方に関する検討会報告書</a></li>
<li><a class="reference external" href="http://www.jcer.or.jp/academic_journal/jer/PDF/54-4.pdf">個人請負の労働実態と就業選択の決定要因</a></li>
<li><a class="reference external" href="http://leverage-share.com/study/post-2344/">経済産業省が発表「雇用関係によらない新しい働き方」社会はどう受け入れ、活用していけばいいのか?</a></li>
<li><a class="reference external" href="http://nlab.itmedia.co.jp/nl/articles/1612/13/news140.html">「クラウドソーシングサイトも共犯だ」 キュレーションメディア炎上騒動についてWELQ記事寄稿ライターが怒りの告発</a></li>
</ul>
</div>
おすすめNodeSchool Workshopper 8選2016-12-13T00:00:00+09:002016-12-13T00:00:00+09:00tai2tag:blog.tai2.net,2016-12-13:/nodejs-advent-calendar-2016.html<p class="first last">数あるワークショッパーの中から、おすすめのものを8つ紹介します。</p>
<p>この記事は、 <a class="reference external" href="http://qiita.com/advent-calendar/2016/nodejs">Node.js Advent Calendar 2016</a> の13日目の記事です。</p>
<p><a class="reference external" href="https://nodeschool.io/#workshopper-list">数あるワークショッパー</a> の中から、おすすめのものを8つ紹介します。</p>
<div class="section" id="nodeschool-workshopper">
<h2>NodeSchool Workshopperとは</h2>
<p><a class="reference external" href="https://nodeschool.io/ja/about.html">公式サイトの説明</a> を見てください。</p>
<p>NodeSchoolは、国内でも大阪・東京・福井などの都市で有志により開催されています。
開催情報は、 <a class="reference external" href="https://nodejs.connpass.com/">東京Node学園のconnpass</a> をチェックしてください。
今週末の12月17日には、 <a class="reference external" href="https://nodejs.connpass.com/event/46884/">NodeSchool Fukui</a> が、永和システムマネジメントで、来年1月29日に、 <a class="reference external" href="https://nodejs.connpass.com/event/45720/">NodeSchool Tokyo</a> がヒカリエで開催されるので、ぜひご参加を。</p>
</div>
<div class="section" id="learnyounode">
<h2>learnyounode</h2>
<p>NodeSchoolでもとくに人気のあるワークショッパーのひとつです。Node.jsの基本が学べます。
筆者自身もこれでNode.js入門しました。</p>
<ul class="simple">
<li>日本語: あり</li>
<li>学べる内容:<ul>
<li>コンソール出力</li>
<li>コマンドライン引数</li>
<li>ファイルI/O(同期・非同期)</li>
<li>ディレクトリ読み込み</li>
<li>モジュール(CommonJS)</li>
<li>HTTPクライアント</li>
<li>ストリーム</li>
<li>HTTPサーバー</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="javascripting">
<h2>javascripting</h2>
<p>JavaScriptの基本を学べます。
他の言語での経験はあるがJavaScriptはやったことがないという人や、プログラミング入門者向け。</p>
<ul class="simple">
<li>日本語: あり</li>
<li>学べる内容:<ul>
<li>変数</li>
<li>文字列</li>
<li>数値</li>
<li>条件分岐</li>
<li>ループ</li>
<li>配列</li>
<li>オブジェクト</li>
<li>関数</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="expressworks">
<h2>expressworks</h2>
<p>Node.jsでもっともポピュラーなWebアプリケーションフレームワークである、Expressの使い方を学べます。</p>
<ul class="simple">
<li>日本語: あり</li>
<li>学べる内容:<ul>
<li>Hello World</li>
<li>静的ファイルの配信</li>
<li>Jadeによるテンプレートのレンダリング</li>
<li>Form値の取得</li>
<li>Stylusによる簡潔なスタイルシート</li>
<li>URLパラメータの使い方</li>
<li>クエリ文字列の使い方</li>
<li>JSONレスポンスの返しかた</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="how-to-npm">
<h2>how-to-npm</h2>
<p>npmで実際に自分のパッケージを公開してみるところまでを、順を追って学べます。</p>
<ul class="simple">
<li>日本語: あり( <a class="reference external" href="https://github.com/nodeschool-ja/how-to-npm-jp">別パッケージ</a> <sup id="sf-nodejs-advent-calendar-2016-1-back"><a href="#sf-nodejs-advent-calendar-2016-1" class="simple-footnote" title="watildeさんが本家に プルリクエスト を出してくれてるので、いずれは統一されると思います">1</a></sup>)</li>
<li>学べる内容:<ul>
<li>アカウント作成</li>
<li>プロジェクト初期化</li>
<li>モジュールの追加</li>
<li>依存モジュールの表示</li>
<li>タスクランナー</li>
<li>公開</li>
<li>バージョン(semver)</li>
<li>配布タグ</li>
<li>古くなったモジュールの確認と更新</li>
<li>モジュールの削除</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="git-it">
<h2>git-it</h2>
<p>gitでのコミットのしかたから、実際にプルリクエストを出してマージされるまでを順を追って学べます。
マージされると、 <a class="reference external" href="http://jlord.us/patchwork/">GitHubのリポジトリに</a> 名前が載ります。</p>
<ul class="simple">
<li>日本語: あり(ただし <a class="reference external" href="https://github.com/jlord/git-it-electron">Electron版</a> のみ)</li>
<li>学べる内容:<ul>
<li>初期設定</li>
<li>コミット・状態確認・差分確認</li>
<li>GitHubアカウントの作成</li>
<li>リモートへのアップロード</li>
<li>フォーク</li>
<li>ブランチ</li>
<li>プルリクエスト</li>
<li>マージ</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="tower-of-babel">
<h2>tower-of-babel</h2>
<p>babelを使って、ES6で導入されたJavaScriptの新しい機能について学べます。
会長ことyosuke_furukawaさんが作ったやつです。</p>
<ul class="simple">
<li>日本語: あり</li>
<li>学べる内容:<ul>
<li>クラス</li>
<li>継承</li>
<li>モジュールの定義方法</li>
<li>ブロックスコープ</li>
<li>computed property(オブジェクトリテラルで動的にキーを定義する)</li>
<li>イテレーター</li>
<li>ジェネレーター</li>
<li>分割代入</li>
<li>アロー関数</li>
<li>rest and spread</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="stream-adventure">
<h2>stream-adventure</h2>
<p>streamは、Node.jsでのデータ入出力を扱うための基本的なAPIで、まるでUNIXのパイプのように繋げて使うことができる便利なものです。
streamの基本的な使いかたや、さまざまな便利なライブラリについて学べます。</p>
<ul class="simple">
<li>日本語: なし</li>
<li>学べる内容:<ul>
<li>streamの繋げかた</li>
<li>through2(ストリームの変換)</li>
<li>split(改行での分割)</li>
<li>concat-stream(stream結合して一個の文字列に)</li>
<li>HTTPサーバーの実装</li>
<li>HTTPクライアントの実装</li>
<li>websocket-stream(WebSocket接続)</li>
<li>trumpet(HTMLのパース)</li>
<li>duplexer2(入力と出力をまとめて一つのstreamにする)</li>
<li>through2とduplexer2の応用</li>
<li>stream-combiner(複数のstreamの結合)</li>
<li>crypto(暗号化)</li>
<li>zlib(圧縮)</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="bug-clinic">
<h2>bug-clinic</h2>
<p>Node.jsでアプリ開発をする際のさまざまなデバッグツールの使い方を学べます。</p>
<ul class="simple">
<li>日本語: なし</li>
<li>学べる内容:<ul>
<li>consoleオブジェクト</li>
<li>jshintとeslint</li>
<li>bunyan(ロガー)</li>
<li>long stacktrace(非同期APIをまたがったスタックトレース)</li>
<li>tape(自動テスト)</li>
<li>NODE_DEBUG環境変数</li>
<li>jstrace(DTrace)</li>
<li>replpad/replify(動作中のアプリにREPLを仕込む)</li>
<li>debuggerステートメント(コード内にブレイクポイントを仕込む)</li>
<li>node-inspector(ChromeのDeveloper ToolsベースのNode用開発ツール)</li>
<li>heapdump(メモリ使用状況をダンプする)</li>
<li>gdb/lldb(C++レイヤーでのデバッグ)</li>
</ul>
</li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-nodejs-advent-calendar-2016-1">watildeさんが本家に <a class="reference external" href="https://github.com/nodeschool-ja/how-to-npm-jp/issues/21">プルリクエスト</a> を出してくれてるので、いずれは統一されると思います <a href="#sf-nodejs-advent-calendar-2016-1-back" class="simple-footnote-back">↩</a></li></ol>クラウドワークスで寿司を食べさせてもらうついでに、React+Reduxの導入状況について聞いてきた2016-09-16T00:00:00+09:002016-09-16T00:00:00+09:00tai2tag:blog.tai2.net,2016-09-16:/crowdworks-sushi.html<p class="first last">クラウドワークスが、社外のエンジニアに寿司をおごる企画をやっているというのを見かけて、寿司がタダで食べられるなら良いなと思い、なにも考えずに申し込んでみました。</p>
<p>クラウドワークスが、 <a class="reference external" href="https://web.archive.org/web/20190515082326/https://crowdworks.co.jp/recruit/engineer/">社外のエンジニアに寿司をおごる企画をやっている</a> というのを見かけて、寿司がタダで食べられるなら良いなと思い、なにも考えずに申し込んでみました。
<sup id="sf-crowdworks-sushi-1-back"><a href="#sf-crowdworks-sushi-1" class="simple-footnote" title="筆者のようになにも考えずに申し込む人間はレアケースで、応募してくるのは知り合いが多いそうです。なお、suzan2goさんを指名したのは筆者で二人目で、一人目は筆者の妻でした。">1</a></sup></p>
<div class="figure">
<img alt="crowdworks recruiting" src="https://blog.tai2.net/images/crowdworks-sushi/recruit.jpg">
<p class="caption">選べる!寿司ランチ</p>
</div>
<p>この企画は、寿司を食べながら対話するエンジニアを指名できるシステムになっています。筆者は、最近案件で使ったReact+Reduxや、RailsとReactを組み合わせて使うことに興味があったため、また、最近子供が生まれてリモートワーク制度を活用していたということで、そのあたりにも興味があり、 <a class="reference external" href="https://twitter.com/suzan2go">suzan2go</a> さんを指名しました。suzna2goさんは、クラウドワークスの中でも、 <a class="reference external" href="http://engineer.crowdworks.jp/entry/2016/05/24/174511">Reactの普及にとくに熱心</a> なエンジニアだそうです。</p>
<div class="section" id="section-1">
<h2>寿司</h2>
<p>こちらがクラウドワークスで提供していただいた寿司になります。</p>
<div class="figure">
<img alt="crowdworks recruiting" src="https://blog.tai2.net/images/crowdworks-sushi/sushi.jpg">
<p class="caption">筆者もたまに出前する銀のさらの寿司</p>
</div>
<p>ごちそうさまでした。なお、お茶は出なかったので、ハックルベリーさんは申し込まないほうが無難かもしれません。
<sup id="sf-crowdworks-sushi-2-back"><a href="#sf-crowdworks-sushi-2" class="simple-footnote" title="実際には、単にうっかり出し忘れていただけで、ふつうはお茶出るそうです。">2</a></sup></p>
<p>以下、聞いてきた内容です。</p>
</div>
<div class="section" id="webpack">
<h2>webpackはどんなプロダクトで使ってる?</h2>
<ul class="simple">
<li>新規の小さめのプロダクトで使ってみた。内部ツールを別アプリとして切り出し。</li>
<li>jsのみwebpackで、それ以外sprocketsのパターンと、全アセットwebpackでビルドするパターン両方試してみた。</li>
<li>CSSをsprokectsでビルドするパターンは、ReactではCSS埋め込むようなのもよくあるので、相性が悪い。</li>
</ul>
</div>
<div class="section" id="react-1">
<h2>なんでReact?</h2>
<ul class="simple">
<li>メインプロダクトはjQueryゴリゴリだが、メンテナンスが辛い。ある箇所をいじったときになにが起きるのかわからない。</li>
<li>小さく試して、徐々に導入していきたい。</li>
</ul>
</div>
<div class="section" id="rails-webpack">
<h2>Rails + webpackで、なにか詰まったことは?</h2>
<ul class="simple">
<li>formの一部分など、Reactをpartialみたいにしてる挿入してる。</li>
<li>ページの一部をReactにする形だと、エコシステムに乗り切れないところがある。Reactのエコシステムは、フルSPAが前提だと思う。</li>
<li>RailsでViewをレンダリングして、その中にReactコンポーネントを埋め込むのではなく、ページ毎に完全に分離したほうが良い。</li>
<li>よくある画面右下に出るチャットウィンドウのような、完全に分離されたコンポーネントなら、埋め込むのもありかも。</li>
</ul>
</div>
<div class="section" id="gem">
<h2>gemはなに使ってる?</h2>
<ul class="simple">
<li><a class="reference external" href="https://github.com/reactjs/react-rails">react-rails</a> は、sprocketsが前提になってる。componentをグローバル(window)にくっつけなきゃいけないので辛い。</li>
<li><a class="reference external" href="https://github.com/shakacode/react_on_rails">react_on_rails</a> は、webpackと統合目指してるので、こっちを使ってる。こっちはグローバルに置かなくても良いが、ライブラリとしてデカいのがちょっと気になる。</li>
<li>CSRFトークンの扱いについて。react-railsは、propsで渡さなきゃならなかったりしてめんどう。react_on_railsは、関数呼べば良いだけ。</li>
</ul>
</div>
<div class="section" id="redux">
<h2>redux使ってみてぶっちゃけどうだった?</h2>
<ul class="simple">
<li>手放しには誉められない。</li>
<li>API覚えるのがけっこう大変。</li>
<li>ReduxのDevToolsがプロダクション環境でもONになってるサービスとかけっこうあるよね。</li>
</ul>
</div>
<div class="section" id="section-2">
<h2>子育て+リモートワークやってみてどうだった?</h2>
<ul class="simple">
<li>子供が生まれてからしばらくは、リモートワークがメインだった。</li>
<li>日中は奥さんがメインでめんどう見てくれて、それをカバーする感じでやってた。</li>
<li>生まれたばかりの子供と時間を共有できたのがありがたかった。</li>
<li>奥さんといっしょに育児レベルを上げげられたのでよかった。</li>
</ul>
</div>
<div class="section" id="crowdworks">
<h2>CrowdWorksのリモートワーク体制について教えて</h2>
<ul class="simple">
<li>基本、前日までに申請しておけばOK。</li>
<li>週4までリモートワーク入れられる。</li>
<li>いまはほぼ毎日会社来てる。案件が佳境で細かい調整が多く、会社に来たほうが進捗が良いので。</li>
</ul>
</div>
<div class="section" id="section-3">
<h2>その他</h2>
<ul class="simple">
<li>Railsはメタプログラミングやりすぎるとヤバい。書いてるときは気持ちいいけど用量用法を守って。社内にすご腕の人が残した黒魔術の遺産がある。</li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-crowdworks-sushi-1">筆者のようになにも考えずに申し込む人間はレアケースで、応募してくるのは知り合いが多いそうです。なお、suzan2goさんを指名したのは筆者で二人目で、一人目は筆者の妻でした。 <a href="#sf-crowdworks-sushi-1-back" class="simple-footnote-back">↩</a></li><li id="sf-crowdworks-sushi-2">実際には、単にうっかり出し忘れていただけで、ふつうはお茶出るそうです。 <a href="#sf-crowdworks-sushi-2-back" class="simple-footnote-back">↩</a></li></ol>Reduxアプリであれば、React Hot LoaderやLiveReactloadを使わずともライブリロードが可能2016-08-30T00:00:00+09:002016-08-30T00:00:00+09:00tai2tag:blog.tai2.net,2016-08-30:/react-redux-reload.html<p class="first last">SPAの開発において、効率良く作業を進めるためには、ライブリロードがなんとしても必要です。アプリの状態を保持したままコードの修正を反映できれば、効率的な開発が可能になります。</p>
<p><a class="reference external" href="https://en.wikipedia.org/wiki/Single-page_application">SPA</a> の開発において、効率良く作業を進めるためには、ライブリロード<sup id="sf-react-redux-reload-1-back"><a href="#sf-react-redux-reload-1" class="simple-footnote" title="ここでは、フルページロードではなく部分的なロード、また、アプリの状態を保持したままのロードとします">1</a></sup>がなんとしても必要です。</p>
<p>たとえば、1.商品選択画面、2.注文画面、3.注文完了画面の3ステップからなるアプリを考えてみましょう。</p>
<p>素朴にReactを使って開発した場合、コードの修正したあと、結果を反映させるためにブラウザをリロードすると、状態がリセットされます。
そのため、最後の注文完了画面の調整をしたいときに、リロード後、毎回商品選択からはじめて、商品を選択し、送付先住所を入力した後、結果を確認しなければなりません。
仮に商品選択画面と注文画面での手動データ入力に1分かかるとすると、注文完了画面を100回修正して、そのたびに結果を確認すると、100分もの無駄な待機時間が発生してしまいます。</p>
<p>アプリの状態を保持したままコードの修正を反映できれば、効率的な開発が可能になります。</p>
<div class="section" id="section-1">
<h2>実現方法</h2>
<p>Reactアプリの開発で、ライブリロードを実現するための現在もっともメジャーな方法は、おそらく <a class="reference external" href="https://webpack.github.io/">Webpack</a> と <a class="reference external" href="http://gaearon.github.io/react-hot-loader/">React Hot Loader</a> プラグインを使うことです。</p>
<p>React Hot Loaderの中身を見ると、ソースコードを解析してReact Component自体を <a class="reference external" href="https://github.com/gaearon/react-proxy">proxyクラス</a> に <a class="reference external" href="https://github.com/gaearon/babel-plugin-react-transform">置き換える</a> など、 <a class="reference external" href="https://medium.com/@dan_abramov/hot-reloading-in-react-1140438583bf">非常に込み入った処理</a> をしており、 <a class="reference external" href="http://blog.namangoel.com/browserify-vs-webpack-js-drama">Webpackにはnpmとの互換性がない</a> という問題もあります。BrowserifyとWebpackは目的は似ているものの、その思想がまったく異なり、筆者個人は、どちらかというとBrowserifyのほうが好みです。</p>
<p>BrowserifyでReactのライブリロードを実現するモジュールに、 <a class="reference external" href="https://github.com/milankinen/livereactload">LiveReactload</a> がありますが、これは現状、 <a class="reference external" href="https://github.com/gaearon/react-hot-boilerplate/pull/61">いくつかの問題</a> を抱えているReact Hot Loader 2と同様のアーキテクチャを採用しています。軽く試してみた感じでは動いていたはいましたが、そもそもReact Hot Loader同様、仕組みが複雑なことに若干の不安があり、不安定なのではないかという疑念が拭えません。</p>
<p>しかしながら、ReduxやReact Hot Loaderの作者Dan Abramov氏も <a class="reference external" href="https://medium.com/@dan_abramov/hot-reloading-in-react-1140438583bf#2727">言うように、</a> Reduxを使って、状態がすべてシングルツリーでStoreに格納されたアプリであれば、複雑な仕組みは不要です。
Storeを永続化してしまえば、例えフルページリロードを行っても、元の状態を復元できるからです。</p>
<p><a class="reference external" href="https://github.com/gaearon/redux-devtools">Redux DevTools</a> では、開発用に状態をlocalStoregeに永続化する機能が提供されているのでこれを使います。
キーを指定することでセッションを区別する仕組みなので、様々なパターンをキーで区別して保存しておくことができる点が便利です。
あとは、 <a class="reference external" href="https://github.com/AgentME/browserify-hmr">browserify-hmr</a> <sup id="sf-react-redux-reload-2-back"><a href="#sf-react-redux-reload-2" class="simple-footnote" title="BrowserifyでWebpackの Hot Module Replacement を使えるようにするモジュール">2</a></sup>を使ってトップレベルのコンポーネントで更新を検知して、以下のようにコンポーネントツリー全体を再描画すれば、状態を維持したまま更新が反映されます。</p>
<div class="highlight"><pre><span></span><span class="nx">render</span><span class="p">(</span><span class="o"><</span><span class="nx">App</span><span class="o">/></span><span class="p">,</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'app'</span><span class="p">));</span>
<span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">module</span><span class="p">.</span><span class="nx">hot</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">module</span><span class="p">.</span><span class="nx">hot</span><span class="p">.</span><span class="nx">accept</span><span class="p">(</span><span class="s1">'app/components/app_dev.jsx'</span><span class="p">,</span><span class="w"> </span><span class="kd">function</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">NextApp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">'app/components/app_dev.jsx'</span><span class="p">);</span>
<span class="w"> </span><span class="nx">render</span><span class="p">(</span><span class="o"><</span><span class="nx">NextApp</span><span class="o">/></span><span class="p">,</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">'app'</span><span class="p">));</span>
<span class="w"> </span><span class="p">});</span>
<span class="p">}</span>
</pre></div>
<p>この方法であれば、ルートコンポーネントをまるごと入れ替えるため、reducerやミドルウェアを含めたstoreのライブリロードにも自動的に対応できます。</p>
<p>ただし、筆者の環境では、Watchifyでの差分ビルドに2秒程度かかってしまいます。簡単に原因を調べたところ、Redux関連をはじめ、いろいろなモジュールをimportしていたら、いつのまにかビルドが遅くなっていました。<sup id="sf-react-redux-reload-3-back"><a href="#sf-react-redux-reload-3" class="simple-footnote" title="なにか改善案があればぜひ教えてください">3</a></sup></p>
<p>CSSのリロードは、ふつうに <a class="reference external" href="http://livereload.com/">LiveReload</a> で行いました。できれば、 <a class="reference external" href="https://github.com/davidguttman/sassify">sassify</a> などですべてをBrowserify/Watchifyに統一したがったのですが、どうしても速度面でLiveReloadに敵わなかったため、このような形に落ち着きました。</p>
</div>
<div class="section" id="section-2">
<h2>サンプルコード</h2>
<p>サンプルコードをGitHubに置きました。</p>
<p><a class="reference external" href="https://github.com/tai2/react-redux-reload-sample">react-redux-reload-sample</a></p>
<div class="figure">
<img alt="サンプルアプリのスクリーンショット" src="https://blog.tai2.net/images/react-redux-reload/screenshot.png">
<p class="caption">サンプルアプリのスクリーンショット</p>
</div>
<ol class="arabic simple">
<li>商品選択画面</li>
<li>注文画面</li>
<li>注文完了画面</li>
</ol>
<p>これら3つの画面からなるECサービスで、1,2,3の順に遷移します。</p>
</div>
<div class="section" id="react-router">
<h2>React Router対応</h2>
<p>サンプルでは、クエリ文字列でセッションキーを渡すようにしてあります。 <a class="reference external" href="https://github.com/reactjs/react-router">React Router</a> を併用する場合は、なにもしないとルート遷移でクエリ文字列が消えてしまうので、以下のようにRouteのonChangeコールバックを利用して、セッションキーを引き回すなどの処理が必要になります。</p>
<div class="highlight"><pre><span></span><span class="kd">function</span><span class="w"> </span><span class="nx">onChange</span><span class="p">(</span><span class="nx">prevState</span><span class="p">,</span><span class="w"> </span><span class="nx">nextState</span><span class="p">,</span><span class="w"> </span><span class="nx">replace</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">prevState</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">debug_session</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="o">!</span><span class="nx">nextState</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">debug_session</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">replace</span><span class="p">({</span>
<span class="w"> </span><span class="nx">pathname</span><span class="o">:</span><span class="w"> </span><span class="nx">nextState</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span><span class="p">,</span>
<span class="w"> </span><span class="nx">query</span><span class="o">:</span><span class="w"> </span><span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">({},</span><span class="w"> </span><span class="nx">nextState</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">query</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="nx">debug_session</span><span class="o">:</span><span class="w"> </span><span class="nx">prevState</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">debug_session</span><span class="p">}),</span>
<span class="w"> </span><span class="p">});</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
</div>
<ol class="simple-footnotes"><li id="sf-react-redux-reload-1">ここでは、フルページロードではなく部分的なロード、また、アプリの状態を保持したままのロードとします <a href="#sf-react-redux-reload-1-back" class="simple-footnote-back">↩</a></li><li id="sf-react-redux-reload-2">BrowserifyでWebpackの <a class="reference external" href="https://webpack.github.io/docs/hot-module-replacement.html">Hot Module Replacement</a> を使えるようにするモジュール <a href="#sf-react-redux-reload-2-back" class="simple-footnote-back">↩</a></li><li id="sf-react-redux-reload-3">なにか改善案があればぜひ教えてください <a href="#sf-react-redux-reload-3-back" class="simple-footnote-back">↩</a></li></ol>やよいの青色申告デスクトップから、freeeに乗り換えたら経理作業が楽になった(IT系個人事業主)2016-06-19T00:00:00+09:002016-06-19T00:00:00+09:00tai2tag:blog.tai2.net,2016-06-19:/freee-review.html<p class="first last">開業以来5年間、確定申告関連の書類は、やよいの青色申告デスクトップ版で作成してきました。しかし、ネットベースのサービスで、いろいろ便利なものが出てきているという話をよく聞くようになり、去年からは、freeeに乗り換えてみました。その結果、たしかに作業が楽になったと体感できたので、どういった点が改善されたのかを述べます。</p>
<p>筆者は、ソフトウェア開発を生業とする個人事業主です。
ソフトウェア開発業という、比較的記帳する項目が少ない業種であることもあり、経理処理は、すべて自分の手で行っています。
<sup id="sf-freee-review-1-back"><a href="#sf-freee-review-1" class="simple-footnote" title="きたみりゅうじ著 フリーランスを代表して 申告と節税について教わってきました の知識だけで、いままで対応できました。">1</a></sup></p>
<p>開業以来5年間、確定申告関連の書類<sup id="sf-freee-review-2-back"><a href="#sf-freee-review-2" class="simple-footnote" title="所得税の確定申告書Bと青色申告決算書">2</a></sup>は、 <a class="reference external" href="https://www.yayoi-kk.co.jp/products/aoiro/">やよいの青色申告デスクトップ版</a> で作成してきました。
しかし、ネットベースのサービスで、いろいろ便利なものが出てきているという話をよく聞くようになり、去年(2015年)からは、 <a class="reference external" href="https://www.freee.co.jp/">freee</a> に乗り換えてみました。
その結果、たしかに作業が楽になったと体感できたので、どういった点が改善されたのかを述べます。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-1" id="toc-entry-1">確定申告に必要な作業</a></li>
<li><a class="reference internal" href="#freee-1" id="toc-entry-2">freeeに乗り換えて楽になった点</a><ul>
<li><a class="reference internal" href="#section-2" id="toc-entry-3">口座の取引履歴を自動で取り込んでくれる</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-4">似たような履歴の仕訳項目を自動で推測してくれる</a></li>
<li><a class="reference internal" href="#ui" id="toc-entry-5">UIが使い易い</a></li>
<li><a class="reference internal" href="#windows" id="toc-entry-6">Windowsを立ち上げなくて良い</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-7">毎年アップデートしなくて良い</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-8">確定申告関連の書類作成が簡単</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-9">プライベート資金での取引入力がすこしわかりやすい</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-10">請求書の発行と自動マッチングをしてくれる</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-8" id="toc-entry-11">やよいの青色申告デスクトップのほうが便利だった機能</a></li>
</ul>
</div>
<p>比較対象は、やよいの青色申告 <strong>デスクトップ版</strong> であることにご注意ください。やよいの青色申告にも <a class="reference external" href="https://www.yayoi-kk.co.jp/products/aoiro_ol/index.html">オンライン版</a> はあるのですが、そちらは使ったことがありません。ブランドイメージ的にfreeeのほうが印象が良かったため、筆者は、freeeを選択しました。<sup id="sf-freee-review-3-back"><a href="#sf-freee-review-3" class="simple-footnote" title="正確に言うと、やよいの青色申告デスクトップを使ってきた経験から、弥生製品のユーザビリティーにあまり期待できなかった。">3</a></sup>また、デスクトップ版でも、あまり使い込なせていない機能があったと思います。いちおう購入時に付属してきた紙のマニュアルは一通り読んだ上で使い始めたのですが、通帳の自動取込・仕訳機能などは、存在自体気付いていませんでした。</p>
<p>価格としては、</p>
<ul class="simple">
<li>やよいの青色申告デスクトップ セルフプラン 12,960円(税込)</li>
<li>やよいの青色申告オンライン セルフプラン8,640円(税込)</li>
<li>freee スタータープラン 10,584円(税込)</li>
</ul>
<p>という感じで、やよいデスクトップよりは安くなりましたが、やよいオンラインよりは若干高いです。</p>
<p>また、freeeは、2016年5月から <a class="reference external" href="https://support.freee.co.jp/hc/ja/articles/202849000?_ga=1.44696420.1545757394.1452246359">プラン</a> が刷新され、月額980円のプランでは、同一価格で、使用できる機能が大幅に減りました。
しかしながら、筆者のユースケースでは、980円のスタータープラン以上の機能は利用していなかったため、とくに問題ないと判断しました。
<sup id="sf-freee-review-4-back"><a href="#sf-freee-review-4" class="simple-footnote" title="たとえば、使えなくなった機能のひとつに、レシートの自動読み取り機能などがありますが、元々、現金で経費を支払うケースが少かったため、なくてもあまり問題ありません。">4</a></sup></p>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-1">確定申告に必要な作業</a></h2>
<p>確定申告に必要な作業はだいたい以下のような感じです。</p>
<ul class="simple">
<li>通帳に記帳された入出金の仕訳をする</li>
<li>財布から支払った経費を振替伝票に記入する</li>
<li>家賃や光熱費などの按分を設定する</li>
<li>固定資産があれば、減価償却の処理をする<sup id="sf-freee-review-5-back"><a href="#sf-freee-review-5" class="simple-footnote" title="筆者の場合、固定資産扱いになるのは10万円弱のパソコンぐらいなので、 租法28の2 で一括償却しています。">5</a></sup></li>
<li>各種控除を設定する</li>
<li>所得の内訳を設定する</li>
<li>作成した書類をe-Taxで国税庁に提出する</li>
</ul>
<p>最後のe-Taxでの提出以外は、やよいでもfreeeでもすべて処理できます。
どちらも、xtxというe-Tax用のフォーマットを出力できるので、専用のe-Taxソフトに読み込ませて、国税庁に提出します。</p>
<p>売上1000万円以上いったことがないので、消費税の処理はしたことがありません。</p>
<p>このうち一番大変なのが、通帳の入出金仕訳です。<sup id="sf-freee-review-6-back"><a href="#sf-freee-review-6" class="simple-footnote" title="筆者の場合、事業用の通帳と個人の通帳が同じであるため、プライベートでの使用も混ざってしまってめんどうになっているという面もありますが。">6</a></sup>また、振替伝票の記入も、クレジットカードでの支払いなどを仕訳して記入する必要があり、それなりに手がかかります。</p>
<p>本来であれば、日々発生した取引を記帳していくほうが良いのでしょうが、筆者の場合は、ふだんはまったく経理のことを意識せず、毎年2月になってから一気に記帳して書類を作成するスタイルになっています。<sup id="sf-freee-review-7-back"><a href="#sf-freee-review-7" class="simple-footnote" title="最初のころは真面目にコツコツと記帳していたりもしていたのですが、いつのまにか、この形に落ち着いてしまいました。">7</a></sup></p>
</div>
<div class="section" id="freee-1">
<h2><a class="toc-backref" href="#toc-entry-2">freeeに乗り換えて楽になった点</a></h2>
<p>では、具体的に楽になった点を挙げていきます。</p>
<div class="section" id="section-2">
<h3><a class="toc-backref" href="#toc-entry-3">口座の取引履歴を自動で取り込んでくれる</a></h3>
<p>個人的には、いままでのやりかたとの違いで、一番労力低下に繋がっているところです。
いままでは、銀行やクレジットカードの取引履歴を目視で確認して、それを一件一件手作業を記帳していました。
freeeでは、対応している銀行やクレジットカード<sup id="sf-freee-review-8-back"><a href="#sf-freee-review-8" class="simple-footnote" title="freeeでは、便宜上クレジットカードも口座の一種として扱われます。">8</a></sup>サービスであれば、自動で同期してくれます。</p>
<div class="figure">
<img alt="auto transaction import" src="https://blog.tai2.net/images/freee-review/import-transactions.png">
<p class="caption">freeeは履歴を自動で同期してくれる</p>
</div>
<p>ただし、この連携は筆者が試したときは不安定で、自動同期はうまくいきませんでした。また、自動同期をするためには、そのサービスのIDとパスワードをfreeeに預ける必要があります。自動同期を使用できない、またはしない場合でも、口座の情報をCSV形式でエクスポートして、freeeにインポートすれば、一括でデータを取り込むことができます(多くのサービスは、取り引き履歴のCSVエキスポート機能を備えているようです)。自動同期に対応していないサービスでも、CSVであれば一括取込みができたりします。筆者にはこれで十分なので、CSV経由での一括取込をしました。</p>
</div>
<div class="section" id="section-3">
<h3><a class="toc-backref" href="#toc-entry-4">似たような履歴の仕訳項目を自動で推測してくれる</a></h3>
<p>自動で取り込まれたばかりの取引は、一時的に、まだ仕訳がされていない未確定状態に置かれます。これをひとつずつ、仕訳して台帳に記入していく作業が必要ですが、freeeでは、過去の似たような仕訳の履歴から、取引に対して勘定科目を提案してくれます。これにより、多くの場合、内容を確認して、確定ボタンを押すだけで仕訳が完了するため、だいぶ作業が楽になります。ただし、間違った勘定科目の提案をしてくることもあるので、その場合は、手動で訂正します。</p>
<div class="figure">
<img alt="transaction inference" src="https://blog.tai2.net/images/freee-review/transaction-inference.png">
<p class="caption">freeeは勘定科目を推測してくれる</p>
</div>
<p>やよいの青色申告デスクップで経理をしていたときには、振替伝票のUIが使いづらかったこともあり、クレジットカードの履歴を振替伝票に落とす作業がとくにめんどうだったのですが、freeeの一括取込と仕訳サポート機能で、格段に楽ができました。</p>
</div>
<div class="section" id="ui">
<h3><a class="toc-backref" href="#toc-entry-5">UIが使い易い</a></h3>
<p>主観でしかないのですが、UIはfreeeのほうがスッキリしていて使い易い気がします。やよいの振替伝票まわりのUIなどはとくに使い辛くストレスを感じていました。処理しなければならない内容が本質的に複雑なので、どうしてもUIも複雑になってしまうのですが、freeeは、一般的なのウェブブラウザのUIで実装されているため、親しみ易さを感じます。Windowsの使い慣れないUIでなく、ふだん使っているMacでふつうに作業ができるということも大きいです。<sup id="sf-freee-review-9-back"><a href="#sf-freee-review-9" class="simple-footnote" title="VirtualBox上のWindowsだと日本語変換ひとつままならず、非常に不便でした。">9</a></sup></p>
<div class="figure">
<img alt="freee ui is not special" src="https://blog.tai2.net/images/freee-review/freee-ui.png">
<p class="caption">freeeのUIは、一般的なWebサービスのUIなので、馴染みやすい</p>
</div>
</div>
<div class="section" id="windows">
<h3><a class="toc-backref" href="#toc-entry-6">Windowsを立ち上げなくて良い</a></h3>
<p>筆者のふだんの作業環境はMacなのですが、いままでは、経理処理をするたびにVirtualBoxでWindowsを立ち上げて、その中で作業をしていました。
これが、仮想マシンを立ち上げないでも、ブラウザの中だけで作業が簡潔するのはありがたいです。</p>
</div>
<div class="section" id="section-4">
<h3><a class="toc-backref" href="#toc-entry-7">毎年アップデートしなくて良い</a></h3>
<p>弥生の青色申告デスクトップでは、契約していると、毎年新しいバージョンがDVDで送られてくるので、それを使って新しいバージョンに更新する必要があります。
freeeはウェブサービスなので、随時アップデートされます。システムの更新作業をこちらでする必要はありません。</p>
</div>
<div class="section" id="section-5">
<h3><a class="toc-backref" href="#toc-entry-8">確定申告関連の書類作成が簡単</a></h3>
<p>確定申告時のフローが、質問に答えていくだけで、自然と書類ができあがるような作りになっているため、とても簡単でした。
やよいの青色申告でも似たような機能はあったのですが、freeeのほうが項目が見易く、操作が容易な印象です。</p>
</div>
<div class="section" id="section-6">
<h3><a class="toc-backref" href="#toc-entry-9">プライベート資金での取引入力がすこしわかりやすい</a></h3>
<p>自分の財布から出した費用は、事業主借として処理しますが、freeeでは、このための仮想的な口座として「プライベート資金」という口座が用意されており、これを選択して、対象の勘定科目を選択すれば、自動的に事業主借として仕訳されるため、直感的に入力できます。</p>
</div>
<div class="section" id="section-7">
<h3><a class="toc-backref" href="#toc-entry-10">請求書の発行と自動マッチングをしてくれる</a></h3>
<p>これまで請求書は自分で手作りしたみすぼらしいフォーマットを使っていたのですが、freeeには請求書の作成機能があり、そのテンプレートを使用するようにしたため、管理番号が自動で払出されたりして、ちょっとそれっぽいフォーマットになりました。また、発行した請求書と、口座の入金を自動で突き合わせて、仕訳処理してくれます。</p>
</div>
</div>
<div class="section" id="section-8">
<h2><a class="toc-backref" href="#toc-entry-11">やよいの青色申告デスクトップのほうが便利だった機能</a></h2>
<p>やよいお青色申告デスクトップには、仕訳アドバイザーという機能がありました。
キーワードを入力すると、そのキーワードに関連する仕訳についての説明が検索できる機能です。</p>
<p>たとえば、「期首振替については、どのように処理すればいいんだっけ?」という疑問が湧いたら、「期首振替」と入力すれば、勘定科目と簡単な解説が見られるので、簿記についての知識があまりない人間が経理の処理をする際にとても助かりました。freeeにはこの機能はないため、インターネットで検索するなどして補完しています。</p>
</div>
<ol class="simple-footnotes"><li id="sf-freee-review-1">きたみりゅうじ著 <a class="reference external" href="https://www.amazon.co.jp/dp/4534040016/">フリーランスを代表して 申告と節税について教わってきました</a> の知識だけで、いままで対応できました。 <a href="#sf-freee-review-1-back" class="simple-footnote-back">↩</a></li><li id="sf-freee-review-2">所得税の確定申告書Bと青色申告決算書 <a href="#sf-freee-review-2-back" class="simple-footnote-back">↩</a></li><li id="sf-freee-review-3">正確に言うと、やよいの青色申告デスクトップを使ってきた経験から、弥生製品のユーザビリティーにあまり期待できなかった。 <a href="#sf-freee-review-3-back" class="simple-footnote-back">↩</a></li><li id="sf-freee-review-4">たとえば、使えなくなった機能のひとつに、レシートの自動読み取り機能などがありますが、元々、現金で経費を支払うケースが少かったため、なくてもあまり問題ありません。 <a href="#sf-freee-review-4-back" class="simple-footnote-back">↩</a></li><li id="sf-freee-review-5">筆者の場合、固定資産扱いになるのは10万円弱のパソコンぐらいなので、 <a class="reference external" href="https://www.nta.go.jp/shiraberu/zeiho-kaishaku/tsutatsu/kobetsu/shotoku/sochiho/801226/sinkoku/57/28/02.htm">租法28の2</a> で一括償却しています。 <a href="#sf-freee-review-5-back" class="simple-footnote-back">↩</a></li><li id="sf-freee-review-6">筆者の場合、事業用の通帳と個人の通帳が同じであるため、プライベートでの使用も混ざってしまってめんどうになっているという面もありますが。 <a href="#sf-freee-review-6-back" class="simple-footnote-back">↩</a></li><li id="sf-freee-review-7">最初のころは真面目にコツコツと記帳していたりもしていたのですが、いつのまにか、この形に落ち着いてしまいました。 <a href="#sf-freee-review-7-back" class="simple-footnote-back">↩</a></li><li id="sf-freee-review-8">freeeでは、便宜上クレジットカードも口座の一種として扱われます。 <a href="#sf-freee-review-8-back" class="simple-footnote-back">↩</a></li><li id="sf-freee-review-9">VirtualBox上のWindowsだと日本語変換ひとつままならず、非常に不便でした。 <a href="#sf-freee-review-9-back" class="simple-footnote-back">↩</a></li></ol>GitHub公式クライアントでだれでも簡単GitHubライフをはじめよう2016-04-28T00:00:00+09:002016-04-28T00:00:00+09:00tai2tag:blog.tai2.net,2016-04-28:/github_client_howto.html<p class="first last">本稿では、 GitHub 上で運用されている開発プロジェクトに、公式クライアントであるGitHub Desktopを使用して参加する方法を説明します。本稿は、デザイナーやディレクターなど、プログラマ以外の方を対象とします。 GitHub Desktopを使用すれば、コマンドラインからの操作をほとんど1行わずに開発に参加することが可能です。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#git" id="toc-entry-1">Gitとは</a></li>
<li><a class="reference internal" href="#github-1" id="toc-entry-2">GitHubとは</a></li>
<li><a class="reference internal" href="#github-desktop" id="toc-entry-3">GitHub Desktopの特徴</a></li>
<li><a class="reference internal" href="#section-1" id="toc-entry-4">セットアップ</a></li>
<li><a class="reference internal" href="#github-2" id="toc-entry-5">GitHubでのワークフロー</a><ul>
<li><a class="reference internal" href="#section-2" id="toc-entry-6">1.フォークする</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-7">2.リポジトリをクローンする</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-8">3.ブランチを作成する</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-9">4.機能追加あるいはバグ修正をコミットする</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-10">5.プッシュする</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-11">6.プルリクエストを作成する</a></li>
</ul>
</li>
<li><a class="reference internal" href="#github-2-1" id="toc-entry-12">GitHubでのワークフロー(2回目以降)</a><ul>
<li><a class="reference internal" href="#upstream" id="toc-entry-13">1.upstreamから更新を取得する</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-14">2.以降</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-9" id="toc-entry-15">コンフリクトの解消</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-16">自分がオーナーまたはコラボレーターに指定されている場合のワークフロー</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-17">まとめ</a></li>
<li><a class="reference internal" href="#section-12" id="toc-entry-18">更新履歴</a></li>
</ul>
</div>
<p>本稿では、 <a class="reference external" href="https://github.com/">GitHub</a> 上で運用されている開発プロジェクトに、公式クライアントである <a class="reference external" href="https://desktop.github.com/">GitHub Desktip</a> を使用して参加する方法を説明します。</p>
<p>本稿は、デザイナーやディレクターなど、プログラマ以外の方を対象とします。
GitHub Desktopを使用すれば、基本的なフローからはずれない限り、コマンドラインからの操作をまったく行わずに開発に参加することが可能です。</p>
<div class="section" id="git">
<h2><a class="toc-backref" href="#toc-entry-1">Gitとは</a></h2>
<p><a class="reference external" href="http://git-scm.com/">Git</a> は、VCS(Version Control System)、あるいは、SCM(Software Configuration Management)などと呼ばれるソフトウェアの一種です。
同種のソフトウェアとして、 <a class="reference external" href="http://mercurial.selenic.com/">Mercurial</a> や、 <a class="reference external" href="https://subversion.apache.org/">Subversion</a> といったものが広く使われています。これらは、いずれもフリーソフトウェアです。</p>
<p>Gitを使用すると、ディレクトリに含まれるファイルのスナップショットを、好きなときに好きなだけ保存しておくことができます。
スナップショットを保存してく場所のことをリポジトリと呼びます。
Gitを使えば、システム上にある任意のディレクトリをリポジトリ化できます。<sup id="sf-github_client_howto-1-back"><a href="#sf-github_client_howto-1" class="simple-footnote" title="リポジトリ化されたディレクトリのルートには、.gitという隠しディレクトリが作成されます。この中にGitが必要とするすべての情報が格納されます。">1</a></sup>
実際の使用では、プロジェクトごと(例えば、ウェブサイトに関連するファイル一式など)にリポジトリを作成する場合が多いと思います。</p>
<div class="figure">
<img alt="Git Snapshots" src="https://blog.tai2.net/images/github_client_howto/git-snapshots.png">
<p class="caption">Gitではディレクトリの内容を「スナップショット」として積み重ねていく</p>
</div>
<p>リポジトリ上に保存されたスナップショットは、いつでも好きなときに復元して、現在のディレクトリに置かれているファイルセットを切り替えることができます。</p>
<p>Gitは行指向のツールです。つまり、Gitのファイル管理機能のほとんどは、テキストファイルを対象としています。
リポジトリ内にPSDやJPEGなどのバイナリファイル<sup id="sf-github_client_howto-2-back"><a href="#sf-github_client_howto-2" class="simple-footnote" title="バイナリファイルの例としては、PNG,JPEGなどの画像ファイル、MP4,AVIなど動画ファイル、PSD,AIなどAdobeソフトのファイルなどが挙げられます。HTML、CSS、Java Scriptなどのソースコードは、テキストファイルで、こちらはGitでの管理が上手く機能します。">2</a></sup>を格納することもできますが、スナップショット間の差分を確認したり、異なるスナップショットを統合するなどの便利な機能は、それらに対しては使えません。</p>
<p>ちなみに、Gitクライアントのオリジナルのバージョンは、コマンドラインで操作するツールです。
ですが、オリジナル版以外にも、いくつものGUIバージョンが開発されています。GitHub Desktopも数あるGUI版クライアントのうちのひとつです。</p>
</div>
<div class="section" id="github-1">
<h2><a class="toc-backref" href="#toc-entry-2">GitHubとは</a></h2>
<div class="figure">
<img alt="Octocat" src="https://blog.tai2.net/images/github_client_howto/octocat.png">
<p class="caption">GitHubのキモかわいいマスコットキャラクターOctocat君</p>
</div>
<p>GitHubは、Gitで管理しているリポジトリをインターネット上で公開するためのウェブサービスです。<sup id="sf-github_client_howto-3-back"><a href="#sf-github_client_howto-3" class="simple-footnote" title="GitHub上で管理しているリポジトリを非公開にしておくこともできますが、インターネットを通じてリポジトリにアクセスする点は同じです。">3</a></sup></p>
<p>GitHubでは、リポジトリのホスティング以外にも、Issue管理やWikiといったプロジェクト運営のためのいくつかの機能が使えます。
また、後述のプルリクエスト機能により、公開されているプロジェクトに気軽に貢献できるのが最大の特徴です。</p>
</div>
<div class="section" id="github-desktop">
<h2><a class="toc-backref" href="#toc-entry-3">GitHub Desktopの特徴</a></h2>
<p>さきほど述べたように、Mac/Windows用ともに、 <a class="reference external" href="http://git-scm.com/downloads/guis">いくつものGUI版Gitクライアント</a> があります。
GitHub Desktopの特徴としては、</p>
<ul class="simple">
<li>コマンドライン版と比べ、大幅に機能が制限されている。</li>
<li>GitHubと連携するための機能が実装されている。</li>
<li>Mac/Windows用があり、ほとんど同じUIを提供している。</li>
</ul>
<p>といったものが挙げられます。</p>
<p>GitHub Desktopは、コマンドライン版ほどパワフルな管理機能は備えていませんが、日々の業務で使うのに必要な最低限の機能は備えています。機能が制限されているぶん、ときに難解と言われるGitの導入障壁がいくぶん軽減されることが期待できます。<sup id="sf-github_client_howto-4-back"><a href="#sf-github_client_howto-4" class="simple-footnote" title="Gitに慣れてくると、履歴を綺麗に保ちたいといった欲求が出てきますが、そのときは素直にコマンドライン版を使うのがいいと思います。">4</a></sup></p>
<p>また、GitHubアカウントと連動し、GitHub上で自分と関連付けられているリポジトリを一覧表示したり、プルリクエストといったGitHub特有の機能を直接使うことができるのは、GitHub Desktopならではと言えるでしょう。
Windows版では、GitHub Desktopのインストール時に、コマンドライン版(GitHub Shell)もいっしょに導入できます。Mac OS Xには最初からgitがインストールされています。</p>
<p>Mac/Windows両対応なのも人に使い方を説明するときには重要です。
なお、以降では、Mac版のスクリーンショットを使用します。Windows版でも、若干の見た目の違いはあるものの、UIのレイアウトや機能などはほとんど同じですので、支障はないかと思います。</p>
<p>なお、この記事で使用しているクライアントのバージョンは、</p>
<ul class="simple">
<li>Mac版 Beset by Computers (220)</li>
<li>Windows版 Proctional Fungramming (3.0.17.0)</li>
</ul>
<p>です。</p>
</div>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-4">セットアップ</a></h2>
<p>アプリケーションをダウンロードしたら、まずはじめに、GitHub DesktopにGitHubのアカウント情報を登録する必要があります。
アカウント情報の扱いについてはMacとWindowsで若干違いがあり、Macでは、キーチェーンにIDとパスワードが保存されます。
Windowsでは、初回の設定時に、SSHのキーペアが自動的に生成され、GitHubに登録されます。</p>
</div>
<div class="section" id="github-2">
<h2><a class="toc-backref" href="#toc-entry-5">GitHubでのワークフロー</a></h2>
<p>それでは、実際の使い方の説明に入っていきましょう。
GitHubで一般に公開されているプロジェクト(リポジトリ)に貢献するためのおおまかなフローは次のとおりです。
<sup id="sf-github_client_howto-5-back"><a href="#sf-github_client_howto-5" class="simple-footnote" title="Gitを使用したワークフローにはさまざまな形態があります。開発現場に入る際には、その現場でのワークフローがどうなっているのか確認しましょう。この記事で紹介するのは、 GitHub Flow と呼ばれるワークフローになっています。">5</a></sup></p>
<ol class="arabic simple">
<li>フォークする</li>
<li>リポジトリをクローンする</li>
<li>ブランチを作成する</li>
<li>機能追加あるいはバグ修正をコミットする</li>
<li>プッシュする</li>
<li>プルリクエストを作成する</li>
</ol>
<div class="figure">
<img alt="GitHub Workflow" src="https://blog.tai2.net/images/github_client_howto/github-workflow1.png">
<p class="caption">GitHubでのワークフロー</p>
</div>
<p>以下、順を追って説明していきます。</p>
<div class="section" id="section-2">
<h3><a class="toc-backref" href="#toc-entry-6">1.フォークする</a></h3>
<p>フォークとは、GitHub上のプロジェクトを自分のアカウントにインポートすることです。
フォークされたプロジェクトは、自分のアカウントに完全にコピーされ、元のプロジェクトに直接影響を与えることはないので、自分の好きに改変することができます。
自分がオーナーでない、またはコラボレーターに入っていないGitHub上のプロジェクトに貢献するためには、まずはフォークをする必要があります。</p>
<p>まずは、フォークしたいプロジェクトのGitHub上のページをブラウザで開きましょう。</p>
<div class="figure">
<img alt="Fork Button" src="https://blog.tai2.net/images/github_client_howto/github-fork-button.png">
<p class="caption">フォークボタンの場所</p>
</div>
<p>ページ右上あたりにある“Fork”と書かれたボタンを押せばフォークできます。</p>
</div>
<div class="section" id="section-3">
<h3><a class="toc-backref" href="#toc-entry-7">2.リポジトリをクローンする</a></h3>
<p>クローンとは、読んで字のごとく、リポジトリを複製することです。
リポジトリをクローンすると、クローンした(自分の手元にある)リポジトリには、どこからクローンしてきたかという情報が記録されます。
クローンされた元のことをoriginと言います。</p>
<div class="figure">
<img alt="Clone Button" src="https://blog.tai2.net/images/github_client_howto/github-clone-button.png">
<p class="caption">クローンボタンの場所</p>
</div>
<p>フォークしたプロジェクトのページを表示して、“Download Zip” の左隣にあるボタンを押しましょう。自分のローカル環境にリポジトリをクローンできます。</p>
<div class="figure">
<img alt="Desktop Clone" src="https://blog.tai2.net/images/github_client_howto/github-clone-desktop.png">
<p class="caption">GitHub Desktopからクローンする</p>
</div>
<p>または、GitHub Desktopの左上にある+ボタンからクローンしてもかまいません。</p>
<p>GitHub Desktopでは、GitHubからリポジトリをクローンする以外にも、既存のローカルリポジトリ(他のソフトウェアで作成したものなど)をインポートしたり、新規にリポジトリを作成することもできます。</p>
</div>
<div class="section" id="section-4">
<h3><a class="toc-backref" href="#toc-entry-8">3.ブランチを作成する</a></h3>
<p>Gitのファイル管理では、スナップショットを任意のタイミングで保存していき、履歴を形作ります。
なにもしなければ、それは一本のまっすぐな更新履歴という形になりますが、ブランチを追加する<sup id="sf-github_client_howto-6-back"><a href="#sf-github_client_howto-6" class="simple-footnote" title="ブランチを新しく作成することをブランチを 切る と言ったりもします。">6</a></sup>ことで、履歴を分岐させることができます。
作成したブランチは、いつでも自由に切り替えることができて、それに追随して、実際のディレクトリの中身も入れ替わります。Gitでは、ブランチの作成や切り替えは、非常に高速に行うことができます。<sup id="sf-github_client_howto-7-back"><a href="#sf-github_client_howto-7" class="simple-footnote" title="Subversionのような中央管理型のSCMでは、そうはいきません。">7</a></sup>
リポジトリを新規に作成すると、masterと呼ばれるデフォルトのブランチがひとつ自動的に作成されます。</p>
<div class="figure">
<img alt="Branch" src="https://blog.tai2.net/images/github_client_howto/git-branch.png">
<p class="caption">ブランチでバージョンを「派生」させる</p>
</div>
<p>プロダクトに新しい機能を実装したり<sup id="sf-github_client_howto-8-back"><a href="#sf-github_client_howto-8" class="simple-footnote" title="新機能用のブランチをフィーチャーブランチあるいはトピックブランチと呼んだりします。">8</a></sup>バグフィックスをしたりするときには、その作業用のブランチを作成します。ブランチの名前は、これから行おうとしている作業を適切に表した名前にしましょう。たとえば、サイトにサイドバーを追加しようとしているなら、add-sidebarのようなブランチ名にします。</p>
<div class="figure">
<img alt="GitHub Client Branching" src="https://blog.tai2.net/images/github_client_howto/github-client-branch.png">
<p class="caption">ブランチボタンの場所</p>
</div>
<p>GitHub Desktopでブランチを作成するには、ウィンドウ上部にあるブランチ追加ボタンを押します。Fromで、どのブランチの先端から新たなブランチを派生させるかを指定します。どのブランチから派生させるべきかは、プロジェクトの運用形態により異なりますが、masterやdevelopといった名前のブランチから派生させることが多いでしょう。以降、masterからブランチを作成することを前提として記述しますが、適宜読み替えてください。</p>
</div>
<div class="section" id="section-5">
<h3><a class="toc-backref" href="#toc-entry-9">4.機能追加あるいはバグ修正をコミットする</a></h3>
<p>ブランチを作成したら、実際にファイルを更新していきます。
ディレクトリのスナップショットをリポジトリに追加することを <strong>コミットする</strong> と言います。あるいは、スナップショットに加える変更全体を指して <strong>コミット</strong> と言ったりもします。</p>
<p>リポジトリ化したディレクトリ内にファイルを追加したり、あるいはリポジトリに含まれるファイルを更新・削除したりすると、下記の図のように変更点が表示されます。</p>
<div class="figure">
<img alt="GitHub Commit" src="https://blog.tai2.net/images/github_client_howto/github-commit.png">
<p class="caption">コミットの差分</p>
</div>
<p>ファイル名の左側についているチェックボックスは、コミットにそのファイルを含めるかどうかを表しています。チェックをはずすと、そのファイルに対して加えた変更は、コミットから除外されます。右側のビューでの赤い行は削除される行、緑の行は追加される行を示しています。
変更内容を確認の上、その内容でスナップショットを保存していいと判断したら、コミットログを記入してコミットボタンを押します。</p>
<div class="figure">
<img alt="GitHub Commit Button" src="https://blog.tai2.net/images/github_client_howto/github-commit-button.png">
<p class="caption">コミットするときは、変更内容の説明を記入する</p>
</div>
<p>コミットをする際には、コミットログとして、最低限1行の要約を記入する必要があります。変更内容を端的に表した文面を考えましょう。</p>
<p>もしコミットした後にコミットログや変更自体の誤りに気付いたら、直前のコミットに限ってアンドゥをすることができます。
“Undo”ボタンを押してから、再度コミットをし直しましょう。<sup id="sf-github_client_howto-9-back"><a href="#sf-github_client_howto-9" class="simple-footnote" title="Undoできるのは、GitHubと同期する前のコミットのみです。1度GitHubと同期してしまったら、例え直前のコミットであってもUndoできません。">9</a></sup></p>
<p>ひとつのコミットで、あまり大きな修正をするのは避けましょう。大き過ぎるコミットがひとつあるよりは、細かいコミットがたくさんあるほうがいいです。
その上で、できればひとつのコミットが、論理的な修正単位と対応するようにし、できれば、どのコミットの時点でもコードが、最低限の基準を満たす<sup id="sf-github_client_howto-10-back"><a href="#sf-github_client_howto-10" class="simple-footnote" title="たとえば、自動テストが通るとか、ビルドが通るなど">10</a></sup>ようにしておくのが望ましいでしょう。</p>
</div>
<div class="section" id="section-6">
<h3><a class="toc-backref" href="#toc-entry-10">5.プッシュする</a></h3>
<p>実装が完了して、すべてコミットできたら、ローカルで作成したブランチをGitHubに送信しましょう。
これを <strong>プッシュ</strong> と言います。</p>
<div class="figure">
<img alt="GitHub Publish Button" src="https://blog.tai2.net/images/github_client_howto/github-publish-button.png">
<p class="caption">Publishボタンの場所</p>
</div>
<p>対象ブランチの右側にある“Publish”ボタンを押せば、ブランチをプッシュできます。</p>
<div class="figure">
<img alt="GitHub Sync Button" src="https://blog.tai2.net/images/github_client_howto/github-sync-button.png">
<p class="caption">Syncボタンの場所</p>
</div>
<p>一度プッシュすると“Publish”ボタンが“Sync”ボタンに変化します。プッシュ済みのブランチにコミットを追加して、再度GitHubに送信したい場合は、“Sync”ボタンを押せばOKです。</p>
</div>
<div class="section" id="section-7">
<h3><a class="toc-backref" href="#toc-entry-11">6.プルリクエストを作成する</a></h3>
<p>いよいよ最後、 <strong>プルリクエスト</strong> です。
プルリクエストとは、フォーク元のプロジェクトに対して、ブランチで追加したコミットを取り込んで欲しいという要求を送ることです。
ちなみに、フォーク元のプロジェクトのことをupstreamと呼んだりします。</p>
<p>プルリクエストを送りたいブランチを選択した状態で、ウィンドウ右上の“Pull Request”ボタンを押すと、以下のようにプルリクエストペインが表示されます。</p>
<div class="figure">
<img alt="GitHub Pull Request Button" src="https://blog.tai2.net/images/github_client_howto/github-pullrequest-button.png">
<p class="caption">プルリクエストボタンの場所</p>
</div>
<p>2本の線(コミットログ)が表示されており、下側は現在選択しているローカルのブランチ、上側がマージ先のブランチを表しています。
マージ先のブランチは選択可能です。
これらが適切であることを確認し、必要に応じてコメントを記入したら、プルリクエストペインの下部にある “Send Pull Request”ボタンを押しましょう。</p>
<div class="figure">
<img alt="GitHub Pull Request Which Branch Merge to" src="https://blog.tai2.net/images/github_client_howto/github-merge-to.png">
<p class="caption">必要に応じてプルリクエストのマージ先を選択する</p>
</div>
<p>これで、プルリクエストの作成ができました。
プルリクエストを作成すると、“Pull Request”ボタンの表示が変化し、このボタンを押すとブラウザ上でプルリクエストを確認できます。</p>
<p>プルリクエストの作成ができたら、あとは、upstreamのプロジェクトオーナーによるレビューを待ちます。
レビューした結果、問題がなければ、無事あなたのプルリクエストは、取り込まれることでしょう。
ブランチに含まれるコミットを、派生元に取り込むことを <strong>マージする</strong> と言います。</p>
<p>もしレビューの結果、問題箇所を指摘された場合は、その点を修正して、再度コミット・プッシュ(Sync)しましょう。
すると、プルリクエスト上にコミットが積み重なっていきます。</p>
<p>マージされれば、プロジェクトへの貢献作業は一段落です。おつかれさまでした。</p>
</div>
</div>
<div class="section" id="github-2-1">
<h2><a class="toc-backref" href="#toc-entry-12">GitHubでのワークフロー(2回目以降)</a></h2>
<p>さて、最初のプルリクエストはマージされました。その後、プロジェクトに対してさらなる貢献をしたくなったとしましょう。
この場合には、すでにフォークとクローンは済んでいるので、1回目とはすこしフローが異なります。</p>
<ol class="arabic simple">
<li>upstreamから更新を取得する</li>
<li>ブランチを作成する</li>
<li>機能追加あるいはバグ修正をコミットする</li>
<li>プッシュする</li>
<li>プルリクエストを作成する</li>
</ol>
<div class="figure">
<img alt="GitHub Workflow 2" src="https://blog.tai2.net/images/github_client_howto/github-workflow2.png">
<p class="caption">GitHubでのワークフロー(2回目以降)</p>
</div>
<div class="section" id="upstream">
<h3><a class="toc-backref" href="#toc-entry-13">1.upstreamから更新を取得する</a></h3>
<p>自分のリポジトリとは独立して、upstreamのほうでも、日々刻々と更新が積み重ねられているため、upstreamのmasterと、自分のリポジトリのmasterの間でズレが生じていきます。作業を開始する前に、必ず、最新の更新を手元のリポジトリに取り込みましょう。GitHub Desktopでは、以前のGitHub for Mac/Windowsと比べて、upstreamからの更新の取り込みが非常に簡単になりました。</p>
<p>まず、masterブランチを選択します。upstreamのmasterではなく、ローカルのmasterを選択する必要があるので注意してください(XXXXX/masterでなく、ただのmasterを選択する)。
upstreamに更新がある場合には、“Update from XXXXX/master”というボタンが押せる状態になっています。このボタンを押すと、upstreamでの更新をローカルに取り込んでマージすることができます。</p>
<div class="figure">
<img alt="GitHub Update from Master" src="https://blog.tai2.net/images/github_client_howto/update-from-master.png">
<p class="caption">クローン元から最新の状態を取得する</p>
</div>
<p>これで、ローカルのmasterが、upstreamのmasterと同期されました。</p>
</div>
<div class="section" id="section-8">
<h3><a class="toc-backref" href="#toc-entry-14">2.以降</a></h3>
<p>データの更新ができたら、あとの流れは1回目と同様です。
新しい機能を追加したり、不具合を修正するときには、あらためてmasterからブランチを切ることからはじめてください。</p>
</div>
</div>
<div class="section" id="section-9">
<h2><a class="toc-backref" href="#toc-entry-15">コンフリクトの解消</a></h2>
<p>プルリクエストを出したときに、GitHubウェブサイトのプルリクエスト画面上で、次の図のように、左側のマージアイコンが灰色で表示され、自動的にマージができない旨のメッセージがでる場合があります。これは、あなたのブランチを取り込むときに <strong>コンフリクト</strong> が発生することを示しています。</p>
<div class="figure">
<img alt="GitHub Conflict" src="https://blog.tai2.net/images/github_client_howto/github-conflict.png">
<p class="caption">コンフリクトが発生しているとマージできない</p>
</div>
<p>GitHub Desktopであれば、プルリクエストペインに次のように警告が表示されます。</p>
<div class="figure">
<img alt="GitHub Desktop Conflict" src="https://blog.tai2.net/images/github_client_howto/desktop-conflict.png">
<p class="caption">GitHub Desktopでのコンフリクト表示。</p>
</div>
<p>基本的に、Gitはブランチのマージを自動で行ってくれますが、マージ元とマージ先で同じファイルの同じ箇所に対して同時に編集がなされていると、機械的にマージできない場合があります。場合によっては、upstreamのオーナーがコンフリクトを解消してくれることもあるかもしれんが、基本的には、プルリクエストを出す側がコンフリクトを解消したほうが親切でしょう。</p>
<p>コンフリクトが発生したということは、ブランチを作成した後に、upstreamでmasterに対してコミットが追加されたことを示しています。</p>
<div class="figure">
<img alt="GitHub Conflicing Branches" src="https://blog.tai2.net/images/github_client_howto/github-conflict-branches.png">
<p class="caption">自分の作業と並行して、upstreamにも変更が加えられている場合がある</p>
</div>
<p>そのため、まずは、ローカルのブランチをupstreamと同期させる必要があります。
さきほどのupstreamからmasterへの更新取得と同じ要領で更新を取得します。
対象のブランチを選択した上で、“Update from XXXXX/master”ボタンを押せばOKです。</p>
<div class="figure">
<img alt="Retrieve Updates from Upstream" src="https://blog.tai2.net/images/github_client_howto/retrieve-update-to-branch.png">
<p class="caption">Upstreamからブランチに更新を取り込む</p>
</div>
<p>するとコンフリクトが生じるはずなので、更新取得後、マージの途中で止まります。</p>
<div class="figure">
<img alt="Conflict message on a file" src="https://blog.tai2.net/images/github_client_howto/conflict-message.png">
<p class="caption">ファイルにコンフリクトが発生していることを示すメッセージ</p>
</div>
<p>コンフリクトが発生したファイルを選択すると、上図のようなメッセージが表示されます。
該当ファイルをテキストエディタで開くと、次の例のように、衝突した部分が<<<<<と>>>>>で囲まれた状態になります。</p>
<div class="highlight"><pre><span></span> <span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"col-sm-6 col-md-3"</span><span class="p">></span>
<span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">""</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"thumbnail"</span><span class="p">></span>
<span class="cm"><!-- ここに自分の写真を追加 --></span>
<span class="p"><</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">"http://tai2.net/img/tai2.jpg"</span> <span class="p">/></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"caption"</span><span class="p">></span>
<span class="p"><</span><span class="nt">h3</span><span class="p">></span>
<span class="p"><</span><span class="nt">span</span><span class="p">></span>Name<span class="p"></</span><span class="nt">span</span><span class="p">></span>
Taiju Muto(tai2)
<span class="p"></</span><span class="nt">h3</span><span class="p">></span>
<span class="p"><</span><span class="nt">h4</span><span class="p">></span>
<span class="p"><</span><span class="nt">span</span><span class="p">></span>Job<span class="p"></</span><span class="nt">span</span><span class="p">></span>
<span class="err"><<<<<<</span><span class="p"><</span> <span class="nt">HEAD</span>
<span class="na">Sniper</span>
<span class="o">=</span><span class="s">======</span>
<span class="na">Chef</span>
<span class="p">></span>>>>>>> master
<span class="p"></</span><span class="nt">h4</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">a</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
</pre></div>
<p>HEADから=====までの部分が自分のブランチで加えた変更を、=====からmasterまでの部分が、upstreamで加えられた変更を表しています。
正しい編集内容に自分で編集して、ファイルを保存しましょう。
そして、通常の変更と同様にコミットします。コミットログには、自動的にこのコミットがマージ時にコンフリクトを解消したものである旨が記入されます。コミットができたら、GitHubにプッシュ(Sync)しましょう。
自動的にマージできる状態になっていれば、最初のプルリクエストのようにアイコンが緑色で表示されているはずです。</p>
</div>
<div class="section" id="section-10">
<h2><a class="toc-backref" href="#toc-entry-16">自分がオーナーまたはコラボレーターに指定されている場合のワークフロー</a></h2>
<p>自分自身がオーナーである場合、またはコラボラーターに指定されている場合には、リポジトリに直接プッシュする権限があります。
そのため、これまで説明したフローよりも簡単で、最初にプロジェクトをフォークする必要はありませんし、
また、プルリクエストを作成せずに直接masterに変更を加えることも可能です。
ただし複数人で共同作業をする場合には、プルリクエストを利用すると、メンバーにコードレビューをしてもらうときに便利です。</p>
</div>
<div class="section" id="section-11">
<h2><a class="toc-backref" href="#toc-entry-17">まとめ</a></h2>
<p>本稿では、Gitの基本概念について述べました。また、GitHub Desktopを使用して、プロジェクトをフォークし、ローカルマシンで修正を加えて、プルリクエストを作成するまでの流れをひととおり説明しました。</p>
</div>
<div class="section" id="section-12">
<h2><a class="toc-backref" href="#toc-entry-18">更新履歴</a></h2>
<ul class="simple">
<li>2015/2/26 作成</li>
<li>2016/4/28 GitHub Desktopに合わせて改訂。</li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-github_client_howto-1">リポジトリ化されたディレクトリのルートには、.gitという隠しディレクトリが作成されます。この中にGitが必要とするすべての情報が格納されます。 <a href="#sf-github_client_howto-1-back" class="simple-footnote-back">↩</a></li><li id="sf-github_client_howto-2">バイナリファイルの例としては、PNG,JPEGなどの画像ファイル、MP4,AVIなど動画ファイル、PSD,AIなどAdobeソフトのファイルなどが挙げられます。HTML、CSS、Java Scriptなどのソースコードは、テキストファイルで、こちらはGitでの管理が上手く機能します。 <a href="#sf-github_client_howto-2-back" class="simple-footnote-back">↩</a></li><li id="sf-github_client_howto-3">GitHub上で管理しているリポジトリを非公開にしておくこともできますが、インターネットを通じてリポジトリにアクセスする点は同じです。 <a href="#sf-github_client_howto-3-back" class="simple-footnote-back">↩</a></li><li id="sf-github_client_howto-4">Gitに慣れてくると、履歴を綺麗に保ちたいといった欲求が出てきますが、そのときは素直にコマンドライン版を使うのがいいと思います。 <a href="#sf-github_client_howto-4-back" class="simple-footnote-back">↩</a></li><li id="sf-github_client_howto-5">Gitを使用したワークフローにはさまざまな形態があります。開発現場に入る際には、その現場でのワークフローがどうなっているのか確認しましょう。この記事で紹介するのは、 <a class="reference external" href="https://gist.github.com/Gab-km/3705015">GitHub Flow</a> と呼ばれるワークフローになっています。 <a href="#sf-github_client_howto-5-back" class="simple-footnote-back">↩</a></li><li id="sf-github_client_howto-6">ブランチを新しく作成することをブランチを <strong>切る</strong> と言ったりもします。 <a href="#sf-github_client_howto-6-back" class="simple-footnote-back">↩</a></li><li id="sf-github_client_howto-7">Subversionのような中央管理型のSCMでは、そうはいきません。 <a href="#sf-github_client_howto-7-back" class="simple-footnote-back">↩</a></li><li id="sf-github_client_howto-8">新機能用のブランチをフィーチャーブランチあるいはトピックブランチと呼んだりします。 <a href="#sf-github_client_howto-8-back" class="simple-footnote-back">↩</a></li><li id="sf-github_client_howto-9">Undoできるのは、GitHubと同期する前のコミットのみです。1度GitHubと同期してしまったら、例え直前のコミットであってもUndoできません。 <a href="#sf-github_client_howto-9-back" class="simple-footnote-back">↩</a></li><li id="sf-github_client_howto-10">たとえば、自動テストが通るとか、ビルドが通るなど <a href="#sf-github_client_howto-10-back" class="simple-footnote-back">↩</a></li></ol>mutt + notmuch でコマンドラインメール送受信環境を構築する(Mac OS X編)2016-02-12T00:00:00+09:002016-02-12T00:00:00+09:00tai2tag:blog.tai2.net,2016-02-12:/mutt-and-notmuch.html<p class="first last">この記事では、muttというコマンドラインのメールクアイアントにnotmuchというメール検索プログラムを組み合わせて、Mac OS X上で、メール送受信環境を構築する方法を説明します。</p>
<blockquote>
「メールクライアントはどれだってひどい。このメールクライアントは、ひどさがマシってだけ」 -- mutt作者</blockquote>
<p>この記事では、muttというコマンドラインのメールクアイアントにnotmuchというメール検索プログラムを組み合わせて、Mac OS X上で、メール送受信環境を構築する方法を説明します。不明点があればコメントでどうぞ。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-1" id="toc-entry-1">メールの基礎知識</a><ul>
<li><a class="reference internal" href="#mxa" id="toc-entry-2">MxA</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-3">関連するプロトコル</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-4">メールボックス</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-4" id="toc-entry-5">筆者のメール環境</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-6">動機</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-7">使用するプログラム</a><ul>
<li><a class="reference internal" href="#mutt-1" id="toc-entry-8">mutt</a></li>
<li><a class="reference internal" href="#notmuch-1" id="toc-entry-9">notmuch</a></li>
<li><a class="reference internal" href="#getmail-1" id="toc-entry-10">getmail</a></li>
<li><a class="reference internal" href="#spamassassin-1" id="toc-entry-11">SpamAssassin</a></li>
<li><a class="reference internal" href="#razor2-1" id="toc-entry-12">Razor2</a></li>
<li><a class="reference internal" href="#terminal-notifier-1" id="toc-entry-13">terminal-notifier</a></li>
<li><a class="reference internal" href="#launchd" id="toc-entry-14">launchd</a></li>
<li><a class="reference internal" href="#ssh-sendmail" id="toc-entry-15">ssh + sendmail</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-7" id="toc-entry-16">メール受信のシーケンス</a><ul>
<li><a class="reference internal" href="#pop3" id="toc-entry-17">1. POP3でメール受信</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-18">2. スパム判定</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-19">3. 一時フォルダにメールファイルを保存</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-20">4. スパム報告&学習</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-21">5. メールファイルをメールボックスに移動</a></li>
<li><a class="reference internal" href="#section-12" id="toc-entry-22">6. 検索インデックス作成</a></li>
<li><a class="reference internal" href="#section-13" id="toc-entry-23">7. 配信先フォルダタグ付与</a></li>
<li><a class="reference internal" href="#spam" id="toc-entry-24">8. spamフォルダへ移動</a></li>
<li><a class="reference internal" href="#new" id="toc-entry-25">9. newタグを除去</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-14" id="toc-entry-26">各種の設定</a><ul>
<li><a class="reference internal" href="#mutt-2" id="toc-entry-27">mutt</a></li>
<li><a class="reference internal" href="#notmuch-2" id="toc-entry-28">notmuch</a></li>
<li><a class="reference internal" href="#getmail-2" id="toc-entry-29">getmail</a></li>
<li><a class="reference internal" href="#spamassassin-razor2" id="toc-entry-30">SpamAssassin & Razor2</a></li>
<li><a class="reference internal" href="#launchd-1" id="toc-entry-31">launchd</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-15" id="toc-entry-32">データ移行</a></li>
<li><a class="reference internal" href="#section-16" id="toc-entry-33">今後の改善</a></li>
</ul>
</div>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-1">メールの基礎知識</a></h2>
<p>環境構築の説明に入る前に、メール関連の基礎知識を簡単に説明しておきます。</p>
<div class="section" id="mxa">
<h3><a class="toc-backref" href="#toc-entry-2">MxA</a></h3>
<p>メールシステムに関わるサブシステムとして、以下のようなものがあります。どれも(Mail|Message) * Agentという名前なので、総称してMxAと呼ばれたりします。
必ずしも、それぞれが、ひとつのプログラムに対応しているとは限らず、あるサブシステムの役割に相当するプログラムを同時に複数使うこともありますし、単一のプログラムが、複数のサブシステムの役割を担うこともあります。</p>
<div class="figure">
<img alt="メールシステム" src="https://blog.tai2.net/images/mutt_and_notmuch/MxA.png">
<p class="caption">メールに関わるサブシステムの図 <a class="reference external" href="http://dev.mutt.org/trac/wiki/MailConcept/Layout">General overview of the mail processing chain</a> から転載</p>
</div>
<ul class="simple">
<li>MTA(Message Transfer Agent): メールを転送するためのシステム。</li>
<li>MUA(Mail User Agent): いわゆるメーラー。人間がメールを読み書きするためのシステム。</li>
<li>MRA(Mail Retrieval Agent): メールを受信するためのシステム。</li>
<li>MDA(Mail Delivery Agent): メールを処理して振り分けるためのシステム。</li>
<li>MSA(Mail Submission Agent): ざっくり言うと認証機能付きのMTA。</li>
</ul>
</div>
<div class="section" id="section-2">
<h3><a class="toc-backref" href="#toc-entry-3">関連するプロトコル</a></h3>
<p>メールに関連するプロトコルとして、以下の3つがあります。</p>
<ul class="simple">
<li>SMTP: メールの転送をするための基本プロトコル。MTA間での転送や、MUA -> MTA間の転送で使われる。</li>
<li>POP3: リモートにあるメールボックスからローカルにメールをダウンロードするためのプロトコル。</li>
<li>IMAP: リモートにあるメールボックスをローカルから閲覧・操作するためのプロトコル。自宅のデスクトップと会社のノートPCなど、複数の環境からメールボックスにアクセスするときに便利。</li>
</ul>
</div>
<div class="section" id="section-3">
<h3><a class="toc-backref" href="#toc-entry-4">メールボックス</a></h3>
<p>メールボックス(メールを格納するためのデータ構造)には、いくつかの普及しているフォーマットがあります。
大きくわけて、mbox系のすべてのメールを1ファイルに格納するフォーマットと、Maildir系の1メール1ファイルで格納するフォーマットの2種類です。
1メール1ファイルで格納する形式のほうが、スクリプトで処理する場合などになにかと扱いやすく便利なので、Maildirに格納することにします。</p>
</div>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-5">筆者のメール環境</a></h2>
<p>このブログもそうですが、自分のサイトのホスティング先として、さくらインターネットの <a class="reference external" href="http://www.sakura.ne.jp/">レンタルサーバー</a> を利用しています。
プランは、スタンダードプランです。
このプランでは、無制限のメールアドレスを利用できて、メールボックスには、POP3とIMAPでアクセスできます。
また、sshでリモートホストにログインして作業することができます。</p>
<p>本題からは逸れますが、実際にリモートにログインすると、メールは、Maildir形式のメールボックスに保存されているようです。
また、sendmailもセットアップ済みで、コマンドラインからメールを送信できます(実際にメールの送信手段として利用します)。</p>
<p>やりたいことは、レンタルサーバー上のメールボックスから、定期的にPOP3でメールを受信して、閲覧できる環境を構築することです。
サーバーで利用できる容量が限られており、受信したメールはサーバー上から削除したいので、IMAPは使いません。</p>
<p>筆者は、さくらサーバー上で、用途に応じた3つのメールボックスを運用しています。
それから、パートナー企業から発行された別のサーバー上のもの(POP3)も加えて、計4つのメールボックスを日常的に使用します。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-6">動機</a></h2>
<p>筆者は、これまでいくつかのメール環境を試してきました。</p>
<p>はるか昔には、GMail一本でやっていた時期がありました。快適な環境ではありましたが、宗教上の理由により、あるときから、クラウドでのメール環境の使用をやめました。
また、仕事上、パートナー企業のメールアカウントを使う必要があるため、どちらにしろローカルでのメール受信が必要となるのですが、ローカルでひとつの環境に統一できれば、スッキリします(統一するだけなら、GMailのメールボックスにPOP3やIMAPでアクセスすれば可能ではありますが)。</p>
<p>Mac OS Xを使い始めてからしばらくは、標準のMail.appを使用していました。しかし、これはメールボックスが複数になり、メールの件数が増えてくるにつれ、次第に動作が遅くなり、使うのが苦痛になってきました。
メールボックスの切り替えに異常に時間がかかったり、突発的に固まったりするのです。</p>
<p>Mail.appの代わりになる、軽量なメールクライアントはないか探した結果、Sparrowを見付けました。なんとなく見た目がスッキリしていて良さそうだったので、しばらく使っていましたが、Gmailのフロントエンドとして使うことが前提の設計になっているためスパムフィルターがない、しばしばメールが文字化けする、開発終了してしまった、などの問題があり、他のソフトを探しはじめました。</p>
<p>また、個人的に、コマンドラインでできることは、できる限りコマンドラインで済ませたい人間なので、CLIのプログラムであることを条件としました。
メールの読み書きは、本質的にテキストのみで成立するはずだからです。</p>
</div>
<div class="section" id="section-6">
<h2><a class="toc-backref" href="#toc-entry-7">使用するプログラム</a></h2>
<p>メール送受信システムを構築するために利用するプログラムのリストは、細かいものも含めると、以下のようになります。</p>
<ul class="simple">
<li><a class="reference external" href="http://www.mutt.org/">mutt</a></li>
<li><a class="reference external" href="https://notmuchmail.org/">notmuch</a></li>
<li><a class="reference external" href="http://pyropus.ca/software/getmail/">getmail</a></li>
<li><a class="reference external" href="http://spamassassin.apache.org/index.html">spamassassin</a></li>
<li><a class="reference external" href="http://razor.sourceforge.net/">Razor2</a></li>
<li><a class="reference external" href="https://github.com/julienXX/terminal-notifier">terminal-notifier</a></li>
<li>launchd</li>
<li>ssh + sendmail</li>
</ul>
<p>ずいぶん数が多いと思われるかもしれませんが、これは、muttやnotmuchが、単機能のプログラムを組み合わせて使うという <a class="reference external" href="https://ja.wikipedia.org/wiki/UNIX%E5%93%B2%E5%AD%A6">UNIX哲学</a> に従った設計になっているためです。
ここに挙げたプログラムすべてを使うことが必須というわけではなく、気に食わないものがあれば、部分的に別のプログラムに変えることもできます。
また、ちょっとしたスクリプトを書いて、自分好みにカスタマイズすることも容易にできるのです。</p>
<p>ほとんどのプログラムは、Homebrewでパッケージ化されているので、 <code>brew install</code> でインストールできます。SpamAssassinとRazor2だけは、Homebrewにはないため、CPANからインストールします。</p>
<div class="section" id="mutt-1">
<h3><a class="toc-backref" href="#toc-entry-8">mutt</a></h3>
<div class="figure">
<img alt="muttのインデックス画面" src="https://blog.tai2.net/images/mutt_and_notmuch/mutt.png">
<p class="caption">muttのインデックス画面</p>
</div>
<p>コマンドラインで使えるスクリーン指向のMUAです。
Linuxカーネル開発者の中にもmuttを使用している人は <a class="reference external" href="http://cpplover.blogspot.jp/2013/06/linux.html">多いようです。</a>
軽量で、マクロによってある程度柔軟に拡張できます。
また、サイドバー表示など本体への拡張機能もいろいろ開発されていますが、パッチを当てて自分でビルドしなければならないのは、すこしめんどうです。
むかしながらのフリーソフトウェアなのでしょうがないですね。
mutt自体は、あくまでMUAであり、ローカルのメールボックスからメールを読み込んで表示したり、メールを書くためだけのソフトウェアなのですが、
いちおうオマケ機能として、POP3やIMAPでメールを受信したり、SMTPでメールを送信するための機能も付いているので、これ単体でもクライアント環境として成立します。</p>
<p>muttには、日本語化パッチがありますが、最新のバージョンに追随していなかったりするため、Homebrewにあるバージョンをそのまま使っています。メール一覧で、件名が長い場合などにときどき表示崩れが起きたりすることがありますが、ウィンドウを十分に長くすれば問題は起きないので、筆者はあまり気にしていません。</p>
<p>実のところ、キーバインディングに統一性がない部分など、UI的にあまり良いとは思っていないのですが、OS X環境で他に手軽に使えるものが他になかったため、これを使っています。</p>
</div>
<div class="section" id="notmuch-1">
<h3><a class="toc-backref" href="#toc-entry-9">notmuch</a></h3>
<p>Maildirに格納されているメールのインデックスを作成し、高速に検索するためのプログラムです。
似たようなプログラムに <a class="reference external" href="http://www.djcbsoftware.nl/code/mu/">mu</a> というのもあって、これも良さそうだったのですが、残念ながら <a class="reference external" href="https://github.com/djcb/mu/issues/544">日本語の扱いに問題があった</a> ため、使えませんでした。
notmuchはCJK環境でも問題なく使えます。</p>
<p>notmuchは、ただのメール検索プログラムに留まりません。
メールへのタグ付けと高速なタグ検索機能、そしてマッチしたメールのパス一覧を取得する機能を備えています。
これにより、メール処理の中心となるミドルウェア、あるいは糊付けプログラムとして機能するポテンシャルを秘めているのです。</p>
<p>例えば、新着メールにスパムチェックをかけたいとします(あくまで説明のための例です)。</p>
<ol class="arabic simple">
<li>notmuchでは、新着メールに付与するタグを指定できるので、spam-check-requiredというタグをつけることにします。</li>
<li>次に、spam-check-requiredタグのついたメールを検索し、それらのパスを取得してスパムチェックをかけ、スコアを記録するためのヘッダを追加します。</li>
<li>再度、spam-check-requiredタグのついたメールを検索し、spam-check-requiredタグを除去します。</li>
</ol>
<p>このようにして、新着メールに一度だけ処理をかけることができます。</p>
<p>また、notmuchにはemacs用のフロントエンドも付属しています。これは、notmuchのタグ機能をフルに活かしたMUA環境になっているようですが、筆者はvim使いなので残念ながら使えません。emacs使いの方は、これを利用するのも良いかもしれません。</p>
</div>
<div class="section" id="getmail-1">
<h3><a class="toc-backref" href="#toc-entry-10">getmail</a></h3>
<p>POP3やIMAPで、メールを受信するためのプログラム(MRA)です。
muttのメール受信機能では、OS Xのキーチェーンにアクセスすることはできないため、設定ファイルにパスワードを直接記述しなければなりません。
これはあまり好ましくないため、メールの受信はgetmailにさせます。</p>
</div>
<div class="section" id="spamassassin-1">
<h3><a class="toc-backref" href="#toc-entry-11">SpamAssassin</a></h3>
<p>スパムフィルタです。パイプでメールファイルを渡すと、スパム判定のスコアを示したX-Spam-Statusというヘッダを挿入してくれます。
このプログラム自体、単純ベイズによるスパム判定機能を搭載していますが、それ以外にも、実にさまざまなスパムフィルタプログラムを統合するファサード的なプログラムとして機能します。複雑なプログラムで、非常に動作が重いです。</p>
</div>
<div class="section" id="razor2-1">
<h3><a class="toc-backref" href="#toc-entry-12">Razor2</a></h3>
<p>協調フィルタリングによるスパムフィルタプログラムです。SpamAssassinと連携させて使います。
類似のプログラムとして、PyzorやDCCなどがあります。これら3つすべてを同時にSpamAssassinと組み合わせて使うことも可能です。
どんどん重くなりそうな気がするので、筆者はひとつに留めています。</p>
</div>
<div class="section" id="terminal-notifier-1">
<h3><a class="toc-backref" href="#toc-entry-13">terminal-notifier</a></h3>
<p>好み次第ですが、新着メール受信時にデスクトップに通知が届くと便利です。
terminal-notifierを使えば、コマンドラインからデスクトップに通知を送ることができます。
副作用として、通知センターから、フォルダをまたがった新着メール一覧を確認できるようになります。
<sup id="sf-mutt-and-notmuch-1-back"><a href="#sf-mutt-and-notmuch-1" class="simple-footnote" title="muttでフォルダをまたがって新着を一覧する方法がないものか考えましたが、いまのところ良い術を思い付いていません。">1</a></sup>
結果として、新着メールを確認するために、常にmuttを立ち上げておく必要がなくなります。</p>
<p>Homebrewからインストールできるバージョンンでも実用上問題ありませんが、アイコンを変更できない点が不満だったため、
筆者は、アイコンを差し替えて自分でビルドしたバージョンを使用しています。</p>
</div>
<div class="section" id="launchd">
<h3><a class="toc-backref" href="#toc-entry-14">launchd</a></h3>
<p>launchdは、OS X組込のジョブ管理プログラムです。cronの代替として使えます。cron自体はOS Xでも使えますが、バックグラウンドでのキーチェーンへのアクセスがうまくいかなかったため<sup id="sf-mutt-and-notmuch-2-back"><a href="#sf-mutt-and-notmuch-2" class="simple-footnote" title="深くは追ってません">2</a></sup>、こちらにしました。
リモートメールボックスのポーリングをするために使います。</p>
</div>
<div class="section" id="ssh-sendmail">
<h3><a class="toc-backref" href="#toc-entry-15">ssh + sendmail</a></h3>
<p>muttには、実は、オマケ機能としてSMTPでメールを送信する機能もついているので、muttからレンタルサーバーへSMTPを通じてメールを送信することもできます。
しかし、残念ながら、OS Xのキーチェーンと連携する機能はないため、パスワードを直接設定ファイルに記述しなければなりません。</p>
<p>sshを利用すれば、リモートサーバー上のコマンドをローカルにあるかのように実行することができるため、これでレンタルサーバー上のsendmailコマンドを実行します。
こうすれば、(SSHの公開鍵設定がしてあれば)パスワードは不要になります。ちなみに、sendmailは、よく使われているメジャーなMTAのひとつです。</p>
<p>ローカルでsendmailコマンドが使えるように設定するという選択肢もありましたが、外向けのポート25は <a class="reference external" href="https://ja.wikipedia.org/wiki/Outbound_Port_25_Blocking">ISPで制限されている場合が多い</a> という問題があることや、ほとんど設定をせずに済む一番楽な方法がssh + sendmailだったことから、こうしました。</p>
</div>
</div>
<div class="section" id="section-7">
<h2><a class="toc-backref" href="#toc-entry-16">メール受信のシーケンス</a></h2>
<p>スパムと判定されたメールは、spamフォルダに振り分けます。
ただし、false positiveの可能性があるため、本来のフォルダに戻せるようにしておく必要があります。
そのため、タグに受信メールボックスを記録しておきます。
false negativeの場合は、マニュアルでspamフォルダに移動します(それ用のmuttマクロで行います)。</p>
<div class="section" id="pop3">
<h3><a class="toc-backref" href="#toc-entry-17">1. POP3でメール受信</a></h3>
<p>まずは、getmailでメールを受信します。</p>
</div>
<div class="section" id="section-8">
<h3><a class="toc-backref" href="#toc-entry-18">2. スパム判定</a></h3>
<p>getmailには、受信したメールを外部プログラムに渡してフィルターをかける機能があるので、SpamAssassinに渡します。
データの受け渡しはパイプによって標準入出力で行われます。
これにより、メールにX-Spam-Status等のヘッダが挿入されます。</p>
<p>筆者の環境では、1通あたり3秒くらいかかります。</p>
</div>
<div class="section" id="section-9">
<h3><a class="toc-backref" href="#toc-entry-19">3. 一時フォルダにメールファイルを保存</a></h3>
<p>受信したメールを直接メールボックスに入れてしまうと、この次のスパム報告処理に時間がかかるため、spamフォルダに振り分けられることになるメールが、しばらくmutt上に表示されてしまいます。
これを避けるため、メールファイルは、いったんmuttから見えない一時フォルダに保存しておきます。</p>
</div>
<div class="section" id="section-10">
<h3><a class="toc-backref" href="#toc-entry-20">4. スパム報告&学習</a></h3>
<p>スパム判定の結果に基いて、Razor2のサーバーにレポートを送り、ベイズフィルターの分類器に学習をさせます。
スパムの場合は、X-Spam-StatusヘッダにYesという値が入るため、これをgrepで判定します。
結果に応じて、 <code>spamassassin</code> コマンドにメールファイルを与えて、処理を行います。</p>
<p>筆者の環境では、1通毎に2秒ほどかかります。</p>
</div>
<div class="section" id="section-11">
<h3><a class="toc-backref" href="#toc-entry-21">5. メールファイルをメールボックスに移動</a></h3>
<p>この後は、もう処理時間のかかる工程はないのと、インデックスを作成するため、メールボックスにメールを移します。</p>
</div>
<div class="section" id="section-12">
<h3><a class="toc-backref" href="#toc-entry-22">6. 検索インデックス作成</a></h3>
<p>新規受信したメールのインデックスを作成します。新規のメールは、notmuchが自動的に認識します。
また、この後の工程のために、新規のメールには、自動的に <code>new</code> タグを付与するよう設定しておきます。</p>
</div>
<div class="section" id="section-13">
<h3><a class="toc-backref" href="#toc-entry-23">7. 配信先フォルダタグ付与</a></h3>
<p>スパム判定でfalse positiveが出てしまった場合に、後で元のメールボックスに戻せるように、 <code>delivered-to-メールボックス名</code> というタグを付与しておきます。spamフォルダから元のフォルダに戻すときには、タグを見て移動先を判断します。</p>
<p>処理対象のメールは、 <code>new</code> タグ付与されたすべてのメールです。</p>
</div>
<div class="section" id="spam">
<h3><a class="toc-backref" href="#toc-entry-24">8. spamフォルダへ移動</a></h3>
<p>X-Spam-Status: Yesの含まれるメールをspamフォルダに移動します。</p>
<p>処理対象のメールは、 <code>new</code> タグ付与されたすべてのメールです。</p>
</div>
<div class="section" id="new">
<h3><a class="toc-backref" href="#toc-entry-25">9. newタグを除去</a></h3>
<p>最後に、<code>new</code> タグのついたすべてのメールから、<code>new</code> タグを除去します。
これでメールの受信処理がすべて完了となります。</p>
</div>
</div>
<div class="section" id="section-14">
<h2><a class="toc-backref" href="#toc-entry-26">各種の設定</a></h2>
<p>すでに筆者の環境では、4つのメールアドレスを使い分けています。
メールアドレスは、仮に、<a class="reference external" href="mailto:ryu@example.com">ryu@example.com</a>, <a class="reference external" href="mailto:ken@example.com">ken@example.com</a>, <a class="reference external" href="mailto:guile@example.com">guile@example.com</a>, <a class="reference external" href="mailto:zangief@example.com">zangief@example.com</a> としておきます。</p>
<p>メールアドレス毎に、ryu,ken,guile,zangiefという4つのフォルダと、スパムメール用にspamというフォルダを作ります。</p>
<div class="section" id="mutt-2">
<h3><a class="toc-backref" href="#toc-entry-27">mutt</a></h3>
<p>詳しくは、muttの <a class="reference external" href="http://www.mutt.org/doc/devel/manual.html">マニュアル</a> を参照してください。</p>
<p>~/.mutt/muttrc</p>
<div class="highlight"><pre><span></span>set realname = "Taiju Muto"
set use_from = yes
set signature = ~/.mutt/signature
set sleep_time = 0 # フォルダの切り替えが速くなります。
unset record # 送信メールの保存は、BCCで自分宛てに送るのでOFFにしてます。
set postponed = +postponed
set sort_aux = reverse-date
set assumed_charset="iso-2022-jp:euc-jp:shift_jis:utf-8" # 文字コード指定がなかったりする場合のために優先順位を設定しておきます
set index_format = "%4C %Z %{%b %d} %-15.15L %H%s(%?l?%4l&%4c?)" # デフォルトに加えて、スパムのスコアが表示されるようにしてます。
set sendmail = "ssh tai2@tai2.net sendmail -oem -oi" # メールの送信はレンタルサーバー上のsendmailで。
set header_cache = ~/.mutt/cache/ # ヘッダーキャッシュをしておくとフォルダの切り替えがだいぶ良くなります。
spam "X-Spam-Status: Yes, score=([^ \t]+)" "(Spam %1)" # スパムヘッダの定義
# 色付けは、muttのソースコードに同梱されているcolors.defaultを使っています。
source ~/.mutt/colors.default
# HTMLメールを自動で見易くするための設定です。.mailcapも参照。
auto_view text/html
alternative_order text/plain text/enriched text/html
# メールボックス設定
# mailboxesに追加しておくと、対象フォルダをポーリングして、muttのステータス行で通知してくれます
set folder = ~/Dropbox/Mail/
set spoolfile = +ryu
mailboxes +ryu +ken +guile +zangief
folder-hook +ryu source ~/.mutt/rc-ryu
folder-hook +ken source ~/.mutt/rc-ken
folder-hook +guile source ~/.mutt/rc-guile
folder-hook +zangief source ~/.mutt/rc-zangief
folder-hook +spam source ~/.mutt/rc-spam
# マクロのための設定
set pipe_split = yes
set my_wait_key=$wait_key
# スパム報告用のマクロ。スパム報告してから、spamフォルダにメールを移動します。
macro index S "\
<enter-command>set wait_key=no<enter>\
<pipe-message>report_spam.py $folder<enter><enter-command>\
set wait_key=$my_wait_key<enter>\
<first-entry>\
" "report a spam message"
# スパム解除用のマクロ。false-positiveが起きたときに使います。
# スパム否定報告をしてから、本来の受信メールボックスに戻します。
macro index X "\
<enter-command>set wait_key=no<enter>\
<pipe-message>revoke_spam.py $folder<enter><enter-command>\
set wait_key=$my_wait_key<enter>\
<first-entry>\
" "revoke a spam message"
# notmuchで条件指定して検索した一覧を表示します。
macro index <F8> \
"<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
<shell-escape>notmuch-mutt -r --prompt search<enter>\
<change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
<enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
"notmuch: search mail"
# カーソル上のメールの関連したスレッドを表示します。
macro index <F9> \
"<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
<pipe-message>notmuch-mutt -r thread<enter>\
<change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
<enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
"notmuch: reconstruct thread"
</pre></div>
<p>マクロで利用しているreport_spam.pyとrevoke_spam.pyは、 <a class="reference external" href="https://gist.github.com/tai2/d186222bc9755c943e6f">gist</a> に置いておきます。</p>
<p>~/.mutt/rc-ryu</p>
<div class="highlight"><pre><span></span># スレッド表示にします。
set sort = threads
# メールボックスごとにfromを変えます。
set from="ryu@example.com"
# 送信メールの保存用途と、ツリーで完全な会話が見られるようにBccとして自分を入れておきます。
my_hdr Bcc: ryu@example.com
</pre></div>
<p>~/.mutt/rc-spam</p>
<div class="highlight"><pre><span></span># スパムメールについてはスレッド表示せず、単純に日付の降順で表示します。
set sort = reverse-date
</pre></div>
<p>~/.mailcap</p>
<div class="highlight"><pre><span></span># HTMLメールはw3mで整形して見易く表示されるようにします。
# see. http://jasonwryan.com/blog/2012/05/12/mutt/
text/html; w3m -I %{charset} -T text/html; copiousoutput;
</pre></div>
</div>
<div class="section" id="notmuch-2">
<h3><a class="toc-backref" href="#toc-entry-28">notmuch</a></h3>
<p>ほぼデフォルトで生成される設定で使ってますが、新着の記事に <code>new</code> というタグを付与するようにしてます。
それから、草稿用のメールボックスと.DS_Storeはインデックス対象から除外するようにしています。</p>
<p>~/.notmuch-configから抜粋。</p>
<div class="highlight"><pre><span></span>[new]
tags=unread;inbox;new;
ignore=postponed;.DS_Store;
</pre></div>
<p><a class="reference external" href="https://notmuchmail.org/initial_tagging/">Approaches to initial tagging of messages</a> という記事を参考にしました。</p>
<p>notmuchでは、インデックスの前後にフックを仕込めるようになっているため、それを利用しています。</p>
<p>インデックス前処理</p>
<div class="highlight"><pre><span></span><span class="ch">#!/bin/sh</span>
<span class="nv">status</span><span class="o">=</span><span class="sb">`</span>ifconfig<span class="w"> </span>en0<span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span>-n<span class="w"> </span><span class="s1">'s/^.*status: \(.*\)/\1/p'</span><span class="sb">`</span>
<span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="nv">$status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'active'</span><span class="w"> </span><span class="o">]</span><span class="p">;</span>
<span class="k">then</span>
<span class="w"> </span><span class="c1"># getmailでメール受信</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span>folder<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nv">$MAIL_FOLDERS</span>
<span class="w"> </span><span class="k">do</span>
<span class="w"> </span><span class="nv">getmail_opt</span><span class="o">=</span><span class="s2">"</span><span class="nv">$getmail_opt</span><span class="s2"> --rcfile rc-</span><span class="nv">$folder</span><span class="s2">"</span>
<span class="w"> </span><span class="k">done</span>
<span class="w"> </span>/usr/local/bin/getmail<span class="w"> </span><span class="nv">$getmail_opt</span>
<span class="w"> </span><span class="c1"># スパム報告</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span>new_file<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="sb">`</span>find<span class="w"> </span>~/.getmail/tmp<span class="w"> </span>-type<span class="w"> </span>f<span class="sb">`</span>
<span class="w"> </span><span class="k">do</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span>grep<span class="w"> </span>--max-count<span class="o">=</span><span class="m">1</span><span class="w"> </span><span class="s1">'^X-Spam-Status: Yes'</span><span class="w"> </span><span class="nv">$new_file</span><span class="w"> </span>><span class="w"> </span>/dev/null
<span class="w"> </span><span class="k">then</span>
<span class="w"> </span>/usr/local/bin/spamassassin<span class="w"> </span>--report<span class="w"> </span><span class="nv">$new_file</span>
<span class="w"> </span><span class="k">else</span>
<span class="w"> </span>/usr/local/bin/spamassassin<span class="w"> </span>--revoke<span class="w"> </span><span class="nv">$new_file</span>
<span class="w"> </span><span class="k">fi</span>
<span class="w"> </span><span class="k">done</span>
<span class="w"> </span><span class="c1"># 一時フォルダからメールボックスに移動</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span>dir<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nv">$MAIL_FOLDERS</span>
<span class="w"> </span><span class="k">do</span>
<span class="w"> </span><span class="nv">src</span><span class="o">=</span>~/.getmail/tmp/<span class="nv">$dir</span>/new/
<span class="w"> </span><span class="k">for</span><span class="w"> </span>file<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="sb">`</span>ls<span class="w"> </span><span class="nv">$src</span><span class="sb">`</span>
<span class="w"> </span><span class="k">do</span>
<span class="w"> </span>mv<span class="w"> </span><span class="nv">$src</span>/<span class="nv">$file</span><span class="w"> </span>~/Dropbox/Mail/<span class="nv">$dir</span>/new/
<span class="w"> </span><span class="k">done</span>
<span class="w"> </span><span class="k">done</span>
<span class="k">fi</span>
</pre></div>
<p>インデックス後処理</p>
<div class="highlight"><pre><span></span><span class="ch">#!/bin/sh</span>
<span class="c1"># メールボックス毎のタグを付与</span>
<span class="k">for</span><span class="w"> </span>folder<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nv">$MAIL_FOLDERS</span>
<span class="k">do</span>
<span class="w"> </span>/usr/local/bin/notmuch<span class="w"> </span>tag<span class="w"> </span>+delivered-to-<span class="nv">$folder</span><span class="w"> </span>--<span class="w"> </span>tag:new<span class="w"> </span>folder:<span class="nv">$folder</span>
<span class="k">done</span>
<span class="c1"># スパムメールをspamフォルダに移動</span>
<span class="k">for</span><span class="w"> </span>new_file<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="sb">`</span>/usr/local/bin/notmuch<span class="w"> </span>search<span class="w"> </span>--output<span class="o">=</span>files<span class="w"> </span>tag:new<span class="sb">`</span>
<span class="k">do</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span>grep<span class="w"> </span>--max-count<span class="o">=</span><span class="m">1</span><span class="w"> </span><span class="s1">'^X-Spam-Status: Yes'</span><span class="w"> </span><span class="nv">$new_file</span><span class="w"> </span>><span class="w"> </span>/dev/null
<span class="w"> </span><span class="k">then</span>
<span class="w"> </span><span class="nv">p1</span><span class="o">=</span><span class="sb">`</span>dirname<span class="w"> </span><span class="nv">$new_file</span><span class="sb">`</span>
<span class="w"> </span><span class="nv">p2</span><span class="o">=</span><span class="sb">`</span>dirname<span class="w"> </span><span class="nv">$p1</span><span class="sb">`</span>
<span class="w"> </span><span class="nv">dest_dir</span><span class="o">=</span><span class="sb">`</span>dirname<span class="w"> </span><span class="nv">$p2</span><span class="sb">`</span>/spam/new
<span class="w"> </span>mv<span class="w"> </span><span class="nv">$new_file</span><span class="w"> </span><span class="nv">$dest_dir</span>
<span class="w"> </span><span class="k">else</span>
<span class="w"> </span>/Users/tai2/bin/notify-message.py<span class="w"> </span><span class="nv">$new_file</span>
<span class="w"> </span><span class="k">fi</span>
<span class="k">done</span>
<span class="c1"># newタグを除去</span>
/usr/local/bin/notmuch<span class="w"> </span>tag<span class="w"> </span>-new<span class="w"> </span>--<span class="w"> </span>tag:new
</pre></div>
<p><a class="reference external" href="https://gist.github.com/tai2/16b8acf511a92638da46">notify-message.py</a> は、メールをパースして、terminal-notifierに送信者と件名を渡して起動するためのスクリプトです。</p>
</div>
<div class="section" id="getmail-2">
<h3><a class="toc-backref" href="#toc-entry-29">getmail</a></h3>
<p>ほとんど同内容の設定ファイルが4つあるのは、DRY原則に反していてアレですが、筆者の運用環境ではメールアカウントの個数が変動することはほとんどないことなので、許容しています。</p>
<p>~/.getmail/rc-ryu</p>
<div class="highlight"><pre><span></span>[retriever]
type = SimplePOP3SSLRetriever
server = pop3.example.com
username = ryu@example.com
# SpamAssassinでのスパム判定を行います。
[filter]
type = Filter_external
path = /usr/local/bin/spamassassin
# 一時的な保存先として、~/.getmail/tmp/下に置いておくようにしています。
[destination]
type = Maildir
path = ~/.getmail/tmp/ryu/
# リモートのメールは取得後すぐ消す設定にしてます。
# SpamAssassinでのエラーの可能性を考えると、数日間残す設定のほうが良いのかもしれません。
[options]
delete = True
message_log = ~/.getmail/log
</pre></div>
<p>また、securityコマンドを使用して、キーチェーンにパスワードを設定しておく必要があります。</p>
<div class="highlight"><pre><span></span>security -i -p 'enter password' add-internet-password -a 'ryu@example.com' -s 'pop3.example.com' -r 'pop3'
</pre></div>
</div>
<div class="section" id="spamassassin-razor2">
<h3><a class="toc-backref" href="#toc-entry-30">SpamAssassin & Razor2</a></h3>
<p>デフォルトだと、SpamAssassinは、positive判定をした場合にメールの本文を改変してしまいます。
これはgetmailにとって <a class="reference external" href="http://comments.gmane.org/gmane.mail.getmail.user/1204">よろしくない</a> 挙動のため、report_safeを0に設定して、ヘッダを挿入するだけにします。また、SUBJ_ILLEGAL_CHARSで8bitヘッダを許容するようにします。</p>
<p>また、 /etc/mail/spamassassin/local.cf を編集してRazor2を使用するようにします。</p>
<p>SpamAssassinのインストールと設定は、ソースコードに含まれるINSTALLや <a class="reference external" href="http://wiki.apache.org/spamassassin/SingleUserUnixInstall">wikiのインストールガイド</a> などを熟読しつつ設定します。 <a class="reference external" href="https://wiki.apache.org/spamassassin/RazorSiteWide">Razor2の設定ガイド</a> もあります。ちょっとめんどうですが、マニュアルをしっかり読めばできるはずです。-Dオプションをつけるとデバッグログが出力されるので、適宜挙動を確認しながら進めていくと良いと思います。</p>
<p>スパムの閾値は、デフォルトの5のまま運用していますが、ときどきスパムが4.xとスコアリングされてfalse negativeが生じます。頻度は低いので問題視していません。</p>
</div>
<div class="section" id="launchd-1">
<h3><a class="toc-backref" href="#toc-entry-31">launchd</a></h3>
<p>launchdを利用して、notmuch newコマンドを定期的に実行します。メールの受信や、その他の前後処理は、notmuchのフック機能を介して実行されます。</p>
<p>launchdからコマンドを実行するときには、bash_profileで設定している項目は反映されないため、注意が必要です。
必要な環境変数は、plistの中で定義しましょう。</p>
<p>notmuchの全文検索エンジンであるxapianに日本語を正しく処理させるために、XAPIAN_CJK_NGRAMを設定する必要があります。
これをしないと日本語の検索ができないので注意しましょう。</p>
<p>MAIL_SOUNDとMAIL_FOLDERSは、自作スクリプトのための環境変数です。</p>
<div class="highlight"><pre><span></span><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="cp"><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"></span>
<span class="nt"><plist</span><span class="w"> </span><span class="na">version=</span><span class="s">"1.0"</span><span class="nt">></span>
<span class="nt"><dict></span>
<span class="w"> </span><span class="nt"><key></span>EnvironmentVariables<span class="nt"></key></span>
<span class="w"> </span><span class="nt"><dict></span>
<span class="w"> </span><span class="nt"><key></span>XAPIAN_CJK_NGRAM<span class="nt"></key></span>
<span class="w"> </span><span class="nt"><string></span>1<span class="nt"></string></span>
<span class="w"> </span><span class="nt"><key></span>TERMINAL_NOTIFIER<span class="nt"></key></span>
<span class="w"> </span><span class="nt"><string></span>/Users/tai2/Applications/terminal-notifier.app/Contents/MacOS/terminal-notifier<span class="nt"></string></span>
<span class="w"> </span><span class="nt"><key></span>MAIL_SOUND<span class="nt"></key></span>
<span class="w"> </span><span class="nt"><string></span>Hero<span class="nt"></string></span>
<span class="w"> </span><span class="nt"><key></span>MAIL_FOLDERS<span class="nt"></key></span>
<span class="w"> </span><span class="nt"><string></span>ryu<span class="w"> </span>ken<span class="w"> </span>guile<span class="w"> </span>zangief<span class="nt"></string></span>
<span class="w"> </span><span class="nt"></dict></span>
<span class="w"> </span><span class="nt"><key></span>Label<span class="nt"></key></span>
<span class="w"> </span><span class="nt"><string></span>net.tai2.notmuch<span class="nt"></string></span>
<span class="w"> </span><span class="nt"><key></span>ProgramArguments<span class="nt"></key></span>
<span class="w"> </span><span class="nt"><array></span>
<span class="w"> </span><span class="nt"><string></span>/usr/local/bin/notmuch<span class="nt"></string></span>
<span class="w"> </span><span class="nt"><string></span>new<span class="nt"></string></span>
<span class="w"> </span><span class="nt"></array></span>
<span class="w"> </span><span class="nt"><key></span>StartInterval<span class="nt"></key></span>
<span class="w"> </span><span class="nt"><integer></span>180<span class="nt"></integer></span>
<span class="nt"></dict></span>
<span class="nt"></plist></span>
</pre></div>
</div>
</div>
<div class="section" id="section-15">
<h2><a class="toc-backref" href="#toc-entry-32">データ移行</a></h2>
<p>Mail.appからエクスポートしたmboxファイルがあったため、Maildir形式に変更する必要がありました。
検索すると、mb2mdというような名前でいくつかのプログラムが出てくるのですが、なぜかどれも手元では動作しません。
しかたがないので、 <a class="reference external" href="https://gist.github.com/tai2/0d4e8ea30dc7a97850bf">Pythonでスクリプトを書いて</a> データ移行しました。</p>
<p>Sparrowは、データをエクスポートするための機能を備えていません。
ドラッグ&ドロップでファイル化できるので、手作業ですべて選択してファイル化し、Maildirにつっこみました。<sup id="sf-mutt-and-notmuch-3-back"><a href="#sf-mutt-and-notmuch-3" class="simple-footnote" title="実際には、ファイル名の規則が他と違ってしまい、なにか気持ち悪いので、Maildirの命名規則に合わせてリネームするスクリプトを書きました。そのままでも実用上は問題ないと思います。">3</a></sup></p>
</div>
<div class="section" id="section-16">
<h2><a class="toc-backref" href="#toc-entry-33">今後の改善</a></h2>
<p>muttの操作性を改善するためにパッチを当ててみたり、あるいは別のMUAも検討してみたいと考えています。その場合にも、UNIX哲学のメリットを活かして、MUAのみ差し替えて残りはなるべくそのままの構成でできればなと、ぼんやり考えています。</p>
<p>また、SpamAssassinが重いので、代替のスパムフィルターに変更することも検討しています。いまのところの候補は、 <a class="reference external" href="http://getpopfile.org/">POPFile</a> です。</p>
<p>当面は、このまま運用して様子を見ます。</p>
</div>
<ol class="simple-footnotes"><li id="sf-mutt-and-notmuch-1">muttでフォルダをまたがって新着を一覧する方法がないものか考えましたが、いまのところ良い術を思い付いていません。 <a href="#sf-mutt-and-notmuch-1-back" class="simple-footnote-back">↩</a></li><li id="sf-mutt-and-notmuch-2">深くは追ってません <a href="#sf-mutt-and-notmuch-2-back" class="simple-footnote-back">↩</a></li><li id="sf-mutt-and-notmuch-3">実際には、ファイル名の規則が他と違ってしまい、なにか気持ち悪いので、Maildirの命名規則に合わせてリネームするスクリプトを書きました。そのままでも実用上は問題ないと思います。 <a href="#sf-mutt-and-notmuch-3-back" class="simple-footnote-back">↩</a></li></ol>デバッグフィールドガイド(A Field Guide to Debugging 翻訳)2016-01-05T00:00:00+09:002016-01-05T00:00:00+09:00tai2tag:blog.tai2.net,2016-01-05:/a-field-guide-to-debugging.html<p class="first last">p5.jsのドキュメントに含まれる 初心者向けにデバッグの心得を解説したドキュメント の翻訳です。書いてあることはプログラミング一般に通じるもので、初心者にぜひとも実践して欲しいポイントが、かわいらしいイラストを添えてまとめられています。</p>
<p>p5.jsのドキュメントに含まれる <a class="reference external" href="http://p5js.org/tutorials/debugging.html">初心者向けにデバッグの心得を解説したドキュメント</a> の翻訳です。 <a class="reference external" href="http://p5js.org/">p5.js</a> は、 <a class="reference external" href="https://processing.org/">Processing</a> というグラフィックス描画環境の書き味そのままに、JavaScriptで使用できるグラフィクス描画ライブラリです。書いてあることはプログラミング一般に通じるもので、初心者にぜひとも実践して欲しいポイントが、かわいらしいイラストを添えてまとめられています。</p>
<p>ライセンスは、CC BY-NC-SA 4.0。</p>
<hr class="docutils">
<blockquote>
このチュートリアルは、2015年5月、カーネギーメロン大学の <a class="reference external" href="http://studioforcreativeinquiry.org/">The Frank-Ratchye STUDIO for Creative Inquiry</a> にて催された p5.js コントリビューターカンファレンスにおいて、教育ワーキンググループによって作成された。このチュートリアルには、 <a class="reference external" href="http://huah.net/jason/">Jason Alderman</a> 、 <a class="reference external" href="http://tegabrain.com/">Tega Brain</a> 、 <a class="reference external" href="http://taeyoonchoi.com/">Taeyoon Choi</a> および <a class="reference external" href="http://luisaph.com/">Luisa Pereira</a> が貢献した。</blockquote>
<div class="figure">
<img alt="デバッグフィールドガイド" src="https://blog.tai2.net/images/a-field-guide-to-debugging/0-0.jpg">
</div>
<p>これは、みんなのためのデバッグ向けフィールドガイドだ。コードを書きはじめたばかりの人にとっても、長年コーディングをしてきた人にとっても、このガイドは、問題を解くという謎に満ちたプロセスを解剖する。</p>
<div class="section" id="section-1">
<h2>0.デバッグは創造的な行為</h2>
<p>どんなレベルであれ、プログラマーというものはバグに遭遇するし、実際にアプリケーションをプログラミングするよりも、多くの時間をデバッグに使うこともよくあることだ。デバッグには、たくさんの時間を消費することになるはずだから、バグを特定し、取り組むための、良い戦略を身につけることが重要だ。デバッグ技法を学ぶのは、p5.jsでのプログラミングのしかたを学ぶのと同様たいせつなことだ。</p>
<p>バグというのは、システムがやっているとキミが思っていることと、実際にシステムがやっていることとの間にあるギャップなんだ。 <a class="reference external" href="https://vimeo.com/channels/debugging">Clay Shirkyがうまいこと言ってくれたんだけど、</a> バグとは「コードにある技術的な問題にとどまらず、コード内でなにが起きているかについて、あなたが頭の中で思い描いていることにも問題がある瞬間」だ。</p>
<div class="figure">
<img alt="バグとはなにか" src="https://blog.tai2.net/images/a-field-guide-to-debugging/0-1.jpg">
<p class="caption">「あなたはネズミを円に動かしたと思っている」「しかし、実際には、コンピューターはネズミを円から離す命令を与えてしまっている」</p>
</div>
<p>キミは、あることをコンピューターにさせようとしているつもりなんだけど、コンピューターはなにか別のことをしてしまう。クラッシュするかもしれないし、エラーを投げるかもしれない。ギャップをなくすために、キミは調査をしなくちゃならない。</p>
<p>プロジェクトに取り組んでいるとき、キミは、いくつもの異なる役割りを演じるかもしれない。プログラムを設計し、計画しているときは設計者だし、開発しているときはエンジニアだ。そして、探検家になって、問題と誤りを探し、プログラムが動く必要のあるあらゆるシチュエーションでテストをすることになるだろう。キミは、プログラムが、いかにも壊れそうなところを探そうとする。最後に、デバッグするとき、キミは探偵だ。どのようにして、なんで、それが壊れるのか、把握するよう勤めるんだ。</p>
<img alt="プログラマーの役割 ー 設計者" src="https://blog.tai2.net/images/a-field-guide-to-debugging/0-3.png">
<img alt="プログラマーの役割 ー 開発者" src="https://blog.tai2.net/images/a-field-guide-to-debugging/0-4.png">
<img alt="プログラマーの役割 ー 探検家" src="https://blog.tai2.net/images/a-field-guide-to-debugging/0-5.png">
<img alt="プログラマーの役割 ー 探偵" src="https://blog.tai2.net/images/a-field-guide-to-debugging/0-6.png">
<p>じゃあ、どうすれば、良い探偵になって、プログラムをデバッグできるだろうか? 以下の10のステップが、コード名探偵になるための助けになる。</p>
</div>
<div class="section" id="section-2">
<h2>1.視点を変える</h2>
<p>取り乱さないで。</p>
<p>どう解決したものかわからないバグに遭遇したら、立ち止まって、手を止めて、深く息を吸おう。席を立ち、飼い犬に声をかけて散歩しよう。あるいは、時間が遅すぎるなら、寝てしまおう。フラストレーションが溜まっていたり、疲れていたり、ナーバスになっているときは、学んだり、問題を解いたりするのに適した精神状態ではないんだ。</p>
<p>誤りを見つけるには、視点を変えて、探偵になる必要があるだろう。目標は、なぜプログラムがすべきことをしないのかというよりは、むしろプログラムがなにをしているのかを理解することだ。コンピューターがなにをやっているのか、見えるようにさせる必要がある。</p>
<p>手掛かりは、変数の値と、プログラムの流れの中にある。</p>
<div class="figure">
<img alt="視点を変える" src="https://blog.tai2.net/images/a-field-guide-to-debugging/1-0.jpg">
</div>
</div>
<div class="section" id="section-3">
<h2>2.問題を観察する</h2>
<p>問題をだれかに一歩一歩解説しよう、その人自身はプログラムができなくてもかまわない。まわりにだれもいなければ、Eメールの草稿を作って、その中で、キミがなにをしたのか説明し、問題を分解しよう。</p>
<div class="figure">
<img alt="メールを書いてみる" src="https://blog.tai2.net/images/a-field-guide-to-debugging/2-1.png">
<p class="caption">「サムへ ボクの関数は、2度走る"はず"なんだ、だけど、3度走ってしまう!それは、i=0から2までのループで…」</p>
</div>
<p>このメールは、実際には送る必要がないかもしれない。なぜなら、書くという行為は、しばしば、次にすべきことを見つけさせ、識別させてくれるからだ。プログラマーの中には、問題をラバーダックのような親しみ深い無機物に説明することで知られている人さえいる。</p>
<div class="figure">
<img alt="ラバーダックに問題を説明する" src="https://blog.tai2.net/images/a-field-guide-to-debugging/2-2.png">
<p class="caption">「でね、関数があって、ボタンをクリックすると走る"はず"なんだ。なのに…」</p>
</div>
<p>また、コードにコメントを追加する良いタイミングでもある。各関数が正確になにをしているのかがわかるようにするんだ。コード(もしくはその一部分)をプリントアウトして、行ごとに精査し、変数のパスを追跡して、メモ書きするコーダーもいる。</p>
<div class="figure">
<img alt="コードをプリントアウトして精査する" src="https://blog.tai2.net/images/a-field-guide-to-debugging/2-3.jpg">
</div>
</div>
<div class="section" id="section-4">
<h2>3.はじめる前に…</h2>
<p>なにかをはじめる前には、コードのコピーを保存して、戻れるようにしておこう。デバッグ中に他の問題を引き起こして、壊したり、意図せず問題ない部分を消してしまうというのは、よくあることだ。</p>
<div class="figure">
<img alt="コードを保存しておく" src="https://blog.tai2.net/images/a-field-guide-to-debugging/3-1.png">
<p class="caption">「うーん、キミは壊れてるっぽいけど、もっとおかしくしちゃうかもしれないし、コピーを取っとこうかな…!」</p>
</div>
<p>デバッグの過程で、さらなるバグを生み出すべきではない。</p>
<div class="figure">
<img alt="バグの出現" src="https://blog.tai2.net/images/a-field-guide-to-debugging/3-2.png">
<p class="caption">バグ「こんにちは!」</p>
</div>
<p>もしミスをしたり、問題が悪いほうに運んだりした場合には、いつでもアンドゥ、すなわち、保存したファイルに戻すことができる。</p>
<div class="figure">
<img alt="ここってなにがあったんだっけ…" src="https://blog.tai2.net/images/a-field-guide-to-debugging/3-3.jpg">
</div>
<p>Githubのようなバージョン管理を試すのもアリだ。</p>
<div class="figure">
<img alt="gitを使おう" src="https://blog.tai2.net/images/a-field-guide-to-debugging/3-4.png">
<p class="caption">キミがウィザードなら、gitバージョン管理を使うのもアリ</p>
</div>
<p>やろうとしていることのリストを書いて、チェックの必要なことを追跡できるようにしよう。方法論的にいこう、長い目で見れば、それが時間の節約になるんだ。</p>
<img alt="いちどにひとつのことを" class="align-right" src="https://blog.tai2.net/images/a-field-guide-to-debugging/3-5.jpg">
<p>一度に一つのことだけ変更するようにする。デバッグの最中、キミは、コードを部分的にONにしたりOFFにしたりすることだろう。変更をするときは、いつでもプログラムをテストして。テストする前に複数の変更をしてしまうと、どの変更が、どんな効果をもたらしたのかわからなくなって、状況がいっそう悪くなる可能性が高いんだ。</p>
</div>
<div class="section" id="section-5">
<h2>4.基本を確認する</h2>
<p>多くのバグは、とても基本的なミスであったことが判明するものだ。ちょうど、電源コードを入れ忘れていたというような。</p>
<ul class="simple">
<li>実際に動かしているファイルを編集してる? (そして、例えば、ローカルファイルを編集しつつ、サーバー上の異なるファイルを見ていたりしない?)</li>
<li>すべての外部ファイルは、キミの思っているとおりの場所にある?</li>
<li>ファイルの依存関係は正しい?</li>
<li>パスに打ち間違いはない?</li>
<li>サーバーは確認した? 他</li>
</ul>
<div class="figure">
<img alt="ファイルの依存関係を確認しよう" src="https://blog.tai2.net/images/a-field-guide-to-debugging/4-1.png">
<p class="caption">「なんでボクのロボットはレモネードを持ってきてくれないんだ?」「ああ、左手を付けるの忘れてた!」依存関係を確認しよう</p>
</div>
<div class="figure">
<img alt="正しいファイルをテストしてる?" src="https://blog.tai2.net/images/a-field-guide-to-debugging/4-2.png">
<p class="caption">「なんでボクのロボットはレモネードを持ってきてくれないんだ?」「ああ、オフィスに電話してた、携帯番号じゃなくて!」正しいファイルをテストしてる?</p>
</div>
<div class="figure">
<img alt="正しいファイルを編集・保存してる?" src="https://blog.tai2.net/images/a-field-guide-to-debugging/4-3.png">
<p class="caption">「なんでボクのロボットはレモネードを持ってきてくれないんだ?」「ああ、間違ってエリカのロボットにレモネードを頼んじゃってた!」正しいファイルを編集・保存してる?</p>
</div>
</div>
<div class="section" id="section-6">
<h2>5.ブラックボックス</h2>
<p>ブラックボックスというのは、内部がどうなっているのか理解していない、システムの部分を意味している。例えば、自分で書いていないライブラリ、あるいは関数だ。系統的に、各ブラックボックスをひとつひとつ取り出して、プログラムを走らせてみて。それらのプログラムの中に誤りが含まれているか見るのに役立つから。</p>
<img alt="ブラックボックスは中身がわからない" src="https://blog.tai2.net/images/a-field-guide-to-debugging/5-1.jpg">
<img alt="ブラックボックスをひとつひとつ確認する" src="https://blog.tai2.net/images/a-field-guide-to-debugging/5-2.png">
</div>
<div class="section" id="section-7">
<h2>6.エラー報告を追加する</h2>
<img alt="エラー報告がないと、プログラムの流れがわからない" class="align-right" src="https://blog.tai2.net/images/a-field-guide-to-debugging/6-1.png">
<p>エラー報告というのは、プログラムがやっていることをいかにしてキミに伝えるか、ということだ。p5.js には、いくつかの組み込みエラー報告があって、特定の文法エラーがあるかどうか教えてくれる。</p>
<div style="clear:both;"></div><img alt="エラー報告があると、プログラムの流れがわかる" class="align-right" src="https://blog.tai2.net/images/a-field-guide-to-debugging/6-2.png">
<p>独自のエラー報告を console.log() 関数<sup id="sf-a-field-guide-to-debugging-1-back"><a href="#sf-a-field-guide-to-debugging-1" class="simple-footnote" title="(訳注) C言語で言えばprintf、Javaで言えばSystem.outや、その他フレームワークのログ出力機能など">1</a></sup>を使用して追加するのも便利だ。プログラムの流れをチェックするために、console.log() ステートメントをコードの中に足そう。すると、コンソールを見たときに、ものごとの起きる順序や、どこで問題に遭遇するのかが見て取れる。</p>
<p>変数の値を表示して、なにが起きているのか見られるようにするために、console.log() を足すのも便利だ。</p>
<div class="figure">
<img alt="console.logは便利" src="https://blog.tai2.net/images/a-field-guide-to-debugging/6-3.jpg">
</div>
</div>
<div class="section" id="section-8">
<h2>7.他のヘルプを探す</h2>
<p>それでも、どれもうまくいかなかったら? オンラインには、助けになる場所がたくさんある。</p>
<ul class="simple">
<li>Google検索をする。キミに起こった問題は、他のたくさんの人にも起き得ることだ。</li>
<li>Processingフォーラムを p5.js タグで検索する。</li>
<li>Stack Overflowのような開発者フォーラムを検索する。<sup id="sf-a-field-guide-to-debugging-2-back"><a href="#sf-a-field-guide-to-debugging-2" class="simple-footnote" title="(訳注) 日本語版は以下 http://ja.stackoverflow.com/ ">2</a></sup></li>
</ul>
<div class="figure">
<img alt="他のヘルプを探す" src="https://blog.tai2.net/images/a-field-guide-to-debugging/7-1.jpg">
</div>
</div>
<div class="section" id="section-9">
<h2>8.人に聞く</h2>
<img alt="人に聞く" class="align-right" src="https://blog.tai2.net/images/a-field-guide-to-debugging/8-0.jpg">
<p>まだ動かない? 人に助けてもらうこともできるよ! きっと喜んで助けてくれるはず。</p>
<p>最初に説明したようにEメールを書いて送ろう。問題と知りたいことを簡潔にまとめて、<a class="reference external" href="http://forum.processing.org/two/categories/p5-js">Processingフォーラム</a> に投稿しよう。<sup id="sf-a-field-guide-to-debugging-3-back"><a href="#sf-a-field-guide-to-debugging-3" class="simple-footnote" title="(訳注) フォーラムは残念ながら英語用しかありません。">3</a></sup></p>
<div style="clear:both;"></div></div>
<div class="section" id="section-10">
<h2>9.良いコーディング習慣、および、いかにしてバグを防ぐか</h2>
<ul class="simple">
<li>早すぎる最適化をしないこと。明瞭なコードは、高パフォーマンスなコードよりも重要だ。プログラムを作っている最中は。</li>
<li>早すぎる抽象化をしないこと。何度も使いそうだと思うからと言って、関数にまとめる必要はない。実際に2回以上使う必要が出てきてからにしよう。</li>
<li>コメントとしての疑似コードからはじめよう。それから、各ステップにコードを足すんだ。開発中は、console.log() をコード内に置いておこう(そして、頻繁にテストして、なにかが変わったら、最後にテストしてから自分がなにをしたのかがわかるようにしておくんだ)。</li>
</ul>
<p>これも大事: 小さな問題からはじめよう! 一度にひとつのことをしよう。ひとつのことを確かめるのに、小さなスケッチ<sup id="sf-a-field-guide-to-debugging-4-back"><a href="#sf-a-field-guide-to-debugging-4" class="simple-footnote" title="(訳注) Processingの世界では、プログラムのことをスケッチと呼ぶ">4</a></sup>を作るのが良いだろう(星を描く! twitterをチェックする!)、そして、それらをボルトロン<sup id="sf-a-field-guide-to-debugging-5-back"><a href="#sf-a-field-guide-to-debugging-5" class="simple-footnote" title="(訳注) 1984年にアメリカで放送された、合体ロボットアニメ。https://www.youtube.com/watch?v=tZZv5Z2Iz_s ">5</a></sup>させて、より大きなスケッチにするんだ(星を描いて、twitterの通知が届いたら赤くする!)。</p>
<div class="figure">
<img alt="コンピューターと対話するプログラマ" src="https://blog.tai2.net/images/a-field-guide-to-debugging/9-1.jpg">
</div>
</div>
<div class="section" id="section-11">
<h2>10.他のリソース</h2>
<p>このガイドは、コーディング中のデバッグについて、いくつかのすばらしいリソースにインスパイアされだ。以下は、そのうちのいくつか:</p>
<ul class="simple">
<li>Matt Gemmel, <a class="reference external" href="http://mattgemmell.com/what-have-you-tried/">What have you tried?</a></li>
<li>Clay Shirky, <a class="reference external" href="https://vimeo.com/channels/debugging">A brief introduction to debugging</a></li>
<li>Eric Steven Raymond, <a class="reference external" href="http://www.catb.org/esr/faqs/smart-questions.html">How to ask questions the smart way</a></li>
<li>ITP Residents, <a class="reference external" href="https://docs.google.com/presentation/d/1RXzITwS4otVKnYkuNu2w7CrpYy35WBO2HUlmkSc2p8g/edit?copiedFromTrash#slide=id.g2ffb36b3_0_44">10 Tips for Debugging</a></li>
<li>Rurouni Jones, <a class="reference external" href="http://rurounijones.github.io/blog/2009/03/17/how-to-ask-for-help-on-irc//">How to ask for help on IRC</a></li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-a-field-guide-to-debugging-1">(訳注) C言語で言えばprintf、Javaで言えばSystem.outや、その他フレームワークのログ出力機能など <a href="#sf-a-field-guide-to-debugging-1-back" class="simple-footnote-back">↩</a></li><li id="sf-a-field-guide-to-debugging-2">(訳注) 日本語版は以下 <a class="reference external" href="http://ja.stackoverflow.com/">http://ja.stackoverflow.com/</a> <a href="#sf-a-field-guide-to-debugging-2-back" class="simple-footnote-back">↩</a></li><li id="sf-a-field-guide-to-debugging-3">(訳注) フォーラムは残念ながら英語用しかありません。 <a href="#sf-a-field-guide-to-debugging-3-back" class="simple-footnote-back">↩</a></li><li id="sf-a-field-guide-to-debugging-4">(訳注) Processingの世界では、プログラムのことをスケッチと呼ぶ <a href="#sf-a-field-guide-to-debugging-4-back" class="simple-footnote-back">↩</a></li><li id="sf-a-field-guide-to-debugging-5">(訳注) 1984年にアメリカで放送された、合体ロボットアニメ。<a class="reference external" href="https://www.youtube.com/watch?v=tZZv5Z2Iz_s">https://www.youtube.com/watch?v=tZZv5Z2Iz_s</a> <a href="#sf-a-field-guide-to-debugging-5-back" class="simple-footnote-back">↩</a></li></ol>Doctrineのベストプラクティス(Doctrine 2 documentationから抜粋翻訳)2015-12-14T00:00:00+09:002015-12-14T00:00:00+09:00tai2tag:blog.tai2.net,2015-12-14:/doctrine-best-practices.html<p class="first last">Doctrineのドキュメント第27章 Best Practices の翻訳です。</p>
<p>この記事は、 <a class="reference external" href="http://qiita.com/advent-calendar/2015/symfony">Symfony Advent Calendar 2015</a> 14日目の記事です。</p>
<p>Doctrineのドキュメント第27章 <a class="reference external" href="http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/best-practices.html">Best Practices</a> の翻訳。ライセンスは、MIT。</p>
<hr class="docutils">
<div class="section" id="section-1">
<h2>ベストプラクティス</h2>
<p>ここで言及されている、データベース設計に影響するベストプラクティスは、おおむね、Doctrineを使用するときのベストプラクティスであり、一般的なデータベース設計のベストプラクティスを反映しているとは限らない。</p>
<div class="section" id="section-2">
<h3>関係を可能な限り制限する</h3>
<p>可能な限り関係を制限するのは重要だ。これが意味するのは:</p>
<ul class="simple">
<li>探索する方向を制限する(可能なら双方向の関係を使わない)</li>
<li>本質的でない関係を除去する</li>
</ul>
<p>ということだ。これにはいくつかの利点がある:</p>
<ul class="simple">
<li>ドメインモデルの結合を減らす</li>
<li>ドメインモデルのコードがシンプルになる(双方向性を適切に保つ必要がない)</li>
<li>Doctrineのための作業が少なくなる</li>
</ul>
</div>
<div class="section" id="section-3">
<h3>複合キーを使わない</h3>
<p>Doctrineは複合キーを完全にサポートしているが、それでも、可能な限り使わないのがベストだ。複合キーは、Doctrineによる追加の仕事を要求するし、それゆえ、エラーの可能性がより高くなる。</p>
</div>
<div class="section" id="section-4">
<h3>イベントを慎重に使う</h3>
<p>Doctrineのイベントシステムは、すばらしいし、速い。イベント(とくに、ライフサイクルイベント)の使い過ぎが、アプリケーションに悪い影響をもたらすとしてもだ。それゆえ、イベントは慎重に使うべきだ。</p>
</div>
<div class="section" id="section-5">
<h3>カスケードを慎重に使う</h3>
<p>persist/remove/merge等の自動的なカスケードは、とても便利だが、賢く使われるべきだ。単純にすべての関連にすべてのカスケードを追加したりしないこと。どのカスケードが特定の関連にとって実際に意味があり、それがいかにも使われそうなシナリオはあるのか、考えること。</p>
</div>
<div class="section" id="section-6">
<h3>特殊文字を使わない</h3>
<p>クラス、フィールド、テーブルあるいはカラム名には、いかなる非ASCII文字も使わないこと。Doctrineそのものは、多くの箇所でUnicode安全ではないし、PHPそのものがUnicode対応になるまで、そうはならないだろう。</p>
</div>
<div class="section" id="section-7">
<h3>識別子のクオートを使わない</h3>
<p>識別子のクオートは、予約語を使うためのワークアラウンドだ。予約語を使うことは、エッジケースでしばしば問題を起こす。識別子のクオートを使わないこと、そして、テーブルまたはカラム名に予約語を使わないこと。</p>
</div>
<div class="section" id="section-8">
<h3>コンストラクタ内でコレクションを初期化する</h3>
<p>コンストラクタ内で、任意のビジネスコレクションを初期化するのが、推奨されるベストプラクティスだ。例:</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="k">namespace</span> <span class="nx">MyProject\Model</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">Doctrine\Common\Collections\ArrayCollection</span><span class="p">;</span>
<span class="k">class</span> <span class="nc">User</span> <span class="p">{</span>
<span class="k">private</span> <span class="nv">$addresses</span><span class="p">;</span>
<span class="k">private</span> <span class="nv">$articles</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__construct</span><span class="p">()</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">addresses</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ArrayCollection</span><span class="p">;</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">articles</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ArrayCollection</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
</div>
<div class="section" id="section-9">
<h3>エンティティのフィールドに外部キーをマップしない</h3>
<p>外部キーは、オブジェクトモデルにおいて、なんの意味も持たない。外部キーは、リレーショナルデータベースが、どのようにして関係を確立するか、というものだ。オブジェクトモデルは、オブジェクトの参照を通じて関係を確立する。それゆえ、外部キーをオブジェクトフィールドにマッピングすることは、オブジェクトモデルにリレーショナルモデルの詳細を大胆に漏らしてしまう。こういうことはすべきではない。</p>
</div>
<div class="section" id="section-10">
<h3>明示的にトランザクションを分離する</h3>
<p>Doctrineが、flush()時に、すべてのDML<sup id="sf-doctrine-best-practices-1-back"><a href="#sf-doctrine-best-practices-1" class="simple-footnote" title="(訳注)Data Manipulation Language">1</a></sup>操作を自動的にトランザクションで囲む一方で、自分で明示的にトランザクション境界を設定することは、ベストプラクティスであると考えられている。さもなくば、どの単一のクエリも、小さなトランザクションで囲まれることになる(そう、SELECTクエリもだ)。そうすれば、トランザクションを越えてデータベースと対話することがなくなるためだ。読み取りのみの(SELECT)クエリのための、こういった小さなトランザクションは、一般に目立ったパフォーマンス上のインパクトをもたらさない一方、より少ない数の、よく練られたトランザクションを使うのが、やはり好ましい。つまり、明示的なトランザクション境界によって確立されるトランザクションだ。</p>
</div>
</div>
<ol class="simple-footnotes"><li id="sf-doctrine-best-practices-1">(訳注)Data Manipulation Language <a href="#sf-doctrine-best-practices-1-back" class="simple-footnote-back">↩</a></li></ol>DQLのJOIN WITH構文を使えば、無用な関係を定義せずにテーブルの結合ができる2015-12-07T00:00:00+09:002015-12-07T00:00:00+09:00tai2tag:blog.tai2.net,2015-12-07:/doctrine-join-with-syntax.html<p class="first last">DQLでクエリを書く際に、JOIN WITH構文を使用することで、@OneToManyアノテーションなどで関係するプロパティを定義せずとも、関係するエンティティを結合して絞り込みをかけることができる。</p>
<p><a class="reference external" href="http://qiita.com/advent-calendar/2015/symfony">Symfony Advent Calendar 2015</a> 7日目。</p>
<div class="section" id="section-1">
<h2>要約</h2>
<p>Doctrine 2.4以降では:</p>
<ul class="simple">
<li>DQLでクエリを書く際に、</li>
<li>JOIN WITH構文を使用することで、</li>
<li>@OneToManyアノテーションなどで関係するプロパティを定義せずとも、</li>
<li>関係するエンティティを結合して絞り込みをかけることができる。</li>
</ul>
</div>
<div class="section" id="section-2">
<h2>前提</h2>
<p>JOINをしたいが、エンティティ間の関係を定義するほどではない。あるいは、プロジェクトのポリシーで、所与のもの以外には、@OneToManyなどの対多関係を追加しないということになっている。</p>
</div>
<div class="section" id="section-3">
<h2>例題</h2>
<p>以下の、ブログポストへのタグ付けを意図した多対多の関係を考える。</p>
<p>postテーブル</p>
<table border="1" class="docutils">
<colgroup>
<col width="50%" />
<col width="50%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">id</th>
<th class="head">title</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>1</td>
<td>Symfonyのルーティング</td>
</tr>
<tr><td>2</td>
<td>Symfonyで知っておくと便利なconfig</td>
</tr>
</tbody>
</table>
<p>tagテーブル</p>
<table border="1" class="docutils">
<colgroup>
<col width="50%" />
<col width="50%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">id</th>
<th class="head">name</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>1</td>
<td>symfony</td>
</tr>
<tr><td>2</td>
<td>routing</td>
</tr>
</tbody>
</table>
<p>post_tagテーブル</p>
<table border="1" class="docutils">
<colgroup>
<col width="50%" />
<col width="50%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">post_id</th>
<th class="head">tag_id</th>
</tr>
</thead>
<tbody valign="top">
<tr><td>1</td>
<td>1</td>
</tr>
<tr><td>1</td>
<td>2</td>
</tr>
<tr><td>2</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>ここで、</p>
<ol class="arabic simple">
<li>「symfony」タグが付与された全ポストを取得したい。あるいは、</li>
<li>「Symfonyのルーティング」に付随する全タグを取得したい。</li>
</ol>
</div>
<div class="section" id="join">
<h2>通常のJOIN構文</h2>
<p>Doctrineにおける一般的な方法では、以下のように、@OneToMay,@ManyToOneという関係を定義するための機能を利用する。</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Entity</span>
<span class="sd"> */</span>
<span class="k">class</span> <span class="nc">Post</span>
<span class="p">{</span>
<span class="sd">/**</span>
<span class="sd"> + @ORM\Column(type="integer")</span>
<span class="sd"> + @ORM\Id</span>
<span class="sd"> + @ORM\GeneratedValue(strategy="AUTO")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$id</span><span class="p">;</span>
<span class="sd">/**</span>
<span class="sd"> + @ORM\OneToMany(targetEntity="PostTag", mappedBy="post")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$post_tags</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__construct</span><span class="p">()</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">post_tags</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ArrayCollection</span><span class="p">();</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Entity</span>
<span class="sd"> */</span>
<span class="k">class</span> <span class="nc">Tag</span>
<span class="p">{</span>
<span class="sd">/**</span>
<span class="sd"> + @ORM\Column(type="integer")</span>
<span class="sd"> + @ORM\Id</span>
<span class="sd"> + @ORM\GeneratedValue(strategy="AUTO")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$id</span><span class="p">;</span>
<span class="sd">/**</span>
<span class="sd"> + @ORM\OneToMany(targetEntity="PostTag", mappedBy="tag")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$post_tags</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="fm">__construct</span><span class="p">()</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">post_tags</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ArrayCollection</span><span class="p">();</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Entity</span>
<span class="sd"> * @ORM\Table(</span>
<span class="sd"> * indexes={</span>
<span class="sd"> * @ORM\Index(name="post_idx", columns={"post_id"}),</span>
<span class="sd"> * @ORM\Index(name="tag_idx", columns={"tag_id"})</span>
<span class="sd"> * },</span>
<span class="sd"> * )</span>
<span class="sd"> */</span>
<span class="k">class</span> <span class="nc">PostTag</span>
<span class="p">{</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Column(type="integer")</span>
<span class="sd"> * @ORM\Id</span>
<span class="sd"> * @ORM\GeneratedValue(strategy="AUTO")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$id</span><span class="p">;</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\ManyToOne(targetEntity="Post", inversedBy="post_tags")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$post</span><span class="p">;</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\ManyToOne(targetEntity="Tag", inversedBy="post_tags")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$tag</span><span class="p">;</span>
<span class="o">...</span>
<span class="p">}</span>
</pre></div>
<p>すると、以下のようにDQLのJOIN機能を使用してエンティティを取得できる。</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="nv">$em</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">getContainer</span><span class="p">()</span><span class="o">-></span><span class="na">get</span><span class="p">(</span><span class="s1">'doctrine'</span><span class="p">)</span><span class="o">-></span><span class="na">getManager</span><span class="p">();</span>
<span class="nv">$posts</span> <span class="o">=</span> <span class="nv">$em</span><span class="o">-></span><span class="na">createQuery</span><span class="p">(</span>
<span class="s1">'SELECT p FROM AppBundle:Post p '</span> <span class="o">.</span>
<span class="s1">'JOIN p.post_tags pt '</span> <span class="o">.</span>
<span class="s1">'JOIN pt.tag t '</span> <span class="o">.</span>
<span class="s1">'WHERE t.id = :tag_id'</span><span class="p">)</span>
<span class="o">-></span><span class="na">setParameter</span><span class="p">(</span><span class="s1">'tag_id'</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="o">-></span><span class="na">getResult</span><span class="p">();</span>
<span class="nv">$tags</span> <span class="o">=</span> <span class="nv">$em</span><span class="o">-></span><span class="na">createQuery</span><span class="p">(</span>
<span class="s1">'SELECT t FROM AppBundle:Tag t '</span> <span class="o">.</span>
<span class="s1">'JOIN t.post_tags pt '</span> <span class="o">.</span>
<span class="s1">'JOIN pt.post p '</span> <span class="o">.</span>
<span class="s1">'WHERE p.id = :post_id'</span><span class="p">)</span>
<span class="o">-></span><span class="na">setParameter</span><span class="p">(</span><span class="s1">'post_id'</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="o">-></span><span class="na">getResult</span><span class="p">();</span>
</pre></div>
<p>しかし、JOINを利用するためだけに@OneToManyによる関係プロパティを追加するのは、過剰な場合がある。</p>
</div>
<div class="section" id="native-sql">
<h2>Native SQL</h2>
<p>Native SQLを使用すれば、SQLのクエリ結果のカラムをエンティティにマップすることができる。</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Entity</span>
<span class="sd"> */</span>
<span class="k">class</span> <span class="nc">Post</span>
<span class="p">{</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Column(type="integer")</span>
<span class="sd"> * @ORM\Id</span>
<span class="sd"> * @ORM\GeneratedValue(strategy="AUTO")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$id</span><span class="p">;</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Entity</span>
<span class="sd"> */</span>
<span class="k">class</span> <span class="nc">Tag</span>
<span class="p">{</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Column(type="integer")</span>
<span class="sd"> * @ORM\Id</span>
<span class="sd"> * @ORM\GeneratedValue(strategy="AUTO")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$id</span><span class="p">;</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Entity</span>
<span class="sd"> * @ORM\Table(</span>
<span class="sd"> * indexes={</span>
<span class="sd"> * @ORM\Index(name="post_idx", columns={"post_id"}),</span>
<span class="sd"> * @ORM\Index(name="tag_idx", columns={"tag_id"})</span>
<span class="sd"> * },</span>
<span class="sd"> * )</span>
<span class="sd"> */</span>
<span class="k">class</span> <span class="nc">PostTag</span>
<span class="p">{</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Column(type="integer")</span>
<span class="sd"> * @ORM\Id</span>
<span class="sd"> * @ORM\GeneratedValue(strategy="AUTO")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$id</span><span class="p">;</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Column(type="integer")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$post_id</span><span class="p">;</span>
<span class="sd">/**</span>
<span class="sd"> * @ORM\Column(type="integer")</span>
<span class="sd"> */</span>
<span class="k">protected</span> <span class="nv">$tag_id</span><span class="p">;</span>
<span class="o">...</span>
<span class="p">}</span>
</pre></div>
<p>このように@OneToMany等による関係プロパティのないエンティティ定義でも、Native SQLを使用することで、SQLの結果を直接エンティティにマッピングできる。</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="nv">$em</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">getContainer</span><span class="p">()</span><span class="o">-></span><span class="na">get</span><span class="p">(</span><span class="s1">'doctrine'</span><span class="p">)</span><span class="o">-></span><span class="na">getManager</span><span class="p">();</span>
<span class="nv">$rsm</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ResultSetMappingBuilder</span><span class="p">(</span><span class="nv">$em</span><span class="p">);</span>
<span class="nv">$rsm</span><span class="o">-></span><span class="na">addRootEntityFromClassMetadata</span><span class="p">(</span><span class="s1">'AppBundle:Post'</span><span class="p">,</span> <span class="s1">'p'</span><span class="p">);</span>
<span class="nv">$posts</span> <span class="o">=</span> <span class="nv">$em</span><span class="o">-></span><span class="na">createNativeQuery</span><span class="p">(</span>
<span class="s1">'SELECT p.id, p.title FROM post AS p '</span> <span class="o">.</span>
<span class="s1">'JOIN post_tag AS pt ON pt.post_id = p.id '</span> <span class="o">.</span>
<span class="s1">'JOIN tag AS t ON t.id = pt.tag_id '</span> <span class="o">.</span>
<span class="s1">'WHERE t.id = :tag_id'</span><span class="p">,</span> <span class="nv">$rsm</span><span class="p">)</span>
<span class="o">-></span><span class="na">setParameter</span><span class="p">(</span><span class="s1">'tag_id'</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="o">-></span><span class="na">getResult</span><span class="p">();</span>
<span class="nv">$rsm</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ResultSetMappingBuilder</span><span class="p">(</span><span class="nv">$em</span><span class="p">);</span>
<span class="nv">$rsm</span><span class="o">-></span><span class="na">addRootEntityFromClassMetadata</span><span class="p">(</span><span class="s1">'AppBundle:Tag'</span><span class="p">,</span> <span class="s1">'t'</span><span class="p">);</span>
<span class="nv">$tags</span> <span class="o">=</span> <span class="nv">$em</span><span class="o">-></span><span class="na">createNativeQuery</span><span class="p">(</span>
<span class="s1">'SELECT t.id, t.name FROM tag AS t '</span> <span class="o">.</span>
<span class="s1">'JOIN post_tag AS pt ON pt.tag_id = t.id '</span> <span class="o">.</span>
<span class="s1">'JOIN post AS p ON p.id = pt.post_id '</span> <span class="o">.</span>
<span class="s1">'WHERE p.id = :post_id'</span><span class="p">,</span> <span class="nv">$rsm</span><span class="p">)</span>
<span class="o">-></span><span class="na">setParameter</span><span class="p">(</span><span class="s1">'post_id'</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="o">-></span><span class="na">getResult</span><span class="p">();</span>
</pre></div>
<p>SQLを直接使えるため強力ではあるが、低級な部分が剥き出しになるため、やや醜い。</p>
</div>
<div class="section" id="join-with">
<h2>JOIN WITH構文</h2>
<p>上記と同様のエンティティ定義でも、JOIN WITH構文を使用すれば、Native SQLを使わずに同様のクエリを実現できる。</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="nv">$em</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">getContainer</span><span class="p">()</span><span class="o">-></span><span class="na">get</span><span class="p">(</span><span class="s1">'doctrine'</span><span class="p">)</span><span class="o">-></span><span class="na">getManager</span><span class="p">();</span>
<span class="nv">$posts</span> <span class="o">=</span> <span class="nv">$em</span><span class="o">-></span><span class="na">createQuery</span><span class="p">(</span>
<span class="s1">'SELECT p FROM AppBundle:Post p '</span> <span class="o">.</span>
<span class="s1">'JOIN AppBundle:PostTag pt WITH pt.post_id = p.id '</span> <span class="o">.</span>
<span class="s1">'JOIN AppBundle:Tag t WITH t.id = pt.tag_id '</span> <span class="o">.</span>
<span class="s1">'WHERE t.id = :tag_id'</span><span class="p">)</span>
<span class="o">-></span><span class="na">setParameter</span><span class="p">(</span><span class="s1">'tag_id'</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="o">-></span><span class="na">getResult</span><span class="p">();</span>
<span class="nv">$tags</span> <span class="o">=</span> <span class="nv">$em</span><span class="o">-></span><span class="na">createQuery</span><span class="p">(</span>
<span class="s1">'SELECT t FROM AppBundle:Tag t '</span> <span class="o">.</span>
<span class="s1">'JOIN AppBundle:PostTag pt WITH pt.tag_id = t.id '</span> <span class="o">.</span>
<span class="s1">'JOIN AppBundle:Post p WITH p.id = pt.post_id '</span> <span class="o">.</span>
<span class="s1">'WHERE p.id = :post_id'</span><span class="p">)</span>
<span class="o">-></span><span class="na">setParameter</span><span class="p">(</span><span class="s1">'post_id'</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="o">-></span><span class="na">getResult</span><span class="p">();</span>
</pre></div>
<p>ただし、@OneToManyで関係を定義した場合には、外部キー制約が付与されるのに対して、
Native SQLの説明で用いたEntity定義では、外部キー制約がないため、厳密に同一ではない。
筆者の調べた限り、Doctrineで、@OneToMany等での関係プロパティ定義をせずに外部キー制約をつける方法はなさそうだ。</p>
</div>
ヘキサゴナルアーキテクチャ(Hexagonal architecture翻訳)2015-10-09T00:00:00+09:002015-10-09T00:00:00+09:00tai2tag:blog.tai2.net,2015-10-09:/hexagonal_architexture.html<p class="first last">Alistair Cockburn による Hexagonal architecture の翻訳です。</p>
<p><a class="reference external" href="http://alistair.cockburn.us/">Alistair Cockburn</a> による <a class="reference external" href="http://alistair.cockburn.us/Hexagonal+architecture">Hexagonal architecture</a> の翻訳です。PoEAAで言及されていることから、2002年ごろにはすでにC2 Wikiにページがあった模様。似たようなアーキテクチャである <a class="reference external" href="https://blog.tai2.net/the_clean_architecture.html">クリーンアーキテクチャ</a> も翻訳したので参考にしてください。</p>
<p>この記事は著者から許可を得て公開しています。Thanks to Alistair Cockburn!</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#ports-and-adapters" id="toc-entry-1">パターン: Ports and Adapters (構造に関するパターン)</a><ul>
<li><a class="reference internal" href="#section-1" id="toc-entry-2">意図</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-3">動機</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-4">解決法の本質</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-5">構造</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-6">サンプルコード</a><ul>
<li><a class="reference internal" href="#fit" id="toc-entry-7">ステージ1: FIT <span class="underline">アプリ</span> 定数をモックデータベースとして</a></li>
<li><a class="reference internal" href="#ui" id="toc-entry-8">ステージ2: UI <span class="underline">アプリ</span> 定数をモックデータベースとして</a></li>
<li><a class="reference internal" href="#fitui" id="toc-entry-9">ステージ3: (FITまたはUI) <span class="underline">アプリ</span> モックデータベース</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-6" id="toc-entry-10">応用ノート</a><ul>
<li><a class="reference internal" href="#section-7" id="toc-entry-11">左右の非対称性</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-12">ユースケースとアプリケーションの境界</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-13">ポートはいくつ?</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-14">既知の用例</a></li>
<li><a class="reference internal" href="#mac-windows-google-flickr-web-2-0" id="toc-entry-15">Mac, Windows, Google, Flickr, Web 2.0</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-16">ストアード出力</a></li>
<li><a class="reference internal" href="#c2-wiki" id="toc-entry-17">C2-wikiからの匿名の例</a></li>
<li><a class="reference internal" href="#section-12" id="toc-entry-18">分散された、大きなチームでの開発</a></li>
<li><a class="reference internal" href="#ui-1" id="toc-entry-19">UIとアプリケーションロジックの開発を分割する</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-13" id="toc-entry-20">関連するパターン</a><ul>
<li><a class="reference internal" href="#adapter" id="toc-entry-21">Adapter</a></li>
<li><a class="reference internal" href="#model-view-controller" id="toc-entry-22">Model-View-Controller</a></li>
<li><a class="reference internal" href="#mock-objects-loopback" id="toc-entry-23">Mock Objects と Loopback</a></li>
<li><a class="reference internal" href="#pedestals" id="toc-entry-24">Pedestals</a></li>
<li><a class="reference internal" href="#checks" id="toc-entry-25">Checks</a></li>
<li><a class="reference internal" href="#dependency-inversion-dependency-injection-spring" id="toc-entry-26">Dependency Inversion (Dependency Injection) と SPRING</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-14" id="toc-entry-27">謝辞</a></li>
<li><a class="reference internal" href="#section-15" id="toc-entry-28">リファレンスと、関連した読み物</a></li>
</ul>
</li>
</ul>
</div>
<hr class="docutils">
<p>UIやデータベースがなくても動くようにアプリケーションを作ること。そうすれば、アプリケーションに対して自動化された回帰テストを動かすことができるし、データベースが利用できなくなっても動作するし、ユーザーの介入がなくともアプリケーション群を結合できる。</p>
<div class="figure">
<img alt="Microscope" src="https://blog.tai2.net/images/hexagonal_architecture/3005.jpeg">
</div>
<div class="section" id="ports-and-adapters">
<h2><a class="toc-backref" href="#toc-entry-1">パターン: Ports and Adapters (構造に関するパターン)</a></h2>
<p><strong>別名: 「Ports & Adapter」</strong></p>
<p><strong>別名: 「Hexagonal Architecture」</strong></p>
<div class="section" id="section-1">
<h3><a class="toc-backref" href="#toc-entry-2">意図</a></h3>
<p>アプリケーションを、ユーザー、プログラム、自動テストあるいはバッチスクリプトから、同じように駆動できるようにする。そして、実際のランタイムデバイスとデータベースから隔離して、開発とテストをできるようにする。</p>
<p>イベントが外側の世界からポートに届くと、特定テクノロジーのアダプターが、利用可能な手続き呼び出しか、メッセージにそれを変換して、アプリケーションに渡す。よろこばしいことに、アプリケーションは、入力デバイスの正体を知らない。アプリケーションがなにかを送る必要があるとき、それはポートを通じてアダプターに送られて、受信側のテクノロジーが必要とする信号を生む(人力であれ自動であれ)。アプリケーションは、実際に他方のアダプターの正体を知ることはなしに、全側面のアダプターと意味的に完全なやりとりをする。</p>
<div class="figure">
<img alt="Figure 1" src="https://blog.tai2.net/images/hexagonal_architecture/2301.gif">
<p class="caption">図1</p>
</div>
</div>
<div class="section" id="section-2">
<h3><a class="toc-backref" href="#toc-entry-3">動機</a></h3>
<p>数年来、ソフトウェアアプリケーションで一番怖いことのひとつは、ビジネスロジックがユーザーインターフェイスコードに侵入することだった。これが引き起す問題は、3つある:</p>
<ul class="simple">
<li>はじめに、システムを自動テストスイートで綺麗にテストすることができない。なぜなら、テストを必要とするロジックの部分が、フィールドサイズやボタン配置など、頻繁に変わるビジュアルの詳細に依存するからだ。</li>
<li>同じ理由により、人間駆動のシステム使用から、バッチ処理システムに移行することが不可能になる。</li>
<li>これも同じ理由から、他のプログムからプログラムを駆動したくなったときに、そうすることが難しいか、または不可能になる。</li>
</ul>
<p>(多くの組織によって繰り返し)試みられた解法は、アーキテクチャに新しい層を足すことだ。そのときには、今度は、本当に絶対に、ビジネスロジックが新しいレイヤーに置かれることはないという取り決めをする。しかしながら、取り決めへの違反が起きたときに検出する仕組みはなく、組織は、数年後、新しいレイヤーがビジネスロジックでとっちらかっており、同じ問題が起きたことに気付く。</p>
<p>アプリケーションの提供する機能の部品すべてを、API(application programmed interface)ないし関数呼び出しと通して利用できるとしたらどうなるか、想像してみよう。この状況では、テストないし品質保証部は、新しいコードが以前動いていた機能を壊したときにそれを検出するために、アプリケーションに対して自動化されたテストスクリプトを走らせることができる。ビジネスエキスパートは、GUIの詳細が確定する前に、自動化されたテストケースを作成できる。それは、プログラマーに、自分の作業が正しく完了したことを教える(そして、それらのテストは、テスト部署によって実行されることになる)。APIだけが利用可能でも、アプリケーションを「ヘッドレス」モード<sup id="sf-hexagonal_architexture-1-back"><a href="#sf-hexagonal_architexture-1" class="simple-footnote" title="(訳注) GUIなしバージョン">1</a></sup>でデプロイすることができる。そして、他のプログラムは、その機能を利用することができる。これは、複雑なアプリケーションスイートの設計全体をシンプルにできるし、また、B2Bサービスアプリケーション群が、互いに、ウェブ経由で人力を介さずに、利用しあうことを許す。最後に、自動化された回帰テストは、ビジネスロジックをプレゼンテーションレイヤーから隔離しておくという取り決めへの違反を検出できる。組織は、ロジックの漏洩を検出して訂正できる。</p>
<p>興味深い同様の問題が、アプリケーションの「反対側」と通常考えられている部分にも存在する。アプリケーションのロジックが、外部のデータベースや他のサービスと結びつけられている部分だ。データベースサーバーが落ちたとき、あるいは、大規模な改善や置き換え中のときなど、プログラマーは作業をすることができない。なぜなら、作業がデータベースの存在と結びついているからだ。これは、遅延のコストと、しばしば人々の間の嫌な雰囲気を生じさせる。</p>
<p>二つの問題が関連していることは明らかではない、しかし、解決法の本質で見るように、これらの間には対称性がある。</p>
</div>
<div class="section" id="section-3">
<h3><a class="toc-backref" href="#toc-entry-4">解決法の本質</a></h3>
<p>ユーザーサイドとサーバーサイドの問題は、どちらも、実際には、設計とプログラミングにおける同様の誤りから生じている。ビジネスロジックと、外部エンティティーとのやりとりが、絡み合っているのだ。利用すべき非対称性は、アプリケーションの「左側」と「右側」ではなく、アプリケーションの「内側」と「外側」だ。従うべきルールは、「内側」の部分にあるコードが「外側」の部分に漏れ出さないようにすべき、ということだ。</p>
<p>左右または上下の非対称性からはしばらく離れて、アプリケーションが、「ポート」を越えて外部のエージェントと通信することを見よう。「ポート」という語には、オペレーティングシステムの「ポート」を想起させることが期待される。それは、ポートのプロトコルに従うデバイスが、差し込まれる場所だ。そして、電子ガジェットの「ポート」、ここでもまた、機械的かつ電気的なプロトコルに適合するデバイスが、差し込まれる。</p>
<ul class="simple">
<li>ポート用のプロトコルは、2つのデバイスの会話を目的として、与えられる。</li>
</ul>
<p>このプロトコルは、アプリケーションプログラムインターフェイス(API)の形を取る。</p>
<p>各外部デバイスには「アダプタ」があり、それは、API定義をデバイスが必要とする信号に変える、逆もまた然り。グラフィカルユーザーインターフェイスすなわちGUIは、人間の動作をポートのAPIと対応付けるアダプタの例だ。同じポートに適合するその他のアダプタは、FIT<sup id="sf-hexagonal_architexture-2-back"><a href="#sf-hexagonal_architexture-2" class="simple-footnote" title="(訳注) MS Wordなどで作成したHTMLのテーブルとして記述されたフィクスチャを元にテストケースを自動生成して走らせるツール。顧客のドメイン知識を活用して、早期から開発に参加してもらうことができる。http://fit.c2.com/wiki.cgi?IntroductionToFit ">2</a></sup>やFitnessのようなテストハーネス、バッチドライバー、あるいは、企業やネットをまたがるアプリケーション間の通信で必要とされるあらゆるコードだ。</p>
<p>アプリケーションの他方では、アプリケーションは、データを取得するために外部のエンティティーと通信する。そのプロトコルの典型は、データベースプロトコルだ。アプリケーションの観点からは、もしデータベースがSQLデータベースから、フラットなファイルや、その他のデータベースに移行しても、APIとの会話は変わるべきではない。ゆえに、同じポートへの追加のアダプターは、SQLアダプター、フラットファイルアダプター、そしてもっとも重要なものとして、「モック」データベースのアダプターを含む。これは、メモリ内に居座るもので、実際のデータベースの存在にまったく依存しない。</p>
<p>多くのアプリケーションは、ポートを2つだけ持つ: ユーザー側の対話と、データベース側の対話だ。これは、非対称的な様相をもたらすので、アプリケーションを1次元、3,4,あるいは5層のスタックアーキテクチャで構築するのが自然だと思わせる。</p>
<p>これらの素描には2つの問題がある。はじめに、そしてもっとも悪いのは、人々がレイヤー素描の「線」を深刻に受け取らない傾向があるということだ。かれらは、アプリケーションロジックをレイヤー境界を越えて侵食させ、上述した問題を生む。2番目に、アプリケーションには、2つ以上のポートがあるかもしれないということだ、そうなると、そのアーキテクチャは、1次元レイヤーの素描に適合しない。</p>
<p>ヘキサゴナル(またはPorts and adapters)アーキテクチャーでは、こうした状況において対称なものがなにもないことによって、問題を解決する: 内部にはアプリケーションがあり、いくつかのポートごしに外部のものと通信する。アプリケーションの外側のものは、対称的に扱うことができる。</p>
<p>六角形は、視覚的に、</p>
<ol class="loweralpha simple">
<li>内側と外側の非対称性と、ポートの似たような特性(1次元のレイヤーの絵と、それが想起させるものから完全に離れるために)と、</li>
<li>定義された数の異なるポートの存在 ー 2,3,あるいは4つの(4が、わたしがこれまで遭遇した中では一番多かった)</li>
</ol>
<p>に焦点を当てるよう意図されている。</p>
<p>この六角形は、6という数字が重要だから六角形なのではなく、人々が、必要に応じて、ポートとアダプターを挿入するための余分を素描に持たせ、1次元レイヤーの素描に制限されないようにするから、六角形なのだ。ヘキサゴナルアーキテクチャという用語は、この視覚効果から来ている。</p>
<p>「ポートとアダプター」という用語は、素描のパーツの「目的」を強調している。ポートは、目的の会話を識別する。典型的には、どのひとつのポートにも複数のアダプターがあるだろう。それらは、ポートに差し込まれるさまざまな技術のためのものだ。典型的には、これには、留守番電話、人間の声、プッシュホン、グラフィカルユーザーインターフェイス、テストハーネス、バッチドライバー、HTTPインターフェイス、プログラムからプログラムへの直接インターフェイス、(インメモリ)モックデータベース、実際のデータベース(おそらく、開発用、テスト用、実利用用で異なるもの)が含まれる。</p>
<p>応用ノートでは、左右の非対称性について再度述べる。しかしながら、このパターンの主たる目的は、内側と外側の非対称性にフォーカスすることであり、つかの間、外部の要素がアプリケーションの観点からは等しいふりをしているのだ。</p>
</div>
<div class="section" id="section-4">
<h3><a class="toc-backref" href="#toc-entry-5">構造</a></h3>
<div class="figure">
<img alt="Figure 2" src="https://blog.tai2.net/images/hexagonal_architecture/2302.gif">
<p class="caption">図2</p>
</div>
<p>図2は、2つのアクティブなポートと、各ポートに複数のアダプターを持つアプリケーションを示している。2つのポートは、アプリケーション制御側と、データ取り出し側だ。この素描は、アプリケーションが、自動化されたシステムレベルの回帰テスト、人間のユーザー、リモートHTTPアプリケーション、あるいは、他のローカルアプリケーションから、同じように駆動されることを示している。データ側では、アプリケーションは、外部のデータベースから分離して実行されるよう構成することができる。これには、インメモリのオラクル(すなわち「モック」)データベースの置き換えを利用する。あるいは、テストまたはランタイムのデータベースに対して、動かすことができる。アプリケーションの機能的な仕様は、(もしかするとユースケース内で)内側の六角形のインターフェイスに対して作られるのであって、使われるかもしれない外部のテクノロジーに対してではない。</p>
<div class="figure">
<img alt="Figure 3" src="https://blog.tai2.net/images/hexagonal_architecture/2303.gif">
<p class="caption">図3</p>
</div>
<p>図3は、同じアプリケーションを3レイヤーのアーキテクチャーに対応付けた素描を示している。簡単のために、素描では、各ポートにつき2つのアダプターしか見せていない。この素描は、複数のアダプターが、いかにして上下のレイヤーに適合するか、そして、システム開発の間にいろいろなアダプターが使われるシーケンスを示すことを意図している。数字の付けられた矢印は、チームがアプリケーションの開発と使用をするかもしれない順番を示している。</p>
<ol class="arabic simple">
<li>FITテストハーネスを使ってアプリケーションを駆動する、そして、モック(インメモリ)データベースを実際のデータベースの代替として使う。</li>
<li>GUIをアプリケーションに追加しつつ、依然モックデータベースを使う。</li>
<li>統合テスト中、自動化されたテストスクリプト(例えばCruise Control<sup id="sf-hexagonal_architexture-3-back"><a href="#sf-hexagonal_architexture-3" class="simple-footnote" title="(訳注) CIツール http://cruisecontrol.sourceforge.net/ ">3</a></sup>から)で、アプリケーションをテストデータを保持した実際のデータベースに対して駆動させる。</li>
<li>実際の利用で、アプリケーションを使う人が、生きたデータベースにアクセスする。</li>
</ol>
</div>
<div class="section" id="section-5">
<h3><a class="toc-backref" href="#toc-entry-6">サンプルコード</a></h3>
<p>Ports & Adaptersのデモをするのにもっとも簡単なアプリケーションが、幸運にもFITのドキュメントに付いてくる。シンプルな割引計算のアプリケーションだ:</p>
<div class="highlight"><pre><span></span>discount(amount) = amount * rate(amount);
</pre></div>
<p>我々のバージョンでは、合計額はユーザーから、レートはデータベースから来るので、ポートは2つになるだろう。段階に分けて実装する:</p>
<ul class="simple">
<li>テストを使って、しかし、モックデータベースの代わりに定数レートで</li>
<li>それから、GUIを使って</li>
<li>それから、実際のデータベースと交換できるモックデータベースとを使って</li>
</ul>
<p>IHCのGyan Sharma、この例のコードを提供してくれてありがとう。</p>
<div class="section" id="fit">
<h4><a class="toc-backref" href="#toc-entry-7">ステージ1: FIT <span class="underline">アプリ</span> 定数をモックデータベースとして</a></h4>
<p>まずはじめに、テストケースをHTMLのtableとして作る(これについてはFITのドキュメントを見よ):</p>
<table>
<tbody><tr><th>TestDiscounter</th></tr>
<tr><th>amount</th><th>discount()</th></tr>
<tr><td>100</td><td>5</td></tr>
<tr><td>200</td><td>10</td></tr>
</tbody></table><p>カラム名が、我々のプログラムでは、クラスと関数名になることに注意。FITには、プログラマ的なスタイルを排除する方法があるが、この記事では、そのまま残しておくほうが簡単だ。</p>
<p>テストデータどんなものになるかわかったら、ユーザー側のアダプターを作る。FITといっしょに配布されているColumnFixtureだ:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">fit.ColumnFixture</span><span class="p">;</span>
<span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">TestDiscounter</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">ColumnFixture</span>
<span class="p">{</span>
<span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="n">Discounter</span><span class="w"> </span><span class="n">app</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Discounter</span><span class="p">();</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="n">amount</span><span class="p">;</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="nf">discount</span><span class="p">()</span>
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">app</span><span class="p">.</span><span class="na">discount</span><span class="p">(</span><span class="n">amount</span><span class="p">);</span><span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>実際のところ、ここにあるのは、すべてアダプタのためのものだ。これまでのところ、テストはコマンドラインから実行する(必要なパスについてはFITの本を見よ)。我々はこのようにした:</p>
<div class="highlight"><pre><span></span><span class="nb">set</span><span class="w"> </span><span class="nv">FIT_HOME</span><span class="o">=</span>/FIT/FitLibraryForFit15Feb2005
java<span class="w"> </span>-cp<span class="w"> </span>%FIT_HOME%/lib/javaFit1.1b.jar<span class="p">;</span>%FIT_HOME%/dist/fitLibraryForFit.jar<span class="p">;</span>src<span class="p">;</span>bin
fit.FileRunner<span class="w"> </span>test/Discounter.html<span class="w"> </span>TestDiscount_Output.html
</pre></div>
<p>FITは、出力ファイルを色付きで作成して、なにがパスしたのか見せてくれる(あるいは、どこかでtypoした場合には、なにが失敗したのか)。</p>
<p>この時点で、コードはチェックインし、Cruise Controlやあなたの自動ビルドマシンに仕込んで、ビルドおよびテストスイートに入れる準備ができている。</p>
</div>
<div class="section" id="ui">
<h4><a class="toc-backref" href="#toc-entry-8">ステージ2: UI <span class="underline">アプリ</span> 定数をモックデータベースとして</a></h4>
<p>わたしは、あなたに自身のUIを作って、それに割引アプリケーションを駆動させてもらうつもりだ。ここに入れるには少々長いコードになるからだ。コードのキーになる行は、このようなものだ:</p>
<div class="highlight"><pre><span></span><span class="p">...</span>
<span class="w"> </span><span class="n">Discounter</span><span class="w"> </span><span class="n">app</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Discounter</span><span class="p">();</span>
<span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">actionPerformed</span><span class="p">(</span><span class="n">ActionEvent</span><span class="w"> </span><span class="n">event</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="p">...</span>
<span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">amountStr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">text1</span><span class="p">.</span><span class="na">getText</span><span class="p">();</span>
<span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Double</span><span class="p">.</span><span class="na">parseDouble</span><span class="p">(</span><span class="n">amountStr</span><span class="p">);</span>
<span class="w"> </span><span class="n">discount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">app</span><span class="p">.</span><span class="na">discount</span><span class="p">(</span><span class="n">amount</span><span class="p">));</span>
<span class="w"> </span><span class="n">text3</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="w"> </span><span class="s">""</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">discount</span><span class="w"> </span><span class="p">);</span>
<span class="w"> </span><span class="p">...</span>
</pre></div>
<p>この時点で、アプリケーションは、デモと回帰テストができる。ユーザー側のアダプターは両方動いている。</p>
</div>
<div class="section" id="fitui">
<h4><a class="toc-backref" href="#toc-entry-9">ステージ3: (FITまたはUI) <span class="underline">アプリ</span> モックデータベース</a></h4>
<p>データベース側の置き換え可能なアダプターを作るために、リポジトリへの「インターフェイス」を作る。モックデータベースや実際のサービスオブジェクトを生成する「RepositoryFactory」と、データベースのインメモリモックだ。</p>
<div class="highlight"><pre><span></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">RateRepository</span>
<span class="p">{</span>
<span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="nf">getRate</span><span class="p">(</span><span class="kt">double</span><span class="w"> </span><span class="n">amount</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">RepositoryFactory</span>
<span class="p">{</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="nf">RepositoryFactory</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kd">super</span><span class="p">();</span><span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="n">RateRepository</span><span class="w"> </span><span class="nf">getMockRateRepository</span><span class="p">()</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">MockRateRepository</span><span class="p">();</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">MockRateRepository</span><span class="w"> </span><span class="kd">implements</span><span class="w"> </span><span class="n">RateRepository</span>
<span class="p">{</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="nf">getRate</span><span class="p">(</span><span class="kt">double</span><span class="w"> </span><span class="n">amount</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="n">amount</span><span class="w"> </span><span class="o"><=</span><span class="w"> </span><span class="mi">100</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mf">0.01</span><span class="p">;</span>
<span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="n">amount</span><span class="w"> </span><span class="o"><=</span><span class="w"> </span><span class="mi">1000</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mf">0.02</span><span class="p">;</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mf">0.05</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>このアダプターを割引アプリケーションに仕込むために、使用するリポジトリアダプターを受け入れるように、アプリケーション自体を更新する必要がある。そして、(FITまたはUI)ユーザー側アダプターに、使用するリポジトリ(実またはモック)をアプリケーション自体のコンストラクタへと渡させる。これが、更新されたアプリケーションと、モックリポジトリを渡すFITアダプターだ(モックか実リポジトリのアダプターどちらを渡すのか選べるFITアダプターのコードは、長いわりに、新しい情報が増えるわけでもないので、ここではそのバージョンは省略する)。</p>
<div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">repository.RepositoryFactory</span><span class="p">;</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">repository.RateRepository</span><span class="p">;</span>
<span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Discounter</span>
<span class="p">{</span>
<span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="n">RateRepository</span><span class="w"> </span><span class="n">rateRepository</span><span class="p">;</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="nf">Discounter</span><span class="p">(</span><span class="n">RateRepository</span><span class="w"> </span><span class="n">r</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">super</span><span class="p">();</span>
<span class="w"> </span><span class="n">rateRepository</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">r</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="nf">discount</span><span class="p">(</span><span class="kt">double</span><span class="w"> </span><span class="n">amount</span><span class="p">)</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="n">rate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rateRepository</span><span class="p">.</span><span class="na">getRate</span><span class="p">(</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="p">);</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">rate</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">app.Discounter</span><span class="p">;</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">fit.ColumnFixture</span><span class="p">;</span>
<span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">TestDiscounter</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">ColumnFixture</span>
<span class="p">{</span>
<span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="n">Discounter</span><span class="w"> </span><span class="n">app</span><span class="w"> </span><span class="o">=</span>
<span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Discounter</span><span class="p">(</span><span class="n">RepositoryFactory</span><span class="p">.</span><span class="na">getMockRateRepository</span><span class="p">());</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="n">amount</span><span class="p">;</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">double</span><span class="w"> </span><span class="nf">discount</span><span class="p">()</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">app</span><span class="p">.</span><span class="na">discount</span><span class="p">(</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>これで、もっとも簡単なバージョンのヘキサゴナルアーキテクチャの実装を終える。</p>
<p>RubyとRackをブラウザの用例に使った異なる実装としては、<a class="reference external" href="https://github.com/totheralistair/SmallerWebHexagon">https://github.com/totheralistair/SmallerWebHexagon</a> を見よ。</p>
</div>
</div>
<div class="section" id="section-6">
<h3><a class="toc-backref" href="#toc-entry-10">応用ノート</a></h3>
<div class="section" id="section-7">
<h4><a class="toc-backref" href="#toc-entry-11">左右の非対称性</a></h4>
<p>ports and adaptersパターンは、意図的に、すべてのポートが基本的に類似しているふりをしながら書かれている。このようなふりをすることは、アーキテクチャレベルで有益だ。実装においては、ポートとアダプターには2種類のものがあることがわかる。すぐに明らかになる理由から、わたしが、「プライマリ」と「セカンダリ」と呼ぶものだ。これらは、「駆動する」アダプターと「駆動される」アダプターと呼ばれることもある。</p>
<p>懸命な読者は気付くだろうが、与えられた例ではすべて、FITフィクスチャは左側にあり、モックが右側にある。3層アーキテクチャでは、FITは、層の最上位にあり、モックは最下層にある。</p>
<p>これは、「プライマリアクター」と「セカンダリアクター」のユースケースから来たアイデアと関連する。「プライマリアクター」は、アプリケーションを駆動するアクターだ(アプリケーションの公開している機能のひとつを実行させるために、アクティブでない状態から起こす)。「セカンダリアクター」は、アプリケーションが駆動するもので、そこから解答を得るか、単に通知する。「プライマリ」と「セカンダリ」の違いは、だれが起動するのか、あるいは、だれが会話の責任を持つのか、ということだ。</p>
<p>「プライマリ」アクターを置き換えるのに自然なテスト用アダプターは、FITだ。このフレームワークは、スクリプトを読んで、アプリケーションを駆動するよう設計されたものだからだ。データベースのような「セカンダリ」アクターを置き換えるのに自然なテスト用アダプターは、モックだ。モックは、問合せに答えたり、アプリケーションからのイベントを記録するために設計されたものだからだ。</p>
<p>これらの観測から導かれるのは、システムのユースケース文脈図に従い、「プライマリポート」と「プライマリアダプター」を六角形の左側(ないし上側)に、「セカンダリポート」と「セカンダリアダプター」を六角形の右側(ないし下側)に描くということだ。</p>
<p>プライマリとセカンダリのポート・アダプター間の関係と、FITとモックでの対応する実装は、覚えておいて損はないが、それは、ports and adaptersアーキテクチャを使うことの帰結として使われるべきなのであって、一足飛びにそこにいくべきではない。ports and adapters実装の究極の便益は、アプリケーションを完全に隔離されたモードで動かすことができるということだ。</p>
</div>
<div class="section" id="section-8">
<h4><a class="toc-backref" href="#toc-entry-12">ユースケースとアプリケーションの境界</a></h4>
<p>ヘキサゴナルアーキテクチャパターンを使って、ユースケースを書く好ましいやりかたを強めるのは、有用だ。 よくある間違いは、ユースケースを書いた結果、各ポートの外側にある技術の親密な知識が入ってしまうことだ。こういったユースケースは、正当にも、長いあいだ業界で悪名を得てきた。読み辛い、退屈、壊れやすい、そして、保守が高くつく。</p>
<p>port and adaptersアーキテクチャを理解すると、ユースケースは、一般にアプリケーション境界(六角形の内側)で書かれるべきということがわかる。外部のテクノロジーと無関係に、アプリケーションによってサポートされた機能やイベントを指定するためだ。これらのユースケースは、短く、読み易く、保守が安く済み、時間が経っても、より安定していられる。</p>
</div>
<div class="section" id="section-9">
<h4><a class="toc-backref" href="#toc-entry-13">ポートはいくつ?</a></h4>
<p>なにがポートで、なにがそうでないかは、ほとんど好みの問題だ。もっとも極端なものは、すべてのユースケースが、それ自身のポートを与えられて、たくさんのアプリケーションのために数百のポートを作るというものだ。別のものとして、すべてのプライマリポートと、すべてのセカンダリポートを合わせて、左側と右側の2つのポートだけにするということも想像できる。</p>
<p>どちらの極端な例も最適とは思われない。</p>
<p>既知の用例で説明する天気システムには、4つの自然なポートがある: 天気フィード、管理者、通知を受ける購読者、購読者のデータベースだ。コーヒーメーカーのコントローラーは、4つの自然なポートを持つ: ユーザー、レシピと価格を保持するデータベース、抽出口、そして硬貨箱だ。病院の医薬システムなら3つかもしれない: 看護婦のためのもの、処方箋データベースのためのもの、そして、コンピューター制御の薬受取機のためのもの。</p>
<p>「間違った」ポートの数を選んだとしても、とくだんダメージがあるようには思われない、なのでこれは直感の問題として残される。わたしの選択は、2,3,4ポートの小さい数字を好む傾向がある。これは上記や、既知の用例で説明される通りだ。</p>
</div>
<div class="section" id="section-10">
<h4><a class="toc-backref" href="#toc-entry-14">既知の用例</a></h4>
<div class="figure">
<img alt="Figure 4" src="https://blog.tai2.net/images/hexagonal_architecture/2304.gif">
<p class="caption">図4</p>
</div>
<p>図4は、4つのポートと、各ポートに複数のアダプターを持つアプリケーションを示している。これは、国立気象局からの、地震、竜巻、家事と洪水についての警報を聴取し、電話や留守番電話で人々に通知するアプリケーションに由来した。このシステムについて議論したとき、システムのインターフェイスは、「目的と結びついた技術」によって特定され、議論された。そこには、有線で届くトリガーデータのためのインターフェイスがあった。それは、留守番電話に送られる通知データのためのインターフェイス、GUIで実装された管理インターフェイス、そして、購読者データを取得するためのデータベースインターフェイスだった。</p>
<p>人々は奮闘していた、なぜなら、気象局からのHTTPインターフェイス、購読者へのEメールインターフェイスを追加する必要があったからだ、そして、成長するアプリケーションスイートを異なる顧客購買嗜好のために組み合わたり、分割する方法を見付けなければならなかった。かれらが目の前にある保守とテストの悪夢に恐怖したのは、別のバージョンをすべての組合わせと順列のために実装、テストそして保守しなければならなかったからだ。</p>
<p>かれらの設計上の変化は、システムのインターフェイスを、技術というよりは「目的」から組織し、そして、技術をアダプターによって(すべての側面において)置き換え可能にするということだった。即座に、HTTPフィードとEメール通知の能力を入れられることに気付いた(新しいアダプターは、図の中で点線とともに描かれている)。各アプリケーションをAPIを通じてヘッドレスモードで実行できるようにすることで、アプリ追加アダプターを追加して、サブアプリケーションを必要に応じて接続し、アプリケーションスイートをばらすことができた。最後に、テストとモックアダプターを適切に配置し、各アプリケーションを完全に隔離環境で実行できるようにすることで、スタンドアローンの自動化されたスクリプトで、アプリケーションを回帰テストできる能力を得た。</p>
</div>
<div class="section" id="mac-windows-google-flickr-web-2-0">
<h4><a class="toc-backref" href="#toc-entry-15">Mac, Windows, Google, Flickr, Web 2.0</a></h4>
<p>1990年代初頭、ワープロアプリケーションのようなMachintoshアプリケーションは、API駆動のインターフェイスを備える必要があった。アプリケーションとユーザーの書いたスクリプトが、アプリケーションの全機能にアクセスできるようにするためだ。Windowsデスクトップアプリケーションも同じ能力を進化させてきた(どちらが先だったか言えるような歴史的知識は持ち合わせていないが、どちらだろうが、話の要点とは関係ない)。</p>
<p>現在(2005年)のウェブアプリケーションにおけるトレンドは、APIを公開して、他のウェブアプリケーションが直接それらのAPIにアクセスできるようにすることだ。ゆえに、地域の犯罪データをGoogleマップを通じて公開することや、Flickrの写真をアーカイブしたり注釈をつけたりする能力を持ったウェブアプリケーションを作成することが可能だ。</p>
<p>これらは、どれも「プライマリ」ポートのAPIを可視化することについての例だ。セカンダリポートについての情報は、ここには見られない。</p>
</div>
<div class="section" id="section-11">
<h4><a class="toc-backref" href="#toc-entry-16">ストアード出力</a></h4>
<p>この例は、C2 wikiで、 Willem Bogaertsによって書かれた:</p>
<p>「わたしも似たようなことに遭遇したが、それは主に、アプリケーションレイヤーが、管理すべきでないものまで管理する一種の電話交換機になってしまう、強い傾向を持っていたからだった。アプリケーションは出力を生成し、ユーザーに表示して、その後、出力を保存する可能性もあった。主な問題は、常に保存する必要はない、ということだった。だから、アプリケーションは出力を生成し、バッファしてからユーザーに表示しなければならなかった。そして、ユーザーが出力を保存することを決めたら、アプリケーションはバッファを取り出し、それを実際に保存する。</p>
<p>わたしは、これがまったく好きではなかった。そして、解決法が受かんだ: ストレージ機能付きの表示制御部を持つということだ。もはや、アプリケーションは、出力を異なる方向に向けないのみならず、単に表示制御部に出力する。答えをバッファして、ユーザーに保存の機会を与えるのは、表示制御部だ。</p>
<p>伝統的なレイヤー構造のアーキテクチャは、『UI』と『ストレージ』を異なるものとして強調する。Port and Adapterアーキテクチャは、出力が、単に再度『出力』されるよう強制できる。」</p>
</div>
<div class="section" id="c2-wiki">
<h4><a class="toc-backref" href="#toc-entry-17">C2-wikiからの匿名の例</a></h4>
<p>「わたしが働いていたあるプロジェクトでは、コンポーネントステレオシステムのシステムメタファーを使っていた。各コンポーネントには、定義されたインターフェイスがあり、それぞれが特定の目的を持っていた。すると、簡単なケーブルとアダプターを使って、ほとんど制限なくコンポーネントを接続することができる」</p>
</div>
<div class="section" id="section-12">
<h4><a class="toc-backref" href="#toc-entry-18">分散された、大きなチームでの開発</a></h4>
<p>これは、まだ試験的な用法なので、このパターンの用例として入れるのは、おそらく適切ではない。しかしながら、考えてみるのはおもしろい。</p>
<p>別の地域にあるチームが、全員ヘキサゴナルアーキテクチャを構築する。チームは、アプリケーションあるいはコンポーネントが、スタンドアロンモードでテストできるように、FITとモックを使う。Cruise Controlのビルドは30分ごとに走り、すべてのアプリケーションを FITとモックの組合せで走らせる。アプリケーションサブシステムとデータベースが完璧になったら、モックがテストデータベースと置き換えられる。</p>
</div>
<div class="section" id="ui-1">
<h4><a class="toc-backref" href="#toc-entry-19">UIとアプリケーションロジックの開発を分割する</a></h4>
<p>これは、まだ早期のトライアルなので、このパターンの用例として数には入れられない。しかしながら、考えてみるのはおもしろい。</p>
<p>UIデザインが不安定なのは、駆動する技術やメタファーをまだ決めていないからだ。バックエンドサービスアーキテクチャは、未決定で、実際、次の数ヶ月で何度か変わるかもしれない。にもかかわらず、プロジェクトは公式に開始され、時間は過ぎていく。</p>
<p>アプリケーションチームは、アプリケーションを隔離し、そして、テスト可能で、デモ可能な機能をユーザーに見せるために、FITテストとモックを作成する。UIとバックエンドサービスが最終的に決まるころには、それらの要素をアプリケーションに追加するのは、「容易であるべき」だ。これがどう機能するのか学びたければ、乞うご期待(もしくは、自分で試して、わたしに教えるために書くとか)。</p>
</div>
</div>
<div class="section" id="section-13">
<h3><a class="toc-backref" href="#toc-entry-20">関連するパターン</a></h3>
<div class="section" id="adapter">
<h4><a class="toc-backref" href="#toc-entry-21">Adapter</a></h4>
<p>「デザインパターン」本は、一般的な「Adapter」パターンの説明を収録している: 「クラスのインターフェイスを、クライアントが期待する異なったインターフェイスに変換する」 ports and adaptersパターンは、「Adapter」パターンのひとつの用例だ。</p>
</div>
<div class="section" id="model-view-controller">
<h4><a class="toc-backref" href="#toc-entry-22">Model-View-Controller</a></h4>
<p>MVCパターンは、1974の早い時期にSmalltalkプロジェクトで実装された。何年にも渡り、Model-InteractorやModel-View-Presenterのような、さまざまなバリエーションが供されてきた。いずれも、ports and adaptersの、セカンダリポートではなく、プライマリポートを実装している。</p>
</div>
<div class="section" id="mock-objects-loopback">
<h4><a class="toc-backref" href="#toc-entry-23">Mock Objects と Loopback</a></h4>
<p>モックオブジェクトは、他のオブジェクトの挙動をテストするための"2重のエージェント"だ。はじめに、モックオブジェクトは、インターフェイスやクラスの擬似的な実装として振舞い、ほんとうの実装の外向けの振舞いを模倣する。二番目に、モックオブジェクトは、他のオブジェクトが、そのメソッドとどのようにやりとするかを監視し、規定の、期待される実際の振舞いと比較する。齟齬が起きると、モックオブジェクトは、テストに割り込んで、状況を報告することができる。テスト中齟齬が発見されなければ、テスターから呼ばれた検証メソッドは、すべて期待と合致したことを保証する。さもなくば、失敗が報告される。 <a class="reference external" href="http://MockObjects.com">http://MockObjects.com</a> より。</p>
<p>モックオブジェクトのアジェンダに沿って完全に実装されるなら、モックオブジェクトは、外部インターフェイスのみにとどまらず、アプリケーション全体を通して利用される。モックオブジェクトムーブメントの主要な論点は、個別のクラスとオブジェクトレベルで、指定されたプロトコロルを満たせるということだ。わたしは、彼等の「モック」という語を、外部のセカンダリの役割を演じるものへの、インメモリーな代替の、最も簡単な説明として借用している。</p>
<p>Loopbackパターンは、外部デバイスのための内部の代替を作成する、明示的なパターンだ。</p>
</div>
<div class="section" id="pedestals">
<h4><a class="toc-backref" href="#toc-entry-24">Pedestals</a></h4>
<p>「Patterns for generating a layers architecture」の中で、Barry Rubelは、制御ソフトウェアにおいて対象な軸を作ることについてのパターンを記述した。これは、ports and adaptersに非常に似ている。「Pedestal」<sup id="sf-hexagonal_architexture-4-back"><a href="#sf-hexagonal_architexture-4" class="simple-footnote" title="(訳注) 台座、という意味">4</a></sup>パターンは、システムの各ハードウェアデバイスを表すオブジェクトの実装を必要とし、それらのオブジェクトを制御レイヤーで繋ぐ。「Pedestal"パターンは、ヘキサゴナルアーキテクチャのどちらかの側を記述するのに使えるが、アダプター間の類似性をまだ強調してはいない。また、機械制御環境のために書かれており、ITアプリケーションにこのパターンを適用するのは、それほど容易ではない。</p>
</div>
<div class="section" id="checks">
<h4><a class="toc-backref" href="#toc-entry-25">Checks</a></h4>
<p>Ward Cunninghamのユーザー入力エラーを検出し扱うためのパターン言語で、内側の六角形境界をまたがってエラーハンドリングするのに良い。</p>
</div>
<div class="section" id="dependency-inversion-dependency-injection-spring">
<h4><a class="toc-backref" href="#toc-entry-26">Dependency Inversion (Dependency Injection) と SPRING</a></h4>
<p>Bob Martin の依存関係逆転の原則(Martin Fowlerからは、依存性注入(Dependency Injection)とも呼ばれている)は、「高レベルのモジュールは、低レベルのモジュールに依存すべきでない。ともに、抽象に依存すべきだ。抽象は、詳細に依存すべきではない。詳細が抽象に依存すべきだ」と述べている。Martin Fowlerによる「Dependency Injection」パターンは、いくらか実装を与えている。これらは、入れ替え可能な、セカンダリーアクターアダプターをいかにして作成するかを示す。コードは、この記事のサンプルコードのように、直接型付けすることができる。あるいは、設定ファイルを使って、SPRINGフレームワークに同等のコードを生成させるやりかたがある。</p>
</div>
</div>
<div class="section" id="section-14">
<h3><a class="toc-backref" href="#toc-entry-27">謝辞</a></h3>
<p>Intermountain Health CareのGyan Sharma、ここで使ったサンプルコードを提供してくれてありがとう。 書籍「Object Design」のRebecca Wirfs-Brockありがとう。この本を「デザインパターン」本の「Adapter」パターンといっしょに読むことで、六角形がなんであるのかを理解する助けになった。Ward’s wikの人々もありがとう。彼等は、何年にもわたって、パターンについてコメントを提供してくれた(とくに、 Kevin Rutherfordの <a class="reference external" href="http://silkandspinach.net/blog/2004/07/hexagonal_soup.html">http://silkandspinach.net/blog/2004/07/hexagonal_soup.html</a>)</p>
</div>
<div class="section" id="section-15">
<h3><a class="toc-backref" href="#toc-entry-28">リファレンスと、関連した読み物</a></h3>
<ul class="simple">
<li>FIT, A Framework for Integrating Testing: Cunningham, W., online at <a class="reference external" href="http://fit.c2.com">http://fit.c2.com</a>, and Mugridge, R. and Cunningham, W., ‘’Fit for Developing Software’’, Prentice-Hall PTR, 2005.</li>
<li>The ‘’Adapter’’ pattern: in Gamma, E., Helm, R., Johnson, R., Vlissides, J., ‘’Design Patterns’’, Addison-Wesley, 1995, pp. 139-150.</li>
<li>The ‘’Pedestal’’ pattern: in Rubel, B., “Patterns for Generating a Layered Architecture”, in Coplien, J., Schmidt, D., ‘’PatternLanguages of Program Design’’, Addison-Wesley, 1995, pp. 119-150.</li>
<li>The ‘’Checks’’ pattern: by Cunningham, W., online at <a class="reference external" href="http://c2.com/ppr/checks.html">http://c2.com/ppr/checks.html</a></li>
<li>The ‘’Dependency Inversion Principle’‘: Martin, R., in ‘’Agile Software Development Principles Patterns and Practices’’, Prentice Hall, 2003, Chapter 11: “The Dependency-Inversion Principle”, and online at <a class="reference external" href="http://www.objectmentor.com/resources/articles/dip.pdf">http://www.objectmentor.com/resources/articles/dip.pdf</a></li>
<li>The ‘’Dependency Injection’’ pattern: Fowler, M., online at <a class="reference external" href="http://www.martinfowler.com/articles/injection.html">http://www.martinfowler.com/articles/injection.html</a></li>
<li>The ‘’Mock Object’’ pattern: Freeman, S. online at <a class="reference external" href="http://MockObjects.com">http://MockObjects.com</a></li>
<li>The ‘’Loopback’’ pattern: Cockburn, A., online at <a class="reference external" href="http://c2.com/cgi/wiki?LoopBack">http://c2.com/cgi/wiki?LoopBack</a></li>
<li>‘’Use cases:’’ Cockburn, A., ‘’Writing Effective Use Cases’’, Addison-Wesley, 2001, and Cockburn, A., “Structuring Use Cases with Goals”, online at <a class="reference external" href="http://alistair.cockburn.us/crystal/articles/sucwg/structuringucswithgoals.htm">http://alistair.cockburn.us/crystal/articles/sucwg/structuringucswithgoals.htm</a></li>
</ul>
</div>
</div>
<ol class="simple-footnotes"><li id="sf-hexagonal_architexture-1">(訳注) GUIなしバージョン <a href="#sf-hexagonal_architexture-1-back" class="simple-footnote-back">↩</a></li><li id="sf-hexagonal_architexture-2">(訳注) MS Wordなどで作成したHTMLのテーブルとして記述されたフィクスチャを元にテストケースを自動生成して走らせるツール。顧客のドメイン知識を活用して、早期から開発に参加してもらうことができる。<a class="reference external" href="http://fit.c2.com/wiki.cgi?IntroductionToFit">http://fit.c2.com/wiki.cgi?IntroductionToFit</a> <a href="#sf-hexagonal_architexture-2-back" class="simple-footnote-back">↩</a></li><li id="sf-hexagonal_architexture-3">(訳注) CIツール <a class="reference external" href="http://cruisecontrol.sourceforge.net/">http://cruisecontrol.sourceforge.net/</a> <a href="#sf-hexagonal_architexture-3-back" class="simple-footnote-back">↩</a></li><li id="sf-hexagonal_architexture-4">(訳注) 台座、という意味 <a href="#sf-hexagonal_architexture-4-back" class="simple-footnote-back">↩</a></li></ol>クリーンアーキテクチャ(The Clean Architecture翻訳)2015-10-05T00:00:00+09:002015-10-05T00:00:00+09:00tai2tag:blog.tai2.net,2015-10-05:/the_clean_architecture.html<p class="first last">Robert Martin (a.k.a.ボブおじさん) による、The Clean Architecture の翻訳です。</p>
<p><a class="reference external" href="https://twitter.com/unclebobmartin">Robert Martin (a.k.a. ボブおじさん)</a> による、 <a class="reference external" href="http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html">The Clean Architecture</a> の翻訳です。似たようなアーキテクチャである <a class="reference external" href="https://blog.tai2.net/hexagonal_architexture.html">ヘキサゴナルアーキテクチャ</a> も翻訳したので参考にしてください。</p>
<p>この記事を翻訳して公開したことは <a class="reference external" href="http://8thlight.com/">8th Light, Inc.</a> に報告済みです。いまのところ苦情は来ていません。</p>
<hr class="docutils" />
<div class="figure">
<img alt="The Clean Architecture" src="https://blog.tai2.net/images/CleanArchitecture.jpg" />
</div>
<p>ここ数年以上、システムのアーキテクチャに関する実にさまざまなアイデアを見てきた。これには、次のものが含まれる:</p>
<ul class="simple">
<li><a class="reference external" href="http://alistair.cockburn.us/Hexagonal+architecture">Hexagonal Architecture</a> (別名 Ports and Adapters) by Alistair Cockburn。Steve FreemanとNat Pryceが、<a class="reference external" href="http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627">Growing Object-Oriented Software</a> というすばらしい本で採用した。</li>
<li><a class="reference external" href="http://jeffreypalermo.com/blog/the-onion-architecture-part-1/">Onion Architecture</a> by Jeffrey Palermo</li>
<li><a class="reference external" href="http://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html">Screaming Architecture</a> わたしの去年のブログより。</li>
<li><a class="reference external" href="http://www.amazon.com/Lean-Architecture-Agile-Software-Development/dp/0470684208/">DCI</a> James CoplienとTrygve Reenskaugより。</li>
<li><a class="reference external" href="http://www.amazon.com/Object-Oriented-Software-Engineering-Approach/dp/0201544350">BCE</a> Ivar Jacobsonの本、Object Oriented Software Engineering: A Use-Case Driven Approach より。</li>
</ul>
<p>これらのアーキテクチャはどれも細部は異なるけれども、とてもよく似ている。これらはいずれも同じ目的を持っている。関心の分離だ。これらはいずれも、ソフトウェアをレイヤーに分けることによって、関心の分離を達成する。どれも、最低ひとつは、ビジネスルールのためのレイヤーと、インターフェイスのためのレイヤーがある。</p>
<p>これらのアーキテクチャは、いずれも、次のようなシステムを生み出す:</p>
<ol class="arabic simple">
<li>フレームワーク独立。アーキテクチャは、機能満載のソフトウェアのライブラリが手に入ることには依存しない。これは、そういったフレームワークを道具として使うことを可能にし、システムをフレームワークの限定された制約に押し込めなければならないようなことにはさせない。</li>
<li>テスト可能。ビジネスルールは、UI、データベース、ウェブサーバー、その他外部の要素なしにテストできる。</li>
<li>UI独立。UIは、容易に変更できる。システムの残りの部分を変更する必要はない。たとえば、ウェブUIは、ビジネスルールの変更なしに、コンソールUIと置き換えられる。</li>
<li>データベース独立。OracleあるいはSQL Serverを、Mongo, BigTable, CoucheDBあるいは他のものと交換することができる。ビジネスルールは、データベースに拘束されない。</li>
<li>外部機能独立。実際のところ、ビジネスルールは、単に外側についてなにも知らない。</li>
</ol>
<p>記事冒頭の図は、これらのアーキテクチャを単一の概念に無理なく統合する試みである。</p>
<div class="section" id="section-1">
<h2>依存ルール</h2>
<p>これらの同心円は、ソフトウェアの異なる領域を表している。一般に、内側にいくほど、ソフトウェアは高レベルになる。外側の円はメカニズムで、内側の円は方針だ。</p>
<p>このアーキテクチャを機能させる重要なルールが、依存ルールだ。このルールにおいては、ソースコードは、内側に向かってのみ依存することができる。内側の円は、外側の円についてなにも知ることはない。とくに、外側の円で宣言されたものの名前を、内側の円から言及してはならない。これは、関数、クラス、変数、あるいはその他、名前が付けられたソフトウェアのエンティティすべてに言える。</p>
<p>同様に、外側の円で使われているデータフォーマットを内側の円で使うべきではない。とくに、それらのフォーマットが、外側の円でフレームワークによって生成されているのであれば。外側の円のどんなものも、内側の円に影響を与えるべきではないのだ。</p>
</div>
<div class="section" id="section-2">
<h2>エンティティー</h2>
<p>エンティティーは、大規模プロジェクトレベルのビジネスルールをカプセル化する。エンティティは、メソッドを持ったオブジェクトかもしれない、あるいは、データ構造と関数の集合かもしれない。エンティティが、大規模プロジェクト内で、たくさんの異なるアプリケーションから使われるのであれば、どちらでも問題ない。</p>
<p>大規模プロジェクトではなく、ひとつのアプリケーションを書いているだけであれば、エンティティは、そのアプリケーションのビジネスオブジェクトである。それらは、もっとも一般的で高レベルなルールをカプセル化する。それらは、外側のなにかが変わっても、変わらなさそうなものだ。たとえば、それらのオブジェクトは、ページナビゲーションの変更やセキュリティからの影響を受けないことが期待できる。アプリケーションの動作への変更が、エンティティーレイヤーに影響を与えるべきではない。</p>
</div>
<div class="section" id="section-3">
<h2>ユースケース</h2>
<p>このレイヤーのソフトウェアは、アプリケーション固有のビジネスルールを含む。このレイヤーは、システムのユースケースすべてをカプセル化および実装する。これらのユースケースは、エンティティからの、あるいはエンティティーへのデータの流れを組み立てる。そして、エンティティ、プロジェクトレベルのビジネスルールを使って、ユースケースの目的を達成せよと指示する。</p>
<p>このレイヤーの変更は、エンティティーには影響を与えないことを期待する。このレイヤーが、データベース、UIあるいは、共通のフレームワークの変更から影響を受けないことも期待する。このレイヤーは、そういった関心からは隔離される。</p>
<p>しかしながら、アプリケーションの操作への変更は、ユースケースに、つまりは、このレイヤーのソフトウェアに影響することを期待する。ユースケースの詳細が変われば、このレイヤーのコードは、確実に影響を受ける。</p>
</div>
<div class="section" id="section-4">
<h2>インターフェイスアダプター</h2>
<p>このレイヤーのソフトウェアは、アダプターの集合だ。これは、ユースケースとエンティティにもっとも便利な形式から、データベースやウェブのような外部の機能にもっとも便利な形式に、データを変換する。たとえば、このレイヤーは、GUIのMVCアーキテクチャを完全に内包するだろう。プレゼンター、ビュー、そしてコントローラーは、すべてここに属す。モデルは、コントローラーからユースケースに渡され、そして、ユースケースからプレゼンターやビューに戻される、単なるデータ構造である可能性が高い。</p>
<p>同じように、データはこのレイヤーで、エンティティーやユースケースにもっとも便利な形から、どんな永続化フレームワークが使われているにしろ、それにとってもっとも便利な形に変換される。例えば、データベースなど。この円よりも内側のコードは、データベースについてなにも知るべきではない。もしこのデータベースがSQLデータベースであるならば、どんなSQLであれ、このレイヤーに、もっと言うと、このレイヤーの中のデータベースに関連した部分に、制限されるべきだ。</p>
<p>また、このレイヤーには、その他すべてのアダプターもある。それらは、外部の形式(たとえば外部サービス)から、ユースケースとエンティティーで使われる内部形式にデータを変換するために必要なものだ。</p>
</div>
<div class="section" id="section-5">
<h2>フレームワークとドライバー</h2>
<p>一番外側のレイヤーは、一般に、フレームワークやツールから構成される。データベースやウェブフレームワークなどだ。一般に、このレイヤーには、多くのコードは書かない。ただし、ひとつ内側の円と通信するつなぎのコードは、ここに含まれる。</p>
<p>このレイヤーには、詳細がなにもかも詰め込まれる。ウェブは、詳細だ。データベースは、詳細だ。これのものが悪影響を与えることのないように、外側に保っておく。</p>
</div>
<div class="section" id="section-6">
<h2>4つの円じゃないとダメなの?</h2>
<p>いや、この円は、コンセプトを伝えるための方便だ。これらの4つ以外が欲しくなる可能性はある。ちょうど4つでなければいけないという決まりはない。しかしながら、依存ルールは、常に適用される。ソースコードの依存性は、常に内側に向かう。内側に移るにつれて、抽象化のレベルは上がる。一番外側の円は、低レベルで具体的な詳細だ。内側に移るにつれて、ソフトウェアは抽象的になっていき、高レベルの方針をカプセル化する。一番内側の円は、もっとも一般性がある。</p>
</div>
<div class="section" id="section-7">
<h2>境界をまたがる</h2>
<p>右下の図は、どのように円の境界をまたがるのかの例だ。これは、コントローラーとプレゼンターが、次のレイヤーのユースケースと通信する様子を示している。制御の流れに注意して欲しい。コントローラーからはじまり、ユースケースを抜けて、プレゼンターで実行されることがわかる。ソースコードの依存性にも注意。いずれも、内側のユースケースを向いている。</p>
<p>われわれは、この明らかな矛盾を <a class="reference external" href="https://en.wikipedia.org/wiki/Dependency_inversion_principle">依存関係逆転の原則(Dependency Inversion Principle)</a> で解決することが多い。たとえば、Javaのような言語では、インターフェイスと継承関係を組み合わせて、ソースコードの依存性が、境界をまたがった右隣の点の制御の流れとは、逆になるようにするだろう。</p>
<p>たとえば、ユースケースがプレゼンターを呼び出す必要がある場合を考えてみよう。しかしながら、この呼び出しは、直接行われるべきではない。なぜなら、依存性ルール:外側の名前を、内側から言及することはできない、に違反するからだ。なので、ユースケースには、内側の円にあるインターフェイス(Use Case Output Portと書かれている)を呼ばせる。そして、円の外側のプレゼンターには、それを実装させる。</p>
<p>まったく同じテクニックが、アーキテクチャーの境界をまたがる、いたるところで使われる。動的な多体のアドバンテージを利用して、ソースコードの依存性が制御の流れの逆になるように作る。そうすれば、制御の流れがどこに入り込もうとも、依存性ルールを満たすことができる。</p>
</div>
<div class="section" id="section-8">
<h2>どんなデータが境界をまたがるの?</h2>
<p>典型的には、境界をまたがるデータは、シンプルなデータ構造だ。基本的な構造体や、シンプルなデータ転送オブジェクト(Data Transfer object)を好みに応じて使うことができる。あるいは、データは、単純に関数の引数でも良い。または、それをハッシュマップにしても良いし、オブジェクトとして構築しても良い。重要なことは、隔離された、シンプルなデータ構造が、境界をまたがって渡されるということだ。われわれは、ズルをして、エンティティやデータベースの行を渡すべきではない。データ構造が、依存性ルールに抵触するような依存性を持つべきではない。</p>
<p>たとえば、多くのデータベースフレームワークは、クエリーに応答して便利なデータフォーマットを返す。これをRowStructure(行構造)と呼ぶとしよう。この行構造を境界をまたがって内側に渡すべきではない。それは、依存性ルールに違反する。なぜなら、内側の円に外側の円についてなにがしかを知ることを強制するからだ。</p>
<p>なので、境界をまたがってデータを渡すときには、常に、内側の円にとって扱いやすい形式になる。</p>
</div>
<div class="section" id="section-9">
<h2>結論</h2>
<p>これらの簡単なルールに従うのは、難しいことではない。そして、頭痛がひどくなるのを防いでくれるだろう。ソフトウェアをレイヤーに分けることで、そして、依存性ルールに従うことで、本質的にテストしやすいシステムを作れるし、依存性ルールがもたらす恩恵ものきなみ受けられるだろう。システムの外部パーツ(データベースやウェブフレームワークなど)が古くなったら、それらの古臭い要素を、最小の取り組みで置き換えられる。</p>
</div>
AndroidアプリのSquare風MVP仕立て 〜Dagger 2をそえて〜2015-09-30T00:00:00+09:002015-09-30T00:00:00+09:00tai2tag:blog.tai2.net,2015-09-30:/android_mvp.html<p class="first last">本稿では、FlowとMortarにDIライブラリであるDagger 2を組み合わせて、Androidアプリを構築する方法について説明します。また、これらのライブラリがどのような仕組みで動いているのかも解説します。</p>
<p>Androidアプリプログラミングで、ある程度経験を積んだ開発者なら、Fragmentにまつわる操作で不意に発生するIllegalStateExceptionには、いくどとなく苦しめられたことがあるでしょう。
<a class="reference external" href="http://developer.android.com/intl/ja/guide/components/fragments.html">Fragment</a> は、スマートフォンのためのOSから、タブレットなどより幅広いスクリーンに対応できるマルチデバイスなOSに進化するために、Android 3.0で登場したコンポーネントです。
Fragmentを利用すれば、画面をいくつかの要素に分割して、それぞれをMVCで構築し再利用するという、 <a class="reference external" href="http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html">Smalltalk-80のMVC的な方法論</a> が可能になります。
一方、いまでは広く認められていることですが、Fragmentのライフサイクルは <a class="reference external" href="https://github.com/xxv/android-lifecycle">よく見てみると複雑</a> で、足をすくわれがちです。
そこで、 Fragmentに対するカウンターとして、 <a class="reference external" href="https://squareup.com/jp">Square</a> は、FlowとMortarという2つのライブラリを開発し、MVPアーキテクチャによるAndroidプログラミングを <a class="reference external" href="https://corner.squareup.com/2014/10/advocating-against-android-fragments.html">提唱しました</a> 。<sup id="sf-android_mvp-1-back"><a href="#sf-android_mvp-1" class="simple-footnote" title="筆者自身、Fragmentを使わずともViewで十分なのではないかという 疑念を抱いていた ので、この主張はスッと飲み込めるものでした。">1</a></sup></p>
<p>本稿では、FlowとMortarにDIライブラリであるDagger 2を組み合わせて、Androidアプリを構築する方法について説明します。
合わせて、FlowとMortarがどのような仕組みで動いているのかも学びます。</p>
<p>これから説明するMVPプログラミングは、ユニットテストをしやすくするために、オリジナルのSquareのやりかたから若干アレンジしています。
変更点については <a class="reference external" href="#id29">ユニットテストの節</a> で詳しく述べます。</p>
<p>理解の助けとなるよう図をたくさん使いましたが、図中の線、形状、色の意味付けはかなりいい加減で一貫性もないので、逆に混乱させてしまったら申し訳ありません。
文章と合わせて適切に解釈してもらえると助かります。</p>
<p>また、この記事の主眼は、あくまでFlow/MortarとDagger 2を組み合わせて使うということであり、Dagger 2そのものの使い方には焦点を当てていないので、それについては他の文献を当たってください。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-1" id="toc-entry-1">ライブラリのバージョン</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-2">サンプルアプリ</a></li>
<li><a class="reference internal" href="#squaremvp" id="toc-entry-3">Square風MVPのメリット・デメリット</a><ul>
<li><a class="reference internal" href="#section-3" id="toc-entry-4">メリット</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-5">デメリット</a></li>
</ul>
</li>
<li><a class="reference internal" href="#dagger-1-vs-dagger-2" id="toc-entry-6">Dagger 1 vs Dagger 2</a></li>
<li><a class="reference internal" href="#model-view-presenter" id="toc-entry-7">Model-View-Presenter</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-8">コンポーネントの責務</a><ul>
<li><a class="reference internal" href="#flow" id="toc-entry-9">Flowの責務</a></li>
<li><a class="reference internal" href="#mortar" id="toc-entry-10">Mortarの責務</a></li>
<li><a class="reference internal" href="#dagger-2-2" id="toc-entry-11">Dagger 2の責務</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-12">アプリの責務</a></li>
</ul>
</li>
<li><a class="reference internal" href="#squaremvp-1" id="toc-entry-13">Square風MVP詳解</a><ul>
<li><a class="reference internal" href="#section-7" id="toc-entry-14">基本的な構成</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-15">サービスの追加</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-16">追加されるサービス一覧</a></li>
<li><a class="reference internal" href="#mortarscope" id="toc-entry-17">MortarScope</a></li>
<li><a class="reference internal" href="#presenter" id="toc-entry-18">Presenterのライフサイクル</a></li>
<li><a class="reference internal" href="#pathpathcontext" id="toc-entry-19">PathとPathContext</a><ul>
<li><a class="reference internal" href="#flow-1" id="toc-entry-20">Flow</a></li>
<li><a class="reference internal" href="#pathcontainerview" id="toc-entry-21">PathContainerView</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-22">状態の保存と復元メカニズム</a></li>
</ul>
</li>
<li><a class="reference internal" href="#component" id="toc-entry-23">Componentのスコープと階層化</a></li>
<li><a class="reference internal" href="#pathscoper" id="toc-entry-24">PathScoper</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-25">縦横での切り替え</a></li>
<li><a class="reference internal" href="#section-12" id="toc-entry-26">レスポンシブ対応</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-13" id="toc-entry-27">ユニットテスト</a></li>
<li><a class="reference internal" href="#proguard" id="toc-entry-28">Proguard設定</a></li>
<li><a class="reference internal" href="#section-14" id="toc-entry-29">まとめと展望</a></li>
<li><a class="reference internal" href="#section-15" id="toc-entry-30">参考文献</a></li>
</ul>
</div>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-1">ライブラリのバージョン</a></h2>
<p>本稿の説明は、以下のバージョンのライブラリを前提とします。</p>
<ul class="simple">
<li><a class="reference external" href="https://github.com/square/flow">Flow 0.12</a></li>
<li><a class="reference external" href="https://github.com/square/mortar">Mortar 0.19</a></li>
<li><a class="reference external" href="https://github.com/google/dagger">Dagger 2.0.1</a></li>
</ul>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-2">サンプルアプリ</a></h2>
<p>Flow/Mortar/Dagger2を利用したアプリのサンプルとして、Todoアプリを用意しましたので、参考にしてください。</p>
<p><a class="reference external" href="https://github.com/tai2/flowmortardagger2demo">https://github.com/tai2/flowmortardagger2demo</a></p>
<p>このアプリは、Todoリスト画面、Todo追加画面、Todo編集画面という3つの画面からなります。各画面のユニットテストも実装してあります。</p>
</div>
<div class="section" id="squaremvp">
<h2><a class="toc-backref" href="#toc-entry-3">Square風MVPのメリット・デメリット</a></h2>
<p>Square風MVPでは、Androidの標準コンポーネントであるFragmentを完全に捨ててしまいます。それどころかActivityさえも基本的に使いません。
標準の開発者ガイドや参考書に書いてあるのとは、まったく違うやりかたでプログラミングをしますし、参考にできる情報も多くはありません。
そこまでして導入する価値のあるものなのかどうかという点が、気になっている方が多いと思いますので、まずはメリットとデメリットについて挙げます。
以下は、Square風MVPで、そこそこの規模の商用アプリを一本開発してみての感触です。</p>
<p>箇条書きで足りない部分は追加で補足します。</p>
<div class="section" id="section-3">
<h3><a class="toc-backref" href="#toc-entry-4">メリット</a></h3>
<ol class="arabic simple">
<li>当然ながら、Fragment関連でのIllegalStateExceptionからは開放される。</li>
<li>Configuration Changesへの特別な対応が不要になる。</li>
<li>画面遷移をするときに、(画面を表す)Pathのコンストラクタに必要な情報を渡せる。</li>
<li>Presenterは、Viewに直接依存しないのでテストし易い。</li>
<li>Daggerにより、オブジェクトの初期化を自動でできる(フィールドの宣言だけで済むのでコードがスッキリする)。</li>
<li>Daggerにより、「どこから」オブジェクトを取得するかと、「どの」オブジェクトを初期化するかを切り離すことができる。</li>
</ol>
<p>2について。通常のActivityやFragmentは、画面の回転やConfiguration Changesと呼ばれるイベントが起きると、そのたびに破壊と再生成が繰り返されますので、
適切に状態を保存・復元する必要があります。一方、MVPでこれらに相当するPresenterは、シングルトンオブジェクトで、Activityよりも寿命が長いので、
Configuration Changesを気にする必要はありません。</p>
<p>3について。Fragmentでは、画面遷移をするときにFragmentオブジェクトをnewで生成して、FragmentTransactionに渡します。
一見すると普通のオブジェクトのように見えるので、コンストラクタのパラメータとして、そのクラスの依存するデータを定義したくなるのが人情ですが、
これは、Fragmentにあまり慣れていないプログラマーがよく陥る罠の一つです。Fragmentには、デフォルトコンストラクター以外のコンストラクターを定義してはいけません。
一方、Square風MPVで画面遷移時に用いるPathは、なにも特殊なことはないただのオブジェクトなので、気兼ねなくいろいろなコンストラクターを定義して、素直にオブジェクトを生成できます。</p>
<p>4について。一般論として、 <a class="reference external" href="https://en.wikipedia.org/wiki/Presenter_First">MVPはPresenterのユニットテストが容易にできる</a> とされていますが、そのメリットをそのまま受けられます。
ただし、Squareのサンプルコードでは、Presenterが具象Viewと密結合する形となっているので、若干のアレンジが必要です(より.NETのMVPに近い形にします)。</p>
<p>6について。これは、たとえば、開発版とリリース版で使用するオブジェクトを切り替えたり、テスト版では使用するオブジェクトをすべてモックに切り替えるといったことを、一行の変更で行えるようになるということです。</p>
</div>
<div class="section" id="section-4">
<h3><a class="toc-backref" href="#toc-entry-5">デメリット</a></h3>
<ol class="arabic simple">
<li>学習コストが高い。ドキュメントがほとんどなく、使い方を調べるためにライブラリのソースコードを読む必要がある。<sup id="sf-android_mvp-2-back"><a href="#sf-android_mvp-2" class="simple-footnote" title="Flowに関しては、サンプルアプリが削除され、今後はドキュメントの拡充に集中する方針のようです。">2</a></sup></li>
<li>ボイラープレートが多くなる。1画面ごとに、Path,Presenter,View,Componentすべてを記述する必要がある。</li>
<li>APIの互換性についての保証はない。</li>
<li>ライフサイクルの種類はたしかに少ないが、十分な機能を提供していない。</li>
<li>Activityの機能に直接アクセスできないことが不便なときがある。</li>
</ol>
<p>1は、この記事を読めば、多少ハードルが下がるのではないかと思います。</p>
<p>3について。Android SDKは、基本的にAPIの後方互換性を保つ形でバージョンアップされていきますが、FlowとMortarはサードパーティーであるSquareが、自社アプリのためのライブラリとして開発したものをOSSとして公開しているに過ぎないので、そのような保証はありません。
実際、バージョン0.9で、 <a class="reference external" href="https://github.com/square/flow/blob/master/CHANGELOG.md">APIの破壊的変更が行われました</a> 。
このため、これまでFlowとMortarについて書かれた記事の多く<sup id="sf-android_mvp-3-back"><a href="#sf-android_mvp-3" class="simple-footnote" title="といってもそもそもそんなに多くないのですが...">3</a></sup>やコードは、そのままでは使えなくなってしまっています。</p>
<p>4について、たしかにFragmentのライフサイクルよりは、MortarのPresenterのライフサイクルのほうがシンプルだとは思うのですが、実際のアプリを作るのに十分な機能を提供できていない場合があります。
たとえば、基本的にはポートレイト固定で、特定の画面のみ回転可能にしたいといった要求はよくあると思いますが、そのようなケースで実装に試行錯誤が必要になることがあります。<sup id="sf-android_mvp-4-back"><a href="#sf-android_mvp-4" class="simple-footnote" title="実際、試行錯誤が必要になりました。">4</a></sup> また、例えば、スクリーンオフになったときに、動画の再生を停止するといった単純なことも、それをやるのに相応しいライフサイクルメソッドは用意されていないため、Viewのメソッドを駆使してがんばる必要があります。</p>
<p>5は、4とも関連しますが、けっきょくのところ実際のアプリ開発では、Activityの機能にアクセスしたくなることがあります。
たとえばアクションバーのタイトルを変更するためには、Activityへのアクセスが必要です。
しかし、Flowでは、Activityに直接アクセスする手段は用意されていないため、一工夫が必要になります。</p>
</div>
</div>
<div class="section" id="dagger-1-vs-dagger-2">
<h2><a class="toc-backref" href="#toc-entry-6">Dagger 1 vs Dagger 2</a></h2>
<p>FlowとMortarを使ったMVPアーキテクチャでは、 <a class="reference external" href="http://square.github.io/dagger/">Dagger 1</a> または <a class="reference external" href="http://google.github.io/dagger/">Dagger 2</a> を併用します。Presenterのシングルトン化に、これらのライブラリの提供する機能が必要だからです。</p>
<p>オリジナルのDagger 1は、Squareが開発したもので、Squareの提唱するMVPでもDagger 1を前提としていました。
その後、GoogleがDaggerの後継ライブラリとして、 <a class="reference external" href="https://github.com/square/dagger/issues/366">Dagger 2を提案しました</a> 。</p>
<p>どちらも用途としては同じで、アノテーションによってDependency Injectionを実現するためのものであり、javax.inject(JSR-330)の一実装です。
Dagger 2の最大の特徴は、それが <strong>完全な</strong> オブジェクトグラフの構成と検証をコンパイル時に行うということです。そのため、生成されるコードは簡潔で、素早いものになります。
一方で、Dagger 1は、オブジェクトグラフの構成(リンク)をランタイムに行います。また、オブジェクトグラフを動的に拡張することが可能です。つまり、コンパイル時のオブジェクトグラフ検証を完全には行いません。</p>
<p>いま現在Squareが内部的にどちらを使ってるのかはさだかではないのですが、 <a class="reference external" href="https://github.com/JakeWharton/u2020/issues/158">2015年1月時点ではDagger 1を使っており</a> 、すぐに移行する予定もなかったようです。
ただし、Dagger 1は、今年の5月で更新が止まっています。</p>
<p>いまでこそ、Mortarは、Dagger 2とも併用可能なように再設計が行われましたが、もともとDagger 1と併用する前提で開発されたものなので、どちらかというとDagger 1とのほうが相性はいいのかもしれません。
すくなくとも、現時点では、Dagger 1と併用するパターンのほうが情報が多く使い方が確立されていると思います。</p>
<p>一方で、Dagger 2とFlow/Mortarを併用したMVPアプリ開発は、Square社員でMortarの開発にも参加している <a class="reference external" href="https://github.com/pyricau">Pierre-Yves Ricau</a> を含め、何人かがやりかたを提案してはいるのですが、
確立された方法はないという状況です。そのため、Dagger 2と併用する場合には、導入までの努力が多く必要になると思います。
また、MVPアプリで使う場合には、画面ごとに異なるオブジェクトグラフを作成したいのですが、それをスマートにやろうとすると、けっきょくランタイムのリフレクションが必要になり、
それであれば、ランタイムに柔軟なオブジェクトグラフの構成ができるDagger 1のほうが向いているかもしれません。<sup id="sf-android_mvp-5-back"><a href="#sf-android_mvp-5" class="simple-footnote" title="Dagger 2がやっているのと同様に、アノテーションを見て、足りない部分を自力でソースコード生成して補うというところまでやれば、Dagger 2の長所を活かせると思いますが、そこまでやる気力はありませんでした。">5</a></sup></p>
<p>そう考えると、けっきょくのところ、Android MVPをやるには、現時点では、Dagger 1のほうがいい選択なのかもしれません。
筆者がプロジェクトをはじめる前には、いまほど深い理解もなく、どちらのほうが良い選択なのかも判断が難しかったので、新しい方のDagger 2を採用しました。</p>
</div>
<div class="section" id="model-view-presenter">
<h2><a class="toc-backref" href="#toc-entry-7">Model-View-Presenter</a></h2>
<p>Model-View-Presenterパターンは、Model-View-Controllerを改変したGUI用のアーキテクチャです。
MVPの世界では、すべてをドメインモデルとユーザーインターフェイスという2つに綺麗に分割し、Controllerのようなどちらにもまたがる半端者はいなくなる、というのが筆者の解釈です。
<a class="reference external" href="http://blog.tai2.net/bower-and-macglashan-mvp-architecture.html">BowerとMacGlashanのMVP論文要約</a> も参考にしてください。</p>
<p>また、 <a class="reference external" href="http://blogs.msdn.com/b/jowardel/archive/2008/09/09/using-the-model-view-presenter-mvp-design-pattern-to-enable-presentational-interoperability-and-increased-testability.aspx">マイクロソフトの提唱するMVP</a> では、PresenterとViewはインターフェイスによって分離されるため、UI(Presenter)の自動テストが容易になります。Viewの表現自体はテストできませんが、ユーザーアクション=Presenterのメソッドとなるため、アプリ動作のシナリオを、通常のユニットテストの枠組みで検証できるようになります。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-8">コンポーネントの責務</a></h2>
<p>この節では、利用する各コンポーネント<sup id="sf-android_mvp-6-back"><a href="#sf-android_mvp-6" class="simple-footnote" title="この記事では、Dagger 2の提供するComponentというクラスと、いわゆる一般的なコンポーネント(機能のまとまり)が両方出てくるためまぎらわしいかもしれません。Dagger 2のほうは、英語でComponentと書くことにします。">6</a></sup>の責務について簡単に説明します。また、それを利用するアプリがすべきこととしては、なにが残るのかについても触れます。</p>
<div class="section" id="flow">
<h3><a class="toc-backref" href="#toc-entry-9">Flowの責務</a></h3>
<p>Flowは、View単位での画面遷移の仕組みを提供します。
これを使うことによって、カスタムビューをベースとしたアプリの構築ができるようになります。</p>
<p>Flowは、独自にバックスタック(History)を管理し、バックスタックに対する操作を定義します。
また、Activityのライフサイクルメソッドの処理を肩代りする、FlowDelegateというクラスも提供します。</p>
</div>
<div class="section" id="mortar">
<h3><a class="toc-backref" href="#toc-entry-10">Mortarの責務</a></h3>
<p>Mortarは、階層化されたスコープ(MortarScope)を提供します。
これにより、Contextを階層化したり、階層毎に異なるサービス<sup id="sf-android_mvp-7-back"><a href="#sf-android_mvp-7" class="simple-footnote" title="ここで言うサービスとは、Context#getSystemService()で取得できるオブジェクトのこと。">7</a></sup>を定義することが可能となります。
また、スコープに応じて適切にリソースを破棄します。</p>
<p>Mortarは、Flowの管理するバックスタックをBundleに保存・復元します。</p>
</div>
<div class="section" id="dagger-2-2">
<h3><a class="toc-backref" href="#toc-entry-11">Dagger 2の責務</a></h3>
<p>Dagger 2は、アプリの定義したオブジェクトグラフ<sup id="sf-android_mvp-8-back"><a href="#sf-android_mvp-8" class="simple-footnote" title="どのオブジェクトがどのオブジェクトに依存しているかという依存関係のこと。">8</a></sup>から、Componentクラスを生成します。
Componentクラスは、オブジェクトを生成してフィールドに注入します。また、シングルトンとして指定されたオブジェクトを保持します。
なお、この場合のシングルトンとは、static変数ということではなく、何度injectしても同じインスタンスが再利用される、という意味です。
したがって、Componentインスタンスが変われば、シングルトンとして指定されているインスタンスでも同一とは限りません。</p>
<p>Dagger 2 Componentは、MortarのPresenterインスタンスをシングルトンとして保持します。</p>
</div>
<div class="section" id="section-6">
<h3><a class="toc-backref" href="#toc-entry-12">アプリの責務</a></h3>
<p>アプリは、Activityのライフサイクルメソッドを、FlowDelegateやBundleServiceRunnerに委譲します。
アプリは、コンテナビューを定義し、 <a class="reference external" href="#pathcontainerview">画面遷移の際の細々とした処理</a> も実装します。
また、バックスタックの状態をParcelableにシリアライズするための方法も定義します。
追加のサービスをApplicationやActivityに埋め込むのもアプリの責務です。
ただし、これらはFlowやMortarのサンプルコードに必要なクラスやコード片が用意されているので、基本的にはそれを使えば済みます。</p>
<p>当然ながら、アプリは、画面毎に、Presenter、Viewのレイアウト、Viewクラス、Dagger 2 Componentを実装し、それらの間の遷移やビジネスロジックを実装します。</p>
</div>
</div>
<div class="section" id="squaremvp-1">
<h2><a class="toc-backref" href="#toc-entry-13">Square風MVP詳解</a></h2>
<div class="section" id="section-7">
<h3><a class="toc-backref" href="#toc-entry-14">基本的な構成</a></h3>
<p>Flow/Mortar/Dagger 2を使用したMVPアプリの構成では、ひとつの画面は、</p>
<ul class="simple">
<li>Path</li>
<li>Presenter</li>
<li>Dagger 2 Component(必要に応じてModule)</li>
<li>カスタムView(とそのレイアウトファイル)</li>
</ul>
<p>の4つから構成されます。</p>
<div class="figure">
<img alt="Squrea Stack Structure" src="https://blog.tai2.net/images/android_mvp/structure.png">
<p class="caption">Squareスタックにおける基本的な構成</p>
</div>
<p>Pathは、画面を特定するアドレスのようなもので、画面遷移時に使われます。
Dagger 2 Componentは、各画面のオブジェクトグラフを定義します。
カスタムViewは、画面の視覚的な表現です。
そして、Presenterは、Viewに保持されるインスタンスで、Viewの初期化時にDagger 2 Componentによってinjectされます。</p>
<p>サンプルTodoアプリのTodo追加画面の実装は、以下のようになっています。</p>
<div class="highlight"><pre><span></span><span class="c1">// Path</span>
<span class="nd">@Layout</span><span class="p">(</span><span class="n">R</span><span class="p">.</span><span class="na">layout</span><span class="p">.</span><span class="na">todo_add</span><span class="p">)</span><span class="w"> </span><span class="nd">@WithComponent</span><span class="p">(</span><span class="n">TodoAddPath</span><span class="p">.</span><span class="na">Component</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">TodoAddPath</span>
<span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">Path</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// Component</span>
<span class="w"> </span><span class="nd">@dagger.Component</span><span class="p">(</span><span class="n">dependencies</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">MyApplication</span><span class="p">.</span><span class="na">Component</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"> </span><span class="nd">@PerScreen</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">Component</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">inject</span><span class="p">(</span><span class="n">TodoAddView</span><span class="w"> </span><span class="n">v</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="c1">// Presenter</span>
<span class="w"> </span><span class="nd">@PerScreen</span><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Presenter</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">ViewPresenter</span><span class="o"><</span><span class="n">View</span><span class="o">></span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nd">@Inject</span><span class="w"> </span><span class="n">Presenter</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">...</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="c1">// View</span>
<span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">TodoAddView</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">RelativeLayout</span><span class="w"> </span><span class="kd">implements</span><span class="w"> </span><span class="n">ActionBarModifier</span><span class="p">,</span><span class="w"> </span><span class="n">TodoAddPath</span><span class="p">.</span><span class="na">View</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nd">@Inject</span><span class="w"> </span><span class="n">TodoAddPath</span><span class="p">.</span><span class="na">Presenter</span><span class="w"> </span><span class="n">presenter</span><span class="p">;</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="nf">TodoAddView</span><span class="p">(</span><span class="n">Context</span><span class="w"> </span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="n">AttributeSet</span><span class="w"> </span><span class="n">attrs</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">super</span><span class="p">(</span><span class="n">context</span><span class="p">,</span><span class="w"> </span><span class="n">attrs</span><span class="p">);</span>
<span class="w"> </span><span class="n">DaggerService</span><span class="p">.</span><span class="o"><</span><span class="n">TodoAddPath</span><span class="p">.</span><span class="na">Component</span><span class="o">></span><span class="n">getDaggerComponent</span><span class="p">(</span><span class="n">context</span><span class="p">).</span><span class="na">inject</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">...</span>
<span class="p">}</span>
</pre></div>
<p>Activityは、アプリケーションでひとつしか存在しません。Square風MVPアプリにおいて、Activityは、Applicationと同様にシングルトン的な存在です。
ただし、Configuration ChangesでActivityは破棄されるので、常に同一のインスタンスであることを期待してはいけません。</p>
<p>また、各画面ごとのViewを格納するためのコンテナビュー(PahtContainerView)が、Activityに対してひとつあります。
スマホ用の1画面レイアウトと、 タブレット用の <a class="reference external" href="https://developer.android.com/intl/ja/tools/projects/templates.html#master-detail-activity">マスター・ディテール</a> のように異なるレイアウトを出し分けする場合には、コンテナビューもその分用意します。</p>
</div>
<div class="section" id="section-8">
<h3><a class="toc-backref" href="#toc-entry-15">サービスの追加</a></h3>
<p>Androidにおいて、Contextは、システムにアクセスするための重要な手段です。
ApplicationやActivity自身もContextの一種であり、通常、いつでもどこからでもアクセスすることが可能です。</p>
<p>Flow/Mortarでは、 <a class="reference external" href="http://developer.android.com/reference/android/content/Context.html#getSystemService(java.lang.String)">getSystemService()</a>
をオーバーライドして、独自のサービスをContextに追加することで、システムを拡張するという方法を多用します。
たとえば、ApplicationやActivityのgetSystemServiceには、 <a class="reference external" href="#mortarscope">階層化されたスコープ</a> を提供するためのMortarScopeインスタンスや、
Activityのライフサイクルメソッドを肩代わりするためのFlowDelegateインスタンスを埋め込みます。</p>
<p>サンプルアプリでは、ApplicationのgetSystemServiceは以下のようになっています。</p>
<div class="highlight"><pre><span></span><span class="nd">@Override</span><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="n">Object</span><span class="w"> </span><span class="nf">getSystemService</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">name</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">rootScope</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Component</span><span class="w"> </span><span class="n">component</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">DaggerService</span><span class="p">.</span><span class="na">createComponent</span><span class="p">(</span><span class="n">Component</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Module</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
<span class="w"> </span><span class="n">rootScope</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">MortarScope</span><span class="p">.</span><span class="na">buildRootScope</span><span class="p">()</span>
<span class="w"> </span><span class="p">.</span><span class="na">withService</span><span class="p">(</span><span class="n">DaggerService</span><span class="p">.</span><span class="na">SERVICE_NAME</span><span class="p">,</span><span class="w"> </span><span class="n">component</span><span class="p">)</span>
<span class="w"> </span><span class="p">.</span><span class="na">build</span><span class="p">(</span><span class="s">"Root"</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">rootScope</span><span class="p">.</span><span class="na">hasService</span><span class="p">(</span><span class="n">name</span><span class="p">)</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="n">rootScope</span><span class="p">.</span><span class="na">getService</span><span class="p">(</span><span class="n">name</span><span class="p">)</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="kd">super</span><span class="p">.</span><span class="na">getSystemService</span><span class="p">(</span><span class="n">name</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>また、これら以外にも <a class="reference external" href="http://developer.android.com/intl/ja/reference/android/content/ContextWrapper.html">ContextWrapper</a>
を使用して、独自のContext定義し、機能を拡張するという手法も使います。
ContextWrapperは、Contextインスタンスを包んで追加の機能やフィールドを持たせるためのプロキシークラスです。
ContextWrapperの派生クラスを見ると、このクラスがAndroid SDK内でも多用されていることがわかります。</p>
<div class="figure">
<img alt="ContextWrappers wrap Context" src="https://blog.tai2.net/images/android_mvp/context_wrapper.png">
<p class="caption">ContextWapperはContextに機能を追加する</p>
</div>
<p>ContextをContextWrapperで何重にも包んでサービスを追加していくので、操作しているContextがどのContext
なのかをただしく認識するのが、コードを理解する鍵になってきます。それによって利用できるサービスが異なるからです。</p>
</div>
<div class="section" id="section-9">
<h3><a class="toc-backref" href="#toc-entry-16">追加されるサービス一覧</a></h3>
<p>Flow/Mortarアプリで独自に定義される(非標準の)サービスとしては、以下のようなものがあります。
ただし、純粋に内部的なもので、アプリから直接は利用しないものも含まれます。</p>
<ul class="simple">
<li>Flow.FLOW_SERVICE(Flow): Flowインスタンスを提供する。</li>
<li>LocalPathWrapper.LOCAL_WRAPPER_SERVICE(Flow): Contextに付随するPathを提供する。</li>
<li>PathContext.SERVICE_NAME(Flow): Path固有のContextを提供する。</li>
<li>MortarScope.MORTAR_SERVICE(Mortar): Contextに付随するMortarScopeを提供する。</li>
<li>BundleServiceRunner.SERVICE_NAME(Mortar): Contextに付随するBundleServiceRunnerを提供する。</li>
<li>DaggerService.SERVICE_NAME(アプリ): Contextに付随するDagger Componentを提供する。</li>
</ul>
</div>
<div class="section" id="mortarscope">
<h3><a class="toc-backref" href="#toc-entry-17">MortarScope</a></h3>
<p>スコープ(MortarScope)は、Mortarの提供する主たる機能のひとつで、サービスの辞書を保持するオブジェクトです。また、スコープは、それ自身ツリー構造を成します。
実際のアプリでは、次の図のように、Rootスコープ(Applicationスコープ)、Activityスコープ、Pathスコープという3階層までになります。
なお、Pathスコープが2ノードになるのは、マスター・ディテールなど複数画面構成の場合のみで、1画面構成のアプリの場合は、常に1ノードです。</p>
<div class="figure">
<img alt="MortarScope consists tree structure" src="https://blog.tai2.net/images/android_mvp/scopes.png">
<p class="caption">MortarScopeのツリー構造</p>
</div>
<p>スコープインスタンスに対して、サービスの検索を要求すると、まず自分の持つ辞書に該当するサービスがあるかを検索し、なければ親に遡って検索していきます。
また、スコープに登録されるサービスがScopedインターフェイスを実装している場合には、スコープへの登録時にonEnterScope()が、スコープの破棄時に、onEnterScope()が呼ばれます。</p>
<div class="highlight"><pre><span></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">Scoped</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onEnterScope</span><span class="p">(</span><span class="n">MortarScope</span><span class="w"> </span><span class="n">scope</span><span class="p">);</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onExitScope</span><span class="p">();</span>
<span class="p">}</span>
</pre></div>
<p>これらのメソッドで、前処理と後処理をすることで、スコープの寿命と同期したリソースの初期化と回収が実現できます。
たとえば、onEnterScopeで、そのスコープ内で有効なRealmインスタンスを取得し、onExitScopeでreleaseするRealmServiceのようなものを実装して、
サービスとしてスコープにに登録することも可能です。</p>
<p>スコープと、それに紐付けられたサービスは、Configuration Changesを生き残ります。Rootスコープは、Applicationによって保持されるインスタンスだからです。</p>
</div>
<div class="section" id="presenter">
<h3><a class="toc-backref" href="#toc-entry-18">Presenterのライフサイクル</a></h3>
<p>Presenterには、次の4つのライフサイクルメソッドが用意されています。</p>
<ul class="simple">
<li>void onEnterScope(MortarScope scope): PresenterがScopeに登録されたとき(画面遷移時)に一度だけ実行される。</li>
<li>void onLoad(Bundle savedInstanceState): Presenterのロード時に呼び出される。</li>
<li>void onSave(Bundle outState): Presenterの中断時に呼び出される。</li>
<li>void onExitScope(): PresenterがScopeから登録解除されたとき(画面遷移時)に一度だけ実行される。</li>
</ul>
<div class="figure">
<img alt="Lifecyle of Presenter" src="https://blog.tai2.net/images/android_mvp/presenter_lifecycle.png">
<p class="caption">Presenterのライフサイクル</p>
</div>
<p>onEnterScopeは、Configuration Changeのたびに呼びだされることはありません。Configuration ChangeのたびにViewインスナンスは再構築されるので、Viewに依存した初期化などはここで実行することはできません。
onLoadのタイミングでは、実際のViewが生成されInflateも完了しているので、初期化処理はこの中でやるのが適切です。</p>
<p>onLoadは、ActivityのonCreateかViewの表示時(takeView)のタイミングで呼ばれます。onSaveは、ActivityのonSaveInstanceStateがトリガーになります。
onEnterScopeとonExitScopeのタイミングは、アプリの実装に依存しますが、通常は画面遷移時です。</p>
<p>Presenterには、ActivityのonStaret,onResume,onSuspend,onStopに相当するようなライフサイクルメソッドが存在しないため、実際のアプリ実装では若干機能が足りないことがあります。
たとえば、スクリーンオフになったときに、動画やオーディオの再生を停止したいといったケースには、上記のメソッドだけでは対応できません。
そういったケースでは、ViewのonDetachedFromWindowや、onWindowFocusChangedといったメソッドを駆使して対応します。</p>
</div>
<div class="section" id="pathpathcontext">
<h3><a class="toc-backref" href="#toc-entry-19">PathとPathContext</a></h3>
<p>Flowでは、Viewが画面遷移の単位になります。
Viewと一対一で対応付けて、そのViewを識別するために用いられるのがPathです。</p>
<p>Flowでの画面遷移は、次のようなコードで実行されます。</p>
<div class="highlight"><pre><span></span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onItemClick</span><span class="p">(</span><span class="kt">int</span><span class="w"> </span><span class="n">position</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Flow</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">getContext</span><span class="p">()).</span><span class="na">set</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">TodoEditPath</span><span class="p">(</span><span class="n">todoItems</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">position</span><span class="p">).</span><span class="na">getId</span><span class="p">()));</span>
<span class="p">}</span>
</pre></div>
<p>サンプルアプリでは、次のコードのように、@Layoutというアノテーションを使って、Pathとレイアウトファイルを関連付けています。
@WithComponentについては <a class="reference external" href="http://localhost:8000/android_mvp.html#pathscoper">後述</a> します。</p>
<div class="highlight"><pre><span></span><span class="nd">@Layout</span><span class="p">(</span><span class="n">R</span><span class="p">.</span><span class="na">layout</span><span class="p">.</span><span class="na">todo_list</span><span class="p">)</span><span class="w"> </span><span class="nd">@WithComponent</span><span class="p">(</span><span class="n">TodoListPath</span><span class="p">.</span><span class="na">Component</span><span class="p">.</span><span class="na">class</span><span class="p">)</span>
<span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">TodoListPath</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">Path</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="p">...</span>
</pre></div>
<p>これ自体はFlowに用意されている仕組みではありません。Pathとレイアウトファイルを関連付けて、レイアウトファイルをinflateするといったあたりは、アプリ側コードになります(SimplePathContainer)。
ただし、定型処理なので、サンプルにあるものをそのまま使えば事足ります。</p>
<p>PathContextは、Pathインスタンスと対になるContextで、Path固有のサービスを提供します。
画面遷移の際には、直前のPathから親Contextを引き継いで、新しいPathContextを作成します。
そして、 <a class="reference external" href="http://developer.android.com/intl/ja/reference/android/view/LayoutInflater.html#cloneInContext(android.content.Context)">cloneInContext</a> で、
新しいContextを指定したLayoutInflaterによって、レイアウトファイルをinflateすることで、そのContextに紐付くViewインスタンスを生成します。
同時に、前の画面のPathContextは破棄します。
ちなみに、PathContextの親Contextは、Activityです。</p>
<div class="figure">
<img alt="PathContext" src="https://blog.tai2.net/images/android_mvp/pathcontext.png">
<p class="caption">PathContextは、遷移時に生成・破棄される</p>
</div>
<p>こうして、PathContextのサービスを利用可能なViewのセットアップ、および、そのクリーンアップがされます。</p>
<div class="section" id="flow-1">
<h4><a class="toc-backref" href="#toc-entry-20">Flow</a></h4>
<p>Flowクラスは、Flowを使うときの窓口になるクラス(いわゆる <a class="reference external" href="http://c2.com/cgi/wiki?FacadePattern">Facade</a> )です。
内部にHistoryインスタンスと、Dispatcherインスタンスをひとつづつ保持しています。
このクラスを通してバックスタック(History)を操作することで、画面遷移を行います。</p>
<p>Flowインスタンスは、FlowDelegateインスタンスの中に保持されます。
FlowDelegateインスタンス自体は、Activityに持たせますので、実質的に、Flowインスタンスはシングルトンのようなものです。</p>
<div class="figure">
<img alt="FlowDelegate" src="https://blog.tai2.net/images/android_mvp/flowdelegate.png">
<p class="caption">ActivityはライフサイクルメソッドをFlowDelegateに委譲する</p>
</div>
<p>また、FlowDelegateインスタンスは、上図のようにActivityのライフサイクルメソッドの委譲先となります。
これにより、適切にActivityのライフサイクルがハンドリングされ、アプリ側ではActivityのことを気にせずにすみます。</p>
<p>Historyのエントリーには、View階層の状態と、対応するPathオブジェクト自身が含まれます。</p>
<div class="figure">
<img alt="History" src="https://blog.tai2.net/images/android_mvp/history.png">
<p class="caption">Historyは、PathとViewの状態を保持する</p>
</div>
<p>Configuration Changesなど状態保存が必要なときには、Historyまるごと、Bundleの中にシリアライズされます。
View階層の状態は、 <a class="reference external" href="http://developer.android.com/intl/ja/reference/android/view/View.html#saveHierarchyState(android.util.SparseArray<android.os.Parcelable>)">saveHierarchyState()</a> で保存、 <a class="reference external" href="http://developer.android.com/intl/ja/reference/android/view/View.html#restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>)">restoreHierarchyState()</a> で復元されます。</p>
<p>また、Pathは、アプリ定義のシリアライザー(StateParceler)で、シリザライズ・デシリアライズされるのですが、サンプル実装では、GsonParcelarというGsonを使った実装になっています。
したがって、PathはGsonでシリアライズ可能なオブジェクトでなければなりません。</p>
</div>
<div class="section" id="pathcontainerview">
<h4><a class="toc-backref" href="#toc-entry-21">PathContainerView</a></h4>
<p>Viewは、PathContainerViewの子ViewとしてViewツリーに追加されます。それらは、PathContainerに管理されます。
そして、PathContainerViewは、Activityによって表示されます。PathContainerViewは、マスター・ディテールなどの場合に複数になることがあります。</p>
<div class="figure">
<img alt="PathContainer manages child views" src="https://blog.tai2.net/images/android_mvp/pathcontainer.png">
<p class="caption">PathContainerは子Viewを管理する</p>
</div>
<p>FlowDelegateは、コンストラクターでDispatcherインスタンスを引数に取ります。
画面遷移が起きたときには、Dispatcher#dispatchが呼ばれて、そこから最終的には、PathContainer#performTraversalが呼ばれます(呼ばれるようにアプリコードを構成します)。
このperformTraversalの中で、Viewの入れ替えや、アニメーションの実行、PathContextの生成・破棄といった処理を行います。
これらの処理は、Flowの外側の部分なのでアプリの責務の範囲ですが、Flowのサンプル実装で提供されているSimplePathContainerをそのまま使えば、十分です。</p>
</div>
<div class="section" id="section-10">
<h4><a class="toc-backref" href="#toc-entry-22">状態の保存と復元メカニズム</a></h4>
<p>History(バックスタック)や、View固有の状態(onSaveで保存されるもの)などは、すべてBundleに保存されます。</p>
<p>Bundleへの状態保存とBundleからの復元は、BundleServiceRunnerが行います。
このインスタンスは、Activityのスコープにサービスとして登録されるので、アプリから見れば、実質的にシングルトンです。
また、このオブジェクトは、保存の大本になるルートBundleを保持します。
BundleServiceRunnerは、ActivityのonCreate/onSaveInstanceStateをトリガーとして、保存と復元を行います。</p>
<div class="figure">
<img alt="Bundle Tree" src="https://blog.tai2.net/images/android_mvp/bundle_tree.png">
<p class="caption">Bundleはツリー構造を成す</p>
</div>
<p>ルートBundleの下には、スコープのパスをキーとして、スコープごとのBundleが格納されます。
そして、スコープごとのBundleの下には、Bundlerインスタンス毎にBundleがぶら下がります。
Bundlerは、Scope毎にリソースを管理するためのインターフェイスです。</p>
<div class="highlight"><pre><span></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">Bundler</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">getMortarBundleKey</span><span class="p">();</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onEnterScope</span><span class="p">(</span><span class="n">MortarScope</span><span class="w"> </span><span class="n">scope</span><span class="p">);</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onLoad</span><span class="p">(</span><span class="n">Bundle</span><span class="w"> </span><span class="n">savedInstanceState</span><span class="p">);</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onSave</span><span class="p">(</span><span class="n">Bundle</span><span class="w"> </span><span class="n">outState</span><span class="p">);</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">onExitScope</span><span class="p">();</span>
<span class="p">}</span>
</pre></div>
<p>PresenterもBundlerを内部に保持しており、同名のライフサイクルメソッドも、これに準じたタイミングで実行されます。
Presenter用のBundlerでは、クラス名が保存用のキーになります。</p>
</div>
</div>
<div class="section" id="component">
<h3><a class="toc-backref" href="#toc-entry-23">Componentのスコープと階層化</a></h3>
<p>Presenterインスタンスは、Configuration Changesを生き残るためにシングルトンである必要があります。
これを実現するために、Dagger 2のScopedインスタンスの機能を使います。</p>
<p>Scopedインスタンスとは、インスタンスのプロバイダーに@Singletonのようなアノテーションを付けておくと、
そのインスタンスが、Componentの寿命の範囲内で使い回される、というDagger 2の機能です。</p>
<p>たとえば、サンプルアプリでは、次のようなDagger 2モジュールが定義されています。</p>
<div class="highlight"><pre><span></span><span class="nd">@dagger.Module</span><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">class</span> <span class="nc">Module</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nd">@Provides</span><span class="w"> </span><span class="nd">@Singleton</span><span class="w"> </span><span class="n">RealmConfiguration</span><span class="w"> </span><span class="nf">provideRealmConfiguration</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">RealmConfiguration</span><span class="p">.</span><span class="na">Builder</span><span class="p">(</span><span class="n">context</span><span class="p">).</span><span class="na">build</span><span class="p">();</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="nd">@Singleton</span><span class="w"> </span><span class="nd">@dagger.Component</span><span class="p">(</span><span class="n">modules</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Module</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">Component</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">RealmConfiguration</span><span class="w"> </span><span class="nf">provideRealmConfiguration</span><span class="p">();</span>
<span class="p">}</span>
</pre></div>
<p>RealmConfigurationには@Singletonアノテーションがついているため、同一のComponentインスタンスを使用する限り、何度injectしても、同一のインスタンスが使い回されます。</p>
<p>Dagger 2では、オブジェクトグラフに含まれるインスタンスは、Componentインスタンスを通して取得できます。
そして、Component自体を他のComponentに依存させることで、オブジェクトグラフを階層化することができます。</p>
<p>階層化されたオブジェクトグラフにおいて、他のComponentに依存するComponentは、依存されるComponentとは異なるスコープを持つ必要があります(依存するComponentのほうが寿命が短かくなります)。
そのことを表現するために、必要に応じて、@Scopeアノテーションを定義する必要があります。
異なるComponent階層に対して、同じ@Scopeアノテーション(例えば@Singleton)をつけようとしても、ビルドエラーになってしまいます。
なお、ここで言うスコープは、Mortarのスコープとは異なるもので、直接の関係はないので注意してください。</p>
<div class="figure">
<img alt="Component Hierarchy" src="https://blog.tai2.net/images/android_mvp/components.png">
<p class="caption">Componentの階層化</p>
</div>
<p>筆者の提案するMVP構成では、Rootスコープと、Pathスコープという2種類のスコープを定義します。
Rootスコープ用には、標準の@Singletonをそのまま使い、Pathスコープ用には、画面毎のスコープという意味で、@PerScreenスコープを新設します。</p>
<p>Rootスコープには、アプリケーション全体を通じて有効なオブジェクトを置き、Pathスコープには、画面毎に必要に応じたオブジェクトを置きます。
PathスコープのComponentインスタンスは、次節の仕組みにより、画面を去るときにいっしょに破棄されます。</p>
</div>
<div class="section" id="pathscoper">
<h3><a class="toc-backref" href="#toc-entry-24">PathScoper</a></h3>
<p>サンプルアプリでは、他の人のやりかたを参考にして、@WithComponentというアノテーションを導入しました。
これをPathに付与することで、PathとComponentを対応付けます。</p>
<div class="highlight"><pre><span></span><span class="nd">@Layout</span><span class="p">(</span><span class="n">R</span><span class="p">.</span><span class="na">layout</span><span class="p">.</span><span class="na">todo_list</span><span class="p">)</span><span class="w"> </span><span class="nd">@WithComponent</span><span class="p">(</span><span class="n">TodoListPath</span><span class="p">.</span><span class="na">Component</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">TodoListPath</span>
<span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">Path</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nd">@dagger.Component</span><span class="p">(</span><span class="n">dependencies</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">MyApplication</span><span class="p">.</span><span class="na">Component</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"> </span><span class="nd">@PerScreen</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">Component</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">inject</span><span class="p">(</span><span class="n">TodoListView</span><span class="w"> </span><span class="n">v</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">...</span>
</pre></div>
<p>そして、Pathへの遷移時に、リフレクションでComponentクラスを検索して、Componentインスタンスを生成し、PathレベルのMortarScopeに紐付けます(MortarContextFactory)。
これをやるためのヘルパーとして、PathScoperというクラスを作成しました。
Androidのリフレクションは重いということなので、一度検索したComponentとModuleコンストラクタはキャッシュするようになっています。</p>
<p>Componentインスタンスは、PathレベルのMortarScopeの管理下にあるため、画面遷移時にきちんと破棄されます。
また、Rootスコープ(Applicationレベル)のオブジェクトグラフは破棄されずに残ります。</p>
<p>PathScoperでは、ApplicationスコープとPathスコープの2階層構成を前提としており、PathレベルComponentの生成時に必要なApplicationレベルのComponentは、
ハードコーディングになっています。構成がこれ以上変更になることはないと思うので、これでも十分な気はしますが、さらなる柔軟性を追求する余地が残っています。</p>
</div>
<div class="section" id="section-11">
<h3><a class="toc-backref" href="#toc-entry-25">縦横での切り替え</a></h3>
<p>Flowでは、ポートレイトとランドスケープで実装を切り替えることも容易です。</p>
<div class="highlight"><pre><span></span><span class="nt"><net.tai2.flowmortardagger2demo.view.TodoListView</span>
<span class="w"> </span><span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="w"> </span><span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="w"> </span><span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
<span class="w"> </span><span class="na">android:orientation=</span><span class="s">"vertical"</span>
<span class="w"> </span><span class="na">android:paddingLeft=</span><span class="s">"10dp"</span>
<span class="w"> </span><span class="na">android:paddingRight=</span><span class="s">"10dp"</span>
<span class="w"> </span><span class="nt">></span>
<span class="w"> </span>...
</pre></div>
<p>画面を定義するViewのレイアウトファイルには、このように対象のカスタムViewクラスが埋め込まれる形となるので、
通常のリソース切り替えのメカニズムを使って、ポートレイト用とランドスケープ用のレイアウトファイルを切り替えることで、実装ごと切り替えることができます。
この方法には、カスタムViewもPresenterもまったく別実装にする、カスタムViewは切り替えるがPresenterは共有する、どちらも共通にするがレイアウトのだけ変化させるなど、
いくつかバリエーションが考えられます。</p>
</div>
<div class="section" id="section-12">
<h3><a class="toc-backref" href="#toc-entry-26">レスポンシブ対応</a></h3>
<p>縦横の切り替えと同じ要領で、ルートのレイアウトファイルをスマートフォンとタブレットで分けることで、実装を変えるという方法があります。
Flowのサンプルアプリが、その方法でレスポンシブ対応をしているので、そちらを見ると良いと思います。
ただし、直近の変更でサンプルコードが削除されてしまったので、0.12以前をチェックアウトしてください。</p>
</div>
</div>
<div class="section" id="section-13">
<h2><a class="toc-backref" href="#toc-entry-27">ユニットテスト</a></h2>
<p>MVPでは、ユニットテストがしやすくなるとよく言われますが、それは、主に、Presenterと具象Viewが、インターフェイスによって分離されているためです。
ところが、SquareのMVP実装では、画面の実装に利用するViewPresenterは次のようになっています。</p>
<div class="highlight"><pre><span></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">ViewPresenter</span><span class="o"><</span><span class="n">V</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">View</span><span class="o">></span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">Presenter</span><span class="o"><</span><span class="n">V</span><span class="o">></span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nd">@Override</span><span class="w"> </span><span class="kd">protected</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">BundleService</span><span class="w"> </span><span class="nf">extractBundleService</span><span class="p">(</span><span class="n">V</span><span class="w"> </span><span class="n">view</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">BundleService</span><span class="p">.</span><span class="na">getBundleService</span><span class="p">(</span><span class="n">view</span><span class="p">.</span><span class="na">getContext</span><span class="p">());</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>ここで、Viewは、android.view.Viewです。これでは、Presenterが具象Viewと直接結びついてしまうため、モックを使ってテストを実装するのがやりにくくなります。</p>
<p>幸い、基底クラスのPresenterはジェネリックになっており、付随するオブジェクトの型を変更できるので、以下のようにします。</p>
<div class="highlight"><pre><span></span><span class="kd">public</span><span class="w"> </span><span class="kd">interface</span> <span class="nc">ContextHolder</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Context</span><span class="w"> </span><span class="nf">getContext</span><span class="p">();</span>
<span class="p">}</span>
<span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">ViewPresenter</span><span class="o"><</span><span class="n">V</span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">ContextHolder</span><span class="o">></span><span class="w"> </span><span class="kd">extends</span><span class="w"> </span><span class="n">Presenter</span><span class="o"><</span><span class="n">V</span><span class="o">></span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nd">@Override</span><span class="w"> </span><span class="kd">protected</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">BundleService</span><span class="w"> </span><span class="nf">extractBundleService</span><span class="p">(</span><span class="n">V</span><span class="w"> </span><span class="n">view</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">BundleService</span><span class="p">.</span><span class="na">getBundleService</span><span class="p">(</span><span class="n">view</span><span class="p">.</span><span class="na">getContext</span><span class="p">());</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Context</span><span class="w"> </span><span class="nf">getContext</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">getView</span><span class="p">().</span><span class="na">getContext</span><span class="p">();</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>より具体的なViewのインターフェイスは、画面ごとにContextHolderを拡張して定義します。
これで、PresenterとViewの結びつきを間接化できます。
モックViewの定義も容易になり、以下のような感じでテストコードが書けます。</p>
<div class="highlight"><pre><span></span><span class="nd">@UiThreadTest</span><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">testAddClick</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">presenter</span><span class="p">.</span><span class="na">onAddClick</span><span class="p">();</span>
<span class="w"> </span><span class="n">getInstrumentation</span><span class="p">().</span><span class="na">waitForIdle</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">Runnable</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nd">@Override</span><span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">run</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">Todo</span><span class="w"> </span><span class="n">todo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">realm</span><span class="p">.</span><span class="na">where</span><span class="p">(</span><span class="n">Todo</span><span class="p">.</span><span class="na">class</span><span class="p">).</span><span class="na">findFirst</span><span class="p">();</span>
<span class="w"> </span><span class="n">assertNotNull</span><span class="p">(</span><span class="n">todo</span><span class="p">);</span>
<span class="w"> </span><span class="n">assertEquals</span><span class="p">(</span><span class="n">mockView</span><span class="p">.</span><span class="na">getContent</span><span class="p">(),</span><span class="w"> </span><span class="n">todo</span><span class="p">.</span><span class="na">getContent</span><span class="p">());</span>
<span class="w"> </span><span class="n">assertEquals</span><span class="p">(</span><span class="n">TodoListPath</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"> </span><span class="n">Flow</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">getActivity</span><span class="p">()).</span><span class="na">getHistory</span><span class="p">().</span><span class="na">top</span><span class="p">().</span><span class="na">getClass</span><span class="p">());</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">});</span>
<span class="p">}</span>
</pre></div>
<p>もちろん、AndroidにはEspressoがあるので、このようなアプローチを用いる必要はないかもしれません。
実際、Espressoでは具象Viewをそのままテストできるので、カバーできている範囲はこのアプローチよりも広いです。</p>
<p>ただ、今回はMVPがテーマですので、よりMVPのメリットを活かせるユニットテストのアプローチを模索してみました。
両者を比較して、どのようなメリットとデメリットがあるのかは興味深い話題ですが、この記事の範囲外です。</p>
</div>
<div class="section" id="proguard">
<h2><a class="toc-backref" href="#toc-entry-28">Proguard設定</a></h2>
<p>Proguard要らずが本来のDagger 2の売りのひとつではありますが、
本稿の手法では、Dagger 2の自動生成するクラスをリフレクションで検索するので、それに関連したクラスをProguardから除外します。</p>
<div class="highlight"><pre><span></span>-keep @dagger.Component public class *
-keep @dagger.Module public class * { *; }
-keep class net.tai2.flowmortardagger2demo.**Dagger** { *; }
</pre></div>
</div>
<div class="section" id="section-14">
<h2><a class="toc-backref" href="#toc-entry-29">まとめと展望</a></h2>
<p>この記事では、Flow/Mortar/Dagger 2を使用したMVPアーキテクチャによるAndroid実装のメリット・デメリットを分析し、その後、ライブラリの詳しい使い方と内部のメカニズムを見ました。</p>
<p>メリットは、Fragmentを使用しないことなどによる安定化や、MVCとは違ったアプローチの自動テストが可能になることでした。
一方、デメリットは、標準的な方法を外れることによる、高い学習コストや、機能の不足、またボイラープレートの増加などです。</p>
<p>Flow/Mortar/Dagger 2とアプリ自身、それぞれの受け持つ責務の振り分けについて学び、各画面の基本的な構成が、View,Path,Presenter,Componentの4つから成ることを学びました。
Flow/MortarがContextWrapperによる機能拡張を多用することを見て、MortarScopeを軸としてツリー構造を形成しつつ、
Flow,History,PathContext,Presenter,Bundler,Componentといった要素が連携してアプリを構成することを学びました。
また、SquareのオリジナルMVPに変更を加えて、Presenterを直接テストするための方法を学びました。</p>
<p>現状で、Flow/MortarとDagger 2を組み合わせたアプローチは、まだ決定的なやりかたが確立されておらず、Flow/Mortarの進化と共に変わっていくと思います。
Flow/Mortarを利用することで、かえってボイラープレートが増加し、煩雑になってしまっている部分があるのが、大きな欠点です。
それらの欠点については、Dagger 2自身のアプローチと同様、アノテーションとコード生成あるいはリフレクションを活用することで、改善の余地があります。
実際、 <a class="reference external" href="https://github.com/lukaspili/Auto-Mortar">lukaspili/Auto-Mortar</a> や、 <a class="reference external" href="https://github.com/lukaspili/Auto-Dagger2">lukaspili/Auto-Dagger2</a>
といった試みが出てきているので、これらを活用することで、記述量を減らせるかもしれません。</p>
</div>
<div class="section" id="section-15">
<h2><a class="toc-backref" href="#toc-entry-30">参考文献</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://corner.squareup.com/2014/01/mortar-and-flow.html">Simpler Android apps with Flow and Mortar</a> Flow/Mortarのメイン開発者の1人であるRay Ryanによる紹介記事。ただしAPIが古い。</li>
<li><a class="reference external" href="https://corner.squareup.com/2014/10/advocating-against-android-fragments.html">Advocating Against Android Fragments</a> SquareがFragmentをやめてMVPに移行した理由の説明。</li>
<li><a class="reference external" href="http://ninjinkun.hatenablog.com/entry/2014/10/16/234611">【翻訳】Android Fragmentへの反対声明)</a> 上記の翻訳。</li>
<li><a class="reference external" href="https://www.bignerdranch.com/blog/an-investigation-into-flow-and-mortar/">An Investigation into Flow and Mortar</a> Flow/Mortarの解説。ただしAPIが古い。</li>
<li><a class="reference external" href="http://qiita.com/KeithYokoma/items/9e049f12ca38d942e4fd">Fragments vs. CustomViews に一つの結論を出してみた</a> Fragmentとカスタムビューベースの設計の比較。</li>
<li><a class="reference external" href="https://github.com/konmik/konmik.github.io/wiki/Snorkeling-with-Dagger-2">Snorkeling with Dagger 2</a> Dagger 2の解説記事。おすすめ。</li>
<li><a class="reference external" href="https://docs.google.com/presentation/d/1fby5VeGU9CN8zjw4lAb2QPPsKRxx6mSwCe9q7ECNSJQ/pub?start=false&loop=false&delayms=3000">Dagger 2 - The redaggering - Google Slides</a> DIライブラリの進化の歴史。</li>
<li><a class="reference external" href="http://blog.tai2.net/bower-and-macglashan-mvp-architecture.html">BowerとMacGlashanのMVP論文要約</a> 現在普及しているMVPの原型を提案した論文の要約。</li>
<li><a class="reference external" href="https://github.com/pyricau/dagger2-mortar-flow-experiment">pyricau/dagger2-mortar-flow-experiment</a> Square社員のPierre-Yves RicauによるFlow/Mortar/Dagger 2の試案。</li>
<li><a class="reference external" href="https://github.com/lukaspili/Mortar-Flow-Dagger2-demo">lukaspili/Mortar-Flow-Dagger2-demo</a> Flow/Mortar/Dagger 2のデモ実装。</li>
<li><a class="reference external" href="https://github.com/JakeWharton/u2020">JakeWharton/u2020</a> Jake WhartonがSquare製ライブラリてんこ盛りで作ったサンプルアプリ。Flow/Mortarは使っていないが、Dagger 1は使っている。</li>
<li><a class="reference external" href="https://github.com/EligijusStarinskas/U2020-mortar-flow">EligijusStarinskas/U2020-mortar-flow</a> 上記のアプリをFlow/Mortar/Dagger 2でリメイクしたもの。</li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-android_mvp-1">筆者自身、Fragmentを使わずともViewで十分なのではないかという <a class="reference external" href="https://twitter.com/__tai2__/status/205235187116806144">疑念を抱いていた</a> ので、この主張はスッと飲み込めるものでした。 <a href="#sf-android_mvp-1-back" class="simple-footnote-back">↩</a></li><li id="sf-android_mvp-2">Flowに関しては、サンプルアプリが削除され、今後はドキュメントの拡充に集中する方針のようです。 <a href="#sf-android_mvp-2-back" class="simple-footnote-back">↩</a></li><li id="sf-android_mvp-3">といってもそもそもそんなに多くないのですが... <a href="#sf-android_mvp-3-back" class="simple-footnote-back">↩</a></li><li id="sf-android_mvp-4">実際、試行錯誤が必要になりました。 <a href="#sf-android_mvp-4-back" class="simple-footnote-back">↩</a></li><li id="sf-android_mvp-5">Dagger 2がやっているのと同様に、アノテーションを見て、足りない部分を自力でソースコード生成して補うというところまでやれば、Dagger 2の長所を活かせると思いますが、そこまでやる気力はありませんでした。 <a href="#sf-android_mvp-5-back" class="simple-footnote-back">↩</a></li><li id="sf-android_mvp-6">この記事では、Dagger 2の提供するComponentというクラスと、いわゆる一般的なコンポーネント(機能のまとまり)が両方出てくるためまぎらわしいかもしれません。Dagger 2のほうは、英語でComponentと書くことにします。 <a href="#sf-android_mvp-6-back" class="simple-footnote-back">↩</a></li><li id="sf-android_mvp-7">ここで言うサービスとは、Context#getSystemService()で取得できるオブジェクトのこと。 <a href="#sf-android_mvp-7-back" class="simple-footnote-back">↩</a></li><li id="sf-android_mvp-8">どのオブジェクトがどのオブジェクトに依存しているかという依存関係のこと。 <a href="#sf-android_mvp-8-back" class="simple-footnote-back">↩</a></li></ol>小規模ソフトウェア開発でディレクター・プロジェクトマネージャーにして欲しい3つのこと2015-08-09T00:00:00+09:002015-08-09T00:00:00+09:00tai2tag:blog.tai2.net,2015-08-09:/wants_for_directors.html<p class="first last">ディレクターあるいはプロジェクトマネージャーに、こういう点を意識してもらえると助かるという点を開発者の視点から列挙します。</p>
<p>ディレクターあるいはプロジェクトマネージャーに、こういう点を意識してもらえると助かるという点を開発者の視点から列挙します。ディレクターとプロジェクトマネージャーの違いというのは、わたしにはよくわからないので、本稿では同じものとして扱います。以後は文字数の少ないディレクターで統一します。</p>
<p>なお、わたしは数人規模の小規模受託ソフトウェア開発しか経験がありませんので、この記事もその規模の開発が対象と考えてください。例えば、アプリ開発であれば、iOSプログラマー1人、Androidプログラマー1人、UIデザイナー1人、ディレクター1人といったあたりが、よく見られる構成です。開発期間は、せいぜい数ヶ月〜半年程度です。この程度の小規模開発であれば、実際のところ、多少の問題が起きても、作業者が何日か徹夜作業をすればどうにか丸く納まるということがほとんどです。大規模開発ではこうはいかないと思うので、ソフトウェア工学への真剣な取り組みを推奨します。</p>
<p>開発に参加しているメンバーが優秀であれば、自主的にいろいろな取り組みをして、どんどんものごとを進めていってくれるかもしれませんが、なりゆきまかせでうまくいかない場合には、ディレクターの介入する余地がありますし、それはディレクターの責務だと思います。</p>
<div class="section" id="section-2">
<h2>トラブルの防止と対応</h2>
<p><a class="reference external" href="https://support.office.com/ja-jp/article/%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E4%B8%89%E8%A7%92%E5%BD%A2-8c892e06-d761-4d40-8e1f-17b33fdcf810?ui=ja-JP&rs=ja-JP&ad=JP">納期・予算・機能(スコープ)・品質</a> すべてを固定しての開発はギャンブルです。プロジェクトの状況は日々刻々と変化するので、当初の予定とのズレを把握して、これら4要素を柔軟に調整してください。とくに、作業者は、実際の作業に集中すると視野が狭くなるものですので、プロジェクト全体の状況を客観的にモニタリングする責務がディレクターには期待されます。</p>
<div class="figure">
<img alt="Project Triangle" src="https://blog.tai2.net/images/project_triangle.png" />
<p class="caption">プロジェクトの三角形</p>
</div>
<p>そうした努力をした上でも、どうしても納期が守れないという状況が発生してしまうことも、ときにはあるかもしれません。実際にトラブルが発生したときに、顧客との間でどうにか妥協できる条件を見付けた上で、作業者の <strong>防護壁</strong> になってくれるディレクターは、ありがたい存在です。</p>
</div>
<div class="section" id="section-3">
<h2>コミュニケーションの円滑化</h2>
<p>作業者は、それぞれの領域における技能と経験を持ったプロフェッショナルなので、その資産を最大限活かすべきです。そのためには、各メンバーが相互にコミュニケーションをして、知識の交換をカジュアルにできる場を設けるのがいいでしょう。お互いが物理的に近い場所で毎日作業しているのであれば、それだけで十分かもしれませんが、そうでなければ、開発用のチャットなどを用意するのがいいと思います。 <a class="reference external" href="https://slack.com/">Slack</a>、 <a class="reference external" href="http://www.chatwork.com/">ChatWork</a>、 <a class="reference external" href="https://www.hipchat.com/">HipChat</a>、Skype、IRCなどツールはいくらでもあります。</p>
<div class="figure">
<img alt="Accelerate Collaboration" src="https://blog.tai2.net/images/accelerate_collaboration.png" />
<p class="caption">参加者の相互コミュニケーションを加速する</p>
</div>
<p>また、開発においては、日々刻々と問題や課題が発生し変化していくので、それらをデータベース化して、イシュートラッカー(ITS)あるいはバグトラッカー(BTS)と呼ばれるシステムに記録し追跡するのが有効です。 <a class="reference external" href="https://github.com/">GitHub</a>、 <a class="reference external" href="https://bitbucket.org/">Bitbucket</a>、 <a class="reference external" href="http://www.redmine.org/">Redmine</a>、 <a class="reference external" href="https://www.atlassian.com/software/jira">JIRA</a>、 <a class="reference external" href="http://www.backlog.jp/">Backlog</a>、 <a class="reference external" href="http://www.pivotaltracker.com/">Pivotal Tracker</a> 等、こちらもいろいろ選択肢があります。個人的には、小規模開発であれば、GitHubについてくる程度の簡単なもので十分な気がしています。</p>
<p>これらのツールを運用する上でディレクターにやって欲しいのが、</p>
<ul class="simple">
<li>ITSはワークフロー込みで成立するツールなので、どのように運用するかを開始時に決めて、全メンバーに周知する</li>
<li>チャットツール・メール・電話でのやりとりは流れてしまうので、重要なことはITSに登録してデータベース化する</li>
<li>バグ報告時には、再現環境・再現手順・対象とするバージョンなどの情報を明記するよう周知する</li>
</ul>
<p>といったことです。このあたりは、なりゆきまかせにしていると必ずグダグダになるので、うまくコントロールしてください。</p>
<p>取り組むプラットフォームや、既存の開発資産などについて詳しくないメンバーがいるのであれば、知識を共有し、認識を合わせるためのオリエンテーションの機会を設けるといったことも、必要かもしれません。</p>
<p>最後に、プロジェクトに関する資料はすべて共有して、メンバーが全員参照できる状態にしておきましょう。これらは、作業をする上での基盤となるものなので重要です。ただし、たくさんある資料が同じディレクトリにゴチャっと置かれていると使いづらいので、ある程度整理して参照しやすい状態になっていることが大切です。古くなって有効性を失った資料は、そうとわかるようにしておいてください。情報が正しく行きわたらないために、無駄な作業が発生するようなことがあってはいけません。</p>
</div>
<div class="section" id="section-4">
<h2>責務の分担と明確化</h2>
<p>複数人で共同作業をするのであれば、どのように作業を分担して、どうやって開発を進めるかを決める必要があります。共同作業を進める上で、顧客も含めて、参加者の間でどのような情報のやりとりが必要なのかの洗い出しと共有・指示もディレクターの責務に含まれると思います。</p>
<p>たとえば、<a class="reference external" href="http://blog.tai2.net/wants_for_designers.html">デザイナーからプログラマーへのデザイン指示書</a> は、どのような情報が必要で、どういったフォーマットで作成するのが望ましいのか、といったことも作業着手前に認識合わせしておくほうが望ましいでしょう。なりゆきまかせでは、期待とは異なる成果物がでてくるかもしれません。</p>
<p>システムの開発であれば、開発者のコミュニケーションの基礎になるインターフェイスだけ先に決めておいて、モック実装のみ先行して提供することで、並行作業が可能になるといった可能性もあるので、検討してください。</p>
<p>その他、アプリのIDはどうするのか、バージョン番号はどう付けるのか、ソースコード管理ツール(Gitなど)の運用はどうするのか、サーバーの開発環境やステージング環境は用意するのか、使用するOSSのライセンスはどういったものならOKなのか、といったさまざまな決定が開発では必要になりますが、専門的な内容になってくるので、開発者と相談して決めてください。</p>
</div>
Gitのオブジェクトモデル(The Git Object Model翻訳)2015-08-05T00:00:00+09:002015-08-05T00:00:00+09:00tai2tag:blog.tai2.net,2015-08-05:/the_git_object_model.html<p class="first last">Git Community Bookから、1章2節のThe Git Object Modelを翻訳。ライセンスは、GPLv2。多くのGit解説本と違い、まずGitの内部モデル解説から入るという、おもしろい構成の本です。人によっては、このほうがわかり易いかもしれません。Gitは差分データを管理していると誤解されることがたまにありますが、それは誤りであるということがこれを読めばわかります。</p>
<p><a class="reference external" href="http://schacon.github.io/gitbook/index.html">Git Community Book</a> から、1章2節の <a class="reference external" href="http://schacon.github.io/gitbook/1_the_git_object_model.html">The Git Object Model</a> を翻訳。ライセンスは、 <a class="reference external" href="https://github.com/schacon/gitbook/blob/master/COPYING">GPLv2</a> 。</p>
<p>多くのGit解説本と違い、まずGitの内部モデル解説から入るという、おもしろい構成の本です。人によっては、このほうがわかり易いかもしれません。Gitは差分データを管理していると誤解されることがたまにありますが、それは誤りであるということがこれを読めばわかります。</p>
<div class="section" id="sha">
<h2>SHA</h2>
<p>プロジェクトの履歴を表すのに必要な情報は、いずれも、40桁の「オブジェクト名」で参照されるファイルに格納されている。オブジェクト名は、このような形をしている:</p>
<pre class="literal-block">
6ff87c4664981e4397625791c8ea3bbb5f2279a3
</pre>
<p>このような40文字の文字列は、Gitのあらゆる場面で見られる。どの場面で出てくるものであれ、その名前は、オブジェクトの内容のSHA1ハッシュを取ることで求められる。SHA1ハッシュは、暗号学的なハッシュ関数である。この意味は、異なるオブジェクトで、同じ名前を持つものを見付けるのは、ほぼ不可能ということである。これにはいくつもの利点がある。とくに重要なのは:</p>
<ul class="simple">
<li>Gitは2つのオブジェクトが同一かどうかをすばやく決定できる。たんに名前を比べるだけで。</li>
<li>オブジェクトの名前は、すべてのリポジトリで、同一の方法で計算されるので、2つのリポジトリに格納されている同一のコンテンツは、常に同じ名前になる。</li>
<li>Gitはオブジェクト読み込み時にエラーを検出できる。オブジェクト名がコンテンツのSHA1ハッシュになっているかをチェックすれば良い。</li>
</ul>
</div>
<div class="section" id="section-1">
<h2>オブジェクト</h2>
<p>どのオブジェクトも、3つのものから構成される。 <strong>型</strong> と <strong>サイズ</strong> と <strong>コンテンツ</strong> である。サイズは、たんにコンテンツのサイズで、コンテンツはオブジェクトの型が何であるかに依存し、そして、4つの異なるオブジェクトの型がある: 「ブロブ」、「ツリー」、「コミット」、そして「タグ」である。</p>
<ul class="simple">
<li>「ブロブ」は、ファイルデータを格納するために使われる。一般的に、これはひとつのファイルである。</li>
<li>「ツリー」は、基本的にはディレクトリのようなものである。これは、他のツリーやブロブを束ねたものを参照している(例えば、ファイルやサブディレクトリのように)。</li>
<li>「コミット」は、単一のツリーを指して、特定の時点でのプロジェクトの見たままの形をマークする。これは、その時点についてのメタ情報を含む。例えば、タイムスタンプ、最後のコミットに変更を加えた人、直前のコミット(群)へのポインタなど。</li>
<li>「タグ」は、特定のコミットに特別な印をつける手段である。これは、通常、いくつかのコミットを、特定のリリース(あるいはそれと似たようなもの)としてタグ付けするために使われる。</li>
</ul>
<p>Gitのすべては、この4つの異なるオブジェクト型からなる構造を操作することにあると言っても過言ではない。これは、ある種、小規模な独自のファイルシステムである。このファイルシステムは、マシンのファイルシステムそのものの上に構築される。</p>
</div>
<div class="section" id="svn">
<h2>SVNとの違い</h2>
<p>重要なのは、これが、読者の慣れ親しんでいるかもしれない、ほとんどのSCMとは異なるということである。Subversion、CVS、Perface、Mercurialのような差分ストレージを使うすべてのシステムが該当する。これらは、あるコミットと次のコミットとの間の差分を格納する。Gitでは、このようなことはしない。Gitは、プロジェクト内のすべてのファイルの見たままの形のスナップショットを、コミットをするたびに、上記のツリー構造に格納する。これは、とても重要な考えかたで、Gitを使うときには理解すべきことだ。</p>
</div>
<div class="section" id="section-2">
<h2>ブロブオブジェクト</h2>
<p>ブロブは、一般的にファイルの内容を格納する。</p>
<div class="figure">
<img alt="Object blob" src="https://blog.tai2.net/images/object-blob.png" />
</div>
<p><a class="reference external" href="https://www.kernel.org/pub/software/scm/git/docs/git-show.html">git show</a> を使えば、ブロブの内容を確認できる。ブロブのSHAがあるとして、このようにすればコンテンツを確認できる。</p>
<div class="highlight"><pre><span></span>$<span class="w"> </span>git<span class="w"> </span>show<span class="w"> </span>6ff87c4664
<span class="w"> </span>Note<span class="w"> </span>that<span class="w"> </span>the<span class="w"> </span>only<span class="w"> </span>valid<span class="w"> </span>version<span class="w"> </span>of<span class="w"> </span>the<span class="w"> </span>GPL<span class="w"> </span>as<span class="w"> </span>far<span class="w"> </span>as<span class="w"> </span>this<span class="w"> </span>project
<span class="w"> </span>is<span class="w"> </span>concerned<span class="w"> </span>is<span class="w"> </span>_this_<span class="w"> </span>particular<span class="w"> </span>version<span class="w"> </span>of<span class="w"> </span>the<span class="w"> </span>license<span class="w"> </span><span class="o">(</span>ie<span class="w"> </span>v2,<span class="w"> </span>not
<span class="w"> </span>v2.2<span class="w"> </span>or<span class="w"> </span>v3.x<span class="w"> </span>or<span class="w"> </span>whatever<span class="o">)</span>,<span class="w"> </span>unless<span class="w"> </span>explicitly<span class="w"> </span>otherwise<span class="w"> </span>stated.
...
</pre></div>
<p>「ブロブ」オブジェクトは、バイナリデータのチャンク以外のなにものでもない。それ以外のものにはなにも言及しておらず、どんな属性も持っていない(ファイル名さえも)。</p>
<p>ブロブは、完全にデータによって定義されるので、ディレクトリツリーの中に、同一の内容を持つ2つのファイルがあれば(あるいはリポジトリの異なるバージョンでもいい)、それらは同じブロブオブジェクトを共有する。ブロブオブジェクトは、ディレクトリツリー内の位置とはまったく関係がなく、ファイル名を変更したとしても、ファイルが関連付けられたオブジェクトは変わらない。</p>
</div>
<div class="section" id="section-3">
<h2>ツリーオブジェクト</h2>
<p>ツリーは、シンプルなオブジェクトで、ブロブや他のツリーへのポインタを束ねたものだ。一般的に、ディレクトリやサブディレクトリの内容を表している。</p>
<div class="figure">
<img alt="Tree Object" src="https://blog.tai2.net/images/object-tree.png" />
</div>
<p>非常に多彩な機能を持つ <a class="reference external" href="https://www.kernel.org/pub/software/scm/git/docs/git-show.html">git show</a> を使って、ツリーオブジェクトの内容を確認することももちろんできるが、 <a class="reference external" href="http://www.kernel.org/pub/software/scm/git/docs/git-ls-tree.html">git ls-tree</a> ならもっと詳しいことがわかる。ツリーのSHAがあるとすると、このように内容を確認できる:</p>
<div class="highlight"><pre><span></span>$<span class="w"> </span>git<span class="w"> </span>ls-tree<span class="w"> </span>fb3a8bdd0ce
<span class="m">100644</span><span class="w"> </span>blob<span class="w"> </span>63c918c667fa005ff12ad89437f2fdc80926e21c<span class="w"> </span>.gitignore
<span class="m">100644</span><span class="w"> </span>blob<span class="w"> </span>5529b198e8d14decbe4ad99db3f7fb632de0439d<span class="w"> </span>.mailmap
<span class="m">100644</span><span class="w"> </span>blob<span class="w"> </span>6ff87c4664981e4397625791c8ea3bbb5f2279a3<span class="w"> </span>COPYING
<span class="m">040000</span><span class="w"> </span>tree<span class="w"> </span>2fb783e477100ce076f6bf57e4a6f026013dc745<span class="w"> </span>Documentation
<span class="m">100755</span><span class="w"> </span>blob<span class="w"> </span>3c0032cec592a765692234f1cba47dfdcc3a9200<span class="w"> </span>GIT-VERSION-GEN
<span class="m">100644</span><span class="w"> </span>blob<span class="w"> </span>289b046a443c0647624607d471289b2c7dcd470b<span class="w"> </span>INSTALL
<span class="m">100644</span><span class="w"> </span>blob<span class="w"> </span>4eb463797adc693dc168b926b6932ff53f17d0b1<span class="w"> </span>Makefile
<span class="m">100644</span><span class="w"> </span>blob<span class="w"> </span>548142c327a6790ff8821d67c2ee1eff7a656b52<span class="w"> </span>README
...
</pre></div>
<p>見てわかるように、ツリーオブジェクトは、エントリーのリストを含んでおり、それぞれにモード、オブジェクト型、SHA1名、ファイル名があって、ファイル名でソートされている。これは、ひとつのディレクトリツリーの内容を表している。</p>
<p>ツリーから参照されるオブジェクトは、ブロブ(ファイルの内容を表す)か、または他のツリー(サブディレクトリの内容を表す)かもしれない。ツリーとブロブは、他のすべてのオブジェクトと同様に、それらの内容のSHA1ハッシュで参照される。2つのツリーが(再帰的にすべてのサブディレクトリについても)同一の内容を持つならば、かつその場合に限り、それらは同じSHA1名を持つ。これにより、Gitは、関連した2つのツリーオブジェクトの間の違いをすばやく判定することができる。オブジェクト名が同一のエントリーは無視できるためだ。</p>
<p>(注意: サブモジュールが存在する場合には、ツリーには、コミットもエントリーとして含まれるかもれない。 <strong>サブモジュール</strong> の節を見よ。)</p>
<p>すべてのファイルは、644か755のモードとなることに注意: Gitは、実際には、実行ビットしか見ない。</p>
</div>
<div class="section" id="section-4">
<h2>コミットオブジェクト</h2>
<p>「コミット」オブジェクトは、ツリーの物理的な状態と、そこにどうやって辿りつくのかの記述、及びその理由を結びつける。</p>
<div class="figure">
<img alt="Commit Object" src="https://blog.tai2.net/images/object-commit.png" />
</div>
<p>--pretty=rawオプションを <a class="reference external" href="https://www.kernel.org/pub/software/scm/git/docs/git-show.html">git show</a> または <a class="reference external" href="https://www.kernel.org/pub/software/scm/git/docs/git-log.html">git log</a> に与えて、好きなコミットの内容を見ることができる。</p>
<div class="highlight"><pre><span></span>$<span class="w"> </span>git<span class="w"> </span>show<span class="w"> </span>-s<span class="w"> </span>--pretty<span class="o">=</span>raw<span class="w"> </span>2be7fcb476
commit<span class="w"> </span>2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4
tree<span class="w"> </span>fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf
parent<span class="w"> </span>257a84d9d02e90447b149af58b271c19405edb6a
author<span class="w"> </span>Dave<span class="w"> </span>Watson<span class="w"> </span><dwatson@mimvista.com><span class="w"> </span><span class="m">1187576872</span><span class="w"> </span>-0400
committer<span class="w"> </span>Junio<span class="w"> </span>C<span class="w"> </span>Hamano<span class="w"> </span><gitster@pobox.com><span class="w"> </span><span class="m">1187591163</span><span class="w"> </span>-0700
<span class="w"> </span>Fix<span class="w"> </span>misspelling<span class="w"> </span>of<span class="w"> </span><span class="s1">'suppress'</span><span class="w"> </span><span class="k">in</span><span class="w"> </span>docs
<span class="w"> </span>Signed-off-by:<span class="w"> </span>Junio<span class="w"> </span>C<span class="w"> </span>Hamano<span class="w"> </span><gitster@pobox.com>
</pre></div>
<p>ここから、コミットの定義がわかる:</p>
<ul class="simple">
<li><strong>ツリー</strong>: ツリーオブジェクトのSHA1名(以下で定義)。特定の時点でのディレクトリの内容を表す。</li>
<li><strong>親(1つ以上)</strong>: いくつかのコミットのSHA1名。これらは、プロジェクト履歴における直前のステップ(1つ以上)を表す。上の例は1つの親を持つ。マージコミットは、1つ以上の親を持つかもしれない。親のないコミットは、「ルート」コミットと呼ばれ、プロジェクトの最初のリビジョンを表す。どのプロジェクトも、すくなくとも1つのルートを持たなければならない。プロジェクトが複数のルートを持つこともあるが、これは普通ではない(し、良いアイデアとも言えない)。</li>
<li><strong>著者</strong>: この変更についての責任を持つ人の名前。日付もいっしょに。</li>
<li><strong>コミッター</strong>: 実際にコミットを作成した人の名前。作成された日付もいっしょに。これは著者とは違うかもしれない。例えば、著者はパッチを書き、メールでそれを他の人に送り、その人がパッチを使ってコミットをするといったことが考えられる。</li>
<li><strong>コメント</strong>: コミットについての説明。</li>
</ul>
<p>コミット自体は、実際になにが変更されたのかについての情報をまったく含んでいないことに注意。すべての変更は、コミットから参照されるツリーの内容と親に関連付けられたツリーを比較することで計算される。とくに、Gitは、ファイル名の変更を明示的には記録しない。にも関わらず、同じファイルデータのパス変更があるときにはそれを検出して、リネームを提案する。(例えば、 <a class="reference external" href="https://www.kernel.org/pub/software/scm/git/docs/git-diff.html">git diff</a> の-Mオプションを見よ)</p>
<p>コミットは、通常、 <a class="reference external" href="https://www.kernel.org/pub/software/scm/git/docs/git-commit.html">git commit</a> によって作られる。これは、通常、現在のHEADを親とするコミットを作成し、そのツリーは、現在インデックスに格納されている内容から取得される。</p>
</div>
<div class="section" id="section-5">
<h2>オブジェクトモデル</h2>
<p>これまで3つの主要なオブジェクト型を見てきた(ブロブ、ツリー、コミット)ので、これらがどのようにまとめられるのか簡単に見てみよう。</p>
<p>次のようなディレクトリ構造を持つシンプルなプロジェクトがあるとする。</p>
<div class="highlight"><pre><span></span>$>tree
.
<span class="p">|</span>--<span class="w"> </span>README
<span class="sb">`</span>--<span class="w"> </span>lib
<span class="w"> </span><span class="p">|</span>--<span class="w"> </span>inc
<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="sb">`</span>--<span class="w"> </span>tricks.rb
<span class="w"> </span><span class="sb">`</span>--<span class="w"> </span>mylib.rb
<span class="m">2</span><span class="w"> </span>directories,<span class="w"> </span><span class="m">3</span><span class="w"> </span>files
</pre></div>
<p>そして、これをGitリポジトリにコミットしたとすると、このように表される。</p>
<div class="figure">
<img alt="Objects structure" src="https://blog.tai2.net/images/objects-example.png" />
</div>
<p>(ルートを含めて)ディレクトリー毎に <strong>ツリー</strong> オブジェクトが、ファイル毎に <strong>ブロブ</strong> オブジェクトができたことがわかる。それから、 ルートを指している <strong>コミット</strong> オブジェクトがあるので、コミットされた時点でのプロジェクトのあるがままの形を追跡することができる。</p>
</div>
<div class="section" id="section-6">
<h2>タグオブジェクト</h2>
<div class="figure">
<img alt="Tag Object" src="https://blog.tai2.net/images/object-tag.png" />
</div>
<p>タグオブジェクトは、オブジェクトの名前(単に「オブジェクト」と呼ばれる)、オブジェクトの型、タグ名、タグを作成した人の名前(タガー)、そしてメッセージが含まれる。メッセージにはシグネチャが含まれることもある。これは <a class="reference external" href="https://www.kernel.org/pub/software/scm/git/docs/git-cat-file.html">git cat-file</a> を使えば見られる:</p>
<div class="highlight"><pre><span></span>$<span class="w"> </span>git<span class="w"> </span>cat-file<span class="w"> </span>tag<span class="w"> </span>v1.5.0
object<span class="w"> </span>437b1b20df4b356c9342dac8d38849f24ef44f27
<span class="nb">type</span><span class="w"> </span>commit
tag<span class="w"> </span>v1.5.0
tagger<span class="w"> </span>Junio<span class="w"> </span>C<span class="w"> </span>Hamano<span class="w"> </span><junkio@cox.net><span class="w"> </span><span class="m">1171411200</span><span class="w"> </span>+0000
GIT<span class="w"> </span><span class="m">1</span>.5.0
-----BEGIN<span class="w"> </span>PGP<span class="w"> </span>SIGNATURE-----
Version:<span class="w"> </span>GnuPG<span class="w"> </span>v1.4.6<span class="w"> </span><span class="o">(</span>GNU/Linux<span class="o">)</span>
iD8DBQBF0lGqwMbZpPMRm5oRAuRiAJ9ohBLd7s2kqjkKlq1qqC57SbnmzQCdG4ui
nLE/L9aUXdWeTFPron96DLA<span class="o">=</span>
<span class="o">=</span>2E+0
-----END<span class="w"> </span>PGP<span class="w"> </span>SIGNATURE-----
</pre></div>
<p><a class="reference external" href="https://www.kernel.org/pub/software/scm/git/docs/git-tag.html">git tag</a> コマンドを見て、タグオブジェクトの作成と検証方法を学ぶこと。(<a class="reference external" href="https://www.kernel.org/pub/software/scm/git/docs/git-tag.html">git tag</a> は、「軽量タグ」を作成するためにも使われることに注意。これはタグオブジェクトとはぜんぜん違うもので、たんに"refs/tags/"ではじまる名前のものを参照するだけだ。)</p>
</div>
プログラマーからデザイナーへの要望(主にアプリ開発について)2015-07-27T00:00:00+09:002015-07-27T00:00:00+09:00tai2tag:blog.tai2.net,2015-07-27:/wants_for_designers.html<p class="first last">アプリ開発においてのフローは、まずデザイナーがデザイン指示書を作成し、プログラマーが、なるべく忠実にそれを再現するように実装していくという順序になることがよくあります。これまでの経験を踏まえて、今後案件をはじめる前にデザイナー、あるいは、プロジェクトマネージャーやディレクターに読んでもらうための資料を作成することにしました。本稿では、開発過程で、プログラマーとデザイナーとのやりとりにおいて発生するいくつかの問題について説明し、より良いコラボレーションのありかたを模索します。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-2" id="toc-entry-1">問題の根っ子</a></li>
<li><a class="reference internal" href="#web-designing-in-the-browser" id="toc-entry-2">Web制作の場合 ー インブラウザデザイン(Designing in the Browser)</a></li>
<li><a class="reference internal" href="#web" id="toc-entry-3">Webアプリ開発の場合</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-4">ネイティブアプリの場合</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-5">イニシアチブの所在</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-6">プログラマーへの指示の出しかた</a><ul>
<li><a class="reference internal" href="#section-6" id="toc-entry-7">判断材料を提供する</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-8">検索性の高い資料</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-9">カラースキーム</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-10">レスポンシブ対応</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-11">インタラクション</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-12">プラットフォームごとに異なる自然な表現</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-12" id="toc-entry-13">まとめ</a></li>
</ul>
</div>
<p>アプリ開発においてのフローは、まずデザイナーがデザイン指示書を作成し、プログラマーが、なるべく忠実にそれを再現するように実装していくという順序になることがよくあります。自然で現実的な発想ではあるのですが、実際の開発の過程では、この流れが思っていたほどスムーズにいかないこともあります。そこで、これまでの経験を踏まえて、今後案件をはじめる前にデザイナー、あるいは、プロジェクトマネージャーやディレクターに読んでもらうための資料を作成することにしました。本稿では、開発過程で、プログラマーとデザイナーとのやりとりにおいて発生するいくつかの問題について説明し、より良いコラボレーションのありかたを模索します。</p>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-1">問題の根っ子</a></h2>
<p>問題の核心を一言で言うと、「どんなプラットフォームにも技術的な制約がある」ということです。<sup id="sf-wants_for_designers-1-back"><a href="#sf-wants_for_designers-1" class="simple-footnote" title="技術的な制約を越えて、あるいは縮小して、アイデアを実現するのがプログラマーの仕事だというようなことを言う人がいることは承知してます">1</a></sup>アプリケーションあるいはシステムを具現化する役割であるところのプログラマーは、当然、取り組んでいるプラットフォームの技術的な制約を一番よく理解しています。理解しなければ具現化のしようがありません。一方、デザイナーはというと、UI上の視覚効果、ユーザビリティー、ユーザー心理などについてはよく理解していますが、その土台となっている技術的な詳細や制約については、あまり把握していない場合が多いでしょう。</p>
<p>最近は、どのプラットフォームも、たいていデザインガイドラインを用意しています。Appleであれば、 <a class="reference external" href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/">Human Interface Guidlines</a> 、Androidであれば、 <a class="reference external" href="https://developer.android.com/design/index.html">Material Design</a> などです。これらを熟読して、そのプラットフォームの標準では、どういったUI要素をどのように使うことが可能なのかを把握しておいてもらえれば、話は早いです。<sup id="sf-wants_for_designers-2-back"><a href="#sf-wants_for_designers-2" class="simple-footnote" title="プラットフォームの都合で標準のガイドラインがいきなり変わって、最新のガイドラインが、現在市場でメインターゲットとなる機種でサポートされていないため、役に絶たないという場合もありますが…">2</a></sup> しかしながら、これまで仕事をしてきて、こういったガイドラインの内容をきちんと把握しているデザイナーにはあまり出会ったことがありません。また、たとえデザインガイドラインレベルで把握していたとしても、実装で生じ得るデザイン起因の問題を完璧に把握することは難しいでしょう。けっきょく、実装の問題は、実装をしなければわかりません。</p>
<div class="figure">
<img alt="Concerns of programmers and designers" src="https://blog.tai2.net/images/concerns-of-programmers-and-designers.png">
<p class="caption">プログラマーとデザイナーの関心領域の違い</p>
</div>
<p>ガイドラインを読んで把握していなくても、市場に出回っている本物のアプリをたくさん調査して、アプリのUI的な構造がどうなっているのか具体的に把握するというやりかたをしている人はけっこういると思います。これは非常に効果的なやりかたで、どんどんやるべきだと思いますが、完璧ではありません。実際のアプリで実装されているからといって、それが必ずしも標準的なUIなのかはわかりませんし、簡単に実現できるものだとも限らないからです。ユーザー目線で魅力的に写る、ちょっと小粋なユーザーインターフェイスは、実は、ベンダーががんばって独自に実装した結果である可能性があるのです。<sup id="sf-wants_for_designers-3-back"><a href="#sf-wants_for_designers-3" class="simple-footnote" title="たとえば、コンテンツをピンチイン・アウトで拡大縮小するという機能は、iOSでは標準のコンポーネントのみで実装できますが、Androidでは、標準では用意されていないので、自力で実装しようとすると(iOSに比べれば)めんどうだったりします">3</a></sup></p>
<p>もちろん、なんであれ技術的に可能なことは、実現することができます。しかし、プロジェクトの予算は常に限られていますので、なるべく効率的に配分する必要があります。非標準的な実装をするために工数が跳ね上がってしまうのであれば、標準的なUIをちょっとカスタマイズするぐらいにしておこう、という判断もあり得るかもしれません。もっともこれは、デザイナーやプログラマーの一存で決めることではないでしょうが。すくなくとも、その実装コストを判断するのにもっとも適しているのは、プログラマーです。</p>
</div>
<div class="section" id="web-designing-in-the-browser">
<h2><a class="toc-backref" href="#toc-entry-2">Web制作の場合 ー インブラウザデザイン(Designing in the Browser)</a></h2>
<p>静的なHTMLの制作では、アプリ開発とはすこし事情が違います。ある意味では、Web制作のほうがアプリ開発よりも一歩先を言っていると言ってもいいかもしれません。</p>
<p>Web制作においては、従来、大雑把な工程として、デザインとコーディングという2工程に分けて作業が進められてきました。前者は、Fireworks,Photoshop,Illustratorといったツールを使用して行い、それが完成してから、テキストエディタを使用してマークアップを記述するという流れがふつうだったようです。この場合も、先程論じたのと同様に、実装の段階で、技術的な制約が出てきて、デザイン通りに実装することが難しかったり、あるいは前工程に戻っての修正が必要になるといったことがあり得ます。</p>
<p>これは、デザインの工程が、2次元のビットマップあるいはベクタ画像を成果物としており、実質的に無制約であるのに対して、コーディングは、ブラウザ上に動的にレイアウトする作業であるため、より制約が強いからです。</p>
<div class="figure">
<img alt="Constraint of HTML/CSS" src="https://blog.tai2.net/images/html-constraint.png">
<p class="caption">単なるビットマップ・ベクトル画像よりもHTML/CSSのほうが制約が強い</p>
</div>
<p>ところで、Web制作においては、デザイナー自身が、実装言語であるところのHTML/CSS(と多少のJavaScript)を習得しており、設計(デザイン)と実装(コーディング)を同じ人が担当する場合もすくなくはないでしょう。そこで、実装時に(ある意味で)無駄な苦労するぐらいであれば、最初から制約の強い環境でデザイン自体をしてしまおうという、インブラウザデザイン(Designing in the Browser)と呼ばれる流れが出てきました。この考えかたで作業を行えば、実質的に設計と実装の不一致はなくなります。最近のウェブでは、モバイルの台頭によりレスポンシブウェブデザインがふつうに求められるようになってきており、画角を固定した静的なレイアウトではなく、より動的なレイアウトが必要になってきていることが、こういった流れの背景にあるようです。</p>
</div>
<div class="section" id="web">
<h2><a class="toc-backref" href="#toc-entry-3">Webアプリ開発の場合</a></h2>
<p>静的なHTMLではなく、従来的なWebアプリケーション<sup id="sf-wants_for_designers-4-back"><a href="#sf-wants_for_designers-4" class="simple-footnote" title="SPAの場合は若干事情が異なる可能性があります。わたしはSPAでのウェブアプリケーション開発は経験がまだないので、よくわかりません">4</a></sup>の場合でも、ネイティブアプリ開発よりは状況が良いように見えます。Webアプリケーションでは、ビューやテンプレートと言われるファイル自体は、HTML+αの記述で書けるようになっている場合が多く、また、ネイティブアプリに比べて、ビジネスロジックとプレゼンテーション(ビュー)の、より明確な分離ができている傾向にあるように思います。ですから、デザインに加えて、ビューの実装自体を完全にデザイナーに任せるという役割分担も実現し易いのではないでしょうか。そうでなくても、デザイナーがHTML/CSSレベルまで落とし込んでくれれば、それをビュー実装に変換するのは容易なので、デザイナーの成果物=HTML/CSSという形で、プログラマーとデザイナーがうまく協業できているプロジェクトも多いでしょう。</p>
<div class="figure">
<img alt="Roles of programmers and designers" src="https://blog.tai2.net/images/roles-of-programmers-and-designers.png">
<p class="caption">プログラマーとデザイナーの綺麗な役割分担</p>
</div>
<p>このように、デザイナーがプレゼンテーションの実装まで責任を負ってくれるのであれば、プログラマーはビジネスロジックに集中できます。非常にわかり易くて綺麗な役割分担です。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-4">ネイティブアプリの場合</a></h2>
<p>ところが、(iOSでもAndroidでも)ネイティブアプリ開発では、ウェブのような綺麗は役割分担は難しいです。要因はいくつかあります。</p>
<p>まず、冒頭でも述べたように、デザイナーが、ネイティブアプリのアーキテクチャに不慣れなケースが多いように見受けられることです。ウェブブラウザについてはよく理解しているデザイナーが多いのですが、ネイティブアプリとなると、どのようなコントロールが使えるのかといったことや、基本的なアプリのナビゲーションなど、ガイドラインレベルの知識を持っていないデザイナーが多く見られます。また、アプリの実装となると、Webのようにブラウザとテキストエディタでは成立せず、XcodeやAndroid StudioといったIDEを使用してビルドすることが必要になるので、単に実行環境を作るだけでも、ハードルが高いようです。</p>
<p>ネイティブアプリでは、実際に、プレゼンテーションの実装がウェブよりもヘヴィーな作業であるように思います。iOSでは、Storyboardという優れたUIデザインツールが標準で用意されているので、ある程度状況が良いのですが、これ自体使うのに立ち入った知識が必要ですし、込み入ったレイアウトを実装するには、どうしてもプログラミングが必要になってきます。プレゼンテーションを実装するのに(大量の)XMLとJavaの必要なAndroidは、言うに及びません。さらに言うと、最近のアプリ開発では必須と言えるアニメーションの実装ともなれば、プログラミングをしながらの試行錯誤は必須です。つまり、これらのプラットフォームでは、プレゼンテーションの実装からプログラミング言語を完全に分離することができず、また、Webで学んだHTML/CSSの知識はまったく役に立たないのです。</p>
<p>こういった状況があるのは、ある意味でプログラマーの責任ではあります。つまり、プレゼンテーションの実装と、それ以外の実装が、ワークフローを意識して明確に分離をされていない環境を作ったのもまた、プログラマーだからです。理想論を言えば、プレゼンテーションの実装は、Webのようにビジネスロジックとは明確に分離されており<sup id="sf-wants_for_designers-5-back"><a href="#sf-wants_for_designers-5" class="simple-footnote" title="できればHTML/CSSのようにプレゼンテーション単体で確認可能なのが望ましい">5</a></sup>、また、デザイナーの理解できる言語で記述できるようになっているべきだし、そういう方向を目指すべきだと、わたしは思います。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-5">イニシアチブの所在</a></h2>
<p>これまで、設計(デザイン)と実装(コーディング)の乖離から生じる問題について見てきました。また、ウェブ制作では、設計工程と実装工程の分離をやめることで、問題を解消するアプローチがあることを見ました。</p>
<p>ネイティブアプリ制作のワークフローとしてよくあるのは、冒頭で挙げたような、デザイン指示書作成(デザイナー) → 実装(プログラマー)という流れです。この流れだと、アプリUIの設計に関してイニチアチブを握っているのはデザイナーです。画面に入る要素の種類や配置を考えるのもデザイナーですし、ナビゲーション(画面遷移)を決めるのもデザイナーです。</p>
<p>けれども、この流れは本当に正しいのでしょうか。ネイティブアプリでは、ナビゲーションの種類や意味、バーの配置やそこに入れることのできる要素の制限など、UIのレギュレーションがウェブよりもはるかに厳しく定められています。デザイナーがガイドラインレベルの知識を持たないのであれば、それらの制約を正しく理解しているのはプログラマーだけです。逆説的ですが、それを理解しているが故に、(技術的な実現可能性を踏まえた上で)その枠から出る発想をできるのもプログラマーだという考えかたもできます。</p>
<p>そうだとすれば、デザイナーがプラットフォームの制約を理解せずに野放図に作成したデザインを実現するために、無闇に工数をかけるよりも、制約を理解したプログラマーがイニチアチブを握って、最初から無理のない設計をするほうが良いのではないでしょうか。プログラマーが骨組となる絵を描いて、その上で、細かい肉付けだけをデザイナーがする、という考え方です。</p>
<p>このように言えば、デザイナーは、プログラマーのように視覚表現の発想力がなく、デザイン原則を考えない人間に、そんな重要なことを任せられるかと怒ると思いますし、そうあって欲しいと思います。であれば、デザイナーには、プラットフォームについて、せめてガイドラインレベルでは理解しておいてもらいたいものです。それに、ソフトウェア開発の現場では、プログラマーの作業比重が(デザイナーと比べて)重くなりすぎているというのが現実なので、できるだけ負担を分けあいたいのです。それは、ソフトウェア開発市場におけるデザイナーの職域拡大にも結び付くことだと思います。<sup id="sf-wants_for_designers-6-back"><a href="#sf-wants_for_designers-6" class="simple-footnote" title="開発現場で、プログラマーが偏重されているというデザイナーからの意見が、しばしば耳に入ってきます">6</a></sup></p>
<div class="figure">
<img alt="Weight of programmers and designers" src="https://blog.tai2.net/images/weight-of-programmers-and-designers.png">
<p class="caption">アプリ開発では、デザイナーよりプログラマーのほうが負担が大きい</p>
</div>
<p>そうは言っても、現実には、やはり旧来通りのデザイン指示書→実装という流れになることが考えられます。そもそもワークフローそれ自体はデザイナーやプログラマーの一存では決められないことが多いですし、現状では、インブラウザデザインのようなフローをアプリ開発で実践するのは技術的に難しいからです。ですから、できる限りプラットフォームについて勉強しつつ、実装するプログラマーと相談しながら、柔軟に作業を進めていって欲しいと思います。わからないことがあったらプログラマーに聞くべきです。モバイルプラットフォームが未経験であれば、作業に入る前に、プログラマーに時間を取ってもらって、モバイルプラットフォームのアーキテクチャーについて、ひととおりレクチャーを受けるといいかもしれません。</p>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-6">プログラマーへの指示の出しかた</a></h2>
<p>とりあえず、現実的なやりかたに従って、あらかじめデザイナーがレイアウト・デザインをして、それをもとにプログラマーが実装をするというよくあるフローで開発を進めるとします。これは、組版で例えると、アートディレクターとデザイナー、あるいはデザイナーとDTPオペレーターの関係に相当します。こういったやりかたをする場合に、指示を受けるプログラマーの側からすれば、このように指示を出してもらうとありがたいという具体的なポイントがいくつかありますので、説明します。目標は、無駄なコミュニケーションの削減による時間短縮です。</p>
<div class="section" id="section-6">
<h3><a class="toc-backref" href="#toc-entry-7">判断材料を提供する</a></h3>
<p>どんなものにもバグは入り込みます。たとえそれがデザインであったとしても、指定の詳細度が増せば増すほど、一貫性がない部分、指定の漏れなどミスが入り込む余地は増えてきます。最終的な成果物となるアプリでは、工数をかけてひとつひとつ機能を検証し、バグを潰していきますが、中間成果物であるデザイン指示書では、通常そこまでの検証は行いません。結果として、プログラマーの元には、多分に曖昧さや誤りの含まれた資料が届きます。これはデザイナーの努力不足とかではなく、ワークフローに含まれる構造的な問題なので、しかたのないことです。</p>
<p>このようなデザイン資料に含まれるバグは存在するという前提に立って、どういう資料作りをしてもらえるのがいいかを考えると、方針が見えてきます。</p>
<ol class="arabic simple">
<li>記載する情報は必要最低限にする</li>
<li>不足している情報を実装者が補うための判断材料を提供する</li>
</ol>
<p>という2点がポイントになります。</p>
<p>一般的に言って、情報の量が多ければ多いほど、そこにミスの入り込む余地は増えます。すべてのUI要素のサイズやマージンなどを個別に手動で指定していたら、必ず、何箇所かは一貫性の無い部分や記述漏れが入り込むでしょう。そういったケアレスミスを防ぐために、情報を整理し、カテゴリーや階層化などの手段を用いて重複をなくし、資料のページ数や、余分な情報をできるだけ減らすのです。</p>
<p>そのためには、情報構造になんらかの意味を与えるという作業が必要になってくると思います。例えば、フォントに関する情報でも、見出し、メニュー、リストなどの整理をして、それにスタイルを紐付けるといった整理が考えられます。なにも難しいことはありません。HTML/CSSでやっていることと同じ考えかたをすればいいだけです。こういった考えかたに基いて資料が作成されていれば、実装をする際にも要素の意味を実装者が判断して足りない情報を補えるので、部分部分に事細かに指示が書き込まれていなくても、悩まずに作業が進められます。</p>
<p>あるいは、別のアプローチとして、PhotoshopやIllustratorなどから、直接レイアウト情報を網羅的に出力できる <a class="reference external" href="https://www.specctr.com/">Specctr</a> のようなツールもあります。こういったツールを使用することで、人の手によるミスを無くすという方向性もあり得ますので、検討する価値はあると思います。</p>
</div>
<div class="section" id="section-7">
<h3><a class="toc-backref" href="#toc-entry-8">検索性の高い資料</a></h3>
<p>デザイン資料の作成方法は、個々のデザイナーによりさまざまで、PDFにテキストで色々指示を書き込んで渡してくれる人もいれば、1画面のPNGを何枚も渡してくる人もいますし、あるいは、PSDファイルをそのまま渡してくる人もいます。</p>
<p>PSDでそのまま渡すというやりかたは、レイヤー構造自体が整理されて注釈が適切に付加されており、資料としてそのまま利用できるレベルになっているのというのであれば、あり得るのかもしれません。ただ、個人的にはあまりPhotoshopというツール自体使い慣れていないので、PSDそのままで渡されるとちょっと辛いです。</p>
<p>資料を作る際に考慮して欲しいことは、プログラマーは、それを何度も何度も繰り返し参照しながら、実装に落し込んでいくということです。ですから、その資料自体の検索性<sup id="sf-wants_for_designers-7-back"><a href="#sf-wants_for_designers-7" class="simple-footnote" title="この場合の検索というのは、キーワード検索に限りません。見たい内容にすぐに辿り付けるように整理されているか、ということです。">7</a></sup>が低いと、作業効率に直に響いてきます。個人的には、1冊のPDFかなにかにすべてまとまっていて、スクロールさせたり、キーワード検索をかければ、即座に必要な項目に辿りつけるという作りがいいと思っていますが、それに限らずとも、資料自体の使い方とセットで提示してもらえれば、なんでもかまいません。</p>
</div>
<div class="section" id="section-8">
<h3><a class="toc-backref" href="#toc-entry-9">カラースキーム</a></h3>
<p>アプリケーションで使われる色の指定を効率良く伝達するには、カラースキームの作成が役立ちます。このときひとつやってもらいたいのが、色に名前を付けるということです。</p>
<p>デザインドキュメントに、逐一数値だけで記述されていると、見る側からすれば、どこで使われている色と共通する色なのか、パッと見て識別できません。とくにそれが微妙な色の違いの場合には、ミスにも繋がりかねません。</p>
<div class="figure">
<img alt="Color scheme 1" src="https://blog.tai2.net/images/design-document-1.png">
<p class="caption">数値で色が指定してあるデザイン指示書</p>
</div>
<p>また、プログラムのソースコード内では、多くの場合数値には名前を付けて管理するので、最初から色に名前がついていれば、自分で考える手間が省けて助かります。色の命名方法は、これ自体色々やりかたが考えられますが、いくつか使用する色をピックアップして、その彩度違いのバージョンを末尾に数値を付けて表す、などが考えられます。それをさらにUI的な情報構造と関連付けて整理するというのも有効だと思います。</p>
<div class="figure">
<img alt="Color scheme 2" src="https://blog.tai2.net/images/design-document-2.png">
<p class="caption">名前で色が指定してあるデザイン指示書</p>
</div>
<div class="figure">
<img alt="Color scheme 3" src="https://blog.tai2.net/images/design-document-3.png">
<p class="caption">情報構造と関連付けて色が指定してあるデザイン指示書</p>
</div>
</div>
<div class="section" id="section-9">
<h3><a class="toc-backref" href="#toc-entry-10">レスポンシブ対応</a></h3>
<p>最近では、iOSでもAndroidともに、特定の画面サイズを想定して、固定サイズのキャンバスにデザインすることはできなくなりました。320x480のサイズ固定で考えれば良かった古き良き時代のやりかたは、もう通用しないのです。Webのようにインブラウザデザイン的な方法論を取ることもできません。</p>
<p>しかたがないので、心眼でレスポンシブなデザインをするしかないのですが、タブレットとスマートフォンで、大幅に見せかたを変えるようなことまでするとなれば、プログラマーとの密接な連携なしには実現できません。大中小など想定サイズをいくつかに分類して、そのパターンだけレイアウトを作成するというやりかたもありますが、まあ大変だと思います。</p>
<p>とりあえず、プラットフォーム毎に、レイアウトの実装をどのように行っているのか、どのような指定ができるのかというところまで把握した上で、デザインドキュメントを作成してもらえれば、やりとりがスムーズになると思います。そうしないのであれば、おおざっぱなイメージだけ作成して、細かい部分はプログラマーにまかせるしかありません。ともかく、これに関しては、わたし自身、デザイナーとどう連携するべきなのか、いまだに頭を抱えています。</p>
</div>
<div class="section" id="section-10">
<h3><a class="toc-backref" href="#toc-entry-11">インタラクション</a></h3>
<p>アプリは、紙とは違って、ユーザーが操作したら反応するものです。ボタンであれば、押したときに必ずなにかしら変化がありますので、通常の色だけでなく、押下時の色についても指定する必要があります。アプリのデザインでは、ユーザーが操作したときに、その要素にどのような変化が起きるのかについても、必ず考えてください。また、これはプラットフォームによって、どのような変化が起きるのかや、どのように指定するのかなど細部が異なるので、プラットフォーム個別の知識が必要になってきます。</p>
</div>
<div class="section" id="section-11">
<h3><a class="toc-backref" href="#toc-entry-12">プラットフォームごとに異なる自然な表現</a></h3>
<p>iOSには、1次元または2次元の整列されたデータを表示するのに便利なテーブルビューというビューがあります。あるいは、データの一覧だけでなく、静的なレイアウトのためにも使うことができたりして、非常に便利です。これはiOSでは非常に一般的なものなので、デザイナーのみなさんも、このコンセプトをベースにデザインされることが多いようです。</p>
<p>一方、Androidにも、似たようなものとして、リストビューという標準のビューがありますが、これは、iOSのテーブルビューに比べるとだいぶ貧弱で、2次元のデータは扱えませんし、静的なレイアウトをするのにも向いていません。iOSのコンセプトに慣れている人が、Androidでもテーブルビュー的な考えかたを持ち込んでしまうと、プログラマーは苦労する場合があります。もちろん、iOS的な表現を実装すること自体は可能なのですが、iOSで実装するよりも工数がかかるかもしれないということです。UI的にどうしてもその表現が必要なのであれば、苦労してでも実装すべきですが、なんとなくiOSでそうなってるから合わせてみたというだけであれば、その決定は、無駄に工数を増やしているだけかもしれません。</p>
<p>また、ナビゲーションの概念も、iOSとAndroidで微妙に異なります。iOSでは、アプリのモードを瞬時に切り替えるタブビュー、ナビゲーションバーと関連付けられたナビゲーションビュー、その中に入るコンテンツというように、厳密に画面の階層構造が整理されています。一方、Androidでは、画面のナビゲーションはもっと自由です。Androidでは、画面下部にバックボタンがあることが前提になるため、基本的には左上の「戻る」ボタンは不要ですし、画面上部にアクションバーという多彩な機能を持ったバーがあり、これがUI的に色々な役割を果たします。</p>
<p>この他、iOSとAndroidで異なる部分を挙げればきりがありません。ともかく、同じモバイルでも、プラットフォームごとに自然なUIは異なるということを念頭に置いてデザインをしてください。プラットフォームのガイドラインを読んで自然なUIについて学び、なにが自然なのかわからなければ、プログラマーと相談してください。</p>
</div>
</div>
<div class="section" id="section-12">
<h2><a class="toc-backref" href="#toc-entry-13">まとめ</a></h2>
<ul class="simple">
<li>どんなプラットフォームにも技術的な制約がある</li>
<li>ネイティブアプリ開発では、Webのような綺麗な作業分担が難しい</li>
<li>わからないことはプログラマーに聞いて欲しい</li>
<li>デザイン資料は検索性の良さを意識して作成して欲しい</li>
<li>カラースキームを作るときは、色に名前を付けて欲しい</li>
<li>レスポンシブ対応は難しいけど協力してがんばろう</li>
<li>UI操作時のインタラクションまで考えて欲しい</li>
<li>プラットフォームごとの特性について学んだ上でデザインして欲しい</li>
</ul>
</div>
<ol class="simple-footnotes"><li id="sf-wants_for_designers-1">技術的な制約を越えて、あるいは縮小して、アイデアを実現するのがプログラマーの仕事だというようなことを言う人がいることは承知してます <a href="#sf-wants_for_designers-1-back" class="simple-footnote-back">↩</a></li><li id="sf-wants_for_designers-2">プラットフォームの都合で標準のガイドラインがいきなり変わって、最新のガイドラインが、現在市場でメインターゲットとなる機種でサポートされていないため、役に絶たないという場合もありますが… <a href="#sf-wants_for_designers-2-back" class="simple-footnote-back">↩</a></li><li id="sf-wants_for_designers-3">たとえば、コンテンツをピンチイン・アウトで拡大縮小するという機能は、iOSでは標準のコンポーネントのみで実装できますが、Androidでは、標準では用意されていないので、自力で実装しようとすると(iOSに比べれば)めんどうだったりします <a href="#sf-wants_for_designers-3-back" class="simple-footnote-back">↩</a></li><li id="sf-wants_for_designers-4">SPAの場合は若干事情が異なる可能性があります。わたしはSPAでのウェブアプリケーション開発は経験がまだないので、よくわかりません <a href="#sf-wants_for_designers-4-back" class="simple-footnote-back">↩</a></li><li id="sf-wants_for_designers-5">できればHTML/CSSのようにプレゼンテーション単体で確認可能なのが望ましい <a href="#sf-wants_for_designers-5-back" class="simple-footnote-back">↩</a></li><li id="sf-wants_for_designers-6">開発現場で、プログラマーが偏重されているというデザイナーからの意見が、しばしば耳に入ってきます <a href="#sf-wants_for_designers-6-back" class="simple-footnote-back">↩</a></li><li id="sf-wants_for_designers-7">この場合の検索というのは、キーワード検索に限りません。見たい内容にすぐに辿り付けるように整理されているか、ということです。 <a href="#sf-wants_for_designers-7-back" class="simple-footnote-back">↩</a></li></ol>BowerとMacGlashanのMVP論文要約2015-07-06T00:00:00+09:002015-07-06T00:00:00+09:00tai2tag:blog.tai2.net,2015-07-06:/bower-and-macglashan-mvp-architecture.html<p class="first last">BowerとMacGlashanによるMVPを提唱した論文の要約です。最近MVPモデルのフレームワークでプログラミングをしているのですが、ビューとプレゼンターの境界が曖昧になって、どちらにコードを置くべきか迷う場面が出てきたので、根本的な理解を求めて読んでみました。</p>
<p>BowerとMacGlashanによるMVPを提唱した論文の要約です。最近MVPモデルのフレームワークでプログラミングをしているのですが、ビューとプレゼンターの境界が曖昧になって、どちらにコードを置くべきか迷う場面が出てきたので、根本的な理解を求めて読んでみました。 <a class="reference external" href="https://blog.tai2.net/potel-mvp-architecture.html">PotelのMVP</a> に比べると、自分が理解しているMVPの姿とほぼ同じだったこともあり参考になりました。ただ、現在モバイル開発やウェブ開発でよく言われているMVCと、この論文で言及されているMVC<sup id="sf-bower-and-macglashan-mvp-architecture-1-back"><a href="#sf-bower-and-macglashan-mvp-architecture-1" class="simple-footnote" title="VisualWorksの開発したMVCモデル">1</a></sup>はだいぶかけはなれているので、昨今の典型的なMVC(MVPとかなり似ている)とMVPの微妙な違いは、これを読んでもよくわかりませんでした。</p>
<div class="section" id="twisting-the-triad-the-evolution-of-the-dolphin-smalltalk-mvp-application-framework">
<h2>TWISTING THE TRIAD The evolution of the Dolphin Smalltalk MVP application framework.</h2>
<p>Andy Bower, Blair McGlashan Object Arts Ltd.(2000)</p>
<p><a class="reference external" href="http://www.object-arts.com/downloads/papers/TwistingTheTriad.PDF">http://www.object-arts.com/downloads/papers/TwistingTheTriad.PDF</a> <sup id="sf-bower-and-macglashan-mvp-architecture-2-back"><a href="#sf-bower-and-macglashan-mvp-architecture-2" class="simple-footnote" title="こちらには図は入れていないので、元のPDFと並べて読んだほうがいいと思います。">2</a></sup></p>
<div class="section" id="section-1">
<h3>導入</h3>
<ul class="simple">
<li>この論文では、Model-View-Presenterフレームワークに辿りつくまでの過程を説明する。Object ArtsのMVPは、Dolphin Smalltalkのためのユーザーインターフェイスモデルとして作られた。</li>
</ul>
</div>
<div class="section" id="section-2">
<h3>ウィジェット</h3>
<ul class="simple">
<li>Dolphinの設計は、当初Visual Basicのようなクライアントプログラミング環境を真のオブジェクト指向で置き換えるものだった。それはウィジェットベースと呼ばれるものだった。</li>
<li>ウィジェットベースでは、ユーザーインターフェイスをレイアウトしてから、その要素にコードを貼り付けて、アプリケーションロジックを構築した。</li>
<li>Visual Basicでは、コンポーネントベースのアプローチができなかった(新しいウィジェットを作って再利用することはできなかった)が、Dolphinでは、コンポーネントを階層的に組み合わせて、それ自身をさらに再利用できるようにしたかった。</li>
<li>ウィジェットベースのシステムは、ドメインロジックとインターフェイスロジックが密結合してしまいやすいので、満足な結果は得られなかった。</li>
</ul>
</div>
<div class="section" id="section-3">
<h3>モデル・ビュー・コントローラー</h3>
<ul class="simple">
<li>1995年にウィジェットベースのフレームワークをやめて、MVC実装に置き換え始めた。2ヶ月程でほぼ完了したが、満足な結果は得られなかった。</li>
<li>MVCでは、ビューが、モデルの保持するデータを表示する責務を負う。コントローラーは、低レベルなユーザーのジェスチャをモデルにとって意味のあるアクションに変換する。</li>
<li>一般的に、ビューとコントローラーは互いに直接リンクしている(互いを指すインスタンス変数を保持し合っている)。</li>
<li>ビュー・コントローラーのペアは、モデルとオブザーバー関係になっており間接的にリンクしている。ビュー・コントローラーはモデルを知っているが、逆は成り立たない。</li>
<li>これにより、複数のビュー・コントローラーが、ひとつのモデルを共有できる(ビュー・コントローラーの差し替えができる)。</li>
<li>MVCでは、ほとんどの機能はアプリケーションモデルというモデルクラスに組み込まれる。アプリケーションモデルは、ほんとうのモデルと、ビュー・コントローラーの媒介になる。</li>
<li>ほとんどすべてのアプリケーションロジックが、アプリケーションモデルの中に入る(メニューコマンドやボタンアクションの実行、バリデーションなど)。</li>
<li>アプリケーションモデルは、その性質上、ユーザーインターフェイスに直接アクセスしたくなることが多いが、それはオブザーバー関係に違反する。</li>
<li>しかしながら、オブザーバー関係を厳密に守ろうとすると、簡単なことをやるにも遠回りなイベント通知が必要になるので、現実的ではない。</li>
<li>しかしながら、そうしなければ、モデルが、接続されているビューを知ることになってしまう。それではモデルを共有できなくなってしまう。</li>
<li>また、コントローラーという概念は、Microsoft WindowsのようなGUI開発環境にはうまくフィットしない。Windowsの提供するウィジェットは、単なるビューではなくそれ自体がコントローラーとしての機能を有している(形を合わせるためだけにMVCのコントローラーを作って、OS標準ウィジェットに機能を委譲するとかおかしなことになる)。</li>
</ul>
</div>
<div class="section" id="section-4">
<h3>3つ組をねじる: モデル・ビュー・プレゼンター</h3>
<ul class="simple">
<li>MVCのオブザーバーとしての点とコンポーネントを差し替えられる柔軟性は良かったが、アプリケーションモデルとビューが間接的にリンクしている点は間違っていた。また、Windows環境では、コントローラーは不要なように思われた。</li>
<li>MVCでいきづまっていたときに、同僚の1人が教えてくれたTaligentのMVPに感銘を受けた。それは、MVCの3つ組を60°回転させてMVPとすることで問題を解決するものだった。</li>
</ul>
<div class="section" id="section-5">
<h4>モデル</h4>
<ul class="simple">
<li>ユーザーインターフェイスについての知識を一切持たないドメインオブジェクト(MVCではアプリケーションモデルは、ユーザーインターフェイス的な側面も持っていた点が異なる)。</li>
</ul>
</div>
<div class="section" id="section-6">
<h4>ビュー</h4>
<ul class="simple">
<li>MVPでのビューの振舞いは、MVCのそれと同様。モデルの内容を表示するのはビューの責任。モデルは、データの変更をビューに通知する(オブザーバーパターン)。</li>
<li>MVCとの重要な違いは、コントローラーを取り除いたこと。その代わりに、ビュー自身が生のユーザーインターフェイスイベントを解釈して意味のあるイベントに変換する。</li>
<li>ほとんどの場合、ユーザー入力イベントは、プレゼンターを介して、モデルに作用する。</li>
</ul>
</div>
<div class="section" id="section-7">
<h4>プレゼンター</h4>
<ul class="simple">
<li>ユーザーインターフェイスによって、モデルがどのように操作・変更されるかを管理する。アプリケーションの振舞いの心臓部。</li>
<li>MVPのプレゼンターは、MVCのアプリケーションモデルに相当するが、プレゼンターは、ビューと直接結びついて、密に連携することでユーザーインターフェイスを提供する点が異なる。</li>
</ul>
</div>
</div>
<div class="section" id="mvp">
<h3>MVPの利点</h3>
<ul class="simple">
<li>プレゼンターがビューに直接アクセスできるので便利。</li>
<li>コントローラーを排除したので、Windows OSにもよく馴染む。</li>
<li>ウィジェットベースと比べてユーザーインターフェイスの表示と、ユーザーインターフェイスのロジックがよく分離できている。</li>
<li>プレゼンターに対してビューを複数用意して、「スキン」を変更するとかも簡単。</li>
</ul>
</div>
<div class="section" id="section-8">
<h3>将来の可能性</h3>
<div class="section" id="section-9">
<h4>回路図</h4>
<ul class="simple">
<li>当時の特定のツールに依存した話なので省略。回路図みたいなものでアプリケーションのロジックを記述して、クラスを自動生成させよう、みたいな話。</li>
</ul>
</div>
<div class="section" id="mvp-1">
<h4>ポータブルなMVP</h4>
<ul class="simple">
<li>異なるGUI環境に移植するときにも、MVPなら、MとPはOS非依存なので、Viewさえ対応させれば残りはほぼそのまま使える可能性が微レ存。</li>
</ul>
</div>
</div>
<div class="section" id="section-10">
<h3>結論</h3>
<ul class="simple">
<li>数年間MVPで開発してみたけど、良さそうだよ。</li>
<li>VBとかAWTみたいなウィジェットベースのアプローチよりも柔軟。</li>
<li>MVCと似てるけど、より一貫性があって、プログラミングも気持ち良くできる。</li>
</ul>
</div>
</div>
<ol class="simple-footnotes"><li id="sf-bower-and-macglashan-mvp-architecture-1">VisualWorksの開発したMVCモデル <a href="#sf-bower-and-macglashan-mvp-architecture-1-back" class="simple-footnote-back">↩</a></li><li id="sf-bower-and-macglashan-mvp-architecture-2">こちらには図は入れていないので、元のPDFと並べて読んだほうがいいと思います。 <a href="#sf-bower-and-macglashan-mvp-architecture-2-back" class="simple-footnote-back">↩</a></li></ol>PotelのMVP論文要約2015-07-01T00:00:00+09:002015-07-01T00:00:00+09:00tai2tag:blog.tai2.net,2015-07-01:/potel-mvp-architecture.html<p class="first last">PotelによるMVPを提唱した原典の要約です。最近MVPモデルのフレームワークでプログラミングをしていて、ビューとプレゼンターの境界が曖昧になって、どちらにコードを置くべきか迷う場面が出てきたので、根本的な理解を求めて読んでみました。</p>
<p>PotelによるMVPを提唱した原典の要約です。最近MVPモデルのフレームワークでプログラミングをしているのですが、ビューとプレゼンターの境界が曖昧になって、どちらにコードを置くべきか迷う場面が出てきたので、根本的な理解を求めて読んでみました。結論から言うと、求めていた答は得られませんでした。たぶん、 <a class="reference external" href="http://www.martinfowler.com/eaaDev/uiArchs.html">ファウラーによるまとめ</a> や、.NET以降のマイクロソフトによる <a class="reference external" href="https://msdn.microsoft.com/en-us/library/ff709839.aspx">MVPの再定義</a> を学んだほうが良いのだと思います。</p>
<p>MVPによる分割によって、再利用性、移植性、分散性といったさまざまな帰結が得られるという主張なのですが、こんな上辺の線引きだけから、具体的な様々な果実が直接出てくるわけがないだろう、目を覚ませというのが率直な感想で、いかにもアーキテクチャ屋の考えた絵空事という印象を受けました。まあ、わたしが、小さなコード、小さなプログラムの信奉者で、大規模プログラムとは縁がない人間だからなのかもしれません。実際には、製品化されていてコードを伴うものなので、ちゃんと動くものを見れば、きっとすばらしいものなのでしょう。</p>
<div class="section" id="mvp-model-view-presenter-the-taligent-programming-model-for-c-and-java">
<h2>MVP: Model-View-Presenter The Taligent Programming Model for C++ and Java</h2>
<p>Mike Potel VP & CTO Taligent, Inc.(1996)</p>
<p><a class="reference external" href="http://www.wildcrest.com/Potel/Portfolio/mvp.pdf">http://www.wildcrest.com/Potel/Portfolio/mvp.pdf</a> <sup id="sf-potel-mvp-architecture-1-back"><a href="#sf-potel-mvp-architecture-1" class="simple-footnote" title="こちらには図は入れていないので、元のPDFと並べて読んだほうがいいと思います。">1</a></sup></p>
<ul class="simple">
<li>Taligentは、次世代のC++とJava用プログラミングモデルとして、Model-View-Presenterを開発した。</li>
<li>MVPは、古典的なMVCの一般化である。</li>
<li>MVPは、クライアント・サーバーアプリケーションから、複数の層にまたがったアプリケーションアーキテクチャにまで広範囲に使える。</li>
</ul>
<div class="section" id="smalltalk">
<h3>Smalltalkのプログラミングモデル</h3>
<ul class="simple">
<li>Smalltalkでは、チェックボックスやテキストフィールドのようなGUIオブジェクトを3つの抽象化(MVC)で表す。</li>
<li>モデルは、チェックボックスのON/OFFや、テキスト文字列のような裏側にあるデータを表す。</li>
<li>ビューは、モデルを通じてデータにアクセスし、どのように描画されるかを定義する。</li>
<li>コントローラーは、ユーザーが、ビューを操作する方法や、イベントがモデルを変更する方法を決定する。</li>
<li>モデルは、状態が変化したら、再描画が必要なことをビューに通知する。 <sup id="sf-potel-mvp-architecture-2-back"><a href="#sf-potel-mvp-architecture-2" class="simple-footnote" title="ビューはデータを読むだけで、直接変更しない。コントローラーがモデルを通じてデータを変更する。">2</a></sup></li>
<li>この分割は、最小のGUIオブジェクトにまで適用される。ダイアログボックスのような複合的なGUIオブジェクトは、複数のGUIオブジェクトを積み上げて構築される。すると、全体がMVCで構築されるようになる。</li>
</ul>
</div>
<div class="section" id="taligent-open-class">
<h3>Taligent / Open Classのプログラミングモデルを構築する</h3>
<ul class="simple">
<li>Taligentのアプローチは、MVCのコンセプトを分解して、より複雑なアプリケーションの開発を補助するために改善した。</li>
<li>はじめに、モデルとビュー・コントローラーの2つに分割した。後者をプレゼンテーションと呼ぶ。</li>
<li>これにより問題をデータ管理とユーザーインターフェイスに分解する。</li>
<li>プログラマーは2つの質問にフォーカスすることになる。「データをどのように管理するか」「ユーザーはどのようにデータとやりとりするか」</li>
<li>前者には次のような関心が含まれる。データ構造、アクセスメソッド、変更のプロトコル、永続性、共有性、分散性。</li>
<li>後者には次のような関心が含まれる。オブジェクトの描画、マウス・キーボードのイベント、どのような意味論的な操作(?)が可能か、どのようなユーザーアクションが認識されるか、どのようなジェスチャー言語が使われるか、どのようなフィードバックがあるか。</li>
</ul>
</div>
<div class="section" id="section-1">
<h3>モデルはカプセル化を可能にする</h3>
<ul class="simple">
<li>モデル概念を一般化することのメリット。</li>
<li>モデルとプレゼンテーションを綺麗に分割できるようになる。</li>
<li>こうすると、背後のデータ構造をリスト構造やハッシュテーブルに変更したり、フィールドを追加したとしても、プレゼンテーションコードを一切いじらずに再利用できる。</li>
<li>モデルを再実装せずとも、いろいろなプレゼンテーションを実装できる。</li>
<li>複数人で同時に作業して、あとで統合することができる。</li>
<li>これらは自明なようだが、実際そうなっていないコードが多く、背後のデータモデルを変更したら、その影響が様々な箇所に波及してしまうようなプログラムはたくさんある。</li>
<li>小規模なプログラムであれば、そのようなやりかたも通用するが、それでは大規模にスケールアップできない。</li>
</ul>
</div>
<div class="section" id="section-2">
<h3>モデルは永続化を可能にする</h3>
<ul class="simple">
<li>データをどこにどのように格納するかは完全にモデル次第なので、メモリ内にしてもいいし、なんらかの永続化ストアへの単なるプロキシかもしれないし、RDBにクエリを投げるコードを含んでるかもしれない。パフォーマンスのためにローカルキャッシュを持ってるかもしれないし、課金メカニズムを内包してるかもしれないし、複数のRDBに対応していて切り替えられるかもしれない。</li>
<li>これらはプレゼンテーションからは透過であり、変更することができる。</li>
</ul>
</div>
<div class="section" id="section-3">
<h3>モデルは共有を可能にする</h3>
<ul class="simple">
<li>モデルの抽象化は、複数ユーザーがいるときの柔軟な使い方も可能にする。</li>
<li>リモートデータをカプセル化した複数のプログラムがあるときに、常にデータを同期しておける。</li>
<li>編集可能な管理者、読むだけのユーザーなど権限ごとにプレゼンテーションを変えられる。</li>
</ul>
</div>
<div class="section" id="section-4">
<h3>データ管理の3つの質問</h3>
<ul class="simple">
<li>データ管理に関する質問「データをどのように管理するか」はさらに3つに分解できる。</li>
<li>1.どのようなデータか 2.どのようにデータを指定するか 3.どのようにデータを変更するか</li>
<li>データのサブセットを指定する方法を「セレクション」と呼ぶ。</li>
<li>セレクション上のデータを変更する方法を「コマンド」と呼ぶ。</li>
<li>1.モデル 2.セレクション 3.コマンド という関係。</li>
</ul>
</div>
<div class="section" id="section-5">
<h3>モデル、ビュー、セレクション、コマンド</h3>
<ul class="simple">
<li>前節の抽象化の実例。</li>
<li>モデル: 整数の2次元配列</li>
<li>ビュー: 積み上げ棒グラフ(同じモデルで異なるビューも有り得る、非グラフィカルなビューも有り得る)</li>
<li>セレクション: どのカラムを選択するか</li>
<li>コマンド: カラムにどのような変更を加えるか(色の変更など)</li>
<li>別の例として、モデルがテキストなら、マウスで選択した部分がセレクション、それを削除・移動・コピーしたりするのがコマンド、など。</li>
</ul>
</div>
<div class="section" id="section-6">
<h3>ユーザーインターフェイスの3つの質問</h3>
<ul class="simple">
<li>ユーザーインターフェイスに関する質問「ユーザーはどのようにデータとやりとりするか」はさらに3つに分解できる。</li>
<li>4.どのようにデータを表示するか 5.どのようにイベントとデータ変更を対応付けるか 6.どのようにこれらを結合するか</li>
<li>マウスの移動やキーボード入力などのユーザーアクションをインタラクターイベントと呼ぶ。それらとモデルの関係を定義するのが「インタラクター」</li>
<li>「プレゼンター」は、Smalltalkで言うところのコントローラーに相当するが、アプリケーションレベルにまで引き上げられ<sup id="sf-potel-mvp-architecture-3-back"><a href="#sf-potel-mvp-architecture-3" class="simple-footnote" title="SmalltalkのMVCでは、すべてのGUIコントロールがMVCで構築されることに注意">3</a></sup>、セレクション、コマンド、インタラクターを考慮に入れる点が違う。</li>
<li>4.ビュー 5.インタラクター 6.プレゼンター という関係。</li>
<li>プレゼンターは、ユーザーイベントやジェスチャを解釈して、適切なコマンドに対応付けるビジネスロジックを提供する。</li>
</ul>
</div>
<div class="section" id="section-7">
<h3>インタラクターとプレゼンター</h3>
<ul class="simple">
<li>積み上げ棒グラフの例の続き。</li>
<li>インタラクター: マウス操作、選択の指定、メニュー押下、キーボードなどを担当する。</li>
<li>プレゼンター: その他。モデル・セレクション・コマンド・ビュー・インタラクターを生成し、それらを統合するビジネスロジックを提供する。</li>
</ul>
</div>
<div class="section" id="mvp-1">
<h3>MVP: モデル・ビュー・プレゼンター</h3>
<ul class="simple">
<li>これまでのまとめ。開発者は、6つの質問に答えることでMVPモデルのプログラムを作成できる。</li>
</ul>
</div>
<div class="section" id="taligent">
<h3>Taligentプログラミングモデルのフレームワーク</h3>
<ul class="simple">
<li>Taligentの提供してるフレームワークは、MVPモデルを完全にサポートしているよ!!</li>
</ul>
</div>
<div class="section" id="taligent-1">
<h3>Taligentプログラミングモデルのクラス</h3>
<ul class="simple">
<li>具体的なクラス図を元にしたMVPの説明。</li>
<li>Taligentの環境では、モデル・セレクション・コマンド・ビュー・インタラクター・プレゼンターに相当する基底クラスが用意されており、それらを継承してアプリケーションを作成する。</li>
<li>アプリケーションは、基本的に、独自のインタラクターを作成する必要はない。それどころか、コードでオブジェクトを作成する必要すらなく、GUI構築ツールで指定すれば適切なオブジェクトが生成されるようになってる。<sup id="sf-potel-mvp-architecture-4-back"><a href="#sf-potel-mvp-architecture-4" class="simple-footnote" title="Cocoa用語で言うところのTarget-Actionのようなもの?">4</a></sup></li>
</ul>
</div>
<div class="section" id="section-8">
<h3>アプリケーションの作成</h3>
<ul class="simple">
<li>MVPの力は、それが直感的な抽象化であることに加えて、リッチなデフォルト実装を提供していることにある。</li>
<li>MVPは、必ずしも一気にすべて導入する必要はなく、アプリケーションの成長とともに段階的に付け加えていくことができる。</li>
<li>電卓アプリケーションを題材にした説明。</li>
<li>プレゼンターなしに、メインイベントループレベルから手書きで書くことも可能。</li>
<li>プレゼンターを継承すれば、アプリの起動から終了、イベントループや基本的なキーボードやマウス入力がタダで手に入る。</li>
<li>プレゼンター内に、直でボタンを描画したり、マウス入力を受けての更新といったコードを書くことも可能。</li>
<li>ビューの基底クラスを継承すれば、ウィンドウのリサイズや再描画・デフォルトの描画などが手に入るし、GUI作成ツールを使ってレイアウトすることも可能になる。</li>
<li>プレゼンターとビューだけだと、計算したデータをビュー内にしか保持していないので、電卓を終了したら結果が失われるが、モデルを継承すれば、計算結果を保存しておくことができる。モデルは、電卓の紙テープのようなものも表わせる(元の図参照)。</li>
<li>ここまででは、モデルに対する操作は直接行われる。セレクションを実装すれば、紙テープの部分を選択できる。</li>
<li>さらにコマンドを実装すれば、アンドゥやリドゥが可能になり、紙テープを逆向きに回せる。分散環境で、ネットワークをまたがって異なるユーザーがコマンドを実行することさえできる。</li>
<li>最後に、インタラクタを導入することで、計算の基数を変更したり、通貨を変更したりするメニューアイテムを追加できるようになる。野心的なプログラマーなら、手書きの数値入力用のインタラクターを実装することも可能。</li>
<li>MVPの抽象化を継承することで、インタラクティブで、ドキュメント駆動で、埋め込めて、リンクできて、複数レベルのアンドゥができて、スクリプト化できて、リアルタイムにコラボできて、国際化されたビジネスアプリケーションが手に入るよ!!!!</li>
</ul>
</div>
<div class="section" id="section-9">
<h3>クライアント/サーバー</h3>
<ul class="simple">
<li>MVPのクライアント/サーバーへの応用。</li>
<li>典型的には、モデル、セレクション、コマンドがサーバー側、ビュー、インタラクターがクライアント側で、プレゼンターは両方のまたがる形。</li>
<li>プレゼンターを置く比重によって、fatクライアントだったり、thin GUIアプリケーションだったりが有り得る。その分割は、境界を跨るプロトコルに依って決まる。</li>
<li>あるいは、もっと洗練されたアプリケーションであれば、MVP全体が、どちらか片方に来ることも有り得る。たとえば、クライアント側のモデルが、ネットワーク越しの永続性を抽象化しているとかも有り得る。</li>
</ul>
</div>
<div class="section" id="section-10">
<h3>クライアント/サーバーの色々なバージョン</h3>
<ul class="simple">
<li>既存のいろいろな具体例を見て、これらはMVPの各要素をサーバー側・クライアント側どこどこに配置したバリエーションだと考えられるね、ということを検討。とくに身のある内容でもないので省略。</li>
<li>よくあるウェブアプリケーション<sup id="sf-potel-mvp-architecture-5-back"><a href="#sf-potel-mvp-architecture-5" class="simple-footnote" title="1996年のものであることに注意">5</a></sup>は、ビューとインタラクターがクライアント側、それ以外全部サーバー側に置いたバリエーションと考えられる。</li>
<li>分散アプリケーションをプログラミングするための7つ目の質問。「クライアントとサーバーの間で、どのようにアプリケーションを分割するか」</li>
<li>このように色々な広範囲な例を当て嵌めることができるので、MVPモデルは基本的なアプリケーション開発のデザインパターンであると言える。</li>
</ul>
</div>
<div class="section" id="section-11">
<h3>抽象化の利点</h3>
<ul class="simple">
<li>MVPモデルによる区別に価値があることを論証。</li>
<li>モデル/ビューの区別は、ビューの独立性をもたらす。例えば、異なる電卓レイアウトを、異なるビューとして、同一のモデルの上に実現できる。</li>
<li>セレクション/モデルの区別は、モデルの独立性をもたらす。データ構造やファイルフォーマットをプログラムの他の部分から分離できる。また、永続性、リモートアクセス、共有などを可能とする。</li>
<li>コマンド/セレクションの区別は、コマンドの再利用をもたらす。単一のコマンドを、ひとつ、複数、あるいは全体のセレクションに適用できる(それぞれの場合を特殊形として扱うのではなく)。コマンドはさまざまなアプリケーションから再利用することができる。</li>
<li>インタラクター/ビューの区別は、入力の一般性をもたらす。アプリケーションロジックを変更することなく、異なるメニュー、ダイアログ、キーボードショートカット、ジェスチャ、あるいは手書き入力をサポートできる。</li>
<li>プレゼンターからコマンド・インタラクターを分離することは、再利用可能なロジックをもたらす。例えば、科学計算の単位変換や、金融の複利計算を異なる文脈で再利用できる。</li>
<li>これらによって、マルチプラットフォーム、複数標準での移植性、分散化、複数レイヤーでの分割が得られる。</li>
</ul>
</div>
<div class="section" id="section-12">
<h3>要約</h3>
<ul class="simple">
<li>Taligentはいろんな環境でMVPモデル使ってがんばってるよ!</li>
</ul>
</div>
</div>
<ol class="simple-footnotes"><li id="sf-potel-mvp-architecture-1">こちらには図は入れていないので、元のPDFと並べて読んだほうがいいと思います。 <a href="#sf-potel-mvp-architecture-1-back" class="simple-footnote-back">↩</a></li><li id="sf-potel-mvp-architecture-2">ビューはデータを読むだけで、直接変更しない。コントローラーがモデルを通じてデータを変更する。 <a href="#sf-potel-mvp-architecture-2-back" class="simple-footnote-back">↩</a></li><li id="sf-potel-mvp-architecture-3">SmalltalkのMVCでは、すべてのGUIコントロールがMVCで構築されることに注意 <a href="#sf-potel-mvp-architecture-3-back" class="simple-footnote-back">↩</a></li><li id="sf-potel-mvp-architecture-4">Cocoa用語で言うところのTarget-Actionのようなもの? <a href="#sf-potel-mvp-architecture-4-back" class="simple-footnote-back">↩</a></li><li id="sf-potel-mvp-architecture-5">1996年のものであることに注意 <a href="#sf-potel-mvp-architecture-5-back" class="simple-footnote-back">↩</a></li></ol>NodeSchool TokyoイベントレポートとNodeSchoolコミュニティの紹介2015-04-18T00:00:00+09:002015-04-18T00:00:00+09:00tai2tag:blog.tai2.net,2015-04-18:/nodeschool_tokyo_report.html<p class="first last">PythonJSというPythonからJavaScriptへのトランスレーターで変換されたコードが、元のCPythonコードよりも高速になったという 記事が出ました。ちょっと興味が湧いたので、PythonJSについて調べてみました(この記事はプログラマ向けです)。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-1" id="toc-entry-1">ワークショッパーについて</a></li>
<li><a class="reference internal" href="#nodeschool" id="toc-entry-2">NodeSchoolとは</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-3">当日の雰囲気</a></li>
<li><a class="reference internal" href="#nodeschool-tokyo" id="toc-entry-4">NodeSchool Tokyoの活動</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-5">東京以外のコミュニティ</a></li>
<li><a class="reference internal" href="#nodeschool-international-day" id="toc-entry-6">NodeSchool International Day</a></li>
<li><a class="reference internal" href="#nodeschool-tokyo-1" id="toc-entry-7">NodeSchool Tokyoについての他の記事</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-8">おわりに</a></li>
</ul>
</div>
<img alt="NodeSchool Tokyo logo" class="align-center" src="https://blog.tai2.net/images/nodeschool-tokyo-logo.png">
<p>去る4月12日、渋谷のサイバーエージェントセミナールームで行われた <a class="reference external" href="http://nodejs.connpass.com/event/13182/">NodeSchool Tokyo(東京Node学園 入学式)</a> に、メンターの1人として参加してきました。
本稿では、NodeSchoolとは何かを説明し、イベント当日の雰囲気をお伝えします。</p>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-1">ワークショッパーについて</a></h2>
<p>NodeSchoolについて話すためには、まずワークショッパーについて説明しなければいけません。
ワークショッパーは、Node.jsで実装されたターミナル向けのアプリケーションで、これを使えばNode.jsの基本や、JavaScriptなど、さまざまな技術をクイズ形式で学べます。プログラミングに詳しい人には、ユニットテストのテストスイートがあって、それにパスする実装を書いて提出するようなイメージと言えば伝わり易いかもしれません。</p>
<p>クイズ形式とはどういうことか簡単に説明しましょう。
たとえば、JavaScriptの入門向けワークショッパーであるjavascriptingは、
次のコマンドでインストールできます。<sup id="sf-nodeschool_tokyo_report-1-back"><a href="#sf-nodeschool_tokyo_report-1" class="simple-footnote" title="ワークショッパーは、Node.jsのパッケージリポジトリであるnpm上で公開されています。">1</a></sup></p>
<div class="highlight"><pre><span></span>npm install -g javascripting-jp
</pre></div>
<p>javascriptingを起動すると次のようなメニューが表示されます。</p>
<img alt="javascripting menu" class="align-center" src="https://blog.tai2.net/images/javascripting-menu.png">
<p>覚えたい項目を選択すると次のように問題文が表示されます。</p>
<img alt="javascripting problem" class="align-center" src="https://blog.tai2.net/images/javascripting-problem.png">
<p>出題された条件を満たすプログラムを書けたら、次のコマンドを実行すれば答え合わせができます。</p>
<div class="highlight"><pre><span></span>javascripting-jp verify strings.js
</pre></div>
<p>間違っていたら、どこが間違っているのか丁寧に教えてくれます。</p>
<img alt="javascripting fail" class="align-center" src="https://blog.tai2.net/images/javascripting-fail.png">
<p>正解すると褒めてもらえます:)</p>
<img alt="javascripting succeeded" class="align-center" src="https://blog.tai2.net/images/javascripting-succeeded.png">
</div>
<div class="section" id="nodeschool">
<h2><a class="toc-backref" href="#toc-entry-2">NodeSchoolとは</a></h2>
<p><a class="reference external" href="http://nodeschool.io/">NodeSchool</a> はワークショッパーをベースにして繋がるコミュニティです。
ワークショッパー自体を作成したり、それを利用した学習イベントを世界中で開催していて、
やる気さえあれば、だれでもローカルなコミュニティイベントを開催できます。</p>
<p>NodeSchoolコミュニティで活発に活動している <a class="reference external" href="https://github.com/martinheidegger">マーティンさん(大阪在住)</a> から聞いた話では、
NodeSchoolは元々は、Node.jsを使ってプログラムを作ったり、知識を教え合うような場だったのだそうです。
それがGitHubとワークショッパーを通じて、南極大陸を除くすべての大陸でコミュニティイベントが開催されるほどに広がりました。</p>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-3">当日の雰囲気</a></h2>
<p>今回のイベントは、日本語化されているワークショッパーのうち、以下の4つを使って行われました。</p>
<ul class="simple">
<li><a class="reference external" href="https://www.npmjs.com/package/learnyounode">learnyounode</a> Node.js入門</li>
<li><a class="reference external" href="https://www.npmjs.com/package/javascripting-jp">javascripting-jp</a> JavaScript入門</li>
<li><a class="reference external" href="https://www.npmjs.com/package/how-to-npm-jp">how-to-npm-jp</a> npm入門</li>
<li><a class="reference external" href="https://www.npmjs.com/package/tower-of-babel">tower-of-babel</a> ES6入門</li>
</ul>
<p>参加者は、満員の150名程度。メンターも正確には把握していませんが最終的に20名程度集まったようです。</p>
<img alt="NodeSchool Tokyo picture" class="align-center" src="https://blog.tai2.net/images/nodeschool-tokyo-picture.jpg">
<p>まずは、Node.jsの環境自体のセットアップができている人と、できてない人に分かれてもらって、
セットアップがまだの人にはメンターが手伝いながらインストールをしてもらいました。</p>
<p>環境構築ができたら、さっそくワークショッパーをインストールしてはじめてもらいます。
大規模イベントの常ではありますが、多人数が同時にダウンロードをしたため、
帯域が足りなくなり、なかなか進まない状況が生まれました。
しかし、多少時間はかかったものの、全員セットアップが完了して、それぞれの課題に入ることができました。</p>
<p>今回のワークショッパーの中では、やはり、javascriptingとlearnyounodeの人気が高かったようです。
tower-of-babelも先端的な機能に触れられるということで人気があったと思います。
作りたてホヤホヤのプログラムということもあり、いくつかトラブルも発生していましたが、
作成者自身が会場にいたため、問題はスムーズに解決されていました。
how-to-npmはモジュールを公開するチュートリアルという若干地味な内容ですが、何人かは取り組んでいた方がいたようです。</p>
<p>参加者のバックグラウンドは多岐に渡りましたが、メンターの数も十分だったので滞りなく進んでいた印象で、
時間内にワークショッパーをクリアできた参加者もチラホラいたようです。</p>
<p>イベントの終わりには、LTがいくつか行われ、その後はそのまま会場で懇親会が行われました。
懇親会は、さまざまなプログラマーと交流することができ、とてもたのしいひとときでした。</p>
</div>
<div class="section" id="nodeschool-tokyo">
<h2><a class="toc-backref" href="#toc-entry-4">NodeSchool Tokyoの活動</a></h2>
<p>NodeSchoolの <a class="reference external" href="http://nodeschool.io/tokyo/">東京コミュニティ</a> は、 <a class="reference external" href="https://github.com/sotayamashita">Sota Yamashtia</a> さんの
<a class="reference external" href="https://github.com/nodeschool/tokyo/issues/2">声掛け</a> で、
今年の1月ごろから徐々に人が集まり始めて、この度ついにイベントを開催することができました。
ちなみに、今回のイベントは、「東京Node学園 入学式」と銘打たれていることからもわかる通り、
<a class="reference external" href="http://nodefest.jp/">東京Node学園</a> という有名な日本のNode.jsコミュニティイベントに協賛してもらう形で行われました。
150人もの大人数が集ったのは、東京Node学園の場を借りられたことが大きかったと思います。</p>
<p>NodeSchoolの日本のコミュニティでは、英語で作成されたワークショッパーの翻訳や、
ワークショッパーの作成なども行っています。といっても、いまのところ日本発のものは、
<a class="reference external" href="https://github.com/yosuke-furukawa">古川さん</a> の作成されたtower-of-babelだけですが。tower-of-babelは、英語や韓国語への翻訳が進行中です。</p>
<p>まだまだ日本語化されていないワークショッパーもたくさんあるので、英語が得意な方は、
翻訳をすると喜ばれると思います。ぼく自身もちょこちょこと翻訳作業をしています。</p>
<p>ちなみに、ワークショッパーを作成するためのモジュールは複数あるのですが、i18n対応などを考える
と新規の作成には、 <a class="reference external" href="https://www.npmjs.com/package/workshopper">workshopper</a> というモジュールを使うといいようです。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-5">東京以外のコミュニティ</a></h2>
<p><a class="reference external" href="http://nodeschool.io/osaka/">大阪</a> は、いまのところ、おそらく日本で一番活発にNodeSchoolのコミュニティイベントが <a class="reference external" href="https://nodeschool.doorkeeper.jp/">開催されている</a> 地域です。
月一回くらいの頻度で活発に開催しているようです。それから、過去には、神奈川県の <a class="reference external" href="http://nodeschoolfujisawa.connpass.com/">藤沢</a> でも開催されたことがある模様です。
自分の地域でも開催したいという方は、 <a class="reference external" href="https://gitter.im/nodeschool/nodeschool-japan">チャット</a> などで相談するとコミュニティーの人が話を聞いてくれるのではないかと思います。</p>
</div>
<div class="section" id="nodeschool-international-day">
<h2><a class="toc-backref" href="#toc-entry-6">NodeSchool International Day</a></h2>
<p>今度の5月23日には、 <a class="reference external" href="http://nodeschool.io/international-day/">International Day</a> と題して、世界同時開催のコミュニティイベントが開催されることが決まっています。
詳しい内容は決まっていませんが、きっとたのしいイベントになると思います。</p>
</div>
<div class="section" id="nodeschool-tokyo-1">
<h2><a class="toc-backref" href="#toc-entry-7">NodeSchool Tokyoについての他の記事</a></h2>
<p>以下は、先日のイベントについてレポートしている記事です。</p>
<ul class="simple">
<li><a class="reference external" href="http://togetter.com/li/807537">nodeschool tokyo (東京Node学園 入学式)</a></li>
<li><a class="reference external" href="http://ba-kgr.hatenablog.com/entry/2015/04/12/231154">東京Node学園 入学式に参加してきました:-)</a></li>
<li><a class="reference external" href="http://blog.livedoor.jp/kaidouji85/archives/5004079.html">NodeSchoolに参加しました</a></li>
<li><a class="reference external" href="http://k2lab.hateblo.jp/entry/2015/04/12/233921">nodeschool tokyo 東京Node学園 入学式行ってきました!</a></li>
<li><a class="reference external" href="http://www.chirashiura.com/entry/2015/04/13/005646">nodeschool tokyo(東京Node学園 入学式)に参加してきた</a></li>
<li><a class="reference external" href="http://yosuke-furukawa.hatenablog.com/entry/2015/04/20/104043">NodeSchool Tokyo を開催しました</a></li>
</ul>
<p>他にもレポートしている記事などがあればぜひ教えてください。</p>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-8">おわりに</a></h2>
<p>ぼくは、普段新しい環境に入るときには、公式のチュートリアルを一通りやって使い方を覚えるようにしています。
仕事でNode.jsをやることになり、使い方を覚えようと思って公式サイトのチュートリアルをやってみようと思ったところ、
Node.js自体にはチュートリアルがなく、代わりにリンクされていたのがNodeSchoolでした。</p>
<p>ビギナー向けのワークショッパーをいくつかやってみたところ、Node.jsという環境自体の楽さもあるとは思いますが、非常にお手軽に入門できました。
個人的には、ユニットテストのような枠組みを発想の転換で、学習教材にしてしまおうという試みがおもしろいなと思っています。
また、ただの学習教材では終わらず、それをベースにして、分散的にローカルコミュニティを構築できる、
フレームワークのようなものを提供しようとしているのもすばらしいです。他の言語や環境でも、このやり方は真似できそうな気がしています。</p>
<p>コミュニティの活動はオープンに行われていて誰でも参加できます。興味を持たれた方は覗いてみてください。</p>
</div>
<ol class="simple-footnotes"><li id="sf-nodeschool_tokyo_report-1">ワークショッパーは、Node.jsのパッケージリポジトリであるnpm上で公開されています。 <a href="#sf-nodeschool_tokyo_report-1-back" class="simple-footnote-back">↩</a></li></ol>JavaScriptの行末セミコロンは省略すべきか2015-01-06T00:00:00+09:002015-01-06T00:00:00+09:00tai2tag:blog.tai2.net,2015-01-06:/automatic_semilocon_insertion.html<p class="first last">Bootstrapに含まれるJavaScriptコードを見てみると、基本的にセミコロンが使用されていません。調べてみると、どうも世の中にはJavaScriptのステートメント末尾にセミコロンをつけない派というのが存在するらしいことがわかってきました。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#automatic-semilocon-insertion" id="toc-entry-1">自動セミコロン挿入(Automatic Semilocon Insertion)</a><ul>
<li><a class="reference internal" href="#restricted-production" id="toc-entry-2">Restricted Production</a></li>
</ul>
</li>
<li><a class="reference internal" href="#asi" id="toc-entry-3">ASIの害</a></li>
<li><a class="reference internal" href="#section-1" id="toc-entry-4">セミコロンにまつわる論争</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-5">で、どっちがいいの?</a></li>
</ul>
</div>
<p>JavaScriptには、 <a class="reference external" href="http://cpplover.blogspot.jp/2014/04/javascript.html">自動セミコロン挿入</a> という機能があり、行末でセミコロンを省略しても、多くの場合文法的に問題ありません。
しかしながら、 <a class="reference external" href="http://www.amazon.co.jp/dp/4873113911/">JavaScript: The Good Parts</a> などで指摘されているように、自動セミコロン挿入は有害な機能であるため、JavaScriptのステートメント末尾には必ずセミコロンを付与するというのがフロントエンドエンジニアの共通認識だと思っていました。<sup id="sf-automatic_semilocon_insertion-1-back"><a href="#sf-automatic_semilocon_insertion-1" class="simple-footnote" title=" Hacker Newsでのアンケート では、90%がセミコロンを付ける派でした。">1</a></sup></p>
<p>ところが、 <a class="reference external" href="http://getbootstrap.com/">Bootstrap</a> に含まれるJavaScriptコードを見てみると、基本的にセミコロンが使用されていません。
調べてみると、どうも世の中にはJavaScriptのステートメント末尾にセミコロンをつけない派というのが存在するらしいことがわかってきました。</p>
<div class="section" id="automatic-semilocon-insertion">
<h2><a class="toc-backref" href="#toc-entry-1">自動セミコロン挿入(Automatic Semilocon Insertion)</a></h2>
<p>本題に入る前に、自動セミコロン挿入とはどういうものかについて簡単に説明しておきます。</p>
<p>自動セミコロン挿入(ASI)の詳細なルールは、 <a class="reference external" href="http://www.ecma-international.org/ecma-262/5.1/#sec-7.9">ECMAScriptの仕様(ECMA-262 5.1 Editionの7.9節)</a> に記述されていますが、要約すると次のようになります。<sup id="sf-automatic_semilocon_insertion-2-back"><a href="#sf-automatic_semilocon_insertion-2" class="simple-footnote" title="この要約は、JavaScript Semicolon InsertionEverything you need to know から引っ張ってきました。">2</a></sup></p>
<ol class="arabic simple">
<li>次の条件を満たすとき、プログラムが文法的に許可されないトークン<sup id="sf-automatic_semilocon_insertion-3-back"><a href="#sf-automatic_semilocon_insertion-3" class="simple-footnote" title='文法上の最小の構成要素、1 2 3 "abc"といった数値や文字列リテラル、if,while,for,functionといったキーワード、+,-,=などの記号はすべてトークン'>3</a></sup>を含むならば、セミコロンが挿入される。(a) 当該トークンの前に改行がある、または、(b) 当該トークンが閉じ中括弧である。</li>
<li>ファイル末尾に到達したとき、セミコロンを挿入しなければプログラムを解釈できないならば、セミコロンが挿入される。</li>
<li>Restricted Productionが出現したときに、文法既定で"[no LineTerminator here]"と記述されている場所に改行があるならば、セミコロンが挿入される。</li>
</ol>
<p>具体例を挙げましょう。次のような内容のJavaScriptファイルがあるとします。</p>
<div class="highlight"><pre><span></span><span class="p">{</span><span class="w"> </span><span class="mf">1</span>
<span class="mf">2</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="mf">3</span>
</pre></div>
<p>これは、自動セミコロン挿入により、次のようになります。</p>
<div class="highlight"><pre><span></span><span class="p">{</span><span class="w"> </span><span class="mf">1</span>
<span class="p">;</span><span class="mf">2</span><span class="w"> </span><span class="p">;}</span><span class="w"> </span><span class="mf">3</span><span class="p">;</span>
</pre></div>
<p>1 2というトークンの列は文法的に許されませんが、2というトークンの前に改行があるため、セミコロンが挿入されます。
また、2の直後に閉じ中括弧が来ることも文法的に許されませんが、セミコロンが挿入されることにより文法的に許可されるコードになります。
これらは、(1)のルールで説明できます。また、このファイルの末尾に到達した状態では、文法的に許可されない状態になるので、(2)のルールによりセミコロンが挿入されます。<sup id="sf-automatic_semilocon_insertion-4-back"><a href="#sf-automatic_semilocon_insertion-4" class="simple-footnote" title="文法的に許されるかどうかの判定は、ECMAの定義にあるBNFという文法を形式的に定義したものに照し合わせればわかるのですが、ここで許可されないと言っているのは、要するにステートメント末尾にセミコロンが不足しているためです。">4</a></sup></p>
<div class="section" id="restricted-production">
<h3><a class="toc-backref" href="#toc-entry-2">Restricted Production</a></h3>
<p>3番目のルールにあるRestricted Productionについては、詳しく説明が必要でしょう。</p>
<p>JavaScriptでは、基本的にトークンは改行、スペース、タブなどの最低1個の空白文字で区切られ、空白文字の数や種類を増やしたり減らしたりしても文の意味は変わりません。これは、CやJavaなどのいわゆるC言語系と言われるようなプログラミング言語に共通する特徴です。</p>
<p>しかしながら、JavaScriptには一部例外があり、return, throw, break, continue, ++, --といったトークンの直後または直前の区切りの空白に限っては、その中に改行が含まれる場合と含まれない場合とで、文の意味が変わるのです。これら6種類の要素がRestricted Productionと呼ばれるものです。ECMAScriptの文法定義で、Restricted Productionの前後には、"[no LineTerminator here]"という文言が書かれています。ここに改行が入ると、自動的にセミコロンが挿入されるのです。つまり、</p>
<div class="highlight"><pre><span></span><span class="k">return</span>
<span class="nx">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">b</span><span class="p">;</span>
</pre></div>
<p>このコードが、</p>
<div class="highlight"><pre><span></span><span class="k">return</span><span class="p">;</span>
<span class="nx">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">b</span><span class="p">;</span>
</pre></div>
<p>このようになります。</p>
<p><a class="reference external" href="http://asi.qfox.nl/">ASI Scanning Test Grounds</a> というサイトで、これらのいささか奇妙な規則をどの程度理解できているかが試せるので、お暇な方はチャレンジしてみてください。</p>
</div>
</div>
<div class="section" id="asi">
<h2><a class="toc-backref" href="#toc-entry-3">ASIの害</a></h2>
<p>ASIについてよく挙げられる害のひとつとして、前述の例で上げた、return直後の改行で、(ASIを理解していないと)意図しない動作をしてしまうという点があります。</p>
<p>たとえば、オブジェクトを関数から返したいときに、</p>
<div class="highlight"><pre><span></span><span class="k">return</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">a</span><span class="o">:</span><span class="w"> </span><span class="mf">1</span><span class="p">,</span>
<span class="w"> </span><span class="nx">b</span><span class="o">:</span><span class="w"> </span><span class="mf">2</span><span class="p">,</span>
<span class="w"> </span><span class="nx">c</span><span class="o">:</span><span class="w"> </span><span class="mf">3</span>
<span class="p">};</span>
</pre></div>
<p>であれば、正しくオブジェクトが返るのですが、</p>
<div class="highlight"><pre><span></span><span class="k">return</span>
<span class="p">{</span>
<span class="w"> </span><span class="nx">a</span><span class="o">:</span><span class="w"> </span><span class="mf">1</span><span class="p">,</span>
<span class="w"> </span><span class="nx">b</span><span class="o">:</span><span class="w"> </span><span class="mf">2</span><span class="p">,</span>
<span class="w"> </span><span class="nx">c</span><span class="o">:</span><span class="w"> </span><span class="mf">3</span>
<span class="p">};</span>
</pre></div>
<p>このようにスタイルを変えてしまうと、ASIにより文が区切られてしまうため、関数の返り値はundefinedとなります。
なお、この害は、行末にセミコロンを付加するかどうかとは関係なく、ASIの仕様を知らなければハマってしまう問題です。</p>
<p>また、もうひとつ実際に起こりそうな害、そして、ステートメント末尾のセミコロンを省略するコーディングスタイルで起きる害として、()が意図せず関数呼び出しとして解釈されてしまう問題があります。</p>
<p>JavaScriptでは、名前空間やブロック構文のような仕組みが用意されていないため、グローバル名前空間の汚染を防ぐために、次のようなテクニックがよく使われます。</p>
<div class="highlight"><pre><span></span><span class="p">(</span><span class="kd">function</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="c1">// この中に書けばグローバル空間に影響を与えることなくコードを実行できる</span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">x</span><span class="p">,</span><span class="w"> </span><span class="nx">y</span><span class="p">,</span><span class="w"> </span><span class="nx">z</span><span class="p">;</span>
<span class="p">})()</span>
</pre></div>
<p>ここで、次の一見問題がなさそうなコードを見てみましょう。</p>
<div class="highlight"><pre><span></span><span class="nx">a</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">c</span>
<span class="p">(</span><span class="kd">function</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">x</span><span class="p">,</span><span class="w"> </span><span class="nx">y</span><span class="p">,</span><span class="w"> </span><span class="nx">z</span><span class="p">;</span>
<span class="p">})()</span>
</pre></div>
<p>このケースは、ASIによってセミコロンがcの後に挿入されるように思えるかもしれませんが、そうはなりません。
なぜなら、cの後の()は関数呼び出しと解釈することが可能だからです。この場合、関数オブジェクトをパラメータに取る関数呼び出しと解釈されます。ステートメント末尾に常にセミコロンを付けるスタイルであれば、このようなことは起きません(セミコロンを付け忘れない限りは)。</p>
</div>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-4">セミコロンにまつわる論争</a></h2>
<p>さて、前述の通り、Bootstrapは、ステートメント末尾にセミコロンを付与しないスタイルを取っています。Bootstrapのクリエイターの一人である、Jacob Thorntonはこの理由について <a class="reference external" href="http://www.wordsbyf.at/2011/10/31/i-dont-write-javascript/">ブログで説明しています。</a> <sup id="sf-automatic_semilocon_insertion-5-back"><a href="#sf-automatic_semilocon_insertion-5" class="simple-footnote" title="Jacob Thorntonのブログは現在停止中のようですが、GitHubにソースが残っています。https://github.com/fat/wordsbyf.at/blob/master/articles/2011-10-31-i-dont-write-javascript.txt ">5</a></sup></p>
<p>その主張は、</p>
<ul class="simple">
<li>JavaScriptがそもそも多様な書き方を許す言語である(セミコロン云々を抜きにしても)。</li>
<li>ASIに頼れば、ステートメント末尾は \n のみで十分である(セミコロンを付加するのは冗長)。</li>
<li>改行後の(function() {...})() と結合して関数呼び出しと認識される件については、 変わりに !function() { ... }() と記述すれば回避可能。</li>
</ul>
<p>3番目の理由ですが、たとえば先程の例で言うと、</p>
<div class="highlight"><pre><span></span><span class="nx">a</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">c</span>
<span class="o">!</span><span class="kd">function</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">x</span><span class="p">,</span><span class="w"> </span><span class="nx">y</span><span class="p">,</span><span class="w"> </span><span class="nx">z</span><span class="p">;</span>
<span class="p">}()</span>
</pre></div>
<p>こう書けばcと()が繋がることなく、その場で関数呼び出しを行うことができます。<sup id="sf-automatic_semilocon_insertion-6-back"><a href="#sf-automatic_semilocon_insertion-6" class="simple-footnote" title="このような書き方は、実際のJSコードでたまに見られますが、この記事を読むまで、なぜこのような奇妙なことをするのか筆者は理解できませんでした。">6</a></sup></p>
<p>npmのクリエイターであるIsaac Z. Schlueterも <a class="reference external" href="http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding">セミコロン不要派</a> です。彼の意見では、JSコミュニティのリーダー達が、不用意にセミコロン省略への不安感を与え、JavaScriptの動作について嘘を教えているというのです。彼は、ステートメントの末尾にセミコロンを付与するのではなく、セミコロンが必要な箇所(括弧から始まる行など)でのみ、行の先頭にセミコロンを置くスタイルを提案しています。また、returnの後の改行でASIが起きる仕様については、常にセミコロン省略をする習慣を持てば問題にならないというのです。つまり、</p>
<div class="highlight"><pre><span></span><span class="k">return</span>
<span class="mf">7</span>
</pre></div>
<p>このようなコードを見たときに、改行=ステートメント終了というスタイルが染み付いていれば、returnの後にトークンが続くことはおかしいと即認識できるようになるという主張です。また、人間が文章を読むときの視線誘導の観点からも、重要なもの(セミコロン)は右端ではなく、左端に置くべきである、とも言っています。</p>
<p>また、Restricted Productionsの問題があるので、いずれにせよASIへの正しい理解はJavaScriptを使っていく上で避けて通れない。それならば、いたずらにセミコロン省略への恐怖を煽って、なにも考えずにステートメント末尾にセミコロンをつけておけば問題ない、などと言うのではなく<sup id="sf-automatic_semilocon_insertion-7-back"><a href="#sf-automatic_semilocon_insertion-7" class="simple-footnote" title="実際、それだけでは、return直後の改行を防げない">7</a></sup>、だれもがきちんとASIを理解してJavaScriptを使うよう促すべきだという考えを持っているようです。</p>
<p>一方で、JavaScript: The Good Partsの著者で、JS界のオピニオンリーダーであるDouglas Crockfordは、上記で提案されたような、! を文の区切りとして使うようなスタイルを <a class="reference external" href="https://github.com/twbs/bootstrap/issues/3057#issuecomment-5135562">激しく批判しています</a> 。彼は、著書の中でASIはJavaScriptの悪いパーツだと述べており、ステートメント末尾には必ずセミコロンを付加するのがいいスタイルであるとしています。また、リンク先のスレッドの例を見ればわかるように、改行がASIの必要条件なので、ASIに頼るコードは、ミニファイアーにかけて改行が除去されたときに壊れてしまう場合があります。<sup id="sf-automatic_semilocon_insertion-8-back"><a href="#sf-automatic_semilocon_insertion-8" class="simple-footnote" title="Douglas Crockfordは、JSMinというミニファイアの作者です。">8</a></sup> Jacob Thorntonは、ASIまで含めてJSコードを完全にパースしないミニファイアは欠陥品であると非難しています。<sup id="sf-automatic_semilocon_insertion-9-back"><a href="#sf-automatic_semilocon_insertion-9" class="simple-footnote" title="さすがにそれは少し酷なのでは、という気がしますが…。">9</a></sup></p>
<p>ここまで、ASIを積極的に利用するセミコロン省略に関する相対する主張を見てきましたが、では、ASIの元々の設計意図はどういうものだったのでしょうか?JSの設計者であるBrendan Eichは、 <a class="reference external" href="http://brendaneich.com/2012/04/the-infernal-semicolon/">自身のブログ</a> で、ASIはあくまでセミコロンを誤って書き忘れた場合の訂正措置であり、普遍的に改行に意味を持たせるような使い方をすれば、トラブルに巻き込まれるだろうと言っています。</p>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-5">で、どっちがいいの?</a></h2>
<p>けっきょく、この論争は、(初期の設計意図はどうあれ)JavaScriptが2つの相反するスタイルを許容する言語であることに起因します。ここまで見たように、どちららのスタイルであっても動くコードが書けるのは事実なので、審美的な点を除けば、実用上、どちらかに決定的な優位があるというわけではないように思われます。もちろん、個人の好みがどうあれ、他人と共同作業しているコードであれば、そのプロジェクトのスタイルに合わせるべきであることは、言うまでもありません。</p>
<p>筆者個人としては、デザイナーなどプログラミングに習熟しているわけではない人に教えるときに、ASIの細かいルールまで詰め込ませるのは無理がある気がしますし、それなら、「ステートメント末尾にはセミコロンを付けること( <strong>そしてreturnで値を返すときは決して直後に改行を入れないこと!</strong> )」という単純なルールのほうが、役に立つと思います。</p>
<p>一番いいのは、このようなどうでもいい問題に悩まされないよう、 <a class="reference external" href="http://coffeescript.org/">CofeeScript</a> を使うことかもしません。</p>
</div>
<ol class="simple-footnotes"><li id="sf-automatic_semilocon_insertion-1"> <a class="reference external" href="https://news.ycombinator.com/item?id=1547647">Hacker Newsでのアンケート</a> では、90%がセミコロンを付ける派でした。 <a href="#sf-automatic_semilocon_insertion-1-back" class="simple-footnote-back">↩</a></li><li id="sf-automatic_semilocon_insertion-2">この要約は、<a class="reference external" href="http://inimino.org/~inimino/blog/javascript_semicolons">JavaScript Semicolon InsertionEverything you need to know</a> から引っ張ってきました。 <a href="#sf-automatic_semilocon_insertion-2-back" class="simple-footnote-back">↩</a></li><li id="sf-automatic_semilocon_insertion-3">文法上の最小の構成要素、1 2 3 "abc"といった数値や文字列リテラル、if,while,for,functionといったキーワード、+,-,=などの記号はすべてトークン <a href="#sf-automatic_semilocon_insertion-3-back" class="simple-footnote-back">↩</a></li><li id="sf-automatic_semilocon_insertion-4">文法的に許されるかどうかの判定は、ECMAの定義にあるBNFという文法を形式的に定義したものに照し合わせればわかるのですが、ここで許可されないと言っているのは、要するにステートメント末尾にセミコロンが不足しているためです。 <a href="#sf-automatic_semilocon_insertion-4-back" class="simple-footnote-back">↩</a></li><li id="sf-automatic_semilocon_insertion-5">Jacob Thorntonのブログは現在停止中のようですが、GitHubにソースが残っています。<a class="reference external" href="https://github.com/fat/wordsbyf.at/blob/master/articles/2011-10-31-i-dont-write-javascript.txt">https://github.com/fat/wordsbyf.at/blob/master/articles/2011-10-31-i-dont-write-javascript.txt</a> <a href="#sf-automatic_semilocon_insertion-5-back" class="simple-footnote-back">↩</a></li><li id="sf-automatic_semilocon_insertion-6">このような書き方は、実際のJSコードでたまに見られますが、この記事を読むまで、なぜこのような奇妙なことをするのか筆者は理解できませんでした。 <a href="#sf-automatic_semilocon_insertion-6-back" class="simple-footnote-back">↩</a></li><li id="sf-automatic_semilocon_insertion-7">実際、それだけでは、return直後の改行を防げない <a href="#sf-automatic_semilocon_insertion-7-back" class="simple-footnote-back">↩</a></li><li id="sf-automatic_semilocon_insertion-8">Douglas Crockfordは、JSMinというミニファイアの作者です。 <a href="#sf-automatic_semilocon_insertion-8-back" class="simple-footnote-back">↩</a></li><li id="sf-automatic_semilocon_insertion-9">さすがにそれは少し酷なのでは、という気がしますが…。 <a href="#sf-automatic_semilocon_insertion-9-back" class="simple-footnote-back">↩</a></li></ol>Android Wearのデザイン原則2014-10-02T00:00:00+09:002014-10-02T00:00:00+09:00tai2tag:blog.tai2.net,2014-10-02:/android_wear_design.html<p class="first last">Android Wearは時計型のAndroidデバイスです。コンセプト自体、携帯電話型やタブレット型(以下、ハンドヘルドとまとめて呼びます)のAndroidデバイスとかかなり異なったものになっており、アプリ開発にあたっては、Android Wearの独自の概念を理解しておく必要があります。</p>
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#section-1" id="toc-entry-1">前提と概要</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-2">クリエイティブビジョン</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-3">デザイン原則</a><ul>
<li><a class="reference internal" href="#section-4" id="toc-entry-4">ユーザーの動きを止めないように気をつける</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-5">おおざっぱなジェスチャで使えるようにする</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-6">ストリームカードファースト</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-7">ひとつのことをすばやくやる</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-8">パッと見ただけでわかるようにデザインする</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-9">頻繁に割り込まない</a></li>
</ul>
</li>
<li><a class="reference internal" href="#ui" id="toc-entry-10">アプリの構造とUIパターン</a><ul>
<li><a class="reference internal" href="#bridged-notifications" id="toc-entry-11">Bridged Notifications</a></li>
<li><a class="reference internal" href="#contextual-notifications" id="toc-entry-12">Contextual Notifications</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-13">フルスクリーンアプリ</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-14">カード</a><ul>
<li><a class="reference internal" href="#section-12" id="toc-entry-15">アプリアイコン</a></li>
<li><a class="reference internal" href="#section-13" id="toc-entry-16">ページ</a></li>
<li><a class="reference internal" href="#section-14" id="toc-entry-17">カードの消去</a></li>
<li><a class="reference internal" href="#section-15" id="toc-entry-18">アクションボタン</a></li>
<li><a class="reference internal" href="#section-16" id="toc-entry-19">アクションカウントダウンと確認</a></li>
<li><a class="reference internal" href="#section-17" id="toc-entry-20">ハンドヘルドでアクティビティを継続</a></li>
<li><a class="reference internal" href="#section-18" id="toc-entry-21">カード上でのアクション</a></li>
<li><a class="reference internal" href="#section-19" id="toc-entry-22">カードスタック</a></li>
</ul>
</li>
<li><a class="reference internal" href="#d" id="toc-entry-23">2Dピッカー</a></li>
<li><a class="reference internal" href="#section-20" id="toc-entry-24">ボイスコマンド</a></li>
<li><a class="reference internal" href="#section-21" id="toc-entry-25">選択リスト</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-22" id="toc-entry-26">スタイル</a></li>
<li><a class="reference internal" href="#android-wear-materials" id="toc-entry-27">Android Wear Materials</a></li>
</ul>
</div>
<p><a class="reference external" href="http://www.android.com/wear/">Android Wear</a> は時計型のAndroidデバイスです。コンセプト自体、携帯電話型やタブレット型(以下、ハンドヘルドとまとめて呼びます)のAndroidデバイスとかかなり異なったものになっており、アプリ開発にあたっては、Android Wearの独自の概念を理解しておく必要があります。</p>
<p>Android Wearのクリエイティブビジョンやデザイン原則といったものについては、 <a class="reference external" href="https://developer.android.com/design/wear/index.html">公式の開発者向けページ</a> にドキュメントがありますが、この記事では、それらを読むのがめんどうなデザイナー、企画の方向けの要約を提供します。</p>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-1">前提と概要</a></h2>
<p>まず大前提ですが、Android Wearアプリ単体では動作させることはできません。<sup id="sf-android_wear_design-1-back"><a href="#sf-android_wear_design-1" class="simple-footnote" title="厳密には開発中はデバイスに直接インストールも可能ですが、リリース版では必ずハンドヘルド版といっしょにパッケージ化する必要があります。">1</a></sup>。ウェアラブルアプリと組になるハンドヘルドデバイス用のアプリを、かならず制作しなければなりません。</p>
<p>また、必ずしもAndroid Wear対応をするために専用アプリを制作する必要はありません。通知や簡単なインタラクションのみであれば、ハンドヘルドアプリを拡張するだけで対応できます。</p>
<p>Android Wearには、 <strong>提案(Suggest)</strong> と <strong>要求(Demand)</strong> という、2つの軸になる機能があります。
提案は、 <strong>コンテキストストリーム</strong> によって表現されます。</p>
<img alt="The Context Stream" class="align-center" src="https://developer.android.com/wear/images/screens/stream.gif" style="width: 30%;">
<p>コンテキストストリームとは、上図アニメーションのように縦に連なるカードの流れのことです。
個々のカードは、対応するアプリケーションからの通知を表しています。
それぞれのアプリケーションが、適切なタイミングを判断して、カードを通じてユーザーに情報を提供します。
カードには、追加情報を表す <strong>ページ</strong> や <strong>アクション</strong> を付加することができ、それらはそのカードの右側に配置され、右から左へのスワイプで表示できます。
ウェアラブル、ハンドヘルドどちらのタイプのアプリケーションからもカードを流し込むことができます。</p>
<p>(ユーザーからの)要求は、 <strong>キューカード</strong> によって実現されます。</p>
<img alt="The Cue Card" class="align-center" src="https://blog.tai2.net/images/wear_cuecard.png" style="width: 30%;">
<p>ユーザーが画面背景をタップするか、時計に向かって"OK Google"と発音すると、上図のキューカードがあらわれます。キューカードには、ボイスアクションと呼ばれる一連のコマンドが表示されており、タッチ操作か音声操作によってコマンドを実行できます。
ボイスアクションの種類自体を拡張することはできませんが、それらに、自分の制作したアプリを関連付けることが可能です。 <a class="reference external" href="https://developer.android.com/training/wearables/apps/voice.html">タクシーを呼ぶ、メモを取る、アラームをセットする</a> などのアクションがあります。</p>
<p>また、独自のウェアラブルアプリを一時的に全画面に表示することもできます。ハンドヘルドデバイスのようにマルチタスクという概念はありません。一定時間経過してデバイスがスリープすると、アプリは自動的に終了します。</p>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-2">クリエイティブビジョン</a></h2>
<ul class="simple">
<li>自動で起動し: ウェアラブルアプリは、時間、場所、運動中かどうかなど、ユーザーのコンテキストを認識して、コンテキストストリームにカードを流し込みます。</li>
<li>視認性を高く: ふつうの腕時計が、あなたがいましていることを数秒程度しか阻害しないように、パッと見ただけでアプリからの情報を把握できるようにデザインすべきです。</li>
<li>提案と要求のすべてを: Android Wearは、あなたや、あなたの好みを知っており、ほんとうに必要なとき以外にはユーザーの邪魔をせず、いつでもユーザーからの要求に答えられる準備をしています。</li>
<li>0または非常に少ないインタラクションで: ほんとうに必要なとき以外はユーザーからの入力を必要とせず、基本的に、スワイプと音声入力だけで操作できるようにすべきです。複雑なジェスチャは使いません。</li>
</ul>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-3">デザイン原則</a></h2>
<div class="section" id="section-4">
<h3><a class="toc-backref" href="#toc-entry-4">ユーザーの動きを止めないように気をつける</a></h3>
<ul class="simple">
<li>料理中、食事中、ジョギング中にアプリを使っても支障のないようにします。</li>
<li>典型的なユースケースの時間を計測して5秒以上かかるようなら、デザインを再考します。</li>
<li>会話しながらアプリを使ってみて、邪魔にならないか確認します。</li>
</ul>
</div>
<div class="section" id="section-5">
<h3><a class="toc-backref" href="#toc-entry-5">おおざっぱなジェスチャで使えるようにする</a></h3>
<img alt="Use few and large touch targets." class="align-center" src="https://developer.android.com/design/media/wear/biggesture.png" style="width: 50%;">
<ul class="simple">
<li>Android Wearデバイスの画面は小さいので、個々の要素を大きくして、操作し易くします。</li>
<li>操作に集中するために、歩行や会話が阻害されるようではいけません。</li>
</ul>
</div>
<div class="section" id="section-6">
<h3><a class="toc-backref" href="#toc-entry-6">ストリームカードファースト</a></h3>
<ul class="simple">
<li>ウェアラブルアプリのベストエクスペリエンスは、ユーザーが必要なときに、まさしく必要なものがそこにあることです。</li>
<li>ユーザーがあなたのアプリを必要とするシチュエーションをリストアップしましょう(特定の場所、時間、運動など)。</li>
<li>例) 4Squareのようなアプリでは、ユーザーがチェックインできる場所に来たときに、カードでチェックインを促しましょう。</li>
</ul>
</div>
<div class="section" id="section-7">
<h3><a class="toc-backref" href="#toc-entry-7">ひとつのことをすばやくやる</a></h3>
<ul class="simple">
<li>ユーザーは、ひとつのカードを数秒しか見ない一方で、一日に何度もカードを目にするのがふつうです。</li>
<li>ひとつのカードに多くのことを詰め込みすぎず、小さい単位に分割しましょう。</li>
</ul>
</div>
<div class="section" id="section-8">
<h3><a class="toc-backref" href="#toc-entry-8">パッと見ただけでわかるようにデザインする</a></h3>
<ul class="simple">
<li>一瞬で視認できるデザインを備えることで、ユーザーの実生活を邪魔せずにすみ、結果的にアプリの価値は増大します。</li>
<li>腕時計をした状態で拳を見積めるなど、周辺視でアプリを見てみましょう。その状態でもなにかしら意味のある情報を受けとれるのが望ましいです。</li>
<li>背景画像などを活用して、他のアプリと区別がつくようにしましょう。</li>
</ul>
</div>
<div class="section" id="section-9">
<h3><a class="toc-backref" href="#toc-entry-9">頻繁に割り込まない</a></h3>
<ul class="simple">
<li>頻繁にバイブや音声で通知しないようにしましょう。携帯アプリのような感覚でバイブや音声を使ってはいけません。</li>
</ul>
</div>
</div>
<div class="section" id="ui">
<h2><a class="toc-backref" href="#toc-entry-10">アプリの構造とUIパターン</a></h2>
<div class="section" id="bridged-notifications">
<h3><a class="toc-backref" href="#toc-entry-11">Bridged Notifications</a></h3>
<p>ハンドヘルドの通知エリアへの通知は、自動的にウェアラブルのコンテキストストリームにも通知されます。
また、ページや音声返信といった拡張機能を使用することも可能です。</p>
</div>
<div class="section" id="contextual-notifications">
<h3><a class="toc-backref" href="#toc-entry-12">Contextual Notifications</a></h3>
<p>ウェアラブルアプリは、ユーザーのコンテキストに応じた通知を行います。
ウェアラブルからの通知では、デフォルトの通知スタイル以外にも、独自に通知スタイルを定義することも可能です。</p>
</div>
<div class="section" id="section-10">
<h3><a class="toc-backref" href="#toc-entry-13">フルスクリーンアプリ</a></h3>
<p>デバイスの全面を占有するアプリを実装することもできます。</p>
<ul class="simple">
<li>なるべく単一の操作に集中して、目的が完了したらすぐ終了しましょう。</li>
<li>メイン画面と似たような見ためにするとユーザーが混乱するので、独自アプリだとハッキリわかるようにしましょう。</li>
<li>ウェアラブルデバイスにはバックボタンやホームボタンはありません。必ず終了操作を実装する必要があります。</li>
<li>画面長押しは、必ずフルスクリーン終了に割り当てるべきです。</li>
</ul>
</div>
<div class="section" id="section-11">
<h3><a class="toc-backref" href="#toc-entry-14">カード</a></h3>
<p>カードには、テキスト情報を載せる他、ボタンをつけたり、スタックしたりできます。</p>
<div class="section" id="section-12">
<h4><a class="toc-backref" href="#toc-entry-15">アプリアイコン</a></h4>
<img alt="app icon" class="align-center" src="https://developer.android.com/design/media/wear/clear_bold_type.jpg" style="width: 30%;">
<p>カードの右上にはアプリアイコンを表示することができます。</p>
</div>
<div class="section" id="section-13">
<h4><a class="toc-backref" href="#toc-entry-16">ページ</a></h4>
<p>カードの右側には、補足情報を示すページ(詳細カード)を加えることができます。
たとえば、一番左のカードには本日の天気を表示し、その右側に、数日分の天気を表示するといった使いかたができます。</p>
</div>
<div class="section" id="section-14">
<h4><a class="toc-backref" href="#toc-entry-17">カードの消去</a></h4>
<p>カード上で左から右にスワイプするとカードを消去できます。
ウェアラブル上で消去されたカードは、ペアリングされたハンドヘルド上でも消去されます。</p>
</div>
<div class="section" id="section-15">
<h4><a class="toc-backref" href="#toc-entry-18">アクションボタン</a></h4>
<img alt="action button" class="align-center" src="https://developer.android.com/design/media/wear/action_button.png" style="width: 30%;">
<p>詳細カードの右側には、アクションボタンを置くことができます。
アクションボタンは、青い円に白いアイコンという見た目で表示されます。
アクションボタンを押すと、なにかを実行したり、ハンドヘルドに操作を引き継いだり、
アプリ画面をフルスクリーンで表示したりできます。</p>
</div>
<div class="section" id="section-16">
<h4><a class="toc-backref" href="#toc-entry-19">アクションカウントダウンと確認</a></h4>
<p>アクションボタンを押した後は、次の4つのうちどれかが起きます。</p>
<ol class="arabic simple">
<li>ただちにアクションが完了し、確認アニメーション(confirmation animation)が表示される。</li>
<li>時間がかかるアクションの場合はカウントダウンが表示され、完了すると確認アニメーションが表示される。</li>
<li>ユーザー確認が必要な場合は、ユーザーの意思を確認してから、アクションを実行する。その後、確認アニメーションが表示される。</li>
<li>キューカードに遷移する。キューカードに表示されるアクションは、アプリから指定できる。</li>
</ol>
</div>
<div class="section" id="section-17">
<h4><a class="toc-backref" href="#toc-entry-20">ハンドヘルドでアクティビティを継続</a></h4>
<p>アプリ開発に際しては、できる限りウェアラブルのみでアクションが完結するようにすべきですが、それが無理な場合は、ハンドヘルド側で操作を継続することもできる。その場合には専用のアニメーションが用意されているます。</p>
</div>
<div class="section" id="section-18">
<h4><a class="toc-backref" href="#toc-entry-21">カード上でのアクション</a></h4>
<p>カード上に直接ボタンを配置することもできます(On-cardボタン)。
On-cardボタンには、テキストラベルを使うことはできないので、見ただけで押すとなにが起きるのか明白な場面でだけ使うべきです。
ひとつのカードに置くボタンはひとつまでにし、原則としてアクションはウェアラブル上で起きるようにしましょう(ただしウェブリンクは例外)。</p>
</div>
<div class="section" id="section-19">
<h4><a class="toc-backref" href="#toc-entry-22">カードスタック</a></h4>
<img alt="card stacks" class="align-center" src="https://developer.android.com/design/media/wear/expandable_stacks.png" style="width: 30%;">
<p>カードは複数枚をまとめてスタックさせることができます。</p>
</div>
</div>
<div class="section" id="d">
<h3><a class="toc-backref" href="#toc-entry-23">2Dピッカー</a></h3>
<img alt="1D picker" class="align-center" src="https://developer.android.com/design/media/wear/2D_picker.png" style="width: 100%;">
<p>複数のアイテムから、ひとつのものを選択させたいときには1Dピッカーが使用できます。
アイテムをカテゴリ分けしたい場合には、2Dピッカーを使用します。ピッカーで使用できるカテゴリは最大5つ程度までです。</p>
</div>
<div class="section" id="section-20">
<h3><a class="toc-backref" href="#toc-entry-24">ボイスコマンド</a></h3>
<p>キューカードから実行されるボイスコマンドに応じてアクションを実行することも可能です。</p>
</div>
<div class="section" id="section-21">
<h3><a class="toc-backref" href="#toc-entry-25">選択リスト</a></h3>
<img alt="selection list" class="align-center" src="https://developer.android.com/design/media/wear/selection_list.png" style="width: 30%;">
<p>選択リストを使って、複数アイテムを選択させることが可能です。</p>
</div>
</div>
<div class="section" id="section-22">
<h2><a class="toc-backref" href="#toc-entry-26">スタイル</a></h2>
<p>Android Wear特有のデザイン上の注意点がいくつかあります。</p>
<ul class="simple">
<li>画面サイズや形状の異なるデバイスがあることに注意しましょう。丸型のデバイスも存在します。</li>
<li>カードのデザインには、アプリアイコン、背景画像、アクションアイコン、確認アニメーションなどの要素が必要になります。</li>
<li>背景画像は、ページなしなら400px x 400px、ページありなら600px x 400pxで作成します。</li>
<li>カードの頭だけが覗いている場合でも、内容がわかるようにしましょう。</li>
</ul>
<img alt="peek card" class="align-center" src="https://developer.android.com/design/media/wear/peek-card.png" style="width: 30%;">
<ul class="simple">
<li>カードの意味を伝えるのに役立つような背景画像を使いましょう。背景画像はカードの視認性を向上させます。</li>
<li>ひとつのカードに情報を詰め込みすぎず、ページを使って分割するなどしましょう。</li>
<li>バイブや音を伴う通知は、必要最低限にしましょう。</li>
<li>大きなテキストを使って見易くしましょう。</li>
<li>アプリアイコンを使って、アプリを区別するのに役立てましょう。</li>
<li>アプリアイコンの表示位置はカードの右上です。背景画像にアプリアイコンを表示してはいけません。</li>
<li>テキストは完結に。文ではなく、語や句を使いましょう。</li>
<li>センシティブ情報や恥ずかしい情報は、トップのカードに表示しないようにしましょう(詳細カードなど、意図せず表示されることのない場所に表示しましょう)。</li>
<li>アクションが完了したときには1秒以下程度の確認アニメーションを表示しましょう。</li>
</ul>
</div>
<div class="section" id="android-wear-materials">
<h2><a class="toc-backref" href="#toc-entry-27">Android Wear Materials</a></h2>
<p><a class="reference external" href="https://developer.android.com/design/downloads/index.html#Wear">Android Wear Materials</a> から、デザイン要素のサイズ仕様や、ユーザーフローパターン、デザインモックといったデザイン用のアセットを入手できます。</p>
</div>
<ol class="simple-footnotes"><li id="sf-android_wear_design-1">厳密には開発中はデバイスに直接インストールも可能ですが、リリース版では必ずハンドヘルド版といっしょにパッケージ化する必要があります。 <a href="#sf-android_wear_design-1-back" class="simple-footnote-back">↩</a></li></ol>iOSの設定アプリ(Settings Bundle)にアプリ固有の設定を置くべきではない2014-07-25T00:00:00+09:002014-07-25T00:00:00+09:00tai2tag:blog.tai2.net,2014-07-25:/ios_settings_app.html<p class="first last">iOSアプリのUIを設計するときに、設定をどこに配置するかで悩むことがありますが、ユーザビリティーを考えるのであれば、設定アプリにアプリの設定を置くべきではありません。</p>
<p><strong>追記:iOS8ではAndroidのIntentのようにアプリ間での遷移ができるようになりました。よって、この記事の主張は現在では当てはまりません。</strong></p>
<p>iOSアプリのUIを設計するときに、設定をどこに配置するかで悩むことがあります。
iOSでアプリ設定を置く場所は、大きく2つに分けられます。</p>
<ol class="arabic simple">
<li>設定アプリ(Settings App)</li>
<li>カスタムUI</li>
</ol>
<p>1は、OSにプリインストールされているもので、作成するアプリ自体とは別のアプリになります。
この中に、 OSの基本設定に加えて、アプリ個別の設定を置くことができます。</p>
<img alt="Settings App Screen Shot" class="align-center" src="https://blog.tai2.net/images/settings_app.png" />
<p>2は、アプリ内に設定用の画面を配置したりします。項目が少なければ、設定のためだけの画面を用意しなくても、メインのUIに設定用のコントロールを配置すれば十分かもしれません。次の図は、Twitterアプリの設定画面です。</p>
<img alt="Twitter App Settings Screen Shot" class="align-center" src="https://blog.tai2.net/images/twitter_app_settings.png" />
<p><a class="reference external" href="https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/UserDefaults/AboutPreferenceDomains/AboutPreferenceDomains.html">Appleのガイドライン</a> では、頻繁にアクセスする項目は、カスタムUI、それほど頻繁にアクセスしない項目は、設定アプリに置くと良い、ということになっています。</p>
<div class="section" id="section-1">
<h2>設定アプリの利点</h2>
<p>設定アプリ内に設定を置く場合は、Settings Bundleというリソースファイルを作成し、Xcodeの専用のエディタからデータ定義を作成すれば、それだけでデータ編集用のUI定義・表示まで行ってくれるので、実装コストは非常に低いです。十分なデータ型もそろっており、階層的に整理することもできるので、たいていの設定を記述するために十分な表現力を持っています。次の図は、Settings Bundleの作成画面です。</p>
<img alt="Settings Bundle Screen Shot" class="align-center" src="https://blog.tai2.net/images/settings_root_template.jpg" />
<p>一方、カスタムUIを作成するとなると、それ専用のUIを作成して、NSUserDefaultsという設定保存用の場所にデータを保存するコードを記述する必要があります。技術的に難しいことはなにもありませんが、まあめんどくさいです。</p>
</div>
<div class="section" id="section-2">
<h2>設定アプリの欠点</h2>
<p>しかしながら、設定アプリでの設定には、非常に大きな欠点があるとわたしは思います。
それは、設定画面への動線がどこにもないということです。
ユーザーは、設定画面を立ち上げて、下のほうにスクロールしたときに、はじめてそこにアプリ固有の設定があることに気付きます。
設定アプリ内で、アプリ個別の設定が表示される場所は、かなり長い画面の一番下側になりますので、まったく目立ちません。
設定アプリ内にアプリ用の設定があるかもしれないとユーザーが想像しなければ、ずっと気付かれないままになる可能性があります。
iOS 5.1より前であれば、URLスキームを使って、設定アプリに誘導することもできましたが、現在のiOSではそれもできません。</p>
<p>カスタムUIであれば、動線の設計は自由自在です。</p>
</div>
<div class="section" id="section-3">
<h2>結論</h2>
<p>ユーザビリティーを考えるのであれば、設定アプリ内にアプリ固有の設定を置くべきではありません。</p>
</div>
CPythonよりも(制約付きで)速いPythonJS2014-07-09T00:00:00+09:002014-07-09T00:00:00+09:00tai2tag:blog.tai2.net,2014-07-09:/about_pythonjs.html<p class="first last">PythonJSというPythonからJavaScriptへのトランスレーターで変換されたコードが、元のCPythonコードよりも高速になったという 記事が出ました。ちょっと興味が湧いたので、PythonJSについて調べてみました(この記事はプログラマ向けです)。</p>
<img alt="Python logo" class="align-center" src="https://blog.tai2.net/images/python-logo.png">
<p>PythonJSというPythonからJavaScriptへのトランスレーターで変換されたコードが、元のCPythonコード<sup id="sf-about_pythonjs-1-back"><a href="#sf-about_pythonjs-1" class="simple-footnote" title="Pythonと一口にいっても、オリジナルのCで実装されたCPython、Javaで実装されたJython、.NET上で動くIronPython、RPythonというPythonのサブセットで実装されたPyPyなど、いくつもの実装があります。">1</a></sup>よりも高速になったという <a class="reference external" href="http://pythonjs.blogspot.jp/2014/05/pythonjs-now-faster-than-cpython.html">記事が出ました</a> 。ちょっと興味が湧いたので、PythonJSについて調べてみました(この記事はプログラマ向けです)。</p>
<div class="section" id="pythonjs">
<h2>PythonJSとは</h2>
<p><a class="reference external" href="https://github.com/PythonJS/PythonJS">PythonJS</a> は、正確に言うと、Pythonから、JavaScriptを含む各種プログラミング言語(現状、JavaScript,Dart,Lua,CoffeScript)へのトランスレーターです。
このトランスレーター自体はPythonで書かれていますが、<a class="reference external" href="https://github.com/replit/empythoned">empythoned</a> を使ってNode.jsから実行することもできます(empythoned自体もPythonJSに同梱されてます)。Node.js用のライブラリとしてのインターフェイスもありますので、Node.jsアプリケーションから、Pythonコードを動的に変換して使用するといった(若干変態的な)荒技も可能です。</p>
<img alt="PythonJS conversion flow" class="align-center" src="https://blog.tai2.net/images/pythonjs_flow.png">
<p>変換処理は2パスになっており、まずはPythonコードをpythonjsと呼ばれる中間コードに変換します。
この中間コード自体もPythonで、<a class="reference external" href="http://pythonjs.blogspot.jp/2014/06/automatic-synchronous-to-async-transform.html">同期処理が非同期処理に変換されたり</a> 、import文がホスト先言語の対応するモジュールに変換されたりします(importは、PythonJSのインライン記法
<sup id="sf-about_pythonjs-2-back"><a href="#sf-about_pythonjs-2" class="simple-footnote" title="JS('var sin = Math.sin')のようにJS関数に文字列を渡すことで、ホスト先言語のコードをPython内にインラインで記述できます。">2</a></sup>
を用いてホスト先言語の対応する機能に変換する文として出力されます)。
次に、pythonjsから、ホスト先のコードに変換されます。</p>
<p>Pythonコードは、標準の <a class="reference external" href="https://docs.python.org/2/library/ast.html">astモジュール</a> でパースされ、AST<sup id="sf-about_pythonjs-3-back"><a href="#sf-about_pythonjs-3" class="simple-footnote" title="抽象構文木=コードをプログラムから扱い易いツリー構造に変換したもの">3</a></sup>に変換されます。中間コード自体も構文的にPythonコンパイラで受理される表現になっているので、astモジュールでパースできるのがミソです。最近の言語ではあたりまえなのかもしれませんが、標準でこういうライブラリが用意されていると、開発ツールを作るときとかに非常にありがたいですね。</p>
</div>
<div class="section" id="section-1">
<h2>関数のタイプ</h2>
<p>PythonJSでは、関数の変換のしかたに3つのモードがあります。</p>
<p>通常モードでは、引数の受け渡し方法を含めて、通常のPythonとの互換性が最大限に維持されます。
このモードでは、メソッドをそのままJavaScriptのコールバックとして渡したりすることもできます(selfと結合した形で関数にしてくれます)。
しかしながら、このモードはかなり遅いです。</p>
<p>fastdefモードは、通常モードより速いコードを生成できますが、可変長引数が使えなくなり、JavaScriptから関数を呼び出すときに、引数を配列やオブジェクトに格納してから渡さなければなりません。関数を@fastdefデコレータで修飾するか、with fastdef:という特別なブロックの中で関数を定義するとfastdefモードになります。</p>
<p>javascriptモードは、最速のモードです。こちらも可変長引数を使えないことに加えて、JavaScriptから関数を呼び出すときには、キーワード引数が使えなります。関数を@javascriptデコレータで修飾する、with javascript:という特別なブロックの中で関数を定義する、pythonjs.configure(javascript=True)でグローバルなスイッチをONにする、のいずれかの方法で、javascriptモードの関数を定義できます。</p>
<p>なお、公式ブログでCPythonよりも速くなったと言っているのは、すべての関数をjavascriptモードで変換した場合の話です。</p>
</div>
<div class="section" id="section-2">
<h2>静的型付け用拡張構文</h2>
<p>PythonJSでおもしろいのは、静的型付けができるように <a class="reference external" href="http://pythonjs.blogspot.jp/2014/06/optional-static-typing.html">構文を拡張している</a> ことです。</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">arr</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
<span class="nb">list</span> <span class="n">arr</span>
<span class="nb">int</span> <span class="n">x</span>
<span class="nb">int</span> <span class="n">y</span> <span class="o">=</span> <span class="n">somefunction</span><span class="p">()</span>
<span class="n">arr</span><span class="o">.</span><span class="n">append</span><span class="p">(</span> <span class="n">x</span><span class="o">+</span><span class="n">y</span> <span class="p">)</span>
</pre></div>
<p>上記のようにC風の書き方で型付けができます。文法を拡張しているのにastモジュールでそのままパースできるのが不思議なところですが、パース前に型付けされている文をプリプロセスするというハックで <a class="reference external" href="https://github.com/PythonJS/PythonJS/issues/104">実現している</a> ようです。</p>
<p>静的型付けをすると、listのメソッド呼び出しなどが劇的に高速化されるようです。</p>
</div>
<div class="section" id="section-3">
<h2>ベンチマークを走らせてみた</h2>
<p>せっかくなので公式ブログに出ているベンチマークを動かしてみようと悪銭苦闘してみたのですが、けっきょくテストスイートが動かせませんでした(グラフ出力のためのgnuplotコードが構文エラーになってしまったところで挫折)。テストコードは、いまのところLinuxのみサポートしているようです。付属のnbodyベンチマークを単体で走らせてみましたが、たしかにCPythonよりも断然速いようです。</p>
<p>せっかくなので、グラフィクス野郎にはおなじみの <a class="reference external" href="https://code.google.com/p/aobench/">aobench</a> の <a class="reference external" href="http://leonardo-m.livejournal.com/79346.html">Python版</a> を動かしてみたところ、PyPyとほぼ同じくらいのタイムでした(PyPyのほうがほんのすこし速い)。</p>
<ul class="simple">
<li>PyPy 5秒後半</li>
<li>PythonJS(javascript=True) 6秒弱</li>
<li>CPython 1分半くらい</li>
<li>PythonJS(normal) 4分くらい</li>
</ul>
<p>(Node.js v0.10.28, PyPy 2.3.1、Core i5で計測)</p>
</div>
<div class="section" id="section-4">
<h2>評価</h2>
<p>若干の制約はありますが、言語コアの機能自体は、ほぼほぼ動くようです。
また、V8<sup id="sf-about_pythonjs-4-back"><a href="#sf-about_pythonjs-4" class="simple-footnote" title="Node.jsやChromiumのVM">4</a></sup>はCPythonのVMよりもはるかに高速なので、たしかに速くなります。</p>
<p>ただ、現状だと標準ライブラリが、<a class="reference external" href="https://github.com/PythonJS/PythonJS/blob/59aecdbaa895bc653dd6c74d88a20bd43aa45ddb/pythonjs/ministdlib.py">ほとんど使えない</a> ので、既存のコードベースはほぼ動かないと思われます(ホスト先に対応する関数があれば、追加すること自体はできますが、上手いことマッピングするのはそれなりにめんどくさそうな気がします)。必然的に、AltJSとして、JavaScriptとミックスして使う形になりそうです。</p>
<p>実際のところ、こんなややこしいことをしてまでPythonコードをV8で動かしたいかと言われると、
あまり実用したいとは思わず、素直にJavaScriptか、あるいはもうちょっとJavaScriptとなじみやすいAltJSで書いたほうがいいかなという気がします。わたし自身はPythonに速さは求めていないのですが、どうしても速さが必要な場合は、PyPyを使ったほうがいいでしょう。</p>
</div>
<ol class="simple-footnotes"><li id="sf-about_pythonjs-1">Pythonと一口にいっても、オリジナルのCで実装されたCPython、Javaで実装されたJython、.NET上で動くIronPython、RPythonというPythonのサブセットで実装されたPyPyなど、いくつもの実装があります。 <a href="#sf-about_pythonjs-1-back" class="simple-footnote-back">↩</a></li><li id="sf-about_pythonjs-2">JS('var sin = Math.sin')のようにJS関数に文字列を渡すことで、ホスト先言語のコードをPython内にインラインで記述できます。 <a href="#sf-about_pythonjs-2-back" class="simple-footnote-back">↩</a></li><li id="sf-about_pythonjs-3">抽象構文木=コードをプログラムから扱い易いツリー構造に変換したもの <a href="#sf-about_pythonjs-3-back" class="simple-footnote-back">↩</a></li><li id="sf-about_pythonjs-4">Node.jsやChromiumのVM <a href="#sf-about_pythonjs-4-back" class="simple-footnote-back">↩</a></li></ol>WebSocketによるネイティブアプリ-WebView間でのストリーミングデータ送信実験2014-07-05T00:00:00+09:002014-07-05T00:00:00+09:00tai2tag:blog.tai2.net,2014-07-05:/websocket-bulktransfer-ios.html<p class="first last">モバイルネイティブアプリ側からWebViewへの動画(無圧縮RGB)データ転送の可能性を模索して、 XMLHttpRequestによる実験 を行いました。 XHRを用いた手法は、期待する速度が出ないことと、メモリが蓄積されていってアプリがクラッシュしてしまうことから 実用性がないことがわかったため、今度はWebSocketで同様の実験を行ってみました。</p>
<p>モバイルネイティブアプリ側からWebViewへの動画(無圧縮RGB)データ転送の可能性を模索して、
<a class="reference external" href="https://blog.tai2.net/xhr-bulktransfer-ios.html">XMLHttpRequestによる実験</a> を行いました。
XHRを用いた手法は、期待する速度が出ないことと、メモリが蓄積されていってアプリがクラッシュしてしまうことから
実用性がないことがわかったため、今度はWebSocketで同様の実験を行ってみました。</p>
<div class="section" id="websocket">
<h2>WebSocketでのストリーミング転送</h2>
<p>iOSで、アプリ内にWebSocketサーバーを立てて、WebView内のWebSocketオブジェクトへの転送を行い、どの程度速度が出るか実験してみました。
コードは、githubに置いてあります: <a class="reference external" href="https://github.com/tai2/WebSocketBulkTransferDemo_iOS">WebSocketBulkTransferDemo_iOS</a></p>
<img alt="In-app web server to webview" class="align-center" src="https://blog.tai2.net/images/websocket.png" />
<p>条件は以下のような感じです。</p>
<ul class="simple">
<li>サーバーは、非ブロッキングソケット + イベント通知モデルで実装。</li>
<li>WebSocketペイロードのサイズは1MB。</li>
<li>ペイロードは、ArrayBufferとして受けとる。</li>
<li>ソケット送信バッファサイズも1MB(XHRのときと同様)。</li>
<li>ユーザーランドの送信バッファは10MB。</li>
<li>0.1秒間隔のコールバック内で、ユーザーランド送信バッファを適当なデータで一杯まで埋める。</li>
<li>送信可能イベント通知を利用して、ユーザーランド送信バッファからソケット送信バッファへデータ転送。</li>
</ul>
</div>
<div class="section" id="section-1">
<h2>結果</h2>
<p>以下のようなことがわかりました。</p>
<ul class="simple">
<li>第一世代iPad mini実機で、110Mbps程度。</li>
<li><strong>メッセージのメモリが開放されずにアプリが落ちてしまう</strong></li>
</ul>
<p>期待とは裏腹に、XHRよりも速度が出ませんでした(転送のしかたが悪い?)。
また、XHRのときと同様に、実機で実行するとWebView内で受信したデータが蓄積されていってしまい、最終的にアプリがクラッシュします。
jsコード内からのデータへの参照は、即座に解除されているはずなのですが、どうも実際の開放までに時間差があるようで、
開放される速度よりも受信する速度が上回った結果クラッシュしてしまっているような感じがします。</p>
<p>HTTPやWebSocketでモバイルWebViewに大量のストリーミングデータ転送を行うという手法は、現実的ではないようですね(すくなくとも現状のiOSでは)。</p>
<p>余談ですが、データ送信完了までその場で待ってくれる同期I/Oと比べると、送信可能なデータ量が未確定な非ブロッキングソケットは、プログラミングしづらいなと、改めて思いました。もちろん、イベントモデルにはレースコンディションを考えなくていいという素晴しい利点もあるのですが…。一長一短ですね。</p>
</div>
ffmpegをビジネスで利用したときに特許侵害になる可能性2014-07-02T00:00:00+09:002014-07-02T00:00:00+09:00tai2tag:blog.tai2.net,2014-07-02:/mpegla_and_ffmpeg.html<p class="first last">主に仕事で、ffmpegやVLCのようなOSSを利用して、動画をデコードしたりエンコードすることがちょくちょくあるのですが、そういうのを使ったときにMEPG-LAの保有している特許プールに突っ込むことにならないのか、気になったのでこの際ハッキリさせておくことにしました。</p>
<img alt="ffmpeg logo" class="align-center" src="https://blog.tai2.net/images/ffmpeg-logo.png" />
<p>主に仕事で、ffmpegやVLCのようなOSSを利用して、動画をデコードしたりエンコードすることがちょくちょくあるのですが、
そういうのを使ったときにMEPG-LAの保有している特許プールに突っ込むことにならないのか、気になったのでこの際ハッキリさせておくことにしました。</p>
<div class="section" id="ffmpeg-1">
<h2>ffmpegの見解</h2>
<p><a class="reference external" href="http://www.mpegla.com/main/default.aspx">MPEG-LA</a> は、Apple、Microsoft、Fujitsu、Sony等等等といった名立たる企業が名を連ねたLLCで、MPEG2コーデック、MPEG2-Systems、H264/AVC等、動画にまつわる多数の特許を保持しています。
当然、ffmpeg等を利用して動画をデコード・エンコードできる能力を備えたソフトウェアを提供するときにも、これらの特許が問題になってくる可能性があります。</p>
<p>ffmpeg自体はボランティアで開発されており、MPEG-LAにライセンス料を支払う収入源があるとは思えないにも関わらず、どうして活動を続けられているのか、よくよく考えてみると不思議です。
ffmpegは、特許に関して、次のような立場であることを <a class="reference external" href="https://www.ffmpeg.org/legal.html">表明しています</a> 。</p>
<blockquote>
<p>Q: Does FFmpeg use patented algorithms?</p>
<p>A: We do not know, we are not lawyers so we are not qualified to answer this.
Also we have never read patents to implement any part of FFmpeg, so even if we were qualified we could not answer it as we do not know what is patented.</p>
</blockquote>
<p>つまり、具体的な特許の内容を把握していないので、特許侵害しているのかどうかは知らないという立場のようです。
実際、H264/AVCに関連する特許だけでも、<a class="reference external" href="http://www.mpegla.com/main/programs/avc/Documents/avc-att1.pdf">膨大な数</a> になるので、ひとつひとつ精査するだけでもとんでもない労力がかかるだろうと思います。</p>
<blockquote>
<p>Q: Is it perfectly alright to incorporate the whole FFmpeg core into my own commercial product?</p>
<p>A: You might have a problem here. There have been cases where companies have used FFmpeg in their products. These companies found out that once you start trying to make money from patented technologies, the owners of the patents will come after their licensing fees. Notably, MPEG LA is vigilant and diligent about collecting for MPEG-related technologies.</p>
</blockquote>
<p>商業製品にffmpegを利用した場合、問題になる可能性があることを指摘しています。
また、過去にそういうケースがあったことをほのめかしてもいますが、具体的にどういう問題があったのかまでは、軽く調べた限り見付けられませんでした。</p>
<p>ffmpegのような非商業活動をしているところに対して特許権を主張しても、お金を取れないので放置されているということなんでしょうかね。ソースコードの頒布自体は特許の侵害にならないという議論もありますが、そのあたりは国によって状況が違うようです。例えソースコードの頒布が問題にならなかったとしても、ffmpegをバイナリとして配布している各種のディストリビューションはどうなるんだ、とか考えると、やはり潜在的な問題は深そうです。</p>
</div>
<div class="section" id="section-1">
<h2>商業製品で動画を扱いたい場合はどうするべきか</h2>
<p>いくつか考えられますが、H264をエンコード・デコードできればいいだけなら、OSの提供するAPIを利用すればいいでしょう。
この場合は、OSのベンダーがライセンス料をすでに払ってくれているので問題ないという認識です。
モバイルでは、iOSならAVFoundation、Androidは、4.1以降であればMediaCodecクラスが使えます。</p>
<p>あるいは、 <a class="reference external" href="http://www.mainconcept.com/jp/products/sdks.html">MainConcept</a> のようなMPEG-LAへのライセンス済みの商用ライブラリを使うという手もあります。これであれば、ffmpegでH264エンコードしようとした場合に発生するx264のGPL問題も回避することができます(詳しい値段は知りませんが、噂によるとけっこうお高いようです)。</p>
<p>もちろん、ffmpeg等を使用した上で、MPEG-LAとライセンスを結べば、そもそも問題は発生しません。
いろいろな契約形態があるようですので、くわしくはMPEG-LAに直接問い合わせるのがいいと思います。</p>
<p>あとは、研究開発・実証実験といった内部向けのデモでしか使わないようなソフトウェアであれば、そもそもこういったことは問題にはならないかもしれません。</p>
</div>
<div class="section" id="section-2">
<h2>参考記事</h2>
<ul class="simple">
<li><a class="reference external" href="http://blog.sorensonmedia.com/2010/09/think-h-264-is-now-royalty-free-think-again-and-the-open-source-defense-is-no-defense-to-mpeg-la/">Think H.264 is Now Royalty-Free? Think Again – and the “Open Source” Defense is No Defense to MPEG</a></li>
<li><a class="reference external" href="https://www.ffii.org/Frequently%20Asked%20Questions%20about%20software%20patents">Frequently Asked Questions about software patents</a></li>
</ul>
</div>
(L)GPLとApp StoreとVLC for iOS2014-06-29T00:00:00+09:002014-06-29T00:00:00+09:00tai2tag:blog.tai2.net,2014-06-29:/lgpl_and_appstore.html<p class="first last">主に仕事で、ffmpegやVLCのような(L)GPLのもとに配布されているプログラムを利用することが、ちょくちょくあるのですが、GPLプログラムはApp Storeで配布できないという話をツイッターでみかけたのをきっかけに、そのあたりどうなっているのか調べてみました。</p>
<img alt="LGPL logo" class="align-center" src="https://blog.tai2.net/images/lgpl.png">
<div class="contents topic" id="topic-1">
<p class="topic-title">目次</p>
<ul class="simple">
<li><a class="reference internal" href="#gnu-goapp-store" id="toc-entry-1">ことの発端、GNU GoがApp Storeから消えた</a></li>
<li><a class="reference internal" href="#vlc-develapp-store" id="toc-entry-2">vlc-develでの問題提起、App Storeからの削除</a></li>
<li><a class="reference internal" href="#lgplapp-store" id="toc-entry-3">LGPLへのリライセンス、App Storeへの復活</a></li>
<li><a class="reference internal" href="#section-1" id="toc-entry-4">その他の疑問</a><ul>
<li><a class="reference internal" href="#ioslgpllgpl" id="toc-entry-5">iOSアプリでLGPLライセンスのライブラリを使用した場合、アプリのライセンスもLGPLになる?</a></li>
<li><a class="reference internal" href="#sparrow-for-ioslgpl" id="toc-entry-6">Sparrow for iOSはLGPLに違反していないのか</a></li>
<li><a class="reference internal" href="#lgpl" id="toc-entry-7">LGPLライブラリを使用しているプログラムは、リバースエンジニアリングを許可しなければならないのでは?</a></li>
<li><a class="reference internal" href="#vlcffmpeg" id="toc-entry-8">VLCはffmpegを使用しているのではないのか</a></li>
</ul>
</li>
<li><a class="reference internal" href="#section-2" id="toc-entry-9">結論</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-10">リンク</a><ul>
<li><a class="reference internal" href="#section-4" id="toc-entry-11">ライセンス関連</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-12">参考記事(時系列)</a></li>
</ul>
</li>
</ul>
</div>
<p>主に仕事で、ffmpegやVLCのような(L)GPLのもとに配布されているプログラムを利用することが、
ちょくちょくあるのですが、GPLプログラムはApp Storeで配布できないという話をツイッターで
みかけたのをきっかけに、そのあたりどうなっているのか調べてみました。</p>
<p>以下の話の大前提として、(L)GPLは、著作権に基くライセンスであることに注意する必要があります。
つまり、ライセンスに違反するような形での頒布を行ったとしても、
著作権者がその行為を容認するならば、問題とはならない可能性があるということです。
また、当然ながら、わたしは法律の専門家ではありませんし、そうであろうとなかろうと、
法的なリスクを伴う判断は法律家に相談した上で行ってください。</p>
<div class="section" id="gnu-goapp-store">
<h2><a class="toc-backref" href="#toc-entry-1">ことの発端、GNU GoがApp Storeから消えた</a></h2>
<p>2010年5月、FSFは、GPLv2の元に配布されている <a class="reference external" href="http://www.gnu.org/software/gnugo/gnugo.html">GNU Go</a> という囲碁ソフトをiPhone向けに
移植したプログラムがApp Storeを通じて頒布されている状況は、ライセンス違反であるという <a class="reference external" href="http://www.fsf.org/news/2010-05-app-store-compliance">声明を出しました</a> 。
<sup id="sf-lgpl_and_appstore-1-back"><a href="#sf-lgpl_and_appstore-1" class="simple-footnote" title="iPhone版の作者自身もGPLに違反していたようですが、FSFの記事はAppleにフォーカスを当てており、作者自身によるGPL違反の詳細については触れられていません。">1</a></sup>
その主張によれば、 iTunes Storeのサービス規約には、GPLv2 第6節に違反するような内容が含まれています。
<sup id="sf-lgpl_and_appstore-2-back"><a href="#sf-lgpl_and_appstore-2" class="simple-footnote" title="iTunes Storeサービス規約は、2010年当時から現在にかけて、幾度となく改訂を繰り返しており、その中には、 「オープンソースコンポーネントの使用に関する使用許諾条件」について言及している文が追加されていたり、 5個のデバイスまでしかアプリをインストールできないという部分が削除されるなど、興味深い部分も含まれて いますが、制限があるという基本的な状況は現在でも同じものと思われます。">2</a></sup>
FSFは、App Storeのサービス規約修正を要求したようですが、Appleはそれに答えるかわりに、GNU Goをストアから削除しました。</p>
<p><a class="reference external" href="http://www.fsf.org/blogs/licensing/more-about-the-app-store-gpl-enforcement">FSFがとくに問題にしている</a> 第6節は、次の通りです。</p>
<blockquote>
<ol class="arabic simple" start="6">
<li>Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.</li>
</ol>
<ol class="arabic simple" start="6">
<li>あなたが『プログラム』(または『プログラム』を基にした著作物全般)を再頒 布するたびに、その受領者は元々のライセンス許可者から、この契約書で指定 された条件と制約の下で『プログラム』を複製や頒布、あるいは改変する許可 を自動的に得るものとする。あなたは、受領者がここで認められた権利を行使 することに関してこれ以上他のいかなる制限も課してはならない。あなたには、 第三者がこの契約書に従うことを強制する責任はない。</li>
</ol>
</blockquote>
<p>いまの文脈では、「あなた」をApple、「受領者」をApple Storeのユーザーと読み替えて解釈すればいいでしょう。</p>
<p>一方、iTunes Storeのサービス規約には以下のような文が含まれています。</p>
<blockquote>
<ol class="lowerroman simple">
<li>Your use of the Products is conditioned upon your prior acceptance of the terms of this Agreement.</li>
<li>You shall be authorized to use the Products only for personal, noncommercial use.</li>
<li>You shall be authorized to use the Products on five Apple-authorized devices at any time, except in the case of Movie Rentals, as described below.</li>
</ol>
<p>(当時のサービス規約の日本語版が入手できなかったので英文のみ)</p>
</blockquote>
<p>このように、アプリを使うにあたって、iTunesのサービス規約に同意しなければならないことや、個人使用に限ること、5個のデバイスまででしか使用を許可されないという、GPLで規定された以上の制限を加えています。さらに、Apple Storeでは、サードパーティーとユーザーが個別のEULAを結ぶことも許していますが、これらの条件は、サードパーティーとユーザー間の契約に追加される形で作用することも明記されていました。
<sup id="sf-lgpl_and_appstore-3-back"><a href="#sf-lgpl_and_appstore-3" class="simple-footnote" title="このあたりも現在の規約では微妙に変わってたりしますが、Appleの利用ルールがユーザーに強制されることは変わっていないと思われます。">3</a></sup>
したがって、App Storeを通じてGPLプログラムをユーザーに配布することは、GPLに違反しているということになるわけです。</p>
<p>たとえば一例として、元々GPLプログラムは、個人利用だろうが商用だろうが、関係なく頒布することができるわけですが、
上記のuseという言葉に頒布という意味が含まれていると解釈すると、App Storeを通じて頒布された時点で、
個人利用での頒布しかできなくなってしまう、といったことが問題になってくるのだと思います。
(アーキテクチャ的にデバイス間での自由なアプリのコピーが可能だったとして)。</p>
<p>ただ、個人的には、GPLが、複製、頒布、改変についてのみ規定しているのに対して、Appleの規約には、頒布、配布といった
言葉は、ライセンスアプリケーション・エンドユーザ使用許諾契約<sup id="sf-lgpl_and_appstore-4-back"><a href="#sf-lgpl_and_appstore-4" class="simple-footnote" title="これは、現在の規約では、サードパーティーライセンスで置き換え可能">4</a></sup>を除いて出てこず、利用ルール(Usage rules)の利用(Usage)という言葉に頒布・配布といった意味が含まれないとすると、お互いに衝突する部分はないという解釈もありえるのではないか、という印象を持ちました。</p>
</div>
<div class="section" id="vlc-develapp-store">
<h2><a class="toc-backref" href="#toc-entry-2">vlc-develでの問題提起、App Storeからの削除</a></h2>
<p>GNU Goの一件から5ヶ月後の2010年10月、VLCの開発者メーリングリストで、主要開発者
<a class="reference external" href="https://mailman.videolan.org/pipermail/vlc-devel/2010-October/076868.html">Rémi Denis-Courmon</a>
<sup id="sf-lgpl_and_appstore-5-back"><a href="#sf-lgpl_and_appstore-5" class="simple-footnote" title="実際には、この投稿より前から、開発者間での議論があり、その過程でどうもいざこざがあったらしく、Denis-CourmonのRSS feedがVideoLanのサイトからはずされるという出来事がありました。もはや当事者間で話しあってもどうにもならないと思ったのか、メーリングリストに自分の主張を展開するという手に出たようです。">5</a></sup>
の問題提起に端を発する議論が行われていました。</p>
<p><a class="reference external" href="http://www.videolan.org/vlc/index.html">VLC</a> は、Linux,Windows,Mac等各種プラットフォームで使うことのできる、動画プレイヤーです。
VLCの開発プロジェクトは、VideoLanというNPOによって運営されており、デスクトップ版は、GNU GPLライセンスの元に配布されています。
また、このプロジェクトの特徴として、開発者が著作権をVideoLANに移譲するということはせず、各自がそのまま保持し続ける形で運営されていることが挙げられます(これと対照的に、GNUのリリースしているソフトウェアでは、著作権はFSFに移譲されます)。</p>
<p>さて、このVLCのiOS版が、2010年の9月にリリースされました。リリースしたのは、VideoLANとは無関係なApplidiumという会社ですが、開発に際してはVLCコミュニティの協力もあったようで、VLC開発者たちからは認知されていたようです。GNU Goの件があった後でしたので、iOS版に協力していた、べつの主要開発者であるJean-Baptiste Kempfにも、iOS版がグレーゾーンであるという認識はあったようです。</p>
<p>iOS版のリリース後、Denis-Courmontは、VLCの著作権者の一人としての権限を行使して、Applidiumによる著作権侵害をAppleに訴えました。
この裏には,自由ソフトウェア主義的な思想を持っているDenis-Courmontと、自由ソフトウェア的な考えかたにあまり拘らない他の開発者の対立という構図があったようです。彼のこの行動は、 <a class="reference external" href="http://www.fsf.org/blogs/licensing/vlc-enforcement">FSFからも支持されました</a> 。
ほどなくして、VLC for iOSは、いったん <a class="reference external" href="http://applidium.com/en/news/apple_pulled_vlc_off_the_appstore/">App Storeから姿を消しました</a> 。</p>
</div>
<div class="section" id="lgplapp-store">
<h2><a class="toc-backref" href="#toc-entry-3">LGPLへのリライセンス、App Storeへの復活</a></h2>
<p>その後、どういう議論があってそうなったのかは不明ですが、Kempの主導で、エンジン部分であるlibVLCを含む主要なコードのライセンスをLGPLに変更するという方向に動きはじめました。
GNUプロジェクトのようにひとつの組織に著作権が移譲されているのであれば、プロジェクトのライセンスを変更するのは、やろうと思えば可能かもしれません。しかし、VLCの著作権は、100人を越える開発者に分散されているので、一筋縄ではいきません。すべての著作権者からライセンス変更の許諾を得る必要があるからです。その難行を <a class="reference external" href="http://lwn.net/Articles/525718/">Kempはやりとげました</a> 。リライセンスに <a class="reference external" href="https://mailman.videolan.org/pipermail/vlc-devel/2011-January/078156.html">反対の立場</a> だったDenis-Courmontも、最終的には認める方向で落ち着いたようです。リライセンスが完了すると、いま現在でもApp Storeに見られるように、見事VLC for iOSは、App Storeへの復活を果たしました。</p>
<p>しかしながら、LGPLv2.1の文面とAppleの規約を見比べてみても、果たしてLGPLになったことによって、元々の問題が解消されたのかどうか、あまりはっきりしません。実際、VideoLAN自身、これによってApp Storeで利用可能になるのか <a class="reference external" href="http://www.videolan.org/press/lgpl-modules.html">不明である</a> と言っていますし、AGPL著者であるBradley M. Kuhnなどは、LGPLであっても <a class="reference external" href="http://ebb.org/bkuhn/blog/2012/11/22/vlc-lgpl.html">Apple規約との非互換性は残っている</a> と述べています。私見では、LGPLになったことによって問題が解消されてApp Storeに復帰できたというよりは、開発者間でのコンセンサスが取れて、だれもApplidiumに対して著作権を行使する人間がいなくなったので、表面上問題が解消されたように見えているというのが、ほんとうのところに近いのではないかと思っています。冒頭に述べたように、著作権者が問題にしなければ、(L)GPLに違反していようがしていまいが、関係ないのです。</p>
</div>
<div class="section" id="section-1">
<h2><a class="toc-backref" href="#toc-entry-4">その他の疑問</a></h2>
<div class="section" id="ioslgpllgpl">
<h3><a class="toc-backref" href="#toc-entry-5">iOSアプリでLGPLライセンスのライブラリを使用した場合、アプリのライセンスもLGPLになる?</a></h3>
<p>iOSアプリにはダイナミックリンクの仕組みがなく、静的リンクするしかないので、リンクしたアプリ本体もLGPLで
配布しなければならないのではないかという疑問です。今回ちゃんと調べるまで知らなかったのですが、静的リンクでも、
再リンクが可能なようにオブジェクトファイル一式を配布すれば、問題ありません。</p>
</div>
<div class="section" id="sparrow-for-ioslgpl">
<h3><a class="toc-backref" href="#toc-entry-6">Sparrow for iOSはLGPLに違反していないのか</a></h3>
<p>前述のテクニックを使用して、App Storeで、LGPLライブラリを使用しつつ、アプリ自体はクローズドソースのまま配布しているのが、
<a class="reference external" href="http://www.sparrowmailapp.com/lgpl.php">Sparrow</a> です。
ただし、前述の議論から、LGPLであってもAppleの規約と互換性があるのかは不明ですので、違反しているのか、していないのか、
わたしにはわかりません。この手法については、AppleのDeveloper Programに入会して、
年会費を払わないと実機でアプリを実行できない点が問題になるのではないかという指摘もあります。</p>
</div>
<div class="section" id="lgpl">
<h3><a class="toc-backref" href="#toc-entry-7">LGPLライブラリを使用しているプログラムは、リバースエンジニアリングを許可しなければならないのでは?</a></h3>
<p>Appleの規約で、製品のリバースエンジニアリングを禁止しているならば、LGPLと競合するのではないかという疑問です。
VLCの問題が出てしばらくした後のライセンス更新で、</p>
<blockquote>
お客様は、ライセンスアプリケーション、そのアップグレード、またはそれらの一部について、複製 (本使用許諾および本利用ルールで明示的に認められている場合を除きます)、逆コンパイル、リバースエンジニアリング、逆アセンブル、ソースコードの解明 の試み、改変、または二次的著作物の創作を行うことはできません(但し、上記の制約が、適用法令により禁止される場合、または、<strong>ライセンスアプリケーショ ンに含まれるオープンソースコンポーネントの使用に関する使用許諾条件により許容される場合にはこの限りではありません</strong>)。</blockquote>
<p>の強調部分が追加されたりしているので、すくなくともこの部分については問題にならないのではないかと思います。</p>
</div>
<div class="section" id="vlcffmpeg">
<h3><a class="toc-backref" href="#toc-entry-8">VLCはffmpegを使用しているのではないのか</a></h3>
<p>VLCは、一部GPLライセンスで配布されているffmpegから派生したコードも使用しているのですが、
よくプロジェクト外部の広範囲の人にまで許可を取ることができたなあという部分が、個人的には気になってます。
VLCの開発者コミュニティとffmpegの開発者コミュニティって仲良しだったりするんでしょうかね。
VLC for iOSに含まれる x264.cとかみると、先頭部分のライセンス表記はGPLのままだったりするんすが、
これは単なる修正漏れですかね(x264自体はVideoLANの管理課にあるプロジェクトのようなので、問題ないと思いますが)。</p>
</div>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-9">結論</a></h2>
<p>で、けっきょくのところ、(L)GPLのコードは、iOSで使えるのか、使えないのかというところですが、
LGPLについては、SparrowというLGPLライブラリを使用しながら、クローズドのまま公開され続けている実例があります。
また、すくなくともVLCKitについては、VideoLANとしてApple Storeで頒布しても問題ないという立場でリリースしている
もののはずなので、互換性がないと言って文句を言ってきたりはしないんじゃないでしょうか
(とはいえ、著作権がVideoLANにないのは、前述の通りです)。</p>
<p>しかしながら、いまのところはっきりとした結論が出ているとは言えない状況なので、著作権者に
文句を言われた場合には、アプリを取り下げざるを得なくなるかもしれません。</p>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-10">リンク</a></h2>
<div class="section" id="section-4">
<h3><a class="toc-backref" href="#toc-entry-11">ライセンス関連</a></h3>
<dl class="docutils">
<dt>GNU General Public License, version 2</dt>
<dd><a class="reference external" href="http://www.gnu.org/licenses/gpl-2.0.html">http://www.gnu.org/licenses/gpl-2.0.html</a></dd>
<dt>GNU 一般公衆利用許諾契約書</dt>
<dd><a class="reference external" href="http://www.opensource.jp/gpl/gpl.ja.html">http://www.opensource.jp/gpl/gpl.ja.html</a></dd>
<dt>GNU Lesser General Public License, version 2.1</dt>
<dd><a class="reference external" href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html</a></dd>
<dt>GNU 劣等一般公衆利許諾契約書</dt>
<dd><a class="reference external" href="http://www.opensource.gr.jp/lesser/lgpl.ja.html">http://www.opensource.gr.jp/lesser/lgpl.ja.html</a></dd>
<dt>iTUNES STORE - TERMS AND CONDITIONS</dt>
<dd><a class="reference external" href="http://www.apple.com/legal/internet-services/itunes/us/terms.html">http://www.apple.com/legal/internet-services/itunes/us/terms.html</a></dd>
<dt>iTUNES STORE - サービス規約</dt>
<dd><a class="reference external" href="http://www.apple.com/legal/internet-services/itunes/jp/terms.html">http://www.apple.com/legal/internet-services/itunes/jp/terms.html</a></dd>
</dl>
</div>
<div class="section" id="section-5">
<h3><a class="toc-backref" href="#toc-entry-12">参考記事(時系列)</a></h3>
<dl class="docutils">
<dt>Which open source licenses are compatible with the Apple's iPhone and its official App Store ? [closed] (2009/01/20)</dt>
<dd><a class="reference external" href="http://stackoverflow.com/questions/459833/which-open-source-licenses-are-compatible-with-the-apples-iphone-and-its-offici">http://stackoverflow.com/questions/459833/which-open-source-licenses-are-compatible-with-the-apples-iphone-and-its-offici</a></dd>
<dt>Compatibility between the iPhone App Store and the LGPL (2009/08/24)</dt>
<dd><a class="reference external" href="http://multinc.com/2009/08/24/compatibility-between-the-iphone-app-store-and-the-lgpl/">http://multinc.com/2009/08/24/compatibility-between-the-iphone-app-store-and-the-lgpl/</a></dd>
<dt>GPL Enforcement in Apple's App Store (2010/05/25)</dt>
<dd><a class="reference external" href="http://www.fsf.org/news/2010-05-app-store-compliance">http://www.fsf.org/news/2010-05-app-store-compliance</a></dd>
<dt>More about the App Store GPL Enforcement (2010/05/26)</dt>
<dd><a class="reference external" href="http://www.fsf.org/blogs/licensing/more-about-the-app-store-gpl-enforcement">http://www.fsf.org/blogs/licensing/more-about-the-app-store-gpl-enforcement</a></dd>
<dt>[vlc-devel] Apple AppStore infringing VLC media player license (2010/10/26)</dt>
<dd><a class="reference external" href="https://mailman.videolan.org/pipermail/vlc-devel/2010-October/076868.html">https://mailman.videolan.org/pipermail/vlc-devel/2010-October/076868.html</a></dd>
<dt>VLC developer takes a stand against DRM enforcement in Apple's App Store (2010/10/29)</dt>
<dd><a class="reference external" href="http://www.fsf.org/blogs/licensing/vlc-enforcement">http://www.fsf.org/blogs/licensing/vlc-enforcement</a></dd>
<dt>The VLC-iOS license dispute and how it could spread to Android (2010/11/02)</dt>
<dd><a class="reference external" href="http://arstechnica.com/apple/2010/11/the-vlc-ios-license-dispute-and-how-it-could-spread-to-android/">http://arstechnica.com/apple/2010/11/the-vlc-ios-license-dispute-and-how-it-could-spread-to-android/</a></dd>
<dt>[vlc-devel] FSF position on GPLv2 & current App Store terms (2010/11/02)</dt>
<dd><a class="reference external" href="https://mailman.videolan.org/pipermail/vlc-devel/2010-November/077027.html">https://mailman.videolan.org/pipermail/vlc-devel/2010-November/077027.html</a></dd>
<dt>VLC for iOS likely be pulled from App Store because of incompatibility with source code GPL (2010/11/02)</dt>
<dd><a class="reference external" href="http://www.geek.com/apple/vlc-for-ios-likely-be-pulled-from-app-store-because-of-incompatibility-with-source-code-gpl-1292666/">http://www.geek.com/apple/vlc-for-ios-likely-be-pulled-from-app-store-because-of-incompatibility-with-source-code-gpl-1292666/</a></dd>
<dt>Apple pulled VLC off the AppStore (2011のどこか)</dt>
<dd><a class="reference external" href="http://applidium.com/en/news/apple_pulled_vlc_off_the_appstore/">http://applidium.com/en/news/apple_pulled_vlc_off_the_appstore/</a></dd>
<dt>No GPL Apps for Apple's App Store (2011/01/08)</dt>
<dd><a class="reference external" href="http://www.zdnet.com/blog/open-source/no-gpl-apps-for-apples-app-store/8046">http://www.zdnet.com/blog/open-source/no-gpl-apps-for-apples-app-store/8046</a></dd>
<dt>[vlc-devel] update on AppStore situation please (2011/01/10)</dt>
<dd><a class="reference external" href="https://mailman.videolan.org/pipermail/vlc-devel/2011-January/078046.html">https://mailman.videolan.org/pipermail/vlc-devel/2011-January/078046.html</a></dd>
<dt>The GPL, the App Store, and you (2011/09/01)</dt>
<dd><a class="reference external" href="http://www.tuaw.com/2011/01/09/the-gpl-the-app-store-and-you/">http://www.tuaw.com/2011/01/09/the-gpl-the-app-store-and-you/</a></dd>
<dt>Changing the VLC engine license to LGPL (2011/09/07)</dt>
<dd><a class="reference external" href="http://www.videolan.org/press/lgpl.html">http://www.videolan.org/press/lgpl.html</a></dd>
<dt>[vlc-devel] LGPL and VLC (2011/10/04)</dt>
<dd><a class="reference external" href="https://mailman.videolan.org/pipermail/vlc-devel/2011-October/081869.html">https://mailman.videolan.org/pipermail/vlc-devel/2011-October/081869.html</a></dd>
<dt>Why (not) to relicense VLC under LGPL? (2012のどこか)</dt>
<dd><a class="reference external" href="http://www.remlab.net/op/vlc-lgpl.shtml">http://www.remlab.net/op/vlc-lgpl.shtml</a></dd>
<dt>Apple don't allow any GPL software on iOS. (2012/01/21)</dt>
<dd><a class="reference external" href="https://news.ycombinator.com/item?id=3488833">https://news.ycombinator.com/item?id=3488833</a></dd>
<dt>VLC playback modules relicensed to LGPL (2012/11/20)</dt>
<dd><a class="reference external" href="http://www.videolan.org/press/lgpl-modules.html">http://www.videolan.org/press/lgpl-modules.html</a></dd>
<dt>Relicensing VLC from GPL to LGPL (2012/11/21)</dt>
<dd><a class="reference external" href="http://lwn.net/Articles/525718/">http://lwn.net/Articles/525718/</a></dd>
<dt>LGPL is not compatible with IOS (2012/04/18)</dt>
<dd><a class="reference external" href="https://trac.ffmpeg.org/ticket/1229">https://trac.ffmpeg.org/ticket/1229</a></dd>
<dt>How to properly(?) relicense a large open source project - part 1 (2012/11/07)</dt>
<dd><a class="reference external" href="http://www.jbkempf.com/blog/post/2012/How-to-properly-relicense-a-large-open-source-project">http://www.jbkempf.com/blog/post/2012/How-to-properly-relicense-a-large-open-source-project</a></dd>
<dt>VLC re-licensed as LGPL, ready to head back to the App Store (2012/11/15)</dt>
<dd><a class="reference external" href="http://www.geek.com/apple/vlc-re-licensed-as-lgpl-ready-to-head-back-to-the-app-store-1528626/">http://www.geek.com/apple/vlc-re-licensed-as-lgpl-ready-to-head-back-to-the-app-store-1528626/</a></dd>
<dt>Left Wondering Why VideoLan Relicensed Some Code to LGPL (2012/11/22)</dt>
<dd><a class="reference external" href="http://ebb.org/bkuhn/blog/2012/11/22/vlc-lgpl.html">http://ebb.org/bkuhn/blog/2012/11/22/vlc-lgpl.html</a></dd>
<dt>The Problem with Using LGPL v2.1 Code in an iOS App (2013/07/23)</dt>
<dd><a class="reference external" href="http://roadfiresoftware.com/2013/08/the-problem-with-using-lgpl-v2-1-code-in-an-ios-app/">http://roadfiresoftware.com/2013/08/the-problem-with-using-lgpl-v2-1-code-in-an-ios-app/</a></dd>
</dl>
</div>
</div>
<ol class="simple-footnotes"><li id="sf-lgpl_and_appstore-1">iPhone版の作者自身もGPLに違反していたようですが、FSFの記事はAppleにフォーカスを当てており、作者自身によるGPL違反の詳細については触れられていません。 <a href="#sf-lgpl_and_appstore-1-back" class="simple-footnote-back">↩</a></li><li id="sf-lgpl_and_appstore-2">iTunes Storeサービス規約は、2010年当時から現在にかけて、幾度となく改訂を繰り返しており、その中には、
「オープンソースコンポーネントの使用に関する使用許諾条件」について言及している文が追加されていたり、
5個のデバイスまでしかアプリをインストールできないという部分が削除されるなど、興味深い部分も含まれて
いますが、制限があるという基本的な状況は現在でも同じものと思われます。 <a href="#sf-lgpl_and_appstore-2-back" class="simple-footnote-back">↩</a></li><li id="sf-lgpl_and_appstore-3">このあたりも現在の規約では微妙に変わってたりしますが、Appleの利用ルールがユーザーに強制されることは変わっていないと思われます。 <a href="#sf-lgpl_and_appstore-3-back" class="simple-footnote-back">↩</a></li><li id="sf-lgpl_and_appstore-4">これは、現在の規約では、サードパーティーライセンスで置き換え可能 <a href="#sf-lgpl_and_appstore-4-back" class="simple-footnote-back">↩</a></li><li id="sf-lgpl_and_appstore-5">実際には、この投稿より前から、開発者間での議論があり、その過程でどうもいざこざがあったらしく、Denis-CourmonのRSS feedがVideoLanのサイトからはずされるという出来事がありました。もはや当事者間で話しあってもどうにもならないと思ったのか、メーリングリストに自分の主張を展開するという手に出たようです。 <a href="#sf-lgpl_and_appstore-5-back" class="simple-footnote-back">↩</a></li></ol>XMLHttpRequestによるネイティブアプリ-WebView間でのストリーミングデータ送信実験2014-06-17T00:00:00+09:002014-06-17T00:00:00+09:00tai2tag:blog.tai2.net,2014-06-17:/xhr-bulktransfer-ios.html<p class="first last">モバイルアプリ開発で、ネイティブコードから、WebView内のJavaScriptに大量のデータを高速に流し込めると、いろいろとうれしいことが考えられます(たとえば、ネイティブアプリで生成した映像を、非圧縮動画としてWebViewに転送して表示したりできるかもしれません)。これを実現できないか模索してみます。まずは、XMLHttpRequestでのデータ転送を検証してみました。</p>
<p>モバイルアプリ開発で、ネイティブコードから、WebView内のJavaScriptに大量のデータを高速に流し込めると、いろいろとうれしい
ことが考えられます(たとえば、ネイティブアプリで生成した映像を、非圧縮動画としてWebViewに転送して表示したりできるかもしれません)。</p>
<p>これを実現できないか模索してみます。まずは、XMLHttpRequestでのデータ転送を検証してみました。</p>
<div class="section" id="webview">
<h2>WebViewとの通信手段</h2>
<p>モバイルアプリで、ネイティブコードとWebView内のJavaScriptが通信する方法には、以下のような仕組みなどがあります。</p>
<ol class="arabic simple">
<li>XMLHttpRequest</li>
<li>WebSocket</li>
<li><a class="reference external" href="http://www.html5rocks.com/en/tutorials/eventsource/basics/">Server-sent event</a></li>
<li><a class="reference external" href="https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWebView_Class/Reference/Reference.html">stringByEvaluatingJavaScriptFromString</a> (iOS)</li>
<li><a class="reference external" href="http://developer.android.com/reference/android/webkit/WebView.html">addJavascriptInterface</a> (Android)</li>
</ol>
<p>以下、簡単に各手法の特徴を述べます。</p>
<p>1は、HTTPで通信して、文字列の他にバイナリデータの転送もできます。アプリ内にWebサーバーを立てる必要があります。</p>
<p>2は、WebSocketプロトコルで通信します。ヘッダが小さく、リクエスト・レスポンスという制約もないので、HTTPよりも高速な通信が期待できます。任意の形式のデータ転送が可能です。アプリ内にWebSocketサーバーを立てる必要があります。</p>
<p>3は、HTTPで小さいデータをストリーミング配信できますが、データは文字列のみです。高速データ転送には向かないでしょう。</p>
<p>4は、iOSのWebView用APIで、ネイティブ側から、文字列を渡して、JavaScriptコードとして評価させることができます。毎回JavaScirptを評価させるので、データ転送には向かない気がします(試してはいません)。</p>
<p>5は、ネイティブ(Java)で実装したコードをWebViewにエクスポートするメカニズムですが、致命的な脆弱性があるため、Android 4.1以前を対象にしたアプリでは使えません。Android 4.2以降でよければ、もっともお手軽にデータ転送を実現できる仕組みだと思います。ただし、文字列型のデータしか受け渡せません。</p>
<p>単純に映像の転送という意味では、<a class="reference external" href="http://en.wikipedia.org/wiki/HTTP_Live_Streaming">HLS</a> を使って、videoタグで配信することも可能ですが、
H264でのエンコードをアプリ内で行う必要があるため、処理負荷的に、モバイルでリアルタイムで行うのは厳しい気がします。</p>
</div>
<div class="section" id="xmlhttprequest">
<h2>XMLHttpRequestでのストリーミング転送</h2>
<p>iOSで、アプリ内にWebサーバーを立てて、XMLHttpRequestでの転送を行って、どの程度速度が出るか実験してみました。
コードは、githubに置いてあります: <a class="reference external" href="https://github.com/tai2/XHRBulkTransferDemo_iOS">XHRBulkTransferDemo_iOS</a></p>
<img alt="In-app web server to webview" class="align-center" src="https://blog.tai2.net/images/xhr.png">
<p>XMLHttpRequestでのストリーミング転送を行うにあたっては、いくつか考慮すべき点があります。</p>
<p>まず、1回のリクエストでデータ転送を行うと、メモリ上にすべてのデータを蓄積しなければならないため、ストリーミングには向きません。
したがって、データをチャンク化して、複数回のリクエストに分割して転送を行う必要があります。<sup id="sf-xhr-bulktransfer-ios-1-back"><a href="#sf-xhr-bulktransfer-ios-1" class="simple-footnote" title='Gecko(Firefoxのエンジン)では、"moz-chunked-arraybuffer"などの拡張機能を使うことで、1回の転送を分割受信することもできるようなのですが、残念ながらWebKitには、同様の機能がいまのところはないようです。'>1</a></sup></p>
<p>複数回のリクエストに分割した場合、これらを複数のTCPコネクションに分割してしまうと、TCPはスロースタート戦略で転送を行うことや、3-Wayハンドシェイクのオーバーヘッドがあるので、十分な速度がでないと考えられます。幸い、HTTP 1.1にはpersistent connection機能があるので、1つのコネクションに複数のリクエストを詰め込むことができます。XMLHttpRequestでpersistent connectionを使うには、ひとつのXHRインスタンスを使い回せばOKです。サーバーからのレスポンスには、Connection: keep-aliveヘッダを付加する必要があるようです。HTTP 1.1はデフォルトでpersistent connectionだと思っていたのですが、このヘッダを付加しないと毎回接続しなおしになってしまいました。persistent connectionを使うと使わないでは、2倍程度速度に差が出ました。</p>
<p>また、HTTP 1.1では、pipeliningという、レスポンスを待たずにリクエストをいくつも流し込める機能があります。数個から数十個程度まで、レスポンスを待たずにリクエストするということをやってみましたが、転送速度にはあまり差はありませんでした(これについては、正しいやりかたができてたのか、あまり自信がありません)。</p>
</div>
<div class="section" id="section-1">
<h2>結果</h2>
<p>さて、結果ですが、以下のようなことがわかりました。</p>
<ul class="simple">
<li>チャンクサイズを大きくするほど、転送速度が早くなる</li>
<li>第一世代iPad mini実機で、160Mbps程度(チャンクサイズ1MB)。</li>
<li><strong>レスポンスのメモリが開放されずにアプリが落ちてしまう</strong></li>
</ul>
<p>VGA 30fps生RGBで221Mbps必要であることを考えると、もうすこしで実用可能な速度に届きそうなのですが、いろいろ試しているうちに、レスポンスが開放されないという致命的な問題に気付きました。Instrumentsで見ると、レスポンスに使っているArrayBufferオブジェクトが開放されずにそのまま蓄積されていっています。
WebKitのコードを見ると、XMLHttpRequest::openしたときに開放処理(参照カウントのデクリメント)をしているのですが、メモリ上に残っているということは、他の部分からも参照されていて参照カウントが残っているのだと思います。</p>
<p>速度的には、チューニングをすれば、もう少し上げられそうな気がしていますが、この手法は使えなさそうなので、これ以上追求するのはやめました。
もしかすると、Androidでは、また違った結果になるかもしれません。</p>
<p>次は、<a class="reference external" href="https://blog.tai2.net/websocket-bulktransfer-ios.html">WebSocketで同様の実験</a> をしてみようと思っています。</p>
</div>
<ol class="simple-footnotes"><li id="sf-xhr-bulktransfer-ios-1">Gecko(Firefoxのエンジン)では、"moz-chunked-arraybuffer"などの拡張機能を使うことで、1回の転送を分割受信することもできるようなのですが、残念ながらWebKitには、同様の機能がいまのところはないようです。 <a href="#sf-xhr-bulktransfer-ios-1-back" class="simple-footnote-back">↩</a></li></ol>Appleの新言語Swiftが発表されました2014-06-13T00:00:00+09:002014-06-13T00:00:00+09:00tai2tag:blog.tai2.net,2014-06-13:/about-apple-swiftt.html<p class="first last">先日のWWDC基調講演で、Appleの新言語 <a class="reference external" href="https://developer.apple.com/swift/">Swift</a> が発表されました。これは、いままでApple製品用のソフトウェアを開発するときに使われていたObjective-Cを置き換えるものです。発表と同時に言語のドキュメントも公開されて、開発者からも概ね好意的に受けとめられているようです。</p>
<img alt="swift hello world code" class="align-center" src="https://blog.tai2.net/images/swift_hello.png">
<p>先日のWWDC基調講演で、Appleの新言語 <a class="reference external" href="https://developer.apple.com/swift/">Swift</a> が発表されました。
これは、いままでApple製品用のソフトウェアを開発するときに使われていたObjective-Cを置き換えるものです。
発表と同時に言語のドキュメントも公開されて、開発者からも概ね好意的に受けとめられているようです。</p>
<p>おもしろいのは、Apple向けアプリの開発に関わらなさそうなプログラマたちからも、大きな注目を集めて、
みんなが言語仕様の分析などに熱中していることです。
Apple自身、この言語はCocoa<sup id="sf-about-apple-swiftt-1-back"><a href="#sf-about-apple-swiftt-1" class="simple-footnote" title="Appleのアプリケーション開発用フレームワーク">1</a></sup> 向けの言語であると言っているように、主としてiOSやMac OSX用のアプリケーションを書くために使われることになると思います。
もちろん、Objective-CをCocoaと切りはなして使えるように、Swiftをそれ単体で開発に使うこともできるでしょう。
しかし、多くの人は、実際に開発で使うかどうかという興味から離れたところで、Swiftに注目しているように見えます。</p>
<p>これは、Appleの影響力がそれほど大きいということももちろんありますが、
なによりも、Swiftが、注目を引くような現代的な機能をいろいろ詰め込んでおり、言語として興味深いからなのだと思います。</p>
<p>わたし自身は、Swiftのドキュメントは、まだほとんど読んでいません。
次にiOSアプリ開発案件が発生したら、そのときに覚えようと思っています。
幸い、iOS7アプリの開発にも使えるようですので、新規のプロジェクトにはどんどん使っていきましょう。
いまさらiOS6をサポートしても、<a class="reference external" href="http://bylines.news.yahoo.co.jp/takayukifukatsu/20131031-00029328/">ほとんど意味はないでしょう</a>。</p>
<div class="section" id="swift-2">
<h2>Swiftでアプリ開発がどう変わるか</h2>
<div class="section" id="objective-c">
<h3>Objective-Cよりあきらかに良い</h3>
<p>まず、パッと見ただけでも、スッキリしていて、明らかにSwiftはObjective-Cよりも開発しやすそうです。
ソースコードは、見た目が大事です。
じつのところ、Objective-Cには、一部 <a class="reference external" href="http://love-motif.com/article/art_13.shtml">熱狂的な信者</a> がいたりしたようですが、言語それ自体は、
あまり評判のいい言語ではありませんでした。</p>
<p>Objective-Cの評判の悪そうな性質として、わたしが想像するのが、</p>
<ol class="arabic simple">
<li>独自のメソッド呼び出しの記法(角括弧)が、Cの関数呼び出しと混在しているのが気持ち悪い</li>
<li>メソッド名にキーワードも含まれており、withやatなどの前置詞が名前に入るので、全般的に長すぎる</li>
<li>型チェックもあるが、どちらかというと動的な性質が好まれていた(宣言していないメッセージの送信、id型へのメッセージ送信など)</li>
</ol>
<p>といったあたりです。
すくなくとも、1,2は解消されていそうです。
3は、Objective-Cのように型<sup id="sf-about-apple-swiftt-2-back"><a href="#sf-about-apple-swiftt-2" class="simple-footnote" title="型というのは、プログラミングの誤りを、プログラムを実行する前に発見するのに非常に有用な概念です。いずれ記事にするかもしれません。">2</a></sup>を弱めるような使いかたが容易にできるのかどうか把握していないのですが、
ドキュメントをちょっと読むだけでも型安全という面を押し出しているようなので、開発者のマインドも
変わってくるのではないでしょうか。</p>
<p>ところで、基調講演では、Objective-C - C がSwiftとなんだと言ってましたが、むしろObjective-Cのエッセンスもあまり残ってないのでは。</p>
</div>
<div class="section" id="c">
<h3>Cとの互換性</h3>
<p>ひとつ大きな変更点として、C言語との互換性を捨てたというのがあります。
Objective-Cでは、C言語の関数をそのまま呼び出すことができましたし、C関数をObjective-Cコード内で定義する
ことさえできました(Objective-Cは、Cにオブジェクト指向機能を足したものなので)。
これは、C言語で書かれたOSSなどのライブラリをそのまま使えるので、かなり大きな利点でした。
Swiftでは、Objective-Cとの互換性は保たれていますが、C関数をそのまま実行するのは、どうもできなさそうです。
<a class="reference external" href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-XID_13">Cのデータ型自体はある</a> ようなので、Cライブラリを使いたい場合は、Objective-Cでラップ必要があるのかなと想像しています。</p>
<p>いずれにしろ、Cライブラリを使うことが不可能になるということは有り得ないと思うので、ちょっとめんどうにはなるかもしれませんが、問題はないと思います。</p>
<p><strong>※追記</strong> Cライブラリもインポートして使えるという <a class="reference external" href="https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/buildingcocoaapps/index.html">記述がありました</a>。<em>Any Objective-C framework (or C library) that’s accessible as a module can be imported directly into Swift. This includes all of the Objective-C system frameworks—such as Foundation, UIKit, and SpriteKit—as well as common C libraries supplied with the system.</em> この記述だけみると、ユーザー提供のCライブラリはインポートできないようにも読めますが、どうなんでしょうね。</p>
</div>
<div class="section" id="section-1">
<h3>劇的な工数削減はないかも</h3>
<p>心理的にも実際的にも、かなり開発し易くなることは確かですが、とは言え、それで開発工数が半分とか、十分の一になるかと言われると、そうはならない気がしています。そもそも、Objective-Cがそこまでよくはなかったとは言え、どうしようもなく悪いというわけでもありませんでした(C言語と比べれば)。また、わたしが普段やっているような小さい規模の案件だと、プログラミング言語やコーディング作業自体よりは、フレームワークの使いかたで悩んだりハマったりしてる割合が多いような気がします。もっと大きな案件になってくると、プログラミング言語自体の構造化能力が、より効いてくるというのはありそうですが。とはいえ、気持ち良くプログラミングできるのは良いことなので、やはりSwiftはiOS開発者にとって歓迎すべきものです。</p>
</div>
</div>
<ol class="simple-footnotes"><li id="sf-about-apple-swiftt-1">Appleのアプリケーション開発用フレームワーク <a href="#sf-about-apple-swiftt-1-back" class="simple-footnote-back">↩</a></li><li id="sf-about-apple-swiftt-2">型というのは、プログラミングの誤りを、プログラムを実行する前に発見するのに非常に有用な概念です。いずれ記事にするかもしれません。 <a href="#sf-about-apple-swiftt-2-back" class="simple-footnote-back">↩</a></li></ol>Static Site GeneratorのPelicanを使ってみた2014-06-10T00:00:00+09:002014-06-10T00:00:00+09:00tai2tag:blog.tai2.net,2014-06-10:/pelican-impression.html<p class="first last">主にお客さまに向けて、自分がどんなことをやっているのか、考えているのかを知ってもらう目的で、Blogをはじめることにしました。まずは、Blogシステムとして採用した Python製のStatic Site Generatorである <a class="reference external" href="http://docs.getpelican.com/en/">Pelican</a> についての記事です。</p>
<p>主にお客さまに向けて、自分がどんなことをやっているのか、考えているのかを知ってもらう目的で、Blogをはじめることにしました。
まずは、Blogシステムとして採用した Python製のStatic Site Generatorである <a class="reference external" href="http://docs.getpelican.com/en/">Pelican</a> についての記事です。</p>
<div class="section" id="static-site-generator">
<h2>Static Site Generator</h2>
<img alt="Flying pelican" class="align-center" src="https://blog.tai2.net/images/pelican.jpg" style="width: 100%;" />
<p class="credit">Photo by <a class="reference external" href="http://flic.kr/p/hGPnQM">TexasEagla</a></p>
<p>新しいもの好きのWeb製作者の間で、Webサイトの構築用にStatic Site Generatorというものが注目を集めているようです。
GitHub Pagesで使われている <a class="reference external" href="http://jekyllrb.com/">Jekyll</a> や、 <a class="reference external" href="http://octopress.org/">Octpress</a> などが有名です。</p>
<p>WordPressなどの一般的なブログシステムは、ページをロードしたときに、データベースからデータを取り出して、そのたびにHTMLを生成するので、これは動的なシステムです。動的なシステムでは、(キャッシュされていなければ)毎回生成処理が走るので負荷がかかりますし、設置のためにはサーバー側でプログラムを使える必要があります(レンタルサーバーなどでも、PHPの使えるプラン、使えないプランがあると思います)。</p>
<p>Static Site Generatorでは、ローカルでプログラムを動かして、HTML/CSS/JSをファイルとして出力します。
出力されたファイル群をそのままftpなどでアップロードすればいいので、サーバー側には動的な生成システムは不要です。
したがって、運用に必要な条件は動的システムより弱いと言えます。
また、生成処理がサーバー上で走らないので、動的システムよりも負荷が軽いです。</p>
<p>コンテンツを記述する際の形式としては、<a class="reference external" href="http://daringfireball.net/projects/markdown/">MarkDown</a> や <a class="reference external" href="http://docutils.sourceforge.net/rst.html">reST</a> など、最近流行りのファイルフォーマットが多いようです。これらのフォーマットは、XMLなどとは違い、人間がテキストエディタで直接編集することを念頭に置いて設計されており、そのままでも読み書きし易くなっています。Pelicanでは、reSt,Markdown,AsciiDocをサポートしています(reSTを使うとプログラムコードの表示に使えるオプションが豊富なので、わたしはreSTを使っています)。</p>
<p>まあ、いまどきPHPとDBが使えないサーバーもあまりないでしょうから、実際のところ、どれほど利点があるのかはわかりません。
PelicanやJekyllに関して言えば、コマンドラインからの操作が必要で、いまどきの動的CMSのように、インストールスクリプトを走らせて、あとは管理画面から設定すればいいというようなものでもありません。ただ単に使うためにもけっこう知識と手間が必要で、導入コストが比較的高いです(中にはデスクトップアプリとして動くものもあるようなので、そういうのであれば、簡単に使えるかもしれません)。</p>
</div>
<div class="section" id="disqus">
<h2>コメントシステムDisqus</h2>
<p>Pelicanは静的なHTMLを生成しますが、記事へのコメントもサポートしています。
どうやるのかというと、<a class="reference external" href="http://disqus.com/">Disqus</a> という外部のサービスを使います。
こういうサービスがあるのを知らなかったのですが、非常に便利です。
Disqusでアカウントを作成して、pelicanの設定ファイルに1行設定を追加すれば、それで対応は完了です。
記事ごとにコメントが付けられるようになり、ソーシャル共有ボタンの追加や、関連記事表示表示などもやってくれます。</p>
<p>(かなり昔(10年くらい前?)にこんな感じの任意のページにコメントを付けれるプログラムを日本人のだれかが作ってたような記憶があるんですが、
まったく思い出せないし検索しても出てこないのでムズムズしてます…)</p>
</div>
<div class="section" id="section-1">
<h2>使うときの流れ</h2>
<p>Pelicanでコンテンツを更新するときの流れは簡単です。付属のスクリプトが、コマンド一発でサイトへのアップロードまで世話してくれます。</p>
<ol class="arabic simple">
<li>contents/ 以下の元ファイル(reSTやMarkDown)をテキストエディタで編集</li>
<li>付属の make ssh_uploadコマンドで、ファイルを生成&アップロード</li>
</ol>
<p>contents/以下のrstファイルやmdファイルは、自動的に記事として使われます。
アップロード先のサイトやアカウントの設定は、あらかじめ、設定ファイルに記述しておきます。</p>
</div>
<div class="section" id="static-site-generatorpelican-1">
<h2>Static Site GeneratorのPelicanを採用した理由</h2>
<ul class="simple">
<li>なるべくシンプルなシステムで済ませたい(個人的に凝ったblogシステムとか不要)</li>
<li>HTMLのファイルとして出力されるので、なんとなく安心感がある</li>
<li>Pythonが好きだから</li>
<li>makeコマンドを使用するあたり、センスがいい</li>
<li>WordPressなどよりもテーマの記述や拡張が簡単に行えそう</li>
</ul>
<p>また、導入の過程で、Jinja2,pygments,Fabricといった便利そうなPythonライブラリを知れたのも個人的によかったです。
動的なブログシステムでも同じですが、なんとなくインストールして、自分用にカスタマイズしてるだけでもたのしいですよ。</p>
</div>