torutkのブログ

ソフトウェア・エンジニアのブログ

forEach書いたら負け、for文禁止

本日は日本JavaユーザーグループJJUG)主催のクロスコミュニティカンファレンス2014春に参加してきました。今日の日記は、参加内容のまとめではなく、参加して得たことやそれをきっかけに考えたことなどをだらだらと書き連ねます。

Java SE 8では、forEach書いたら負け、for文禁止

日記のタイトルは、午前のセッション「K-1 詳説 Java SE 8 – CCC Edition」で出た話題です。手続き的なロジックを書いて、ネストが深く制御構造が複雑になってしまう人向けにはJava SE 8で導入されたラムダ式とStream APIを使って、内部イテレータ関数型プログラミングのエッセンスを取り入れた書き方をするといいよ、という話から出た言葉です。
実は昨日開催したJava読書会(「Java 8 Lambdas」を読む会)でも、「for文は禁止だ!」という話題が出てました。for文とif文(コレクションをループで回しながら条件判定して処理するとたいていそうなる)が多段になると、可読性が劣化しバグの温床となりがちです。コードの複雑度を表す代表的な指標(メトリクス)のサイクロマティック複雑度(循環複雑度)を計測すると、ネストが深いコードや条件分岐が多数あるコードでは大きい数値となります。サイクロマティック複雑度とバグの発生数については相関があるという知見が得られていますし、そうしたコードをテスト(少なくても分岐カバレッジを満足するように)するにはテストケースを相当多数用意する必要があります。
サイクロマティック複雑度を測るには通常メトリクス計測ツールを使いますが、簡易的には分岐を伴う制御構文のキーワード数を数えても出すことが出来ます。
ソフトウェアメトリクス - ソフトウェアエンジニアリング - Torutk

一方、ラムダ式とStream APIを使ったプログラミングをすると、メソッドチェインが増えるもののコードの分岐は発生しないため、サイクロマティック複雑度は増えないですし、コードの実行は上から下への単純なフローです*1

ということで、ソフトウェア工学的な視点からは、ラムダ式とStream APIを使ったコードと、ループ構文と条件分岐構文を使ったコードとの品質の違いは明らかです。「ラムダ式禁止」を危惧する声を聞きますが、欠陥予防により品質改善を推進する組織であれば、むしろ「ループ禁止」を導入するのではないでしょうか。

JConsoleもう使わなくていい

普段、Javaで書いたアプリケーションのパフォーマンス計測(プロファイリング)をする際、JConsoleを立ち上げていろいろ見ていますが、そこに衝撃の言葉を聴きました。

せめてVisualVMを、Java SE 8ではJava Mission Controlを使うとより効率的ということです。
なお、Java Mission ControlとFlight Recorderは有償(Oracle Java SE Advanced)という話がありますが、Oracleデータベース製品と同じ価格体系のようで、SIerのように自分で運用するのではなく開発・試験をする場合についてはどう適用されるのか明快な説明が見当たりません・・・。

Collections.synchronziedListの拡張forループは不整合を生じる

List<Person> list = Collections.synchronizedList(...);
for (Person p : list) {
    System.out.println(p);
}

このコードは、拡張for文の中で1つの要素につきイテレータのhasNext()とnext()と2つのメソッドが呼ばれます。それらのメソッド毎にロック、アンロックされるため、並行アクセスに不整合となるとの説明がありました。
一方、ラムダ式を使った次のコードに書き直すことでロック、アンロックは要素につき1回となります。

list.forEach(p -> System.out.println(p));

ということで、「ラムダ禁止」にするとますます品質が危惧されるということに・・・

Stream APIは、Collection全体に一括して行う処理

Stream APIって何?ということに対して明快な説明がありました。数学的には「集合演算」とのこと、なるほど。

システムクロック(計算機の時刻)とDate、Clock

従来からあるDate型はシステムクロック依存、一方、JSR 310のDate and Time APIのClockはシステムクロックから分離されています。とのこと。

Java SE 8に標準搭載するにあたり落とされた仕様にUTC時刻やTAI時刻を扱うUtcInstant、TaiInstantがあり、"ThreeTen Extra"と呼ばれるそうです。

実用で付与すべきJavaVMオプション

トラぶったときの原因究明の手がかりを残すために、GCログ出力を指定しておくと、OutOfMemoryErrorでアプリケーションがとまったときに手がかりが得られます。
典型的なGCログ出力設定です。ログローテーション*2など新しい機能も取り入れられています。

-Xloggc:${環境変数使える}/log/gc.log.`date +%Y%m%d%H%M%S`
-XX:PrintGCDetails -XX:+PrintGCDateStamps
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M

次にOutOfMemoryErrorが実際に発生したときにヒープダンプを生成するオプションです。

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=${環境変数使える}/log/

OutOfMemoryError時のダンプはぜひ標準で指定するようにしたいところですね。
ところで、ログファイル名に日時を含めるための記述はUnixシェルスクリプト)環境特有の記述です。Windowsではどうすればよいのでしょうか?
調べてみると、スマートではないですが方法が見つかりました。
Windowsのバッチファイル中で日付をファイル名に使用する (1/2):Tech TIPS - @IT

次に、スタックトレースやクラスヒストグラムを取得するコマンドですが、以前はjstack、jmapコマンドを使いましたが、今はjcmdで指定するのがよいようです(前のコマンドも使える)。
なお、これらコマンドは定期的に実行したいので、cronなどを使います。

JPAのロック

JPAは未経験の分野なので、聞くことが新鮮でした。
JPAでは、楽観的ロックと悲観的ロックが基本6種類あるのでその特徴、JPA実装系(EclipseLinkとHibernate)での違い、などが整理されました。
楽観的ロックではデータベースのテーブルの列にVersionが追加されます。
JPAログを出すことを推奨しており、またJPAの出すログはDBでロックがあれば待ち行列に入るのでその点には注意する等の話がありました。

本日のセッションスライド

本日のセッションスライド一覧を調べてリンク集をつくっているブログがありました。
http://d.hatena.ne.jp/chiheisen/20140518/1400419566

*1:Stream APIの内部では遅延処理とかいろいろ・・・

*2:Java SE 6u34、7u2から導入