Comments

日本で働きます

高校生の時に米国に来てから、気づいたらあっという間に14年も米国に住んでいたのですが、この度しばらく日本で働くことになりました。日本に長く住むようになるのは、高校生の時以来です。当時は大阪に住んでいたのですが、今回は東京に住むことになりました。

東京では色々な勉強会が日々行われているそうなので、面白そうなのがあれば是非教えて下さい。

Comments

バイリンガルとは

バイリンガルになる、というのはどういう意味なのか。バイリンガルとは、”2か国語を母語として話すこと。また、その人“、だそうです。二ヶ国語を話せるだけなのか、読み書きも出来るのか、私は後者だと思っていますが、ここではバイリンガルの定義は前者であるとして書きます。

二ヶ国語を話せるという事は、二つの別々の言語を話せるという事です。例えば、りんご、を表現したい場合に、二つの言語で”りんご”を知っている事になります。日本語と英語の場合であれば、りんご、と、apple、です。そりゃそうだろう、という話ですが、それが、そりゃそうだろう、ではない状況が意外に身近に存在しています。

“明日のランチは、どこに行こう”。この文章は、日本語でしょうか?英語でしょうか?恐らく、日本語なのでしょう。しかし、例えば、この英語を日本語にして下さい、という仕事を請け負っていたとして、その英語の日本語訳として、”明日のランチは、どこに行こう”、をクライアントに提供したとします。あなたは、その文章を日本語である、と主張する事に躊躇はありませんか?ランチは、昼食、とした方が良いかな、と思うかもしれませんし、思わないかもしれません。しかし、lunch、という英語には、昼食、という日本語が存在しています。だからと言って、昼食の事をランチと言って通じない事もありません。

私は先程、こう書きました。

例えば、りんご、を表現したい場合に、二つの言語で”りんご”を知っている事になります。

お昼に食べる食事の事を、日本語ではランチと言い、英語ではlunchと言う。これって、”二つの別々の言語を話せるという事”、なのでしょうか。これに対する解は、それが通じるかどうかではなく、それを表現するのにそれしか知らないのかどうか、で判断出来ます。

昼食の事をランチと言うものの、ランチは昼食である事は多くの人が思いつきます。つまり、二つの別々の言語で、お昼に食べる食事、の事をなんというのか知っているという事です。ではこれはどうでしょう。”あのグループは、男性のみです”。先程は、多くの人が、ランチが昼食であると思いつくと書きましたが、この例ではどうでしょう。グループには、集団、という日本語があると思いついた人はどれくらいいたでしょう。例えば、グループという言葉に対して、集団、という日本語が思いつかなかった場合、これは何を意味するのかというと、なんらかの集まり、という表現をするのに、グループ、という一つの言語における単語しか思いつかなかったという事です。厳密には、グループとgroupは、表記も発音も違いますが、グループがgroupの日本語読みなのは一目瞭然です。つまり、バイリンガルである条件である、”二つの別々の言語を話せるという事”、を満たしません。

“集団”、という単語を知らないという人はいなかったと思います。しかし、思いつかなかったなどの理由で、グループを集団と表現出来ない場合はあると思います。これは、前は知っていた”集団”という単語が、”グループ”という単語に置き換わっているという事です。日本語が母国語で、英語を学んでいる場合において、日本語が英語に置き換わっているのではないか、という例は多々あります。特にソフトウェアの業界には多いと思います。例えば、ソフィスティケート、と聞いて、自分もそれを使っているのに日本語でなんというのか分からない場合は、洗練という単語がソフィスティケートという単語で置き換わっているという事です。

新しい言語を学ぶにあたって、新しい言語を取り入れるのに、現在知っている言語を保持しつつ新しい言語が足される場合と、現在知っている言語が新しい言語で置き換わる場合、の二種類があります。どちらが良い悪いの話ではありませんが、バイリンガルを目指すなら、この事を気にしながら新しい言語を学ぶと良いのではないかと思います。

Comments

銃規制とその背景

先日のSandy Hook Elementary Schoolにおける銃乱射の事件以来、米国では銃の規制について色々議論されている。皆、今回の様な銃による事件は無くなって欲しいと思っているが、どうやってそれを実現させるかの部分が主な議論の対象である。端から見れば、個人による銃の保持を禁止すれば良いだけじゃないのか、と思うと思う。なのに、それに反対する人達が存在しており、全米ライフル協会の副会長が、”銃を持った悪人を止めることができるのは、銃を持った善人だけだ“、と発言したり、銃の規制による解決を望んでいない様に見える。

これに対して、米国ライフル協会は狂っている、米国は馬鹿だ、等という意見をよく見かけるが、銃の規制に関する問題には背景がある。the second amendment (日本語ではアメリカ合衆国憲法修正第2条?) である。日本語のwikipediaにすらthe second amendmentの項目が無いくらい、日本では馴染みがないものなのかもしれないが (外国の憲法等、基本的にはどっちでも良いだろうし) 、米国では、このthe second amendmentによって、国民の武装が憲法で認められている。つまり、個人における銃の保持を禁止すると、憲法に違反するのである。

米国は、国民の自由を非常に重要視する国民が多い国である。国民の自由が奪われるとなると、当然それには反対する。しかし、その自由によって自分の子供が殺されたのも分かっている。だから悩む。

しかし、そもそも何故、米国の憲法は米国の国民が武装する事を認めているのか。そして、国民が武装出来る権利はそんなに大事な権利なのか。それは、自分の子供が殺されても尚守る必要のある権利なのか。何故米国の国民がこの件で悩むのかは、米国の生い立ちや政府の形態を知る必要がある。

米国は、英国から独立した国である。英国から独立した理由は、国民の意見がどうこうではなく、王様が思うがままに行政を行える事が嫌だったから、である。よって、米国は国民の意見が反映される政治形態である、民主主義を採用した。米国は、複数の州から成り立っているおり、連邦政府はそれらの州から成り立っている。国民の意見がどうこうは関係ない、という政治形態が嫌だったので、連邦政府が当時の英国の様な政治を行える可能性に対しても非常に敏感である。よって、各々の州は、連邦政府から独立出来る仕組みになっているし (ご存知の通り、独立した州は今のところない)、軍を持っている。そして、州が軍を持ったり出来る理由こそが、the second amendmentなのである。

何故、州が軍を持つ必要があるのか。米国が、中央政府の様な形態をとらず、連邦政府という形態をとっている理由は、そもそも政府を無条件で信用するような事はしない、という信念から来ている。もし連邦政府が自分達の意思に反した事をするならば、それに反対する。そしてその反対を武力で抑えようとするならば、それと戦う訳である。しかし、戦って勝ったとしても、あなたの州が連邦政府に対して戦ったのは違憲ですから、戦った事に対して罰を与えます、となってしまうなら、連邦政府からはどうあがいても逃げられないという事になる。そして、これそのものが、米国が英国から独立する際に一番厄介だった部分なのである。なので、戦う事そのものが違憲であるべきではない、という考えから、the second amendmentが採用されたのである。

つまり、国民による銃の保持を禁止するという事は、自分達を英国から独立する前の状態に戻す、という事なのである。国民の武装が違憲になると、もうどうあがいても連邦政府から逃れることが出来なくなるのである。だから、米国の国民は悩むのであって、好きな銃を手放したくないとか、子供はどうでも良いとか、そういう理由で銃の規制に反対している訳ではない。だから、子供のため、という理由を掲げていようと、銃の規制を謳うオバマに手放しで賛成出来ない人がいるのである。

Comments

qilin

去年の東京node学園祭からあっという間に一年が経ち、今年も東京node学園祭が11月18日に行われます。それに伴って、nodeに関する記事を書くアドベントカレンダをやっています。この記事はそのひとつとして、qilinについて書こうと思います (mochaのテストを複数のプロセスを使って実行するparallel-mochaについても書こうと思ったのですが、バグを見つけたのでまたの機会にします)。

qilinは、nodeのアプリケーションを複数のプロセスを使って走らせて、プロセスが死んだら再起動させたり、nodeのアプリケーションをダウンタイム無しに更新したり、という機能を提供します。こういうったものはrailsの分野では沢山あり、unicornpumarainbows!などが有名です。これら相応のものがnodeには無いようだったので、作ってみたのがqilinです。

折角なので少しqilinで遊んでみます。以下の様なファイルをapp.jsとして用意します。

app.js
1
2
3
4
5
6
var http = require('http');

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(3000);

次にqilinをインストールします。

app.js
1
$ npm install qilin

早速app.jsをqiliを使って起動させてみましょう。

app.js
1
$ ./node_modules/.bin/qilin -w 3 app.js

以下の様な出力が見えるはずです。

app.js
1
2
3
2012-10-16T05:51:57.891Z - info: Worker[1] is up: 8768
2012-10-16T05:51:57.897Z - info: Worker[3] is up: 8770
2012-10-16T05:51:57.898Z - info: Worker[2] is up: 8769

-wオプションで、ワーカの数を指定しています。ここでは3を指定したので、上記の出力から3つのワーカがスタートしたのが分かります。qilinで起動されたnodeのアプリケーションにおいて、ワーカが死んでしまった場合qilinが自動で死んだワーカを再起動させます。試してみましょう。

app.js
1
2
3
4
5
6
$ pstree | grep node
   | | |   \-+= 08767 atsuya node ./node_modules/.bin/qilin -w 3 app.js
   | | |     |--- 08768 atsuya /Users/atsuya/.nodebrew/node/v0.8.12/bin/node app.js
   | | |     |--- 08769 atsuya /Users/atsuya/.nodebrew/node/v0.8.12/bin/node app.js
   | | |     \--- 08770 atsuya /Users/atsuya/.nodebrew/node/v0.8.12/bin/node app.js
   | |     \--- 08872 atsuya grep node

pstreeを実行すると上記の様な出力が見えます。qilinを起動しているプロセス(8767)がマスタープロセスで、その下の見える8767、8769、8770がワーカです。それでは8768を殺してみましょう。

app.js
1
2
3
4
5
6
7
$ kill 8768
$ pstree | grep node
   | | |   \-+= 08767 atsuya node ./node_modules/.bin/qilin -w 3 app.js
   | | |     |--- 08769 atsuya /Users/atsuya/.nodebrew/node/v0.8.12/bin/node app.js
   | | |     |--- 08770 atsuya /Users/atsuya/.nodebrew/node/v0.8.12/bin/node app.js
   | | |     \--- 08889 atsuya /Users/atsuya/.nodebrew/node/v0.8.12/bin/node app.js
   | |     \--- 08891 atsuya grep node

8768は死にましたが、新しく8889のワーカが起動しているのが分かります。

次に、app.jsを以下のように更新してみましょう。

app.js
1
2
3
4
5
6
var http = require('http');

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World!!!!!!\n');
}).listen(3000);

新しいapp.jsを反映させるには、本来ならばapp.jsを実行しているnodeを殺して、再度app.jsをnodeで起動する必要があります。しかしqilinを使っている場合はその必要はありません。qilinのマスタープロセスにSIGUSR2を送ると、app.jsを再読み込みしてくれます。

実際にSIGUSR2を送る前に、現時点でapp.jsが返すレスポンスを確認しておきましょう。

app.js
1
2
$ curl -X GET http://localhost:3000/
Hello World

古いapp.jsの通り、Hello Worldが返ってきます。ではqilinのマスタープロセスにSIGUSR2を送ってみましょう。

app.js
1
$ kill -sigusr2 8767

そしてどんなレスポンスが返ってくるか試してみましょう。

app.js
1
2
$ curl -X GET http://localhost:3000/
Hello World!!!!!!

新しいapp.jsによるレスポンスが返ってきました。

この例では分かりにくいですが、実はapp.jsで走っているサーバは一度もシャットダウンしていません。qilinは、SIGUSR2を受け取ると、新しくワーカを作成します。その後に今までのワーカを殺すのですが、もしそのワーカがリクエストを処理しているようだったら、その処理が終わるまで待ちます。そしてその処理が終わったら死んでしまいます。つまり、localhost:3000は常に接続可能のまま、app.jsの更新が行える事になります。

それを実現する実装ですが、非常に簡単です。サーバを立ち上げる際にUNIXのソケットを渡しており、全てのワーカが同じソケットをlistenしているのです。そしてSIGUSR2を受け取った際には、そのソケットは閉じず、そのソケットをlistenしているワーカ達のみを殺します。新しいワーカ達は、引き続き同じソケットをlistenし続けるので、localhost:3000への接続が途切れることはありません。そして嬉しいことに、同じソケットを複数のプロセスがlistenしている場合は、OSがリクエストのロードバランスをやってくれます。楽ですね。

unicornも同じ実装をしているのですが、githubberの方が書いたこの記事が非常に上手くまとまっているので参考にしてみて下さい。unicornの実装では、実際にunicornがソケットを作成してそれをワーカに回しているのですが、nodeではclusterモジュールを使うことでこれが非常に簡単に実装出来ます。clusterを使った場合、ワーカがIPとポートの組み合わせに対してlistenした場合、まずマスタープロセスにその旨が伝えられます。もしそのIPとポートの組み合わせでlistenしているワーカがなければ、マスタープロセスはワーカに新しいソケットを渡します。もしそのIPとポートの組み合わせでlistenしているワーカが既にあれば、そのワーカに渡したソケットが渡されます。つまり、同じソケットの受け渡しをclusterモジュールがやってくれます。qilinは、このclusterの性質を利用して、unicornの人程頭を使うことなく、楽して同様の機能を得ています。

ここではqilinの基本的な使い方と実装を紹介しました。詳しい実装に関しては、README.mdやコードを参照してみて下さい。

最後に、宣伝になりますが、共著で書かせて頂いたnodeの本が遂に販売される事になりました。nodeに関して広く色々なトピックをカバーしています。もし興味がありましたら是非お手にとって見て下さい。

結婚できない若者うんぬんの

最近良く、結婚できない若者がうんぬんという話題を耳にします。それらの話で私がよく見る結論は、女性が男性に自身に見合わない収入を求めているが故、というものです。これに関して、chikirinさんが記事を書きました。彼女によると:

この問題はすぐに「女性が男性に高年収を望むが故のミスマッチ」などと言われがちですが、もう一歩踏み込んで考えれば、

・女性が「結婚するために、自分の教育レベルを上げない」ことをやめたこと

・年齢と収入が相関する、高度成長&年功序列時代が終わったこと

が、根本的な理由でしょう。

私たちが理解しておくべきは、「女性が男性に高い経済力を求めるようになったことが、近年、年収の低い男性が結婚しにくくなってきた理由ではない」、ということです。

のようです。私もこれらが、複数あるであろう原因に含まれていると思っていますが、女性が男性に高年収を望むが故のミスマッチが原因ではない、という部分に関してはそうは思いません。

chikirinさんによると:

20代の女性は、紹介所の会員のなかで最も人気のあるグループです。男性にとって年収が大事であるように、女性にとっては年齢が最重要だからです。

最も人気のある彼女らは、彼以外の多くの相手から会うことを求められており、その中には30代半ば、年収が彼の倍近い、という人もいるんでしょう。

これらが真であるようです。しかしこれらが真であるなら、彼女の記事にもある統計で、女性の未婚率のが男性のそれと同じような上がり方をしている理由が分かりません。絶対的な未婚率は男性の方が大きいですが、deltaは男性も女性もそこまで変わりません。そしてもし若い女性とおっさんが上記の理由で結婚しているようであれば、若い女性とおっさんにおける未婚率の上がり方が、それらにおける他の年齢帯に比べて小さくても良いように思います。しかしその様な傾向は見られません。つまり、結婚相談所に登録している20代の女性は最も人気があり、多くの相手から会うことを求められているのかもしれませんが、それが故に結婚をしているようには見えません。

女性が男性に高年収を望むが故のミスマッチに関してですが、これに関してchikirinさんはこう述べています。

私たちが理解しておくべきは、「女性が男性に高い経済力を求めるようになったことが、近年、年収の低い男性が結婚しにくくなってきた理由ではない」、ということです。女性が経済力を全面的に男性に依存していた昔においてさえ、学歴や収入が低い男性は結婚できていたのですから。

これに関しては、私にはそう言えるかどうかすら分かりません。なぜなら、これを真とするのに前提とされている、女性の考え方が昔と当時で変わっていない、というものが真かどうか私には分からないからです。彼女の主張だと、昔と今とでは必要とする収入は同じで、今は女性も働くのだから男性はより少ない収入でも結婚出来るはずである、となります。逆にこれを理由にするならば、近年の女性における学歴の向上に加え、女性が働いてお金を稼ぐようになったので、私でもこれくらい稼ぐのだから結婚相手にはこれくらい稼いで貰いたい、という考えが出てきても不思議ではないと思います (そして私はそれを悪いとは思いません)。

近年における未婚率の上昇が、男女間におけるミスマッチによるものであるとすれば、男女間になんらかのミスマッチがあるのです。それが、女性が男性に高年収を望むが故のミスマッチなのか、男性が女性に昔よりより優れた容姿を望むが故のミスマッチなのか、はたまたまったく別な理由があるのか、少なくとも私には分かりません。しかしそれが、女性が男性に高年収を望むが故のミスマッチではない、とする理由を上記の理由とするのであれば、女性の求めるものが昔と今で変わっていないという前提の真偽が提示されない以上、そうと言えるか分からない、と思います。

恐らく、近年の未婚率の原因が女性が男性に高年収を望むが故のミスマッチという根拠が何なのか分からないものとされるのを不快と感じてらっしゃるが故の記事だとは思うのですが、流石chikirinさん、と感じる久しぶりの記事でした。

Comments

Kindle Paperwhiteにおける日本語

ずっと欲しいと思ってはいたのですがなかなか買っていなかったkindleを、最近やっと買いました。私が今までkindleを買っていなかった一番の理由は、画面が暗くて読みにくそう、というものでした。しかし今回kindle paperwhiteが出たので買ってみました。

全然知らなかったのですが、まずkindleに本を移すのが凄く簡単なのに驚きました。iTunesに毒されすぎているからだとは思うのですが、usbケーブルでkindleを繋ぐと、外付けのストレージとして認識されます。後はそこにあるdocumentsフォルダ以下にファイルをコピーするだけ。便利。

買った本以外で私がkindleで読みたいと思っていたのが、ウェブでの記事でした。普段はinstapaperを使っていて、後で読みたい記事があればread laterのブックマークレットを使っていたのですが、同じような事が出来るchromeのextensionをamazonが出しています (これを書いていて気づいたのですが、chromeだけでなくmacとpcのクライアントもあるみたいで、それにドラッグアンドドロップするだけでwifi経由でkindleにドキュメントを届けてくれるみたいです)。これがあると、気になった記事を見つけた時に、send to kindleをクリックしておけば、後でkindleを使う時に勝手にサーバからその記事を取ってきてくれます。しかもちゃんと読みやすいように編集してくれているので、非常に読みやすくなっています。

次に気になったのは、ちゃんと日本語が読めるのかどうかです。これもあっけなく、なんのハックもする必要がなくそのまま読めます。日本語の記事も、send to kindle経由でそのまま読めます。後、素晴らしいのは、UIも日本語になります。これは、kindleの日本進出が近い気がしてきます。

日本語の記事1
日本語の記事1

日本語の記事2
日本語の記事2

日本語の記事3
日本語の記事3

画面のズーム
画面のズーム

日本語のUI
日本語のUI

Comments

mochaを使ったテストを書く際のあれこれ

nodeのアプリケーションに対するテストをmochaで書いていて、railsのアプリケーションに対するテストをrspecだとこう書くけどこれはmochaではどうすればいいのだろう、というのが幾つかあったので書き留めておきます。今回解説に使うコードは全てgithubにあるので、そちらも参考にして下さい。

今回テストの対象となるアプリケーションは以下の様なものです:

  • socket.ioを使ってユーザ間でメッセージのやり取りが出来る
  • ユーザは、最初にサーバに接続した歳にユーザ名を含んだjsonをサーバに送り、サーバはそのjsonを受け取ったらそのユーザ名をredisに書く
  • それ以降は、ユーザはユーザ名とメッセージを含んだjsonをサーバに送り、サーバはそのjsonを受け取ったら他の全てのユーザにそのjsonを送る

それでは早速解説していきます。

テストファイルの名前

テストを実行するのには、mochaに含まれているテストにならってMakefileを使います。

Makefile link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
REPORTER = nyan
TESTS = $(shell find test -name "*.test.js" -type f)

all: test-all

test-all:
  NODE_ENV=test ./node_modules/mocha/bin/mocha \
      --reporter $(REPORTER) \
      $(TESTS)

one:
  NODE_ENV=test ./node_modules/mocha/bin/mocha \
      --reporter $(REPORTER) \
      ${FILE}

.PHONY: test-all one

走らせるテストファイルをfindを使って集めていますが、*.test.jsにマッチするファイルのみを探すようにしています。これは、カレントディレクトリ以下にある全てのjsファイルを集めたいけどあれとこのディレクトリは省いて、という様な指定が面倒なので、テストの対象となるファイルを.test.jsで終わるようにする事で、テストファイルを集めやすくしています。非常に便利ですし、どのjsファイルがテストファイルかが一目瞭然になります。

全てのテストを走らせるのではなく、特定のファイルのみをテストしたい場合は多々あります。例えばtest/unko.test.jsのみをテストしたい場合は、$ make one FILE=test/unko.test.jsとする事でtest/unko.test.jsのみをテスト出来ます。

helperを使う

rspecだと、require 'spec_helper'とする事で、全てのテストで必要な共通な色々なものをセットアップしてくれます。これは非常に便利ですし、こういうhelperを用意する事でテストが綺麗に書けます。そこでmochaでも同様なhelperを用意します。

helper.js link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var path = require('path')
  , assert = require('assert')
  , should = require('should')
  , config = require('config');

var RedisCleaner = require('./redis-cleaner')
  , redisCleaner = new RedisCleaner(
        config.store.host
      , config.store.port
      , config.store.prefix
    )
  , App = require('./app')
  , app = new App();

global.test = {
    root: path.resolve(__dirname, '../')
  , config: config
  , appUrl: 'http://' + config.app.host + ':' + config.app.port
};

before(function(done) {
  app.start(done);
});

beforeEach(function(done) {
  redisCleaner.clean(done);
});

afterEach(function(done) {
  // put something if you have something
  done();
});

after(function(done) {
  app.shutdown(done);
});

module.exports = {
    assert: assert
  , should: should
};

最初の方でshouldassert等をrequireしています。こうする事で、テストファイルはこのhelperをrequireするだけで良くなり、各々のテストファイルがshouldassert等をrequireしなくて済みます。require('should')の返り値を使いたい場合のために、それらをmodule.exportsでexploseしています。

global.testの部分では、複数のテストで使う変数をグローバル変数として定義しています。global以下にそのままプロパティを足していっても良いのですが、出来るだけグローバルな環境を汚さないように、全てをglobal.test以下に足すようにしています。

私がhelperで一番便利だと思うのは、helperの中でbeforeafterフックを書ける事です。こうする事で、全てのテストが始まる前にやっておきたい事等をここで定義出来ます。今回のテストではredisを使用するので、beforeEachを使ってテストが始まる前にredisを掃除しています。同時に、beforeを使って、テストするアプリケーションも起動させています。

サポートファイルを再利用しやすく書く

テストで使うユーティリティクラス的なものは、複数のテストから使用したい場合が多々あります。そこで、そういうファイルは再利用しやすいように書くようにします。ここでは例として、アプリケーションに接続するユーザ扱うクラスを見てみましょう。

user.js link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var sio = require('socket.io-client')
  , async = require('async')
  , underscore = require('underscore');

function User(url, data) {
  this.url = url;
  this.data = underscore.extend({}, data);
  this.connection = null;
}

User.prototype.connect = function(callback) {
  var self = this;

  self.connection = sio.connect(self.url, {
      'force new connection': true
    , transports: ['websocket']
    , reconnect: false
  });
  self.connection.on('connect', function() {
    var message = { username: self.data.username };
    self.connection.emit('user.connect', message, function(data) {
      callback(null);
    });
  });
};

User.prototype.disconnect = function(callback) {
  this.connection.disconnect();
  callback(null);
};

User.prototype.sendMessage = function(message, callback) {
  this.connection.emit('user.message', message, function(data) {
    callback(null);
  });
};

module.exports = User;

再利用しやすいように、私は関数の集合よりもクラスを書くようにしています。そしてそのクラスが必要とするデータは、出来るだけパラメータとして渡すようにしています。こうする事で、自身以外に対する依存を少なくし、再利用しやすいようにしています。先程のhelperで見たように、global.testに色々な変数を足す事が出来るので、それに依存するように書くことも出来ますが、私は出来るだけ強い結び付きは避けるようにしています。

テストは簡潔に

テストを書く理由は、バグを少なくするためです。複雑なテストを書いてしまったが故に、そのテストそのものにバグがあったりするのは本末転倒です。ですので、出来るだけテストは簡潔に書きたいものです。では実際にアプリケーションに対するテストを見てみましょう。

message.test.js link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
var helper = require('./support/helper')
  , User = require('./support/user')
  , DoneCriteria = require('./support/done-criteria')
  , DataStore = require(global.test.root + '/../lib/data-store');

describe('messaging', function() {
  describe('when a user is connected', function() {
    var dataStore = new DataStore(
          global.test.config.store.host
        , global.test.config.store.port
        , global.test.config.store.prefix
      )
      , me = new User(global.test.appUrl, { username: 'me' })
      , other = new User(global.test.appUrl, { username: 'other' });

    beforeEach(function(done) {
      me.connect(function() {
        other.connect(done);
      });
    });

    afterEach(function(done) {
      me.disconnect(function() {
        other.disconnect(done);
      });
    });

    it('saves the username to data store', function(done) {
      var doneCriteria = new DoneCriteria(['me', 'other'], done);

      dataStore.isUserLoggedIn(me.data.username, function(error, result) {
        result.should.be.true;
        doneCriteria.done('me');
      });
      dataStore.isUserLoggedIn(other.data.username, function(error, result) {
        result.should.be.true;
        doneCriteria.done('other');
      });
    });

    describe('when a user sends a message', function() {
      it('sends the message to everyone except the sender', function(done) {
        var doneCriteria = new DoneCriteria(
                ['me.sent', 'me.received', 'other.sent', 'other.received']
              , done
            )
          , messageMe = {
                from: me.data.username
              , message: 'hello from ' + me.data.username
            }
          , messageOther = {
                from: other.data.username
              , message: 'hello from ' + other.data.username
            };

        me.connection.on('user.messageReceived', function(data) {
          data.from.should.eql(messageOther.from);
          data.message.should.eql(messageOther.message);
          doneCriteria.done('me.received');
        });
        other.connection.on('user.messageReceived', function(data) {
          data.from.should.eql(messageMe.from);
          data.message.should.eql(messageMe.message);
          doneCriteria.done('other.received');
        });

        me.sendMessage(messageMe, function(error) {
          helper.should.not.exist(error);
          doneCriteria.done('me.sent');
        });
        other.sendMessage(messageOther, function(error) {
          helper.should.not.exist(error);
          doneCriteria.done('other.sent');
        });
      });
    });
  });
});

このアプリケーションにおいては、テストしたい事は全てユーザが接続してからの事なので、ユーザの接続はbeforeEachで済ませてしまいます。

28行目から始まるテストでは、接続したユーザのユーザ名がちゃんとredisに書かれているかを確認しています。DoneCriteriaというものを使っていますが、これはasyncでいうuntilをラップしたようなもので、あれとこれが終わったらコールバックを呼ぶ、というものです。コールバックを呼ぶにあたって終了していないといけない条件を文字列の配列で渡しておいて、後々にそれぞれの条件が終わったらそれらが終わった旨をdoneを使って知らせます。DoneCriteriaみたいなものを使う事で、コールバックのピラミッドを防げるので、コードが綺麗に書けるようになります (DoneCriteriaはパッケージにしてnpmにpushする予定です) 。

42行目から始まるテストでは、ユーザが送信したメッセージを他のユーザが受信するかどうかを確認しています。ここでもDoneCriteriaを利用して、出来るだけ簡潔にテストを書いています。本来であれば、ネストしたコールバックが必要になるであろうテストですが、それらはほとんどありません。

まとめ

railsでrspecを使ったテストを書いていて、あーこれはmochaでも使えるなぁ、みたいのが色々あり、今回はそれをどうやって実践出来るかを試してみました。nodeを使ったアプリケーションをテストするにあたっては、非同期なコードをいかに簡潔にテストするか、というのが非常に重要になってきます。今回は色々便利かなと思った事があったので紹介しましたが、こういう書き方もいいよ、みたいなのがあれば是非教えて下さい。

Comments

Sublime Text 2の流行から見るウェブのソフトウェアエンジニア

最近、TextMate以降なかなかOS Xで使えるそれなりに良いテキストエディタが出てきておらず、尚且つTextMate 2も出ないので、Sublime Text 2Chocolatが出てきました。そしてSublime Text 2はそれなりに人気が出てきているそういう感じます。それに関して考えた事がありました。

私は航空宇宙、教育、医療の業界でソフトウェアエンジニアをしてきたのですが、ソフトウェア業界そのものの健全さには毎回惚れぼれしています。オープンソースのライセンスの元に配布されているソフトウェアが普通に大企業で使われていたり、そりあえず最先端なものはオープンソースのライセンスの元、そのコードが手に入る事が珍しくありません。ところが業界が違うとこんな常識は通用しないどころか、そんな考え方をする者を敵とさえ見る様な環境が沢山あります。唯一ソフトウェア業界の外で同じような概念を共有しているのは、アカデミアです。最先端な研究をして、その過程と結果を含んだREADMEの詳しい版みたいな論文を書いて、それがジャーナルや学会を通して公になるのです。素晴らしい (勿論、特許が絡むと論文を出さない場合は多々あるそうですが)。

話は少しそれましたが、ソフトウェア業界はオープンソースのライセンスの元にソフトウェアが配布される事が多いという意味で、非常に健全です。そして多くのソフトウェアエンジニアがその恩恵を受け、時には色々な形でcontributeしたりします。素晴らしい。それはウェブの業界でも同じで、あらゆるフレームワークやライブラリがオープンソースのライセンスの元に配布されています。しかし、Sublime Text 2やChocolatは違います。コードは公開されていません。

ソフトウェアにお金を払うことは問題ありません。コードが公開されていないのです。Sublime Text 2は恐らくウェブのソフトウェアエンジニアに人気が出てきており、色々なところで”乗り換えた”という話を耳にします。ソフトウェア業界は、オープンソースという概念が通用しているから健全なのに、どうしてその健全が故の理由を否定する行動をとるのでしょうか。

私はTextMateを使っていました。その後、Emacsに変え、今はVimに変えて現在に至っています。私がEmacsに変えた理由は、TextMateがオープンソースのライセンスの元に配布されておらず、それを正当化出来る理由が自分には見当たらなかったからです。もしTextMateが、EmacsやVimよりも比べ物にならないくらいの生産性を与えてくれるなら、私はそれが自分がTextMateを使う理由として正当化出来たと思いますが、そうではありませんでした。なので、Sublime Text 2やChocolatに乗り換えた人達は、どういう理由で乗り換えたのだろうと気になります。そして、それが”流行っているから”とか、”良いと聞いたから”、というものなのであれば、ウェブ業界はこの先どうなるんだろうと感じると同時に、もしかしたら今のソフトウェア業界における、オープンソースを支援するような流れそのものも、”流行り”だったりするのかなと心配になります。

オープンソースのライセンスの元に配布されているソフトウェアはかっこいいです。しかしそれがかっこいいのは、そのソフトウェアがオープンソースのライセンスの元に配布されているが故ではなく、そのソフトウェアをオープンソースのライセンスの元で配布するに至った思想がかっこいいのです。

と、Mac OS XのVimでこの記事を書いています ;)

Comments

nyancat reporterがmochaに取り込まれました

追記: どうやらmocha 1.2.0のリリース直前だったそうで、リリースの記事に載りました。

い、今起こった事をありのままに話すぜ…。

mochaのnyancat reporterを昨日作って、vimeoの載せて記事を書きました。今朝ふと思いつきて、そうだtjに見せようと思ってtwitterでvimeoへのリンクを送りました。すると彼に気に入ってもらえたようで、pull requestを送って!、と言われました。そしてpull requestを用意していたところでそれは起こりました…。彼は既に私のrepoを見つけ、mochaに取り込み、コードに修正まで加えていたのです。恐るべし。

tjが最初の私の彼へのtweetをリツイートしたようで、ぎあーもにcodestream作りなよ!、と言われました。なので初めて公のcodestreamを作ってみました。確かに動画を撮るより賢い。

という事で、nyancat reporterはmochaに取り込まれました。ほんと、何が起こるか分かりませんね。そして、自分の何かを見せたいなら、どんどん見せて行くのは良いことだと感じました。

Comments

nyancat reporter for mocha

先日同僚が、nyancatなrspecのformatterを見つけました。早速職場のプロジェクトで使ってみたのですが、ああ、素晴らしい。

なので、nyancatなmochaのreporterを作ってみました。

今のところ、reporterだけを足したりっていうのは出来ないみたいなので、mochaをforkしてそちらに足しました。いずれはnpm installして、.rspec相当 (今のところ多くの場合Makefileかな) のファイルに指定するだけで使えるようになると便利ですね。

誰か綺麗にスクリーンを録画する方法を教えて下さい…。