😀

EclipseCollectionsをプロダクトで導入した結果、メリット・デメリット

2016/11/02に公開

「EclipseCollectionsって何?」って方は下記をご覧ください。サンプルをいくつか記載しております。
EclipseCollections(旧GS Collections)実践


##1 導入経緯

  • 手続き型主体の実装では冗長になっており、また元々java8には移行済み(ただし書き方はjava7と変わらず)
     だったこともあり関数型に移行したかった。
  • チームの方針として一定のペースで新技術を導入していた。
  • 対象プロダクトのjava使用したアプリケーションの規模感はそこまで大きいものではなかった。

当時のチームの状況は上記で、元々冗長なコードを排除したい、新技術を導入してみたいと考えていたためEclipseCollectionsへの移行はちょうどいい規模感でした。

##2 導入するにあたって
 実は当初の予定ではjava8で提供されているStreamを使用する予定でした。
極力不要な余計なライブラリは入れたくなかったですし、Streamであれば今後もサポートされる可能性が高いためです。
 ただ、Streamを利用すると必ずStream呼び出し終端操作を加える必要があり、思ったほどコードが短くならない。誤って再利用すると例外が発生する(実行しないと分からない)ためバグの原因になりうるため導入を見送りました。
 
 EclipseCollectionsの導入対象がbatchのため、比較的他に依存しない小さなプロセスに対して仮導入・検証し開発や運用の観点で問題ないことを確認後導入しました。

##3 導入した上でのメリット・デメリット
####3.1 メリット

① nullチェックから解放される

下記のようなリストを取得する度に行っていたnullチェックが不要になりました。

before
List<User> users = findUsers();
if(CollectionUtils.isEmpty(users)){
  return;
}
for(User user: users){
 // 処理
}
after
findUsers().collect(u -> //処理);

②複雑な処理がスッキリする

条件分岐やループが絡み合うような複雑な処理でも、EclipseCollectionsのListやMap関連のリッチな関数群を使用すると、コード量が半分程度になります。

③変数のスコープが自然と小さくなる

これをメリットとするのは強引かもしれませんが、関数を利用すると関数内で使用する変数のスコープは関数内のみですので、自然とスコープは小さくなり、final変数であるため他の処理との依存関係が小さく保守性が向上します。
 もちろんこれは手続き型でも実装を徹底すれば可能ですが、明文化することなくチームに普及できるメリットが有ると感じました。

after
//変数uはfinal変数かつスコープはcollect内
findUsers().collect(u -> //処理);

####3.2, デメリット

①Optionalとの連携は完全ではない。

例えば下記のように最初の要素を取得する場合、usersが空の場合nullが返却されます。
nullが存在しうる戻り値をOptionalで徹底している場合、Optionalでラップする必要があります。

getFirst()
ImmutableList<User> users = findUsers();
// nullが渡される。
User user = users.getFirst(); 
// nullを避ける場合Optionalでラップする必要がある。
Optional<User> userOpt = Optional.ofNUllable(users.getFirst())

ただし2016年2Q,3QにOptionalに対応したAPIを導入予定のようです。(下記38P参照)
Eclipse Collections 最新アップデート

②例外ハンドリングがダサい。

普通に実装すると下記のようになります。個人差もあるとは思いますが人によっては違和感があるかもしれません。

関数内での例外でハンドリングする場合
findUsers()
  .each( u -> { 
     try{
       imageDao.saveImage(u.getId(), u.getProfileImg());
     } catch (InvalidDataException e) {
       //例外後の処理
     }
  });

もちろん、EclipseCollectionsでは、この問題に対しての解決策を用意しています。
Proceduresクラスのthrowingを利用すると簡潔に例外ハンドリングを実施できます。
ただしこれはRuntimeExceptionをラップするだけなので、プロジェクト固有の例外をハンドリングすることはできません。

Procedures.throwing
findUsers()
  .each(Procedures.throwing(u -> {
    imageDao.saveImage(u.getId(), u.getProfileImg());
  }));

##4
 以上が導入した上でのメリット、デメリットでした。トータルで考えると導入してよかったと感じています。
まだまだアップデートされそうなので、今後を楽しみにしていきましょう。

参考文献

Eclipse Collections 最新アップデート (2016/1/26)
http://www.slideshare.net/kanjava-jug/eclipse-collections

Discussion