実装オラオラ期
別件でJavaのコードを流し読みしていた時に、ふっと痛い過去を思い出した今日此の頃。。。
基本構文をマスターしプロジェクトを何個かやって、
一人でも問題なく実装が出来るようになってくると、
如何にコードを短く・シンプルに書きたくなる時期
って絶対にやってくるよね?
え、やってこない?もしかして意識低い系エンジニア?
と、当時の君ならきっとそう思うだろう。。。
いらない何も捨ててしまおう
Java6から登場した拡張For文
for(int i = 0;i < list.size(); i++)からfor(String x: list)となったことで、
ただListをぶん回したいだけなのにint i = 0を書かなきゃいけない地獄からの解放。
でも結局index使いたい時は普通for文で書くんだけどね。
Java8から登場したStream
BootstrapCSSとjQueryがまだCSS/JavaScript界隈でブイブイ言わしてた時に出来た構文というのもあり、
JavaでもjQueryみたいに.でメソッドを延々と繋いで書ける、書けるぞと大喜びしていたあの頃。
アレ?Streamさえあれば、もはやforとifは要らないんじゃね?
そんなふうに考えていた時期が、俺にもありました。
クソコードサンプル
List<String> result =
users.stream()
.filter(u -> u.getStatus() == ACTIVE)
.map(u -> u.getOrders())
.flatMap(List::stream)
.filter(o -> o.getPrice() > 1000)
.sorted(Comparator.comparing(Order::getDate).reversed())
.limit(5)
.map(o -> o.getUser().getName() + ":" + o.getPrice())
.collect(Collectors.toList());
書いてるときの自分 > 完璧。美しい。マジ卍。
数カ月後の自分 > ちょ……これ何やってんの?誰が書いたクソコードだよ。
問題点
① 文脈が1行に詰まりすぎている
- ユーザーを絞って
- 注文を取り出して
- 高額なものだけにして
- 並び替えて
- 上位5件取って
- 文字列に変換
👉 情報量が多すぎて脳が処理を拒否する
② デバッグが地獄
途中で値を確認したくなったとき
.peek(System.out::println)
を挟み始めると、もう終わり
③ 変更に弱い
「やっぱり条件1個追加で」
→ どこに入れるのが正しいのか分からなくなる
よくあるアンチパターン
1. なんでもStreamで書く
if (list.stream().anyMatch(...)) { ... }
普通にforでよくね?
もしくは別変数に結果を格納するとかして、分散しろよ。。。
2. ネストが深すぎる
list.stream()
.map(a -> a.getBList().stream()
.filter(...)
.map(...)
.collect(...))
StreamでStreamでStreamしだしたら、もう誰にも読ませる気がない自己満の境地かなと。
ifもforでもそうだけど、ネストが酷いと基本的に読む気失せるよね。。。
そういったネストに限って、すっげー複雑処理で長編大作だったりで読むだけでお腹いっぱいになるよね?
解決策
分割する
List<User> activeUsers = users.stream()
.filter(u -> u.getStatus() == ACTIVE)
.collect(Collectors.toList());
List<Order> orders = activeUsers.stream()
.flatMap(u -> u.getOrders().stream())
.collect(Collectors.toList());
List<Order> expensiveOrders = orders.stream()
.filter(o -> o.getPrice() > 1000)
.sorted(Comparator.comparing(Order::getDate).reversed())
.limit(5)
.collect(Collectors.toList());
List<String> result = expensiveOrders.stream()
.map(o -> o.getUser().getName() + ":" + o.getPrice())
.collect(Collectors.toList());
長くなったけど「何してるか」は一瞬で分かる
※実行時のメモリとか考えなくていいなら、新人でもギリ読めそうだよね
意味のあるメソッドに切り出す
private List<Order> findTopExpensiveOrders(List<User> users) {
return users.stream()
.filter(this::isActive)
.flatMap(this::toOrders)
.filter(this::isExpensive)
.sorted(this::compareByDateDesc)
.limit(5)
.collect(Collectors.toList());
}
「読めるコード」はジャスティス
個人的な判断基準
使おうかなと迷ったらこれ
- 1画面に収まるか?
- 日本語で説明できるか?
- 他人が5秒で理解できるか?
1つでもNOなら分割した方がいい
まとめ
- Streamは強いし正義
- でも強すぎて事故る(未来の巻き込み事故も)
- 「短いコード」より「読めるコード」
どんなに複雑な処理でも読めりゃいいんだよ、読めりゃ