問い: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と出自である関数型言語系はもちろんのこと、RubyやPython、JSといったLL系やJavaと類似の用途で使われるC#も似たようなものを持っています。
現在業務で使われやすい言語では概ね対応しているのでその辺に親しみがある人は、ようやく素直に出来るようになったのかという感じでしょう。
一方で、Stream APIでどんな事ができるのか分かったけど、業務のどこで使えば良いのかが分からないという意見もチラチラ聞きます。 実際、私は初めてRubyで内部イテレータを使ったのですが、最初はfor文を使わない文化に戸惑ったものです。
Webの記事とか見ても「for文禁止!」とかセンセーショナルな書き方がされることが多くて、実際その通りと思いはするものの、Java8怖いとかになって普及が阻まれないかなぁとも思うので、今回は「for文禁止」とまで言われるその背景の説明をしたいと思います。
なお、Stream APIそのものの機能や詳細はすでに色々なところで語られているので、今回は省きます。
語彙によって意図を明確にする
Javaに拡張for文(foreach)が入った時にも同じような話をしたのですが、言語やFWの語彙を適切に使うことでコードの意図を明確に出来ます。
これは本質的には適切なクラス名やメソッド名を記載するというのと同じことです。
まずはこいつを見てくれ・・・どう思う?
Java的に遺失呪文(予約語だがコンパイルエラーになる)なgotoを使ったFizzBuzzです。ループ構文が無い古い言語仕様で書く時は今でもこうかく必要がありますね。
一言で言えば「わけがわからないよ」
gotoはご存知の通り極めて柔軟な仕様なので、ループという概念に限定されておらず、良く読まないと繰り返しであることが分かりません。そして、汎用性の高い構文なので、もちろん記述が冗長です。
つづいて同様にループを表現する構文であるWile文を使ってみましょう。
さっきより大分見やすくなりましたね。Wileを使うことでループ表現であることが瞬時に理解出来ます。 ただ、これでは1から100までの順次表示であることが表現出来ていません。なので、普通はfor文で書きますよね?
ようやく普通の感じになってきました。落ち着きますね。
ここでfor文の良い所は1から100まで1個ずつインクリメントしてプログラムが処理されることをシンプルに保証したことです。
さて、ここまで読んでJavaのようにfor文が使える言語で、FizzBuzzをgotoやwhileを使って書きたいという方はいらっしゃるでしょうか?
え、書きたいんですか!? もの好きですね。。。
でも、仕事では迷惑なので書かないでください。
長々と書いてしまいましたが、ここで言いたかったことは、そのプログラム言語が許す限り適切な表現で目的を記述しましょうということです。
殆どの場合、昔からある既存の書き方でも表現出来るでしょうが、新しい表現が出来たということはそれに特化したユースケースが有るということなので、そのケースでは新しい表現を使うことでより抽象度が高く、簡潔で理解しやすいコードになります。これはもちろん今回導入されたStream APIも同様です。
Loopではなくリスト操作
さて、前章は長かった割には完全にStream API関係無い話でした。ここからはそもそもどういうユースケースを想定しているかを話していきたいと思います。
ところで、前章で最後に書いたFIzzBuzzのコードが業務でコードレビューに上がってきたらどうしますか? 私は基本的にNGにします。
なぜなら、ロジックの中で標準出力である"System.out.println"を使ってるので、ファイル出力とかに変えようとしたり出力フォーマットを変えようとしたら、ロジック書き直すかコピーしないといけないし、戻り値がvoidなのでUnitTestも書けません。プロダクトコードとしては非常にイケてない部類に入るでしょう。
なので、普通は仕事で書くならこんな感じに書くと思います。
直接表示するのではなく、FizzBuzzロジック本体はListを返すことで出力先を容易に切り替えることが出来、テスタビリティも格段と向上しています。
最初のコードだと下記のような思考パターンでコーディングをしていたと思います。
- 1から100までの数を繰り返し処理しよう
- 15で割りれるなら"FizzBuzz"を、5で割り切れるなら"Buzz"を、3で割り切れるなら"Fizz"を、そうでないならそのままListに追加しよう
- Listの中身を全部表示しよう
これがJava 7までの良くやる考え方ですね。ここでパラダイム・シフト。下記のように考えてみましょう。
- 1から100までの数を持ったリストを作ろう
- 1で作ったリストを15で割りれるなら"FizzBuzz"を、5で割り切れるなら"Buzz"を、3で割り切れるなら"Fizz"を、そうでないならそのままというリストに変換しよう
- 2で作ったリストの中身を全部表示しよう
一見同じように見えますが、1と2の意味が少し違います。最初からリストを作ることで「繰り返し処理」からリストの作成・変換といった「リスト操作」に置き換わっています。
これはリストの操作が得意な関数型言語に由来する考え方です。こうして「繰り返し処理」という制御構文を「リスト操作」という関数にしてしまうことで、戻り値のある小さなパーツとして扱いやすくなり、UnixのシェルなどのようなPipes and Filters アーキテクチャを適用出来ます
ようは、この処理を適用して、その結果をこの処理に適用して、それからこの条件で抽出してみたいなちょっとずつ処理を書き足していくやり方ですね。
Java7で書くとこんな感じですね。こうすることで
入力となるリスト -> 変換処理 -> 出力となるリスト
という形にすることが出来、先ほど言ったような恩恵を得ることが出来ます。ただ、あまり簡潔ではないですね。元々、関数型言語由来の考え方ということもあって、Javaで実現出来なくはないのですが、どうしてもストレートには行きません。
ちょうどC言語でもオブジェクト指向で書くことは書けますが、オブジェクト指向言語ではないので、書くのが面倒というのと同じです。
というわけでこのリスト操作スタイルをダイレクトにサポートするために最適な機能がStream APIとなります。
Stream APIによるリスト操作
驚くべきことに、こんなに長々とJava8の記事を書いてるのにまだJava8のコードが出てきていませんwでも、安心してください。ここからようやくJava8のコードが現れます。
まずは、先ほど書いたリスト操作スタイルのFizzBuzz(Java7版)をリスト操作スタイルのFizzBuzz(Java8版) に書き直しましょう。
Java7版と比べるとかなりシンプルになりましたね。
mapの中で使ってるのがif文ではなく三項演算子なことにも注目です。たぶん、if文でも書けるのですがmapは各要素に引数で渡されたラムダ式を実行した結果を持ったリストを返す、という仕様なので値を返す三項演算子の方がずっとシンプルに書けたりします。
boxedを付けないとIntStreamはオブジェクトのStreamに変換できない面倒な仕様があるものの、概ね問題ありません。
さて、せっかくなのでPipes and Filtersっぽくメソッドチェインで処理を追加してみます。
これはFIzzBuzzのルールで作り出した文字列のリストからさらに"FizzBuzz"という文字列を抽出してその数を数えるサンプルです。Unixのシェルっぽい感じですね。
ブコメで中間状態をログに出したりデバック目的で取り出したいという話もあったので、その例も追加してみました。こういうケースではpeekを使います。処理は実行するけどリストには変換を与えずそのまま返すメソッドです。
とはいえ、個人的にはログとか向上的に確認したいほど中間状態の粒度がハッキリしてるなら、変数に入れてしまうのがシンプルと思います。
Unixのシェルやワンライナーで書くときもそうですが、あまり長く書きすぎると書くときは問題無いのですが、読むのが大変になります。
また、Stream API重要なポイントとして、Java7のスタイルに較べてStream APIをはオーダー数が異なることです。Java7版では見て分かる通り、Listの作成処理や変換、そして表示とループが3回回っているため、オーダが3Nとなってしまうので、データが大きな時は非効率です。
その点、Java8のStreamはUnixのシェル等と同様に基本的にはループ数は1回に収まります。パフォーマンスを気にしなくて良くなるので、とても嬉しいですね。
まとめ
色々書いてきましたが、今日書いたことを整理すると以下の2点です。
- ループ処理はリストに変換することで保守性高く書ける
- ストリームAPIはリストスタイルを推奨するための語彙
for文禁止という一見苛烈に見えるコメントは、ほとんどのループ処理はリスト処理で表現可能、ということと、StreamAPIと組み合わせて使うなら、ソッチのほうがずっとシンプルで、安全という事があるからです。
本当はfor文でしか書きづらいケースは無くはないのですが、まずは一旦禁止して思考の仕方を変えるためのテクニックですね。ループ処理ではなく自然とリスト変換でデータ処理を考えられるようになった時、for文を解禁するとよいでしょう。
故に「Java 8のStream APIは業務でどんな時に使うの?」という問いには私はこう答えます。「あなたがfor文使いたい時」と。
それではHappy Hacking!
参考
Java Day Tokyo 2014に参加してきました
勉強会はブログに参加記事を書くまでが勉強会だと以前習ったので、昨日参加してきたJava Day Tokyoの感想とか書いときます。
Java SE 8がちょうどでたばかりということもあって、今回はほとんどのセッションがJava8祭りでした。
https://oj-events.jp/public/application/add/170
当日の雰囲気とかはToggeterにまとめられてるのでこちらを。
基調講演
午前中はOracle本社メンバーによる基調講演。Javaといえばサーバサイド!って印象が強いのですが、今回はJavaMEの話をはじめIoTに注力してることをかなりアピールされて、少し意外でした。 Javaといえば元々組み込み用として始まったわけで、それ故にこその "Write once, run anywhere"を掲げてたと思うのですが、原点回帰というか、ここにきてようやく実ったのかなという印象でした。
実際、デモの中でDukePadでもPCでもアーム型のロボットでもチェスを打つというコードをJavaで実装してたのは面白かったですね。あと、デモでまいんどすとーむやらチェスゲームのDukeが可愛かったのですが公開されてないのかな...
他にも気になったこととしては2016年に出来るであろうJavaEE8ではJAX-RS/MVCが登場予定とのこと。 これで、最近ようやくマイナスがゼロになったJSFとおさらばできます。あれはURLを大事にする近代的なWebサイトを素直に作れないので><
あとは忘れてはいけないのはJava SE8の日本語が出来ました!
Java SE 8日本語ドキュメントの公開 http://docs.oracle.com/javase/jp/8/
これは普及させるためには非常に心強いですね。このスピード感でできてくるのは意味では勢いを感じる象徴的なものに見えます。 他にもコミュニティについてもかなり言及されていてJava Magazine 日本版についても話されてました。
あとNECのロボットとか国内での事例紹介もありましたね。対話型のロボットで現在導入パートナーを探し中とのこと。
講演は全般的に英語でしたが、まあゆっくり喋られてたので、通訳聞かなくてもある程度中身が分かる感じでした。
Java SE 8時代のJava EE 7アプリケーション開発
続いて寺田さんのセッション。 まずはJava EE7 のWebSocketでマインドストームをリモート操作。曰く、組み込みのナレッジが無くても使い慣れたJavaでかなり簡単にキャッチアップ出来たとのこと。 ハードウェア自体の高機能化の恩恵も多分にあると思いますがJavaMEとかでフットプリントが小さくなって、普通に書ける範囲が広がったのはいいことですよね。なんか試したくなる。
その後はJava EE7とJava SE8を組み合わせてのConcurrency Utilities for Java EE(JSR-236)の使い方について。 Future.getが同期なのは知らなかったです。これは確かに嵌りそう... そして、 大事なことは「Java SE8ではCallable禁止」とのことでみなさんCompletableFutureを使いましょう! Java EE7とJava SE 8って組み合わせは新しいもの好きには堪らない選択肢だと思うので罠とかは今のうちに確認しときたいですね。
Java SE 8におけるHotSpotの進化
デイビッド・バックによるセッション。 Java SE 8になってHotSpotがどう変わったかのお話です。言語仕様ではなくVM仕様なので、厳密にはJava8ではなくOracle Java, OepnJDKの最新版に関する話、という理解が正しいですね。
ラムダ式とか言語周りが注目されることが多いですが、今回はJVM周りもプロジェクトHotRokitの本格稼働の一つとしてかなり手が入っているでなにげに一番気になってたセッションです。
まずはやはり気になるPermGenの廃止について。 PermGenは通常のヒープとは違いJavaのstaticのメンバーやクラス情報などJavaの静的な情報を格納する場所でした。 しかし、チューニングがそれなりに難しく、パフォーマンス劣化やOutOfMemoryErrorの原因となっていました。
というPermGenのデメリットを上げた上で、とは言えメリットはと次のスライドをめくったところで白紙w そして「....あったけ?」というコメントに会場が爆笑に包まれましたw
でJava8からPermGenが廃止されてMetaspaceへ。基本的な役割は変わらないのですが、ポイントはヒープではなくネイティブメモリ管理になったこと。 つまり、GCの対象ではなくVMの都合で状況に応じて伸縮出来るようになりました。元々ヒープとは特性が違うものだったので、これは言われてみれば妥当な気がしますね。 クラスローダ単位の管理になっていてHigh Water Mark(HWM)を指定することでクリーニングするタイミングもある程度指定できるようです。 とはいえ、特性が変わっただけでメモリ枯渇自体はありえるのでOutOfMemoryError自体が完全に無くなるわけでもないようです。 この辺のチューニングポイントは多少変わりそうなので、負荷試験とかをして実運用までに情報を集めたいところです。
他の話としては、フォールスシェアリング回避や64bitメモリアドレスを可能なら32bitに圧縮するCompressed Oopsや、C1(-Client)とC2(-server)を従来のように指定するのではなく上手く切り替えて良いとこ取りをするTiered Compilation、AESの性能改善など中々聞けない話がたくさん聞けて大満足です。
また、Java7から導入されたinvokeDynamic(indy)がJava8では大幅強化されているようです。 これは、Java7では初回なので正しく動くことを重要視していたのと、Java8でラムダ式やNashornで積極的に利用してる事もあってかなりチューニングが施されているようです。 ちなみにラムダ式がIndyで実装されてるのはこの時初めて知りました。てっきり内部クラスかと...
あとjcmdがネットワーク越しでも使えるようになったのも地味に便利ですね。まあ、運用環境と開発環境でjcmdが使うようなACLって普通開けないってのも有るのですが...
Javaアプリケーション開発におけるテストとTDDの実践
@t_wadaさん、@nekopさん、@shuji_w6eさんによるTDD談義。TDDというよりテスト談義かな。 自分が聞いた中では、というか多分今日のセッションで唯一かもしれないJava8関係ないセッションでした。
各メンバーがどうやってテストをしているかの話が主にされて、その中であったcmtest-db を使うとJUnit4に上手く統合されるようなので今度試してみたいですね。 Arquallianは私もちょこちょこ使っていて、EJBのテストには本当に便利! とはいえ、結構使いこ直してない所も多いし、Weblogicだと組み込みで動かないみたいだからどうしようかなとも思ってたんだけど、今回仕切りに本物のモジュールで、という話も強調されてたし、また試さないトナー。動画で撮影とか知らない機能もあったし。
総じて言われてたように感じたのは
- システムテストでモックに拘り過ぎず本物のモジュールを使うこと
- カバレッジは目安。テストの点数でもなければ完全でもない
- それはTDDじゃない、とか「テスト書かないとかそれ@t_wadaの前でも言えんの?」とか原理主義的なことを言わない。自分を助けてくれるかを考える
とかですね。この辺の話はうちのチームだとプログラマが多いのでぜひ色々共有したいところです。
Lambda式とストリームAPI、並列処理の詳細
Stuart MarksによるストリームAPIとかの話。 目玉だけあって、Lambda式やストリームAPIは各所ですでに語られまくってる事もあって、聞き取れた範囲ではあまり新鮮味のある内容もなかったけれど、スライドにサンプルがいっぱい書いてあったので後で見なおしてみると色々発見があるかも。 元々自分がRubyやScala、JSやLISPなどmap/reduce/filterがある言語を結構使ってるので、ようやく頭で翻訳せずに使えるようになったか、という以上でも実はなかったりする。
とはいえ、twitterとか見てるとストリームAPIの使い方はわかったけど、業務でどう使うかわからんって話があって、これは私も他の言語でこの辺の概念を学んだ時には感じたことなんで、今度記事とか書いてみようと思う。
Java Day Tech Night
Oracle本社の中の人に質問ある人どうぞコーナー。 勝手にLT枠だと思ってましたがちょっと違いました。そして、ハッキリ言ってグダグダでしたw 通訳挟んでるしテンポやコミュニケーションロスの問題もあるしね。
とはいえ、かなり色んな話が聞けました。Lambda式が内部クラスではなくIndyで実装されてるから初回のコストはともかく2回目以降は通常のメソッドとほぼ変わらない。詳しくはJavaOneの資料見ろ、とか。 Lombokは標準に取り込まれないの? 的な話は試しに使って見てたけど、余計な問題で困ることが多いから別の方法を模索中だけど決定案も無い、というかEJBとかJavaEE系は意図的にgetter/setterとpublic fieldを区別せず動くように作ってあるからそっち使え、とか。 あとLambada式内での例外の取り扱いもやっぱり決定案ないから気をつけろだとか、OpenJDKに協力するならまずはML登録だ!とか。
他にもInterfaceにメソッド実装持てるようになったけど、フィールドてないから片手落ちじゃね? って話にはScalaと違って互換性を大事にしてるからそんなにダイナミックな変更は出来ない。という感じの回答も参考になりました。 とはいえ、基本的にはMix-inをするためのTrait的な用途は想定してないって話だと思うんだけど、こんな感じで書けばフィールドを直接持てなくても、困らないので個人的にはガシガシmix-inに使っていく所存。
そして、JavaEE7でまともに動くのwildflyだけじゃない! 私達はJavaEE7を信じて乗っていいの? 他の言語や非標準FWに行っちゃうよ? という問いには結構ぐだぐだした返しがw まあ、禁断の質問ですからね。標準作る立場としては色いろあるだろうけど、せめてGlassFishとWeblogicはお膝元だからもうちょっと頑張って欲しいってのも有りますねー。早く運用で使いたいし!
まとめ
搭乗予定だった飛行機が30分遅れて、危うく間に合わないかとも思ったのですが無事基調講演から参加出来ました。 実のところ行くかどうかは少し迷ってたのですが、Java熱が自分の中でも高まってきたので、いくつかJava8の機能を試して記事書いてみたくなりまし、なにより楽しかったです!
来年もぜひ行きたいですね。
それでは、Happy Hacking!
Androidのエンドツーエンドテストの自動化も Cucumber から Turnip へ
以前はAndroidのATDDにはCabash-Androidを入れてたのだけど、やっぱ時代はCucumberからTurnipだよねってことで、AndroidなTurnip環境を作ったので構築メモ。
ちなみに出来たものはこちらへ。
github - koduki/turnip_with_android_example
インストール
まずはこの辺参考にGenymotionを入れる。 別にGenymotionじゃなくて実機とかでも問題無いと思うけど、開発とかCIとかにはGenymotionが速くて便利。
インストールしたらデバックモードを有効にする。
Android 4.2以上はデフォルトでは開発者向けオプションがないらしいので
- 「設定」=>「端末情報」=>「ビルド番号」を7回タップすると「設定」に開発者向けオプションが表示される
- 「デバッグモード」をonにする
続いてappiumをnpm経由で入れる。
sudo npm install -g appium sudo npm install wd
これで、実行環境の準備は完了。最後にTurnipの環境を作る。
まずはインストール用のディレクトリを作成.
% mkdir turnip_with_android_example/ % cd turnip_with_android_example % mkdir spec/ % mkdir spec/acceptance/ % mkdir spec/steps/
続いて
% $EDITOR Gemfile
中身は下記の通り。
source "https://www.rubygems.org" gem 'rspec' gem 'selenium-webdriver' gem 'turnip
Turnip用にfeatureを取り込めるように.rspecに下記を記載
% $EDITOR .rspec
中身は下記の通り。
-r turnip
% $EDITOR spec/spec_helper.rb
require 'bundler/setup' require 'selenium-webdriver' Dir.glob("spec/steps/**/*steps.rb") { |f| load f, true }
これで必要なファイルが出来たので、Bundlerで依存ライブラリをインストールする
% bundle install --path vendor/bundle
シナリオの作成
さて、ようやく準備が整ったところでシナリオの作成。
開発中のアプリとかをテストするのが本来だけど、サンプルとしてのポータビリティを加味して、デフォルトで入っている[Settting]を対象にテストを書いてみる。
まずはfeatureファイルを作成。
% $EDITOR spec/acceptance/settings.feature
Feature: 設定画面 Scenario: 端末情報を表示する Given テスト対象は "android" 端末 When "About phone" をタップする Then Android Version は "4.2.2" が表示されていること
一旦この時点で実行してみる。bundlerで環境作ってるのでbundler経由でrspecを実行するのがポイント
% bundle exec rspec
stepファイルが無いので下記のようなエラーが出るはずです。
設定画面 端末情報を表示する テスト対象は "android" 端末 -> "About phone" をタップする -> Android Version は "4.2.2" が表示されていること (PENDING: No such step: 'テスト対象は "android" 端末') Pending: 設定画面 端末情報を表示する テスト対象は "android" 端末 -> "About phone" をタップする -> Android Version は "4.2.2" が表示されていること # No such step: 'テスト対象は "android" 端末' # ./spec/acceptance/settings.feature:3
続いてstepファイルを記述。中身は下記の通り。
# coding: utf-8 module SettingsStep step 'テスト対象は :device 端末' do |device| @device = device end step ':target をタップする' do |target| about = find_first_element('//text[@text="' + target + '"]') about.click end step 'Android Version は :expected が表示されていること' do |expected| version_setting = find_first_element('//list/linear[4]/relative') version_value = version_setting.find_element(:xpath, '//text[2]') expect(version_value.text).to eq(expected) end def find_first_element xpath #flick the screen until find the target item while driver.find_elements(:xpath, xpath).count == 0 begin driver.execute_script 'mobile: flick', :startY => 0.9, :endY => 0.1 rescue end end driver.find_elements(:xpath, xpath).first end def driver case @device when 'android' desired_caps = { 'browserName' => 'android', 'platform' => 'linux', 'version' => '4.1', 'app-activity'=> '.Settings', 'app-package'=> 'com.android.settings' } server_url = "http://127.0.0.1:4723/wd/hub" @driver ||= Selenium::WebDriver.for(:remote, :desired_capabilities => desired_caps, :url => server_url) @driver.manage.timeouts.implicit_wait = 3 end @driver end def cleanup if @driver driver.quit @driver = nil end end end RSpec.configure do |conf| conf.include SettingsStep conf.after(:each) do cleanup end end
基本的にはただのrsepcですね。1点注意としてはfind_first_elementの中でやってるみたいに画面上に表示されてない項目を出すためにスクロールする必要があります。 これ分からなくて、最初嵌った...
これを実行すると、下記の用にGreenな結果になる。
% bundle exec rspec 設定画面 端末情報を表示する テスト対象は "android" 端末 -> "About phone" をタップする -> Android Version は "4.2.2" が表示されていること Finished in 9.14 seconds 1 example, 0 failures
まとめ
とりあえずAndroidでturnipな環境が完成。割と便利そう。 ただ、構築が少し面倒なところも。npmとgem使うので、そっち系の親和性があってかつAnrdoidでJavaも書くぜーってエンジニアが居ないと、使うのはともかく導入に手間取るかなーという印象でした。国内情報少ないしね><
あと、cucumberの時もそうだったけど、Firebugとかに相当するDOMインスペクタが無いとstep書くのがちとしんどいのよね。 でも動画でそれっぽいものがappiumある的なことを言ってたので、調べてみようかな。
なんにしても後はテストをひたすら書くだけじゃー!! それではHappy Hacking!
参考
Macで複数バージョのJavaを切り替えたい
JDK8でたよーって、ことで色々触っている今日このごろ。 やっぱラムダ式とか使えるようになって大分普通になった感じ。
で、ここで問題になるのがまだ全面的にJava8に移行したわけでもないのでJava7とどう共存して環境を切り替えるか。たぶん、Java使っている人の9割9分9毛くらいはIDE使ってると思うので、そこはほぼ問題無し。 ※ 以前eclipseが重たいという理由でメモ帳使ってる業のモノが居ましたが...
ただ、mvnコマンド打つときとか、アプリを起動するときとか色々環境変数レベルで変更したいケースもしばしば有ります。Linuxなんかだったらalternative-javaでサクッと変えれるし、最近のLL系だったらこの辺を支援するツールがだいたいある。rvmとかそれ系。
しかし、MacOSとJavaが合わさるときその道はないのです。。。 まあ、需要か。orz
無いなら作れが世の基本なので、とりあえず作ってみました。と言っても全面的に下記の記事のやり方をaliasコマンドで実行するだけど。
そんなこんなで出来たものは下記。
github - Alternative-Java for Mac
使い方
使い方という程でもないのですが、適当なディレクトリに上記のスクリプトをcloneして、下記の様なaliasを設定します。.zshrc.mineとか.bashrcとかその辺に追加すると良いかと。
alias alt-java='source {YOUR_INSTALL_PATH}/alternative-java-for-mac/alternative-java-for-mac.sh'
引数にバージョン付ける感じでサクサク変えれます。
% alt-java Missing arguments. Usage: alternative-java.sh [1.6|1.7|1.8] % alt-java 1.7 java version "1.7.0_51" Java(TM) SE Runtime Environment (build 1.7.0_51-b13) Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode) % alt-java 1.8 java version "1.8.0" Java(TM) SE Runtime Environment (build 1.8.0-b132) Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode) % alt-java 1.9 Unknown Java version. Usage: alternative-java.sh [1.6|1.7|1.8]
これで色々と捗るぜ! じゅるり。
とはいえ、これはグローバルは変えないので、あくまで実行したターミナルというかシェルだけに影響が限定されます。
個人的には開発や検証で使う想定なのでこの機能で十分だし、rvmとか本物のaltanativeみたく本気は出さなくていいよね。
それではHappy Hacking!
オカルトから科学へ - SonarQubeで静的コード解析を始めよう
ちなみに、タイトルは半分釣りというか、個人に依存したオカルトから、誰がやっても同じになる科学的な方法論をもっと入れてこう、って意図です。
もちろん、そういった部分を全部なくすのでは無くうまく組み合わせるのが大事。魔術と科学が交わるときには何かが起こるのです!(これが言いたかっただけ)
なんで静的コード解析?
チームやプロジェクトでコードレビューをしていますか?
多くの開発プロセスではコードレビューまたはペアプロが実施されていると思います。しかし、人の目で見てるだけでは不十分であり非効率です。
人間によるレビューは経験や勘、あるいはレビューアの好みに依存してしまう部分があります。 そのため、違う人がやれば違う結果になることはしばしば有ります。
また、そういった自体を防ぐためにレビューチェックシートを使う場合もあるでしょう。その場合の多くは退屈な作業を人がやることになります。 数が増えれば増えるほどミスをする可能性も増えてきます。
そんな時に役立つのがFindBugsやPMD, StyleCopなどの静的コード解析ツールです。 これらをIDEやJenkinsなどのCI環境に入れることで、ツールによる差異はありますが概ね下記の点を見つけることが出来ます。
- 命名規則やインデントなどのスタイルが適切か
- 一般的にバグの原因になり易い危険なコードはないか
- コードクローンなど品質の問題になりやすいコードはないか
という感じで色々と見つけてくれます。正直、この手のツールと同じ土俵で人間が勝負するのは無理です。精度で負けます。
なので、誰でも出来る部分は科学の産物たる機械にまかせて、人間にしか出来ない観点でレビューをするのがベストです。導入することで以下の効果が期待できます。
- レビューアに依存しない定量的なレビュー
- コード量やレビューアの忙しさに依存せずに素早く確実にレビュー結果を得られる
個人的にはサーバサイドにレポートが出せてグラフィカルに内容を共有出来るSonarQube(旧名Sonar)を使ってるので、その紹介と導入方法を共有したいと思います。
SonarQubeとは?
まずは、ざっくりしたSonarQubeの紹介です。
かつてはSonarと言われるものでFindbugなどJavaのコード解析ラッパーから始まったツールです。
Pluginを利用することでC#やPHP, JavaScriptやAndroidなど多くの環境をサポートします。詳細は公式参照(Plugin Library - SonarQube - Codehaus)
Sonarの気に入ってるところとしては
- グラフィカルで分かりやすいUI。複数のツールのメトリクスを集約して見れる
- タイムラインでメトリクスの増減を見れる
- 導入が簡単。また、個々の開発メンバーが導入する必要は無いので、負担が少ない
- プラグインやRESTベースのAPIで拡張可能
があります。特にタイムラインで見れるので、コードメトリクスがどう変化していったかを追うことができるので、すでに運用しているコードにも適用しやすいのが便利でした。
Sonarのインストール
インストールはかなり簡単です。
wget http://dist.sonar.codehaus.org/sonarqube-4.1.1.zip unzip ~/Downloads/sonarqube-4.1.1.zip cd sonarqube-4.1.1/ ./bin/macosx-universal-64/sonar.sh start
起動したら
http://localhost:9000/
にアクセス。これで完了。ね、簡単でしょ?
本格的に使うにはDBをMySQLに変えるとか、Apacheと連携させるとかすると良いと思いますけど、まずは動かすだけならこれで大丈夫。
Sonarの使い方
JavaでMaven環境を作ってるならゴールをsonar:sonarにするだけです。ブランチ名の指定とかしたいので、普段は以下のようなオプションにしています。
これをJenkinsに登録するだけで、定期的にSonarのレポートが作られるようになります。
# デフォルト名 mvn sonar:sonar -D.sonar.forceAnalysis=true -Dsonar.host.url=http://localhost:9000/ # ブランチ名とか指定したいとき mvn sonar:sonar -D.sonar.forceAnalysis=true -Dsonar.host.url=http://localhost:9000/ -Dsonar.branch=branch-name
Sonarを使った開発プロセス
やりかたは色いろあると思いますが、うちでは主に以下のようなやり方を実践しています。
- 開発開始時にmasterより開発ブランチを作成
- 各開発ブランチをJenkinsに登録しCI環境に登録毎時でブランチ専用のSonar画面を作成
- 開発メンバーは随時、Sonarの画面を確認。問題が上がれば修正
- 開発完了時にレビューアはSonarの指摘が無いことを確認した上で、レビューを行い開発メンバにFBする
- FBの修正及び再レビューが終われば、リリースブランチにマージ
開発の規模感としては並行して5から10案件程度は動いており、同じ数だけ開発ブランチがあります。これより大きい規模の開発だと、また違った方法論が必要な気がしますが、同程度以下ならある程度同じようなプロセスでいけると思います。
この運用でポイントとなるのは、3のSonarの画面を見て問題があれば修正する、という部分です。
まず、基本的なサイクルとしては指摘を見て修正すれば良いのですが、ルールベースで指摘しているという仕組み上の問題で、設計上は問題なかったりする指摘やら誤検知がそこそこがあります。
これをレビューアなど適切に判断出来る人にエスカレーションした上で、問題なしのチェック等を入れます。そうすると指摘上から消えるので、この部分の指摘は無視して良いとか人間が毎回判断する必要はありません。
残念ながらこの設定がブランチで共有出来ないので、現在は新規でブランチを作る度に手動でコピーしています。 この辺、良い方法があったら、ぜひ教えて下さい。
また、新規コードから適用してるなら指摘を全部0にすれば良いので簡単ですが、すでに運用段階に入ってる場合など、過去の指摘を全て直すのは現実的ではありません。
この場合は、masterの指摘数と比較して、今回の開発等で増えないことを指標とすると良いです。もちろん、たまたま修正が可能だったから減るって分には良いことなので褒める文化に。
Sonarはそれに適した機能があってTimeMachineで比較することが出来る。これを使うことで運用中のコードであっても、適用していくことが比較的簡単に出来ます。
いずれにしても"指摘を増やさない"という運用が大事で、例外は原則認めてはいけない。そうしないとみんな守らなくなるからね。元のルールが厳しすぎるとかなら適度にカスタマイズすればいいし、うちもそうしてる。
あと、Issuee以外のDuplicationsとかComplexityは今はルールにしてない。この辺のルールを入れると縛りが強くなりすぎる気がしたので、あくまで開発者の参考レベル。
そして、怒る人重要。指摘を増やした状態で4や5にプロセスが行ってしまった場合は、きちんと怒る。必要に応じてマネージャとかもCCにして、全体周知して怒る。嫌われ役になっちゃいがちだけど、最初はみんな慣れてないから、ついやっちゃうし、新規メンバーもしてしまいがちなので、文化としてやっちゃダメなこと、とみんなが認識しきれるまでは、きちんと怒り役の人が居た方がうまく回るかな。
プラグインとAPI
拡張性の高さもSonarの良いところです。単に対応している言語やテストツールを増やすだけではなく、PDF出力をはじめレポート類も充実しています。
Plugin Library - SonarQube - Codehaus
また、RESTベースのAPIで簡単に情報を取得したりも出来るので、独自のツールやExcelへの連携も簡単です。
たとえば、プロジェクト毎のIssuesとコードカバレッジの一覧は以下のURL。
http://localhost:9000/api/resources?metrics=coverage,violations,blocker_violations,critical_violations,major_violations,minor_violations,info_violations
APIのドキュメントはこちら(Web Service API - SonarQube - Codehaus)を参考。
うちではこれを使って複数のブランチのタイムラインを1画面で見れるツールを作っています。コード品質が急激に悪くなったチームとかあれば、プロジェクト進捗にも影響するだろうから警戒する必要がありますし。
こういった拡張がさくっと書けるのは良いところです。
まとめ
色々書いてみましたが、この手のツールは単純に入れるだけじゃなくて、運用プロセスを作って文化まで昇格することが大事です。
とりあえず、勝手に導入して技術的な課題をクリアしたら現場メンバに周知。ある程度みんなが慣れたらマネージャとか偉い人系もレポートラインに加えて、進めるのが個人的には好きです。許可を取る前にやってしまえモデル。
導入も簡単だし、まずは入れてみて、自分たちの開発プロセスに合うかどうかを確認してみるのもいいと思います。
実際うちでは入れる前に比べて劇的にコード品質がマシになって、ヒューマンレビューではくだらないこと指摘せずに具体的なレビューに集中できるようになったし、効果があるケースも多いんじゃないかと。
それでは、Happy Hcking!
参考:
Javaを使うメリットはありますか? はい、それはもちろんあるに決まってます!
/.Jに聞け:Javaを使うメリットは? | スラッシュドット・ジャパン デベロッパー
え、ありますよね? Webアプリに限っても。 というか、上げてるデメリットが現代的じゃないなぁ...
個人的には少数精鋭チームを作れてないならJavaはかなり有りな選択だと思っています。
※ Java使ってるメンバーがスキルが低いのではなく、LL系で高品質なものを作るのにスキルが必要で、例えスキルがあっても多人数開発にはあまり向かない、という意図
というわけで、自分が思うLL系言語ではなく、JavaでWebアプリを開発するメリットを書いてみます。
静的型づけ言語
HaskellとかOCamlとかScalaとか、その辺の超強力な型付け言語から見ると弱いですが、多くのLLと違い静的型付けな事が特徴です。
これは型というメタ情報を言語仕様に明確に取り込んでいるということです。以下のメリットがあります。
- 人間が読みやすい
- コンピュータが読みやすい
動的型付けだと何が入ってるか不明のオブジェクトがあります。
メソッドの引数に入る値がフリーダムだったり、逆に戻り値がintだったりオブジェクトだったり。
型をインターフェースに定義することで、この辺りは非常に読みやすくなります。曖昧な書き方をすれば、コンパイラが指摘しますし。
初期のJavaだと記事で上げられてるデメリットにある通り、キャストが増えて結局型安全じゃないケースも多々有りましたが、Java5以降はGenericsがあるので、ほぼキャストに出くわすことは無いでしょう。
他にもJavadocを書くときに型情報を記載してなくても自動的に入れてくれるという地味に便利な機能もあります。
パフォーマンスも付け加えたくなりますが、JITが優秀なら型の有る無しが決定差にならないらしいですし、それ以外のメリットに注目してみました。
IDEが強力
EclipseやNetBeans, IntelliJと言ったIDEが結構強力。 静的型付ということもあって、リファクタリング機能も強力にサポートしてるし、コードアシストも素早くかつ正確に動作します。
リファクタリング機能が安心して使えると、トライ&エラーの開発がしやすくなるので開発スピードもグッとアップしますね。
gitやsvnといったバージョン管理、GlassFishやJettyなどのアプリケーションコンテナ、antやmavenやgradleといったビルドツールとの連携/統合もバッチリ。
IDEとアプリケーションコンテナを連携してホットデプロイすれば、コンパイルが必要という点もほぼ意識することはないかと思います。
IDEなんて嫌だ! vimやemacsが使いたい! って人も居るでしょうけど、統一した環境をサクっと作れるのは開発現場には有用なことだと思います。
静的解析が強力
Javaは静的型付け言語なのでコンピュータが読みやすいという特徴と、長年開発の第一線で利用されてきたこと、そしてアカデミックな研究に使われることも多いという理由で静的解析ツールが豊富です。
コード品質を保ち、コードレビューのコストを減らすことが出来るのでとりあえず入れといたほうが良いレベルの仕組みです。なぜなら、コードの綺麗さは中長期的な生産性のために必須な要素だからです。
とはいえ、レビューをきちんとするのはコスト的に辛くてやってない、ってケースは結構あるんじゃないでしょうか?
JavaだとFindbugやPMDをはじめ様々な解析ツールが揃っていて、最低限のコード品質を保つのが非常に簡単です。コードクローンや命名規約ミス、危険な代入などレビューチェックシートで見つかる程度のことは見つけてくれます。
また、Veracodeとかの商用製品だとセキュリティ観点でも静的解析を行いSQLインジェクションやXSS, CSRFの疑い等を検出してくれます。
もちろん、RubyやPHPにもそういったものは有りますが、一日の長がありJavaのレベルまでは達していない認識です。
コーディング規約の方言が少ない
Javaは全面的にSun時代のコーディング規約をベースとしたものが使われていてコーディング規約に関してJavaエンジニア全員がある程度共通の認識を持っています。
そのため、モジュールごとにCamelCaseだったりSNAKEだったりしないです。もちろん完全に方言というか、それぞれの規約が無いわけではありませんが、 かなり小さいので、厳密なルール無くてもある程度統一でき、宗教戦争も起きにくいです。
それなりに高い生産性
めんどくさい言語、冗長な言語として評判なJavaですが、そこまで生産性の悪い言語ではありません。
オブジェクト指向言語としての基本的な機能は揃えており、標準クラスライブラリもどんどん強化されています。 例えば、元記事で言及されいているデメリットですが
と連想配列がリテラルじゃないので少し冗長なことくらいしか今は残っていません。
加えて1.7ではFilesでファイル処理が簡単になり、1.8からは待望の関数オブジェクトとStreamAPI、型推論、そして日付周りのライブラリ改修が入ります。
この辺が導入されれば、言語機能としても大きくLL系と見劣りすることはないでしょう。 また、リフレクションやAPTで基本的なメタプログラミングを行うことも可能です。
加えて、JavaEEやSpringを使えば、Webアプリケーションを組むのはさほど難しく無いかと思います。
高い上位互換
Javaは言語としての安定性が非常に高いです。
バージョンアップにより非互換の言語仕様の変更が入ることはほぼ無く、リビルドすらしなくても結構そのまま動くことは多いです。 一方、LLでWebでよく使われるPHPやRubyは結構ひどいです(直近のバージョンアップは比較的マシですが)。当たり前のようにAPI等が統廃合されます。
自分たちの書いたコードだけではなく、FWやライブラリが影響を受ける場合もあり、おいそれとは上げれなくなります。 なら上げなければいい、とか言われそうですが、世の中にはセキュリティアップデートというものが有ります。適度に最新化しないと致命的なセキュリティを放置することになってしまいます。
こういった点は、4,5年以上の運用には非常に重要です。
モニタリングのしやすさ
.NET系もそうらしいですが、JMXによるオンライン監視やスレッドダンプやメモリダンプ、GCログといったリソースモニタリングや障害調査のための仕組みが非常に強力です。
プロファイラもVisualVMやJava Mission Controlなど強力なものがデフォルトでついています。
最近だとFlightRecorderやENdoSnipeなど運用時に低負荷でプロファイラ級の情報を取得できるものもあります。GCのタイミングからホットメソッドまで分かるでの、問題発生時の切り分けがずいぶん楽になります。
LLでも使えるNew Relicとかも登場しており、Javaだけの特徴ではないですが、それらを含めた運用ノウハウの蓄積はまだ勝ってると思います。
まとめ
色々書きましたが、下記の2点にメリットを集約できると思います
- 言語機能や開発環境によりコード品質や生産性の底上げがしやすいため人の確保が比較的容易
- 言語の安定性やモニタリングなど運用時に都合の良い部分が多い
別段Javaが最高の言語だとはちっとも思いません。ScalaとかRubyとかの方が普通に好きです。
Javaのデメリットも多くあり、ScalaやRubyほどの生産性はないですし、Haskellほどの安全性もありません。C++のようなパフォーマンスもないです。 メモリをよく食いますし、インメモリセッションレプリケーションを使うなら尚更GCには気をつけねばなりません。バランサ設定をスティッキーにする必要があるのも面倒です。
とはいえ、仕事で中長期的に使うなら、先に上げた2つのメリットは捨てがたいものなので、個人的にはまだまだJavaという選択肢「も」ありだな~、と思っています。
それではHappy Hacking!
参考:
ちょっとだけ追記
想像以上にブクマもらってびっくりな今日このごろです。
で、コメントとか見てて、ちょっと補足した方がいいな、と感じたこともあったので少し追記します。
それはJavaじゃなくてJVMの特徴
障害情報、モニタリング系とかはそうですね。だから私も個人的にはScala押しです。ただ、JVMの機能を使えること「も」Javaの特徴なので、メリットして評価項目には入るかと思います。
逆に、RubyもScalaもGroovyも言語の安定性/静的解析ツールの品質という点ではまだJavaに及んでないと感じてるので、単純置き換えできるものじゃないかな、と。
そのメリットは◯◯言語使ったほうが得られる。
そうかもしれないです。少なくともJavaが常に最適解になるわけでは決して無いです。 当たり前ですが適材適所あります。手持ちの札(人、モノ、金、時間)によっても同じシステム要件でも変わるでしょう。
C#なんかは言語的には同じ方向でかつJavaより機能的なので、代替候補としては最有力ですね。 ただ、個人的にはWindowsサーバの適切な運用ノウハウを持って無いので、Web系とかには採用したくないです。とはいえ、海外では一般的ですし、Azureも出たので国内でも良い選択になるかもですね。
その上で、この記事自体が「Java使うメリットはあるの?」という問い対する回答なので、頭ごなしな否定を(しかも古い情報で)してほしくないなぁ、という意図なので記載したメリットを他の言語で受けれない、という意味では全く無いです。
言語毎、ユースケース毎に採用を判断するための様々な特徴があるので、他言語で似たような観点の記事を書いてみたいですし、別の人が書いたいろんな言語や環境の記事を見てみたいですねー。
あなたの職業は何? RPG風のエンジニアの分類を作ってみた
ひとくちにエンジニアと言っても、いろんなタイプの人が居て、性格に応じたロールがある。 たとえば「◯◯さんはやたら設計に拘って動きが遅いけどバグは出さない」とか「◯◯さんは手が早いけど、よくバグも作りこむ」とか「◯◯さんはいつもCIと叫んでる」とか。
こういったそれぞれの性格のメンバーが自分にあったロールをこなすことによって、良い開発プロセス/運用プロセスってのは出来ていくと思う。
そういったロールを自分なりに分類してDQ風の職業にマッピングしてみた。 というのも以前飲み会でDQ風にエンジニアのスキルマップを作ってる人がいて、なんか面白そうだったから真似してみただけなんだけどw
偉大なる先達の「プログラマの区分」よりも、抽象度を上げてるので、プログラマだけじゃなくて、インフラ屋さんとかもそれなりに当てはまるはず。
僧侶と魔法使いの区分が分かりづらいけど、 イメージとしては品質を上げるための活動と生産性を上げるための活動の違いかな。もっとも、それらは密接に相関があって切り分けしづらいのだけど。
あと、複数の職業を兼任してるとかは普通にあるとおもう。
職業
戦士
- 戦闘(開発/運用)の要
- 重厚な設計、検証された方法を好む
- 守りは固いが、素早さは低め
武道家
- 戦闘(開発/運用)の要
- 軽量な設計、新しい方法を好む
- 素早さは高いが、守りは低め
僧侶
- 品質を維持するための魔法を使う
- レビューやテスト、静的コード解析やCI/CDを好む
- 盗賊と連携することでより強力な魔法を使うことができる
魔法使い
- 強力な魔法を操り、戦士や武道家を支援する
- フレームワークや共通ライブラリを弄ることが多い
- 盗賊と連携することでより強力な魔法を使うことができる
盗賊
- 他所のベスト・プラクティスを盗んでくる
- 基本的に他の職業と兼任してることが多い
- 外部のイベントによく参加している
商人
- お金の計算が得意。外部からの調達も行う
- ROIにもとづき自分たちが"しないこと"を計算できる
- 限られたリソースを最適なところに投入するために重要な職業
振り返り
さて、せっかく作ってみたので自己分析をしてみる。
武道家(lv 6), 盗賊(lv 5), 魔法使い(lv 4), 僧侶(lv 3)
Lv 10を一つの基準にするとだいたいこんな感じかな? いろんな職業についてるので職業技能はそれなりに持ってるんだけど、それぞれのレベルは低いイメージ。
今後の成長プランとして、武闘家とかのレベルをもっと上げるべきか、いっそのこと商人を採って技能を増やすべきかは悩ましいところ。
ちなみにこの職業はバランスよくパーティが構築されている必要があると思う。 一つのパーティに攻撃職、魔法系職、商人が居て、盗賊は全員がLv1以上とってるとかがバランスいい気がする。 戦士や武道家だけでパーティー組むとか縛りプレイの粋だよね?
ただ、現実はクソゲーと言う言葉があり、縛りプレイ大好きな日本人は「戦士、武道家、武道家、武道家」というパーティもよくあるとか。 今いるチームもこの構造な感じで、ギルド所属? の商人、魔法使い、僧侶が複数のパーティーをまとめて面倒見てる感じ。そして、輪をかけて盗賊が少ない。。。
デザインパターンじゃ無いけど、こういったチームメンバの傾向やロールに名前を付けることで、「盗賊が足らない」とか「戦士のスキルを磨きたい」とかチーム戦略や自分の成長プランを考えなおすのに参考になるんじゃないかと思う。
人事系の部署とかはこんな思いつきで作ったようなのじゃなくてちゃんとしたの持ってるだろうけど、自己分析ならこのくらい馴染みのある奴のが自分は好きかな。
でも、この手のロールをファンタジー系職業で表すのは定番といえば定番なので、エンジニアのスキルセットを「ツンデレ」とか「幼なじみ」とかヒロイン属性風に表すとかのが頭がおかしくて良かったかもしれない。誰か作って!
それでは、Happy Hacking!