萌える瞬間英作文バージョン 0.401をリリースしました!

毎年、エイプリルフールはなんか作ろう! と画策しているのですが、今年は「萌える瞬間英作文」というAndroidアプリを作りました。

萌える瞬間英作文

萌える瞬間英作文 - Google Play の Android アプリ

さて、瞬間英作文ってご存知でしょうか?

こちらの英語上達完全マップ●瞬間英作文で触れられている英語学習方法で、簡単な英文を大量に作成することで英語を瞬時に理解・作成できるように訓練する方法です。

基本的な方法は

  1. 日本語の文章を読む
  2. 1で状況をイメージしながら、英文を作成する

となります。慣れないうちは中学1年生レベルの英語ですら、結構疲れるのですが私もこの学習方法をするようになってからTOEICが100点くらい上がったのでそれなりに効果あるんじゃないかと。 ※ 他の勉強もしてたので瞬間英作文だけの効果じゃないとは思いますけど。

この学習を進めるためにこういう本も出てますし

こんな感じのiOSアプリもあります。

ポケット瞬間英作文[中学レベル]

ポケット瞬間英作文[中学レベル]

  • BriarPatch.Co,.Ltd.
  • 教育
  • ¥500

なので、どんどん瞬間英作文を進めていくことができそうな気がするんですが、一つこの手の教材には致命的な欠点が...

というのも、英文が単調すぎたり、不自然すぎてイマイチ状況がイメージし難いんですよね。

例えば例文として出てくるのは

「私は学生です。/I am a student.」

とかなんですが、そんな事を言うシーンがあまりないので、ぱっとイメージが思いつきませんよね? イメージしながら英文を作ることが、この学習方法の最大のポイントなので、これは結構微妙です。

では、これならどうでしょう?

「ジャッジメントですの!/I am a "Judgement".」

f:id:pascal256:20150401021239j:plain

ばっちり、ドヤ顔の少女の顔が即座に浮かびましたよね? 

掲載してある英文の例としては他にも

「もう、何も怖くない/I'm not afraid of anything anymore.」
「俺の妹がこんなに可愛いわけがない/My little sister can't be this cute!」
「そうそう!も~っと私に頼っていいのよ!/Yes, you can count on me more and more!」
「あぁー心がぴょんぴょんするんじゃーっ/Ah^ My Mind is hopping.」
「煩わしい太陽ね。/Good morning.」
「くおえうえーーーるえうおおお/I am Chloe Lemaire.」

こういったイメージしやすく、日常的に使える英文をとりあえず90個くらい作ってみました。

f:id:pascal256:20150401021304p:plain

文法はせいぜい中学2年生くらいで習うものなので、どんどん声に出していきましょう! ただし、英語がわかる人が近くにいるときはちょっと気を付けたほうがいいかも?

なお、英文の追加および間違いの指摘は随時募集中なので、ブログコメントかTwitter、あるいは

github.com

に直接PRいただければと思います。

それでは、Happy Hacking! And let's study English!

Meteorが見せるIsomorphicなDBとリアクティブな開発モデル

最近、Meteorを試して見てます。これはちょっとスゴイ。正直、当時Railsに受けたのと同じ興奮がある。

Meteorの説明は「リアルタイムWebアプリケーションフレームワークMeteorについて」あたりが分かりやすいので、こちら参照。

実は、2012年の公開時から存在は知ってたんだけど、チュートリアル見たくらいで特に興味はありませんでした。 しかし、今は違います。それは主にスマホアプリの開発にかなりの威力を発揮しそうだと気づいたからです。

まず、なんで公開時にあまり興味がなかったかですが、そもそもRailsなど同レイヤーのWebアプリケーションの開発FWとして考えていました。

その場合

  • SPA
  • リアクティブな開発モデル
  • クライアントとサーバを同一コード(JS)で書ける
  • サーバサイドのDBをクライアントから透過的に呼び出せる(IsomorphicなDBモデル)

という特徴はせいぜいリアクティブな開発モデルがちょっと気になるくらいで、他はさほど気になりませんでした。 むしろ、クライアントコードとサーバサイドを同一でってのは初期のASPGWTはじめ多くのFWが目指したものの、そこまで流行らなかった印象だけがありました。 リアクティブプログラミングも気にはなるものの、私が当時書いていたWebアプリはさほどUIがリッチではなかったので、修正したコードが更新ボタン押さなくても反映されるのはスゴイな、という印象止まり。

SPAにはSEOの問題もついて回りますし、ざっくりフルスタック過ぎて密結合なシステムになりそうだなーと、むしろ悪い印象。

というわけで、さほど興味を引くものではなかったのですが、Meteor + PhoneGap/Cordovaと連携して使うことで、クライアントとサーバ(クラウド)のデータ同期というかアクセスモデルを超シンプルにしてくれると気づきました。

そもそも何が問題?

スマホ開発の課題をあげよ、と言われると人によって多くの課題が上がると思いますが、その一つにサーバサイドと連携したデータアクセスがあると考えています。

いろんなケースがあるかとは思いますが、サーバサイドと連携したアプリを書くときは全体として以下の様な構成にするとことが多いかと思います。

f:id:pascal256:20150112112438p:plain

図:モデル1

このモデル1のケースでは以下の様な問題があります。

  • モデルを一致させるためにサーバサイドとクライアントサイドに類似コードが大量のボイラーコードが発生する(+ REST周りのコードが必要)
  • Server DBとClient DBの同期が困難(安定したネットワークではないため不整合の吸収が必須)

クライアントサイドにデータを一切持たず、サーバサイドに直接アクセスすることで、同期の複雑さ等は解決できます。しかし、反面ネットワークに繋がってない時は利用できず、繋がってる時もレスポンスに影響が出るので使い勝手に影響がでます。

実際のシステムモデルはともかくとして、プログラム上はこんな感じでシンプルにアクセスしたい。

f:id:pascal256:20150112112457p:plain

図:モデル2

この場合、クライアントのDBは基本キャッシュになっていて、クライアントDBへの操作を行えば、サーバサイドにも自動的に反映。モデルのメタ情報も何らかの方法でサーバサイドと同期を取っておけばボイルコードが一気に減り、やる気も上がって生産性アップ! となります。

そう、Meteorはこのモデルを実現してくれてるんです。

MeteorにおけるIsomorphicなDBモデル

Meteorがサーバとクライアントを意識しないIsomorphicなDBモデルをどのように実現しているかですが、minimongoというmongodbのJS実装を使っていて、これをクライアントのキャッシュDBとして使っています。

ユーザへのレスポンスは基本的にminimongoの結果をまず返され、それと同時に投げているサーバサイドのリクエストをバックエンドで待つ。

この時、もしサーバサイドのmongodbとクライアントサイドのminimongoの結果が違った場合は、非同期でこっそり書き換えるという仕様。

厳密なデータ一貫性が必要なケースには使えないけれど、リアルタイムなデータを表示するレポート系ツールやチャットやSNSのような場合なら基本問題無いでしょう。

また、Meteorの場合、サーバサイドがnode.jsなので当然のようにモデルが共有されます。

Meteorがサーバサイドもクライアントサイドも同一のコードで書ける、という点は単に同じコードでvalidationロジックが実行出来る、というよりもモデルの共有ができるので、サーバサイドとクライアントサイドで違うビジネスロジック部分だけを意識すれば良いという点が大きいです。

IsomorphicなDBモデルとリアクティブプログラミング

Meteorの特徴はIsomorphicなDBモデルということに加えて、リアクティブプログラミングを採用している点も大きいです。

リアクティブプログラミングはExcelのように宣言的に関係を記述することでデータの変更による再計算を自動化する開発モデル。

GUIだとデータバインディングを利用したMVVMとかがその具体的な方法の認識。

この2つを組み合わせることでMeteorはリアルタイムアプリケーション--- 言い方を変えるとユーザ関連系などサーバサイドの頻繁な更新が伴うアプリケーションを非常にシンプルに書ける。

というのも、モデル1のような通常のシステム構成でchatのようなユーザ間連携があるシステムを作る場合

  1. クライアント側の書き込みのUIからモデルに値を渡す
  2. クライアントのモデルがサーバサイドのREST/IFをコールする
  3. REST/IFから受け取った値をサーバのモデルに渡す
  4. サーバのモデルはDBに値を書き込む
  5. サーバはDBの変更を別のクライアントに呼び出すモデルを実行(WebSocketではなくクライアントがポーリングしてるならクライアントにその実装がいる)
  6. 5で呼び出されたモデルはDBから値を取得してREST/IFに渡す
  7. クライアントはREST/IFから受け取った値をクライアントのモデルに値を渡す
  8. クライアントのモデルはUIに値を渡して変更する

という感じになります。FWによって記述量が変わるところはあっても、1から8の工程を明示的に何かしら記述をしないといけません。これは単純にサーバサイドもクライアントサイドもJSで書いたからといって変わるものではありません。

対してMeteorの場合は

  1. UIからクライアントのDBに書き込むモデルを作る
  2. モデルとクライアントのDBを紐付ける(※ モデルをJSONハードコーディングじゃなくてDBの戻り値にする)
  3. UIとモデルを紐付ける

これだけです。それぞれのステップでの記述量もかなり短くなるのですが、それ以前にステップが8個から3個に減っています。

これが実現できるのはIsomorphicなDBモデルなので、各クライアントは自分たちのローカルDBへの操作だけ意識すれば良く、かつリアクティブなので、データに変更があれば、UIにも自動的に反映されるからです。

この点が開発のシンプルさを激的に改善してくれる部分です。各工程のコストを小さくするのと、工程自体が無くなるのではたとえ1秒で終わることでも人間の意識としては結構違いますしね。

まとめ

では、Meteorは万能で同様の開発モデルを採用していくべきでしょうか? 必ずしもそうではないと思います。 まず、Meteorに限って言えば未成熟な部分もあります。特にモバイル周りはUI周りが弱かったり、Cordovaとの連携にまだ不備があったりとめんどくさい部分もあります。ただ、コレは時間が解決してくれる気もします。

そうではなく、そもそもあまり向いてないケースとしては、トランザクションが厳密なケースと、既存の連携システムが多い場合です。

トランザクションが厳密であれば信頼性の低いクライアントのDBは使えず、結局常にサーバにアクセスする以外ありません。

また、このアプローチは基本的にクライアントに合わせてサーバサイドを作る場合に適したモデルなので、すでに他システムなどでサーバサイドが存在する場合にはかえって面倒になるかもしれません。

ただ、逆に言えば厳密性が重要ではなく、プロトタイプや新規アプリを開発するには非常に向いた方法だと思うので、MBaaSとの連携含めてもうちょっと研究してきたいと思います。

そもそも、AndroidiOSを前提にしたネイティブなMeteorっぽい仕組みのが個人的には嬉しいですし。

それではHappy Hacking!

参考

JavaEEだけどDockerがしたい!- GlassFish on Docker

Java EE Advent Calendar 2014 14日目です。

今年はDockerが大躍進した年だな、と思います。

そこで、このビックウェーブに乗り遅れないように、GlassFishを使って、Dockerベースの環境を作ってみました。

今回利用するコンテナはこちら

Appコンテナ

GlassFishが乗っているサーバです。下記のプロセスが動いています。このコンテナの数を増やす事でスケールアウトが可能。

本体はGlassFishですが、周辺システムとしてFluentdとConsulをインストールしてあります。

Dockerテクニック的に言えば、FuluentdやConsulを入れた状態でいったんイメージを構築して、それを継承する形で、glassfishイメージを作ってるので、wildflytomcatにすげ替えることも、比較的簡単に出来る構成にしてあります。

また、通常、Dockerでは1プロセスしか起動できないので、"run.sh"を作って、Fuluentd, Consul, GlassFishの3つのプロセスを起動させています。この辺りは、もしかたしたらKubernetesとかコンテナを管理するシステムを用いることでもっとシンプルに出来るかもしれないです。

他にも、Dockerらしく作るために気をつけた点としてApplicationを最初からデプロイしています。最初は起動前からデプロイしてある構成を想定してたのですが、アプリケーションのデプロイ時にJDBC Sourceを参照するためエラーになるので、auto-deployにして、起動時にデプロイする方式にしました。

DBコンテナ

App Serverが使うDBコンテナです。GlassFish上のアプリがJDBCを利用しているので、App コンテナより先に起動する必要があります。

Consulコンテナ

サービスディスカバリやオーケストレーションを提供するConsulの中央サーバです。今回の構成ではDNSも兼ねています。

  • Consul server agent

Log集計コンテナ

Fuluentdのログ集計サーバです。今回はファイルに吐いてますが、本番だとmongodbとかasticsearchに連携する形になるかと思われます。

ConsulやFuluentdを入れてる理由は、Dockerを使うユースケースでは古き良きオンプレの静的な構成ではなく、クラウド的な動的な構成が想定されるためです。

起動

コンテナは構築済み&docker-hubに上げてあるので、動作確認はシンプルに下記で実施できます。

git clone https://github.com/koduki/docker-javaee.git
cd docker-javaee
./docker-mng.sh up

これで動作します。"http://localhost:8080/javaee-simple-tester/"にアクセスして頂ければ、アクセスが可能です。

まとめ

こんかいはGlassFishクラスタも組んでませんし、リバースプロクシも入れてないので、高可用構成にはなっていません。

ConsulのDNSを経由してラウンドロビンにアクセスすることは可能ですが、HA ProxyとしてConsulを使っても潰れないのかは調査不足。

一方、Consulのイベント通知機能を使えば、Consulクラスタになった時点でGlassFishクラスタに入れるのは簡単そうです。

ただ、その構成にすべきかは、いまいち思案中。設定もデプロイもDockerレベルで完了しちゃうので、GlassFishクラスタを組むメリットは小さいです。

それよりもLBを入れて、負荷分散するほうがよっぽど重要。ただ、GlassFishは原則スティッキーなアクセスを求めるので、この辺をどう解決するのかが課題。クラスタ組まないとセッションレプリケーションもできないですしね。

単純にセッションを無理やりKVSに詰める仕組みを自作してもJavaEEとの相性が悪いので、なんとかHttpSessionレベルをフックする必要があるので、こと辺りは課題として要調査。

来年はこの構成をベースに、もっとスケールアウト出来る仕組みを考えないとなー。

明日はemagさんの「Arquillian Cubeについて」です。

参考

Excelを使わない技術 - 正しい神の殺し方

ドキュメント作成技術 Advent Calendar 2014 13日目です。

※ なお、本題とは一切関係ありませんが中2力全開気味なのでご注意ください。

Excel、というツールをご存知でしょうか? まあ、知らない人はいないと思います。

それは表計算ツールであり、グラフ描画ツールであり、データーベースであり、ワープロであり、DTPであり、時にはゲームエンジンとなる万能の釜です。

はじめは計算をシンプルにしたいという素朴な願いから生まれたそれは、やがてアレもしたい、コレもしたいという人の願いを叶え続け、万能と成り果てました。 今では、こんなことも出来ます。

Excelすげーですね!

ここまでスゴイ使い方はあまり見ませんが、方眼紙Excelで作った入力フォームとか、方眼紙Excelで作ったUMLとか、あまつさえ方眼紙Excelで作った表とかを見たりメンテナンスして、職人の凄さにむせび泣いた方なら多いかと思います。人はそれらを神Excelといいい、おそれ、あるいは尊びました。

「ネ申 Excel」問題 という論文もありますし、職人ならざる我々もドキュメントを書き、育てることを来年の抱負とするためにも、Excelを使わない技術に関して紹介していきます。

はじめに言葉があった

Advent Calendarを読むような方の期待をいきなり裏切って申し訳ないですが、Wordを使いましょう。文章書きたいなら、ね。

Wordだけではありません。UMLを描きたいならastahを使いましょう、WBSを描きたいならProjectLibreを使いましょう、画像を表示したいならそもそもjpegで良いのです。なんのために貼り付けてあるんですか?

もちろんツールは何でも良いのですが、餅は餅屋です。無理して全部Excelでやるから無駄に保守性が下がるので、適切なツールを使って、それをチーム/社内標準にすれば良いですし、印刷や他社配布ならPDFにプリントしてやれば良いのです。最近のOS使ってるならそのくらい造作も無いことなので、その作業は本当にExcelでするべきか考えてみましょう

どうでも良いですがWordを「言葉 -> コトノハ」と読むと素敵ですね。

されど我は愚者の夢を見る

Excelには適切なツールを使うことでさっくりと消滅してもらえば良いのですが、まだ不満はあります。

それはgitなどのバージョン管理システムとの相性です。

「履歴」という謎のシートやページに編集内容や編集者を記載し、さらに「変更箇所の色を赤で変えておきました」的なコメントを見かける事があります。これはExcelを表として適切に使ってても起こる悲しい問題です。

バイナリの差分管理が適切にできれば良いのですが、あいにく私は良いツールを知らないので、コミットログなんかだけだと更新内容が判断できません。

ソースコードみたいにdiffが出ればいいんですけど。

そう、逆に言えばソースコードみたいにテキストで管理することでdiffを含めて適切に管理できます。テキストファイルで管理することで、Pull Requestで差分管理/レビューできますし、良いことづく目ですね。

テキストで書いた場合は専用ツールで書いた場合に比べて表現力が劣ることが多いですが、後述するツールでかなり補えますし、意外に気にならないものです。

巨人の肩

かのニュートンは言いました。「私がより遠くまで見渡せたとすれば、それは巨人の肩の上に乗ることによってです。」と。先人に倣って、まずは既存のツールを紹介します。

最初の一つは一日目のsky_yさんも紹介されているPandocとMarkdown. Markdownはプレーンテキストで論理構造をいい感じに表現出来る単なる「記法」ですが、githubを初め多くのツールWebサービスが対応してるため、標準化されたWiki記法として人気を博しています。

私も普段書くメモもそうですし、このブログもMarkdown形式で書いています。pandocはMarkdownをHTMLやPDFあるいはWord形式で出力してくれるツールです。 これもかなり便利。以前書いた記事ですが、書きのようにプレゼン資料を作ることも出来ます。

Keynote風のプレゼンテーションをMarkdownで作ってみた - ブログなんだよもん

他にも図形を書くblockdiagとか、サーバの設定を記述するserverspecとか、テスト仕様書を記述するCucumberturnipも便利です。

そして13番目の奴は訪れる

既存ツールも紹介したことなので、個人的にJSONを使った表なんかも作ってるのでちょっと紹介。 Excelを使った表でデータを管理ってのは個人的にというか仕事でよくやります。サーバ一覧とかACL一覧とかパラメータシートとかそういう奴。Excelで作るのが簡単だし、見栄えも悪く無いですしね。

ただ、前述したとおり、まともにdiffが取れないので、更新履歴シートを作ったり、変更箇所を赤色にしたり、ゴミみたいな運用をせざるえません。

更新が噛み合ったらマージも出来ない阿鼻叫喚。マスタドキュメント系なのでこれはかなり不便で、下手をすると変更担当の人に依頼するという謎の運用になります。

そんな時はこんな感じでJSONで記述して、それを埋め込んだHTMLのJavaScriptを使って表として表示することが出来ます。

      "Description":"GlassFish Application Server",
      "Environment":["production"],
      "Network":{
        "Host":["localhost.localdomain"],
        "IP":["enp0s3", "10.0.2.15"]
      },
      "OSName":["Linux dev-ubuntu 3.13.0-32-generic"],

詳細:gitst

f:id:pascal256:20141213221120p:plain

わりと見栄えも悪くないので、個人的には結構お気に入り。

JSONだけ外出しにした方が、そのJSONを解析するツールとかが作りやすくて便利(事実、私もserverspec風のツールを作ってます)ですが、IE以外はセキュリティの問題でローカルファイルは参照禁止なので ローカルで管理が中心の時はgistに書いたみたいにHTMLの中に埋め込む方が良いかと思います。

ちなみに、XMLの方が好きって人はXSLTを使うことも出来ます。なにげにIE, Chrome, Firefox, Sfari, Operaとほぼすべてのブラウザが対応していますしね。 こいつが普及してたらまた違ったのかなー。

幼年期の終わり

Excelは良いツールですが、なんでもExcelでするのは微妙ですし、そもそも現代のソフトウェア技術は残念ながらテキストの方が管理に向いています。

私達は、Excelという揺りかごを飛び立ち、新しい世界へと行く必要があります。ドキュメントを作って印刷するだけの時代は終わりました。現在は、作って、メンテナンスし育てる必要があるので、それに見合った方法を色々模索していかないとなー、と思います。

以上、酔っ払った勢いで、主に見出し的に中2力を出した文章を最後まで読んでいただきありがとうございました。

それでは、Happy Hacking!

Consul with Dockerを手軽に試せる環境を作ってみた

Consulを使ってDockerの名前解決を簡単に実現する - Qiita でConsulを使って、サービスディスカバリをして、DNSの名前解決をする方法を書いたんだけど、検証用の環境を簡単に作れるようにしたのでこっちに公開。

koduki/consul-with-docker-example · GitHub

まあ、基本的に、Qiitaで書いたことをそのまま実行するためのスクリプトとDockerイメージを作っただけだど。

最初はfigで作ってたんだけど、起動のタイミングとかでどうも名前解決が出来ないこととかあって、シンプルにシェルにした。Consulが起動してから、sleepで3秒待つので、その間にクラスタの構築を待つ感じ。

sudo ./docker-mng.sh up # 起動
sudo ./docker-mng.sh  stop # 終了

ってだけの超シンプルシェル。この辺は先達もいるし、本格的に使うなら良いツールはいっぱいあるだろうしね。fig含めて。

あと、Qiitaの記事でも少し触れたけど、おそらくRH7系及びFedoraでDocker起動すると、セキュリティの関係かわからないけど、docker0にフォーワードしたポートはコンテナからはアクセス出来ない。

セキュリティ的には当たり前といえば当たり前の気がするし、かと言ってiptablesを毎回イジるのはさすがに面倒なので、Consulのクライアントになる側ではコンテナをRUNするときにlink名でconsulのサーバIPを抽出して、resolve.confを書き換えるというハックを入れています。

DNS_HOST=consulboot
DNS_IP=`grep ${DNS_HOST} /etc/hosts|awk '{print $1}'`
sed "s/172.17.42.1/${DNS_IP}/g" /etc/resolv.conf  > resolv.conf.tmp
\cp -f resolv.conf.tmp /etc/resolv.conf
rm -f resolv.conf.tmp

ちょっと、特殊なイメージになってしまい、使い回ししづらいけど、致し方なし...

この辺はDockerコンテナをホスティングしてるサービスによってもやり方違いそうだから、本番に上げるときには再度研究かなー。

それでは、Happy Hacking!

Java VM別の簡単なベンチマークをしてみた

最近のJavaは速い。

この言葉は良く聞くけど基本Java1.2とか1.4みたいな古代のバージョンと比較してのこと(まだ動いてそうだが...)

最近のバージョンはどうなんだろう? と気になったのだけど、あんまりVM毎のベンチって出回って無さそうだったので、試しに実施してみた。 本当はベンチマークキットみたいな適切なベンチ項目があれば良いのだけど、良いのが見当たらなかったので、とりあえず自前で適当に。

評価項目は下記の通り。

  • StringBuilderによる文字列結合(100,000,000回)
  • プラス演算子による文字列結合(100,000回)
  • リフレクションによるプロパティアクセス
  • BeanUtilsによるプロパティアクセス

結果はそれぞれ10回呼び出して計測時間の中央値を使ってる。実行した時のマシン状況による差分を多少でも減らすために、一応2回実行してみた。ソースコードはこちら

実行結果は下記の通り。

concat string with StringBuilder(100,000,000) concat string with plus(100,000) replace string with regular expression property access with refrection property access with beanutils
Apple Inc. Java HotSpot(TM) 64-Bit Server VM 1.6.0_65(1回目) 1428.5 12100.5 4434 1133.5|1783
Apple Inc. Java HotSpot(TM) 64-Bit Server VM 1.6.0_65(2回目) 1194.5 10400 3012 1076.5|1726
Oracle Java HotSpot(TM) 64-Bit Server VM 1.7.0_65(1回目) 1166.5 2135 2768.5 2037.5|1785.5
Oracle Java HotSpot(TM) 64-Bit Server VM 1.7.0_65(2回目) 1133 2178 2828.5 2040.5|1754.5
Oracle Java HotSpot(TM) 64-Bit Server VM 1.8.0_11(1回目) 1170 1788 2363 2144.5|1900
Oracle Java HotSpot(TM) 64-Bit Server VM 1.8.0_11(2回目) 1209.5 1779.5 2202.5 2087.5|1942.5

評価項目のせいかもだけど、あまり劇的に性能が変わるところは無さそう。プラス演算子による文字列結合だけやたら1.6で遅いのは、1.6はApple製なことと影響してるのかな? Linuxでも測ってみる必要もあるな。

正規表現と文字列結合は順調にバージョンが上がる毎にパフォーマンスが向上してるみたい。リフレクション周りが少し微妙だけど他の性能を上げるために犠牲になったのかな?

1.8でPermMemoryの使い方とかも変わってるので、メモリとかをもっといっぱい使うようなベンチをちゃんと作らないと面白いデータは取れ無さそう。

まあ、少なくとも遅くなってないことはわかったので、それはそれで収穫か。

それではLet's Happy Hacking!

問い:Java 8のStream APIは業務でどんな時に使うの? 答え:あなたがfor文使いたい時

※ サンプルがJDK7までとJDK8までで意味が変わっていてわかりにくいという指摘があったので、少し直しました。 ※ boxedを使う書き方だと無駄なAutoboxingが走るとの指摘を頂きましたのでmapToObjを利用するように変えました。

Java8の目玉機能の一つにStream APIがあります。

目玉機能だけあって、先日のJava Day Tokyo 2014を含めて色んな所で発表やブログの記事が公開されているので、どんなものかを知ってる人は多いと思います。

Stream APIといえば「".parallel()"と書くだけで並列化してスピードアップ出来る!」という魅惑的なキーワードで紹介されることが多いので、並列化のための仕様だと勘違いされそうですが、そうではありません。 ※ もちろんそういった記事の中をちゃんと読めばそう単純な話じゃないことも分かります。

むしろ、並列化に関しては、Webアプリケーションなど一つの処理が十分に小さく大量にさばくことを期待されるシステムだと積極的に忘れても良い気がします。

もちろん、それでも価値があります。

一般に内部イテレータと呼ばれるもので、Ruby, Python, JavaScript, C#, Scala, PHP, Hakell, LISPと出自である関数型言語系はもちろんのこと、RubyPython、JSといったLL系Javaと類似の用途で使われるC#も似たようなものを持っています。

現在業務で使われやすい言語では概ね対応しているのでその辺に親しみがある人は、ようやく素直に出来るようになったのかという感じでしょう。

一方で、Stream APIでどんな事ができるのか分かったけど、業務のどこで使えば良いのかが分からないという意見もチラチラ聞きます。 実際、私は初めてRubyで内部イテレータを使ったのですが、最初はfor文を使わない文化に戸惑ったものです。

Webの記事とか見ても「for文禁止!」とかセンセーショナルな書き方がされることが多くて、実際その通りと思いはするものの、Java8怖いとかになって普及が阻まれないかなぁとも思うので、今回は「for文禁止」とまで言われるその背景の説明をしたいと思います。

なお、Stream APIそのものの機能や詳細はすでに色々なところで語られているので、今回は省きます。

語彙によって意図を明確にする

Javaに拡張for文(foreach)が入った時にも同じような話をしたのですが、言語やFWの語彙を適切に使うことでコードの意図を明確に出来ます。

これは本質的には適切なクラス名やメソッド名を記載するというのと同じことです。

まずはこいつを見てくれ・・・どう思う?

gist182f1fc4863bf41018df

Java的に遺失呪文(予約語だがコンパイルエラーになる)なgotoを使ったFizzBuzzです。ループ構文が無い古い言語仕様で書く時は今でもこうかく必要がありますね。

一言で言えば「わけがわからないよ

gotoはご存知の通り極めて柔軟な仕様なので、ループという概念に限定されておらず、良く読まないと繰り返しであることが分かりません。そして、汎用性の高い構文なので、もちろん記述が冗長です。

つづいて同様にループを表現する構文であるWile文を使ってみましょう。

gist127ff4b858d89474bc71

さっきより大分見やすくなりましたね。Wileを使うことでループ表現であることが瞬時に理解出来ます。 ただ、これでは1から100までの順次表示であることが表現出来ていません。なので、普通はfor文で書きますよね?

gistfe59ae6d8465a24ac5df

ようやく普通の感じになってきました。落ち着きますね。

ここでfor文の良い所は1から100まで1個ずつインクリメントしてプログラムが処理されることをシンプルに保証したことです。

さて、ここまで読んでJavaのようにfor文が使える言語で、FizzBuzzをgotoやwhileを使って書きたいという方はいらっしゃるでしょうか?

え、書きたいんですか!? もの好きですね。。。 

でも、仕事では迷惑なので書かないでください。

長々と書いてしまいましたが、ここで言いたかったことは、そのプログラム言語が許す限り適切な表現で目的を記述しましょうということです。

殆どの場合、昔からある既存の書き方でも表現出来るでしょうが、新しい表現が出来たということはそれに特化したユースケースが有るということなので、そのケースでは新しい表現を使うことでより抽象度が高く、簡潔で理解しやすいコードになります。これはもちろん今回導入されたStream APIも同様です。

Loopではなくリスト操作

さて、前章は長かった割には完全にStream API関係無い話でした。ここからはそもそもどういうユースケースを想定しているかを話していきたいと思います。

ところで、前章で最後に書いたFIzzBuzzのコードが業務でコードレビューに上がってきたらどうしますか? 私は基本的にNGにします。

なぜなら、ロジックの中で標準出力である"System.out.println"を使ってるので、ファイル出力とかに変えようとしたり出力フォーマットを変えようとしたら、ロジック書き直すかコピーしないといけないし、戻り値がvoidなのでUnitTestも書けません。プロダクトコードとしては非常にイケてない部類に入るでしょう。

なので、普通は仕事で書くならこんな感じに書くと思います。

gist998edee7a06eb26b51f8

直接表示するのではなく、FizzBuzzロジック本体はListを返すことで出力先を容易に切り替えることが出来、テスタビリティも格段と向上しています。

最初のコードだと下記のような思考パターンでコーディングをしていたと思います。

  1. 1から100までの数を繰り返し処理しよう
  2. 15で割りれるなら"FizzBuzz"を、5で割り切れるなら"Buzz"を、3で割り切れるなら"Fizz"を、そうでないならそのままListに追加しよう
  3. Listの中身を全部表示しよう

これがJava 7までの良くやる考え方ですね。ここでパラダイム・シフト。下記のように考えてみましょう。

  1. 1から100までの数を持ったリストを作ろう
  2. 1で作ったリストを15で割りれるなら"FizzBuzz"を、5で割り切れるなら"Buzz"を、3で割り切れるなら"Fizz"を、そうでないならそのままというリストに変換しよう
  3. 2で作ったリストの中身を全部表示しよう

一見同じように見えますが、1と2の意味が少し違います。最初からリストを作ることで「繰り返し処理」からリストの作成・変換といった「リスト操作」に置き換わっています。

これはリストの操作が得意な関数型言語に由来する考え方です。こうして「繰り返し処理」という制御構文を「リスト操作」という関数にしてしまうことで、戻り値のある小さなパーツとして扱いやすくなり、UnixのシェルなどのようなPipes and Filters アーキテクチャを適用出来ます

ようは、この処理を適用して、その結果をこの処理に適用して、それからこの条件で抽出してみたいなちょっとずつ処理を書き足していくやり方ですね。

gist52b41e5ed3d63dcd3997

Java7で書くとこんな感じですね。こうすることで

入力となるリスト -> 変換処理 -> 出力となるリスト

という形にすることが出来、先ほど言ったような恩恵を得ることが出来ます。ただ、あまり簡潔ではないですね。元々、関数型言語由来の考え方ということもあって、Javaで実現出来なくはないのですが、どうしてもストレートには行きません。

ちょうどC言語でもオブジェクト指向で書くことは書けますが、オブジェクト指向言語ではないので、書くのが面倒というのと同じです。

というわけでこのリスト操作スタイルをダイレクトにサポートするために最適な機能がStream APIとなります。

Stream APIによるリスト操作

驚くべきことに、こんなに長々とJava8の記事を書いてるのにまだJava8のコードが出てきていませんwでも、安心してください。ここからようやくJava8のコードが現れます。

まずは、先ほど書いたリスト操作スタイルのFizzBuzz(Java7版)をリスト操作スタイルのFizzBuzz(Java8版) に書き直しましょう。

gist4106ff067830296d5845

Java7版と比べるとかなりシンプルになりましたね。

mapの中で使ってるのがif文ではなく三項演算子なことにも注目です。たぶん、if文でも書けるのですがmapは各要素に引数で渡されたラムダ式を実行した結果を持ったリストを返す、という仕様なので値を返す三項演算子の方がずっとシンプルに書けたりします。

boxedを付けないとIntStreamはオブジェクトのStreamに変換できない面倒な仕様があるものの、概ね問題ありません。

さて、せっかくなのでPipes and Filtersっぽくメソッドチェインで処理を追加してみます。

gist8265b34f5598bafa0843

これはFIzzBuzzのルールで作り出した文字列のリストからさらに"FizzBuzz"という文字列を抽出してその数を数えるサンプルです。Unixのシェルっぽい感じですね。

ブコメで中間状態をログに出したりデバック目的で取り出したいという話もあったので、その例も追加してみました。こういうケースではpeekを使います。処理は実行するけどリストには変換を与えずそのまま返すメソッドです。

とはいえ、個人的にはログとか向上的に確認したいほど中間状態の粒度がハッキリしてるなら、変数に入れてしまうのがシンプルと思います。

Unixのシェルやワンライナーで書くときもそうですが、あまり長く書きすぎると書くときは問題無いのですが、読むのが大変になります。

また、Stream API重要なポイントとして、Java7のスタイルに較べてStream APIをはオーダー数が異なることです。Java7版では見て分かる通り、Listの作成処理や変換、そして表示とループが3回回っているため、オーダが3Nとなってしまうので、データが大きな時は非効率です。

その点、Java8のStreamはUnixのシェル等と同様に基本的にはループ数は1回に収まります。パフォーマンスを気にしなくて良くなるので、とても嬉しいですね。

まとめ

色々書いてきましたが、今日書いたことを整理すると以下の2点です。

  1. ループ処理はリストに変換することで保守性高く書ける
  2. ストリームAPIはリストスタイルを推奨するための語彙

for文禁止という一見苛烈に見えるコメントは、ほとんどのループ処理はリスト処理で表現可能、ということと、StreamAPIと組み合わせて使うなら、ソッチのほうがずっとシンプルで、安全という事があるからです。

本当はfor文でしか書きづらいケースは無くはないのですが、まずは一旦禁止して思考の仕方を変えるためのテクニックですね。ループ処理ではなく自然とリスト変換でデータ処理を考えられるようになった時、for文を解禁するとよいでしょう。

故に「Java 8のStream APIは業務でどんな時に使うの?」という問いには私はこう答えます。「あなたがfor文使いたい時」と。

それではHappy Hacking!

参考