Unix Philosophy と Node.jsのモジュールの作り方

The Art of UNIX Programming

The Art of UNIX Programming

TL;DR

  • Unix Philosophyにおいては、「一つのことをうまくやり、協調する仕組みを持つ」という事が大事
  • Node.jsのモジュールにおいても同じで、「一つのことをうまくやる、Stream APIで協調する」と良い
  • 「一つのことをうまくやる」にはどうするのが良いのか、ということで substack のモジュール実装例
  • Simple と Easyの違い

ちょっと今回長くて文字が多いので、最初と最後にまとめを用意しました。時間がない方はこれを読むだけでもいいかと。

Unix Philosophy

さてさて、Unix Philosophyという考え方があることを知っているでしょうか。

ソフトウェアにおける普遍的な哲学の一つで、「一つのことをうまくやれ」と略される事が多いです。wikipediaからの説明を借りる

これがUNIXの哲学である。
一つのことを行い、またそれをうまくやるプログラムを書け。
協調して動くプログラムを書け。
標準入出力を扱うプログラムを書け。標準入出力は普遍的インターフェースなのだ。

— M. D. マキルロイ、UNIXの四半世紀

というものです。

これはサマリとして紹介されるものなので、すべての考え方を学びたいのであれば、The Art Of Unixを読むことをオススメします。

この考え方はソフトウェアを設計する際の考え方に留まらず、色んな分野に応用できる非常に重要な考え方です。Simpleを徹底的に追求し、責任範囲を分割して制限する、という考え方は何においても応用できます。

Unix Philosophyの説明をさらにX-Windowsシステムを作ったMike Gancarzが9つのポイントにまとめてます。

  • Small is beautiful.
  • 一つのことをうまくやるプログラムを作る
  • 出来る限り早くプロトタイプを作る
  • 効率よりもポータビリティを選ぶ
  • 単純なテキストファイルにデータを保存する
  • ソフトウェアの効率を開発のアドバンテージに利用する
  • 効率とポータビリティを改善するためにshell scriptを利用する
  • 束縛するUIを避ける
  • すべてのプログラムはフィルタとして振る舞うように作る

Node.jsはこの考え方にかなりインスパイアされています。
Node.jsを作ったRyan Dahl は最後のポイントと非常に似た「すべてのプログラムはプロキシである」という言葉を残してますし、最初の3つのポイントはsubstackがモジュールを作る上で信条としている事です。

そのため、Node.jsのモジュールも原則としてこの考え方に則ると良い、という事が前リーダーであるizaacs から言われています

Unix Philosophyと Node.js

izaacsがまとめたNode.jsのモジュールの書き方を翻訳してみました。かなりUnix Philosophyインスパイアされている事がわかります。

一つのことをうまくやるモジュールを書こう。


古いモジュールに機能を足して複雑化させるよりも新しいモジュールを書こう。


継承よりもcompositionを推奨するモジュールを書こう。


統一的なインタフェースとしてデータを Streams API で扱うモジュールを書こう。


入力元もしくは出力先を意識しなくても扱える(ブラックボックスとして扱う)モジュールを書こう。


あなたが知らないものを学ぶために、あなたが抱えている問題を解決するモジュールを書こう。


小さなモジュールを書こう。素早くイテレーションを回し、容赦なくリファクタリングしよう、勇気を持って書き換えよう。


あなたが必要だと思ったら、即モジュールを書こう、規則に従っていくつかのテストを書こう。


広範囲に渡る仕様は避けよう。


修正したバグにはテストを書こう。


公開するためにモジュールを書こう、たとえそれが個人的な問題解決のためであっても。


ドキュメントをちゃんと書こう、あなたは将来、ドキュメントがあることに感謝するだろう。


=======================================


動くことは完璧を追い求めるよりも良いことである。


集中することは多くの機能があることよりも良いことである。


互換性があることはコードが綺麗である事よりも良いことである。


シンプルであることは何よりも良いことである。

シンプルで一つのことをやるモジュールが良いこと、協調して動作するために統一的なインタフェースがあると良い、ということが書かれています。Stream APIが重要だと言われている理由の一つはここから来ていて、Unixで言う、標準入出力に当たるインタフェースとしてStream APIをデータハンドリングに使うことが推奨されています。

さて、このエントリが公開されたのは2013年で、それ以来、色んなモジュールが影響を受けてきたことは言うまでもありません。

最近だとExpress 4.0がそうです、Express 3.0まででプリセットで用意されていたgeneratorやsession等の機能をモジュールに分割し、シンプルな構造を保つことにしています。また、gruntやyeomanもそうです。Simpleを追い求めて色んなモジュールがプリセットされている状況を作るのではなく、モジュールが分割された状態でユーザーが自分でモジュールを組み込む事を推奨しています。

Stream APIで言えば、gulp は各プラグインがStream APIを通してpipeを使い、協調して動作するように設計されています。
モジュールとアプリケーションでは層が違うので、全てをシンプルに保つのは難しいのですが、ある程度依存性を抑えたシンプルな設計をするという考え方に従っていると思われます。

substack が語るnpmモジュールの作り方

browserifyを作成したsubstackは割りとはじめの方からこの考え方に則ってモジュールを作っています。そんな彼が具体的にどうやってモジュールを書いているのかが公開されています。

実際にSimpleなモジュールを書く上で参考になると思うので翻訳しておきました。

1. 10行くらいのexampleファイルを作ってそこにapiの使い方を最初に書く。



2. exampleファイルによって `require` される index.jsファイル(モジュールの起点となるファイル)を作り、肉付けしてく。


3. index.jsファイルに対して変更を加えていき、example ファイルに対しても変更を追従させる、つまり、変更を反映するために必要に応じてexampleファイルを更新する。



4. exampleが正常に動作するならtest/フォルダにそれをコピーする。


5. npm install tap またはブラウザでもテストする場合は npm install tape を行う。私(substack自身)はシンプルなexampleから適用できるシンプルなテストを好む。


6. example コードの周囲にいくつかのアサーションを追加する。


7. pkginitnpm initを使ってpackage.jsonを生成する。



8. example fileはREADMEにも書く、READMEにあれば、モジュールの使い方を探しやすくなる。


9. READMEの先頭にそのモジュールの宣伝文句、説明文を書き、ライセンスやインストール方法を書いておく。


10. github リポジトリを作る。


11. travisとtestling-ci の設定ファイルを書き、github hookを追加する。



12. git push そして npm publish を行う。

このステップの他にも参照元では巨大なアプリケーションを作りそうになったらなるべく、分割して再利用すること、それが如何にメンテナンスコストを下げられるかが書かれています。exampleコードを10行程度で書く、というある程度の目安があってSimpleを実践する時の参考になると思います。

how I write modules

Simple is not easy

ここまでSimpleさを追求するために色んな事を書いてきました。ここからはSimpleという概念とEasyという概念について説明します。

この前、葉桜JSt_wadaさんがpower-assert作った話をしてくれてて、その中でSimpleとEasyは違うという話をしてくれました。

それの基になった話がシンプルさの必要性についてです。

SimpleとEasyは混同されることが多いです。ただ両者には隔たりがあり、別なレイヤーとして考えた方が設計はうまくいきます。

シンプルさの必要性の話の中で感銘をうけたのは、Simpleは一つの役割を持つ、一つのタスクを実施するといった一次元の考え方であり、客観的である、ということに対してEasyは自分の経験則に近い、とか、自分が抱いている考え方と似ている、といった主観的で相対的な考え方であるという説明です。

つまり客観的に見てSimpleな構造を作ることはできるが、誰にとってもEasyな設計というのはありえないというのが分かります。人によって経験則や考え方が違うため、ある人にとっては簡単でも別な人にとっては簡単ではない可能性があるためです。

Node.jsのweb frameworkによるEasy

ここまでの話はあくまでモジュールの話であってアプリケーションの話ではありません。ちょっと脱線してアプリケーション依りの話でEasyの例を紹介します。

Node.jsのweb frameworkにはfullstackなものと層が薄いものがあるんですが、それぞれEasyのアプローチが異なっています。

層が薄いものとしての代表例は Expressですが、ExpressのアプローチはSimpleを重視して、コアモジュールにはルーティング程度の機能しかなく、Easyのレイヤーは各開発者が自分達でライブラリを取捨選択して実現するというアプローチをとっています。

fullstackのアプローチはこれと全く逆で色んなモジュールやツールがプリセットされているため、学習コストは高くなりますが、一旦経験を積んでしまえば経験則を活かして簡単に作れるというアプローチを取っています。

Expressライクな層の薄いフレームワークとfullstackなフレームワークは根本的に提供するものが異なるので、比較してどっちがいいとか言うものではないです。

層の薄いフレームワークは自分でライブラリを取捨選択し、scaffoldと組み合わせることで、独自のbootstrapを作るのに向いてます、ドメインに特化したフレームワークを作るのであればこっちのほうが良いと言えます。

反対にfullstackフレームワークはその経験則や考え方が近い人達が集まった時に採用すると効果を発揮します、ある程度経験を積んでEasyだと感じる人達がたくさんいれば学習コストも低くて済みます。

ここで言えることは各アプリケーションのレイヤーでは、消費者にとって何がEasyなのかを考えないといけない、一方で各種ライブラリやモジュールに関してはできる限りSimpleでお互いが協調できる仕組みを持っている方が良い、という事が言えます。SimpleとEasyのどちらの層を実現しようとしているのか意識的に設計すると良いでしょう。

まとめ

  • Unix Philosophyにおいては、「一つのことをうまくやり、協調する仕組みを持つ」という事が大事だと言われていました。
  • Node.jsのモジュールにおいても同じで、「一つのことをうまくやる、Stream APIで協調する」というモジュール開発指針が出ています。
  • 「一つのことをうまくやる」にはどうするのが良いのか、ということで substack のモジュール実装例を上げました。
  • Simple と Easyの違い、設計においてはどちらを実現しようとしているのか意識したほうが良いということを説明しました。