Eclipse Collectionsで学ぶ コード品質向上の勘所

Eclipse Collectionsで学ぶ
コード品質向上の勘所
ゴールドマン・サックス
テクノロジー部 ヴァイス・プレジデント
伊藤博志
1
JJUG CCC 2016 Spring
#ccc_gh2
Agenda
• 理論編:コードの品質
– コードの品質とは
– 技術的負債とは
– リファクタリングの重要性
• 実践編:Eclipse Collectionsにおけるコード品質
– 品質を意識したAPI設計
– パフォーマンス改善の勘所
– 保守性・生産性向上のための施策
– 技術的負債との付き合い方
– コードレビューのプラクティス
2
理論編:コードの品質
3
コードの品質とは?
• 「コードの品質」と「ソフトウェアの品質」は同義ではない
– オーバーラップする部分はあるが、かならずしもイコールではない
• 実装時点で直接ソフトウェアの価値に影響を与えるコード品質
–
–
–
–
インタフェースの品質(UI、API)
実装の要求適合性・バグの存在
実装のパフォーマンス(メモリ使用量、実行速度)
etc.
• 実装時点では必ずしもソフトフェアの価値に影響を与えないが、
時間軸に沿ったソフトウェアの価値の増加率(生産性)に影響を
与えるコード品質
–
–
–
–
保守性(可読性、一貫性、簡潔性、等々)
拡張性、再利用性
テストカバレッジ
etc.
4
コードの品質とは?
実装時点ではソフトフェアの価値に影響を与えないが、
時間軸に沿ったソフトウェアの価値の増加率に影響を与えるコード品質
ソフトウェア
の価値
ソフトウェア
の価値
ソフトウェア
の価値
ソフトウェア
の価値
時間軸
実装時点で直接ソフトウェアの価値に影響を与えるコード品質
5
技術的負債とは?
• 開発リソースは有限である
–
–
–
–
開発者数
開発者のスキル
開発期間
開発環境の充実度
技術的負債
ソフトウェア
の価値
(資産)
高品質コード
(自己資本)
• 技術的負債
– 会計用語の負債のアナロジー
– コードの品質と引き換えにソフトウェアをリリースするスピードを重視した実装
– 時間を経るにつれ、利子を払うかのごとく開発生産性を圧迫する
• 限られたリソースの中で最大の資産(ソフトウェアの価値)を形成するた
めに必要なコード品質のバランスを見極める
– 高品質を維持したまま実装したコード(自己資本)
– 技術的負債を抱えながら実装したコード(負債)
– ソフトウェアの価値(資産)
=高品質コード(自己資本) + 技術的負債
6
(実際にはここまで単純にモデル化はできないが、あくまで会計用語とのアナロジーとして)
技術的負債とは?
実装時のソフトウェアの価値↑
ソフトウェアの価値の増加率↓
技術的負債
技術的負債
技術的負債
高品質コード
高品質コード
技術的負債
高品質コード
高品質コード
時間
例:
簡易的なコピー&ペースト
によって似たような機能を
一時的に増やすことが可能
技術的負債
変更範囲が増えることに
よって保守性・変更容易性
が下がる
高品質コード
技術的負債
技術的負債
高品質コード
高品質コード
7
技術的負債
高品質コード
リファクタリングの重要性
リファクタリングとは、ある一時点においてソフトウェアの価値を変化させ
ることなく技術的負債を高品質コードに変換する(負債を返済する)ことと
いえる
計画的な負債の返済計画により初期の開発スピードとソフトウェアの高成
技術的負債
長率を両立することが可能
技術的負債
高品質コード
技術的負債
高品質コード
技術的負債
高品質コード
高品質コード
時間
8
コードの品質のバランス
– ソフトウェアの特性によってどこの品質に重きを置くかは
変わってくる
– コードの品質を議論する際には文化的な側面も多く、開
発チームで「良いコード」「悪いコード」の認識を共有する
ことも非常に重要
• 組織のエンジニアリング文化
• プロジェクトチームのエンジニアリング文化
• 個人がエンジニアとして育ってきた環境
9
実践編:
Eclipse Collectionsにおける
コード品質
10
Eclipse Collectionsとは?
GS Collections
• ゴールドマン・サックスの開発したオープンソースコレクショ
ンフレームワーク
– Smalltalkに影響を受けたコレクションフレームワーク
– 2004年より開発を開始
– 2012年にGitHubにて公開 (Apache 2.0 License)
github.com/goldmansachs/gs-collections
Eclipse Collections
• GS CollectionsをEclipse財団へ移管し、名称・ライセンス変更
– コミュニティで育てるフレームワークへ
– 2015年12月にGitHubにて公開 (EPL/EDL Dual License)
– 2016年1月に公式リリース
https://www.eclipse.org/collections/ja/
11
コード品質@Eclipse Collections
• 汎用ライブラリとしての価値
– API設計レベルでの一貫性、簡潔性、可読性、発見容易性
• コレクションライブラリとしての価値
– パフォーマンスの最適化
• メモリ使用量
• 実行速度
• 多くの開発者を要するオープンソースソフトウェアとしての生
産性
– コーディングスタンダードと静的解析ツールの必要性
– 保守性の確保(DRYの徹底、テストカバレッジ)
• 技術的負債とのつきあい方
• コードレビューのプラクティス
12
コード品質@Eclipse Collections
• 汎用ライブラリとしての価値
– API設計レベルでの一貫性、簡潔性、可読性、発見容易性
• コレクションライブラリとしての価値
– パフォーマンスの最適化
ソフトウェ
アの価値
• メモリ使用量
• 実行速度
• 多くの開発者を要するオープンソースソフトウェアとしての生
産性
– コーディングスタンダードと静的解析ツールの必要性
– 保守性の確保(DRYの徹底、テストカバレッジ)
• 技術的負債とのつきあい方
• コードレビューのプラクティス
13
ソフト
ウェアの
価値
ソフト
ウェアの
価値
コード品質@Eclipse Collections
• 汎用ライブラリとしての価値
– API設計レベルでの一貫性、簡潔性、可読性、発見容易性
• コレクションライブラリとしての価値
– パフォーマンスの最適化
• メモリ使用量
• 実行速度
• 多くの開発者を要するオープンソースソフトウェアとしての生
産性
– コーディングスタンダードと静的解析ツールの必要性
– 保守性の確保(DRYの徹底、テストカバレッジ)
• 技術的負債とのつきあい方
• コードレビューのプラクティス
14
API設計における品質
一貫性・簡潔性
例:一貫したファクトリメソッドの設計
List, Set, Bag, Map, Multimap等、すべてのコンテナに対してファクトリメソッドが存在
MutableList<String> mutableList = Lists.mutable.of("One", "One", "Two", "Three");
MutableSet<String> mutableSet = Sets.mutable.of("One", "One", "Two", "Three");
MutableBag<String> mutableBag = Bags.mutable.of("One", "One", "Two", "Three");
MutableMap<String, String> mutableMap =
Maps.mutable.of("key1", "value1", "key2", "value2", "key3", "value3"
Multimap<String, String> multimapWithList =
Multimaps.mutable.list.of("key1", "value1-1", "key1", "value1-2", "key2
"value2-1");
それぞれMutable, Immutableのファクトリが存在
MutableList<String> mutableList = Lists.mutable.of(“One”, “Two”, “Three”);
ImmutableList<String> immutableList =
Lists.immutable.of("One", "Two", "Three");
List, Set, Map等のコンテナにはメモリ効率のよいFixedSize用のファクトリも存在
FixedSizeList<String> fixedSizeList =
Lists.fixedSize.of("One", "Two");
15
安全性
API設計における品質
例:Immutableインタフェースによる型レベルでの安全性確保
Unmodifiable List from JDK
RichIterable
Bag, Set, List,
Stack, Map, etc.
Readable
Interface
実行時例外!
ImmutableList from Eclipse Collections
Mutable
Interface
そもそも破壊的代入不可
add(), addAll()等
破壊的メソッドが存在
16
Immutable
Interface
破壊的メソッドが存在
しない
API設計における品質
発見容易性
例:Utilityを介さずにコレクション直下に存在する豊富なAPI群
17
API設計における品質
可読性
Eclipse Collections
Stream API
例:APIの命名には細心の注意を払う
List<Person> peopleWithCats = this.people.stream().filter(person -> person.hasPet(PetType.CAT))
.collect(Collectors.toList());
List<Person> peopleWithoutCats = this.people.stream().filter(person -> !person.hasPet(PetType.CAT))
.collect(Collectors.toList());
Map<Boolean, List<Person>> peopleWithCatsAndNoCats = this.people.stream().collect(
Collectors.partitioningBy(person -> person.hasPet(PetType.CAT)));
peopleWithCatsAndNoCats.get(true);
peopleWithCatsAndNoCats.get(false);
MutableList<Person> peopleWithCats = this.people.select(person -> person.hasPet(PetType.CAT));
MutableList<Person> peopleWithoutCats = this.people.reject(person -> person.hasPet(PetType.CAT));
PartitionMutableList<Person> peopleWithCatsAndNoCats = this.people.partition(
person -> person.hasPet(PetType.CAT));
peopleWithCatsAndNoCats.getSelected();
peopleWithCatsAndNoCats.getRejected();
18
コード品質@Eclipse Collections
• 汎用ライブラリとしての価値
– API設計レベルでの一貫性、簡潔性、可読性、発見容易性
• コレクションライブラリとしての価値
– パフォーマンスの最適化
• メモリ使用量
• 実行速度
• 多くの開発者を要するオープンソースソフトウェアとしての生
産性
– コーディングスタンダードと静的解析ツールの必要性
– 保守性の確保(DRYの徹底、テストカバレッジ)
• 技術的負債とのつきあい方
• コードレビューのプラクティス
19
コレクション実装の比較
HashSet vs UnifiedSet
JDK HashSet
集合としての性質を
HashMapのKeyに委譲
Map.Entryオブジェクトと
Valueそのものが完全にメモリの無駄
ハッシュテーブル用のデータ構造としてシンプルな
Objectの配列を使用してメモリ効率向上
EclipseCollections UnifiedSet
ハッシュ衝突の解決法として
4つのフィールドを持つChainedBucket
で独自のハッシュチェインを実装
メモリ効率とスピードの両面で最適化
20
UnifiedSetの実装
21
UnifiedSetの実装
22
UnifiedSetの実装
23
パフォーマンス解析ツール紹介と
HashSetとUnifiedSetの比較
24
パフォーマンス最適化
• パフォーマンスの改善は、計測せずにすべきではない
• しかし、Javaのベンチマークは単純ではない
– ガベージコレクションの影響
– JVMのウォームアップ
– etc.
• パフォーマンス解析ツール
– Memory Test Bench (Java Specialist News Letter #193)
– JMH (Java Microbenchmark Harness)
25
Java Specialist News Letter #193
Memory Test Bench
26
メモリ効率の比較
(HashSet vs UnifiedSet)
60
JDK HashSet
50
Eclipse Collections UnifiedSet
Size (Mb)
40
30
20
10
0
Elements
27
JMH(Java Microbenchmark Harness)
• JMH: OpenJDKで提供されているマイクロベンチマークツール
– JVMのウォームアップをフレームワークが自動的に行ってくれる
– 設定された試行回数分の実行結果を元にスループットと誤差を判定
– さまざまな設定変更が容易
28
JMHのベンチマーク例
(HashSet vs UnifiedSet)
UnifiedSetが2倍近いスループットを達成
Benchmark
Mode
SetAddAllTest.HashSet_addAll
thrpt
SetAddAllTest.UnifiedSet_addAll thrpt
29
Cnt Score
Error
20 62.264 ± 1.416
20 116.593 ± 0.919
Units
ops/s
ops/s
コード品質@Eclipse Collections
• 汎用ライブラリとしての価値
– API設計レベルでの一貫性、簡潔性、可読性、発見容易性
• コレクションライブラリとしての価値
– パフォーマンスの最適化
• メモリ使用量
• 実行速度
• 多くの開発者を要するオープンソースソフトウェアとしての生
産性
– コーディングスタンダードと静的解析ツールの必要性
– 保守性の確保(DRYの徹底、テストカバレッジ)
• 技術的負債とのつきあい方
• コードレビューのプラクティス
30
コード品質の静的解析
• 多くの開発者がコミットするコードベースにおいては
一貫したコード品質を保つための施策が必要
– コーディングスタンダードの適用
– バグの可能性の解析
– テストカバレッジの計測
• Eclipse Collectionsで採用する静的解析ツール
– IDEA Inspection
– Checkstyle
– Findbugs
– Clover code coverage
31
コーディングスタンダード
コーディングスタンダードこそ文化的側面の最たるもの。
同一プロジェクト内で一貫していることが大事なのであり、組織やプロジェクトをまた
いだらスタイルが違うのは当たり前だと考えたほうが良い。
自動的にスタイルが適用される開発環境が理想。
32
例:IDEA Inspection
•
•
•
•
問題があったらタイプ時に直すのが一番早い
素早い視覚的なフィードバック
IDE上でauto-fixやスタイルの適用が可能
Eclipse CollectionsではIDEA Inspectionの設定をリポジトリで共有
– IDEからコミット時に自動でコード変換可
– 一括変換も容易
• 例
– Java 7アップデート時にジェネリクスをダイヤモンド演算子へ変換
– Java 8アップデート時に匿名クラスをラムダ式へ変換等
33
DRY原則
• Don’t Repeat Yourself
• Eclipse Collectionsにおけるチャレンジ
– 数百ものコンテナ実装 x それぞれ100以上のメソッドを持つ
– 重複が爆発するとメンテナンス不可能
– クラス設計が非常に重要
施策の例
• Java8デフォルトメソッドを活用したユニットテストの重複排除
• プリミティブコレクションのコード自動生成
34
Java 8デフォルトメソッドを活用した
ユニットテストの重複排除
DRY原則
• APIのテストはさまざまなシナリオが共通
– 例:select()のテストシナリオは数十種類あるコンテナでほとんど同じ
• コレクション特有のテストはそのグループ内でシナリオが共通
– 例:List用のテストはFastList、ListAdapter、SynchronizedMutableList等で
ほとんど同じ
• Java 8のデフォルトメソッドを活用
シナリオを共通化し、
違うレイヤーのテストシナリオを
mix-in可能に
35
Java 8デフォルトメソッドを活用した
ユニットテストの重複排除
DRY原則
• FastListTest上に存在する実装はnewWith(T… elements)のみ
• 上位の複数インタフェースから継承されたメソッド群でテストが実行される
36
プリミティブコレクション生成
DRY原則
• Eclipse Collectionsは8つすべてのプリミティブ型に対応したコ
レクションをもつ
– boolean, byte, char, short, int, long, float, double
• すべてを手で実装するのは非効率かつDRYに反する
• StringTemplateによるテンプレートからのコード生成
– 重複の排除
– 特殊ケースの柔軟な対応
• 例:booleanには必要ないメソッド、float/doubleのみ必要な誤差対応等
37
プリミティブコレクション生成
Int
int
38
DRY原則
プリミティブコレクション生成
39
DRY原則
テストカバレッジの重要性
• 頻繁に変更されるソフトウェアにおいては、テストがないとい
う技術的負債が一番利子が高い
• Eclipse CollectionsはTDDで育ってきたフレームワーク
–
–
–
–
新しい機能はすべてインタフェースとテストに追加
バグが発生したら必ずテスト上で再現
実装後にリファクタリングが容易
リグレッションテストとしての機能
40
eclipse-collections-testutils
• コレクション用の独自assertionフレームワークを実装
• 他のモジュール同様Maven Centralからダウンロード可
– groupId: org.eclipse.collections
– artifactId: eclipse-collections-testutils
– Version: 7.1.0
41
コード品質@Eclipse Collections
• 汎用ライブラリとしての価値
– API設計レベルでの一貫性、簡潔性、可読性、発見容易性
• コレクションライブラリとしての価値
– パフォーマンスの最適化
• メモリ使用量
• 実行速度
• 多くの開発者を要するオープンソースソフトウェアとしての生
産性
– コーディングスタンダードと静的解析ツールの必要性
– 保守性の確保(DRYの徹底、テストカバレッジ)
• 技術的負債とのつきあい方
• コードレビューのプラクティス
42
技術的負債とのつきあい方
• 技術的負債を完全になくすのは、ある程度の規模のソフトウェア開発に
おいてはほぼ不可能といえる
• 良いソフトウェアは技術的負債を「コントロール」する
– 費用対効果を見積もる
– 優先順位をつけ、技術的負債を戦略的に採用する
– 返済計画を立てる
• 例:UnifiedSetとUnifiedSetWithHashingStrategyの重複
– UnifiedSetWithHashingStrategyは初期実装時点でUnifiedSetとのコード重複
が多く存在
– 戦略的に反DRYを許容
• そもそもの実装の複雑性から、重複の排除には時間がかかる
• 重複は2クラスのみにしか存在せず、保守性への影響は限定的
• テストカバレッジを保障することで将来のリファクタリングは安全に実施できる
• すぐに重複を排除するよりも他の機能にリソースを投資したほうがライブラリにとっての価値
が高い
– Issue Trackerに登録し、フォローアップ
– バージョン7.0のリリースでは重複を排除
43
コード品質@Eclipse Collections
• 汎用ライブラリとしての価値
– API設計レベルでの一貫性、簡潔性、可読性、発見容易性
• コレクションライブラリとしての価値
– パフォーマンスの最適化
• メモリ使用量
• 実行速度
• 多くの開発者を要するオープンソースソフトウェアとしての生
産性
– コーディングスタンダードと静的解析ツールの必要性
– 保守性の確保(DRYの徹底、テストカバレッジ)
• 技術的負債とのつきあい方
• コードレビューのプラクティス
44
GitHub上でのコードレビュー
• 最重要:開発者間でコードの品質に
関しての共有認識を持つ
• コミッター同士でも常にプルリクを通し
てピアレビュー
• レビュー中の議論から別の変更が生
まれることも
45
GitHub上でのコードレビュー
• すべてのコードをGitHub(ブラウザ上)のみでレビューするのはなか
なか大変
• ローカルの環境にプルリクエストを適用してIDE上で変更をレビュー
する方法
– git remote add upstream https://github.com/eclipse/eclipse-collections.git
– .git/configに下記の赤線行を追加
[remote "upstream"]
url = https://github.com/eclipse/eclipse-collections.git
fetch = +refs/heads/*:refs/remotes/upstream/*
fetch = +refs/pull/*:refs/remotes/upstream/pr/*
– git fetch --all
– git merge upstream/pr/XX/head
• XXにはプルリク#が入る
– git reset --soft HEAD^
• IDE上で変更リストとして認識されるようにコミットをソフトリセット
• IDE上でのナビゲーションを最大限活用してレビュー可能
46
まとめ
• コードの品質向上を考える上で大切な前提条件
– 開発するソフトウェアの価値を最大化するために重要な品質を見極める
– 開発チーム内でコード品質に関する価値観を共有する
– 高品質コードと技術的負債のバランスを戦略的に考える
– 適切な静的解析の自動化とテストカバレッジでコード品質をコントロール
する
• 上記4点がそろった上でのコードレビューは非常に建設的で生
産的なものになる
47
Eclipse Collectionsに
コントリビュートしてみよう
開発に貢献してみたい方はお知らせ下さい!!
How To Contribute: https://github.com/eclipse/eclipsecollections/blob/master/CONTRIBUTING.md
要約:Eclipse財団のCLAにサインしてプルリクエストでコントリビュート
Issues: https://github.com/eclipse/eclipse-collections/issues
コントリビュート例
– 興味のあるIssueにサインアップして実装
– ドキュメント、チュートリアルの拡充、日本語化
– Kataの日本語化
48
問題を解きながら学ぶEclipse Collections
Eclipse Collectionsには他にも便利な機能が多数備わっています。
さらにEclipse Collectionsを学んでみたい方は、Kataを解いてみてください。
Eclipse Collections Kata
https://github.com/eclipse/eclipse-collections-kata
- ユニットテストをひとつずつパスしていくTDD型トレーニングマテリアル
- Eclipse Collections/GS Collectionsの使用法を学習するのに最適
- ゴールドマン・サックスの研修にも使用
- Stream APIやラムダ式の学習にも使える
スポンサーブースにて
実演します。
ぜひお越し下さい。
49
Resources
• Eclipse Collections website
https://www.eclipse.org/collections/ja/
• Eclipse Collections最新アップデート
http://www.slideshare.net/kanjava-jug/eclipse-collections
• Eclipse Collections on GitHub
https://github.com/eclipse/eclipse-collections
https://github.com/eclipse/eclipse-collections/wiki
https://github.com/eclipse/eclipse-collections-kata
• Parallel-lazy Performance: Java 8 vs Scala vs
GS Collections
http://www.infoq.com/presentations/java-streams-scala-parallel-collections
Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.
50
Learn more at GS.com/Engineering
© 2016 Goldman Sachs. This presentation should not be relied upon or considered investment advice. Goldman Sachs does not warrant or guarantee to anyone the accuracy, completeness or efficacy of this
presentation, and recipients should not rely on it except at their own risk. This presentation may not be forwarded or disclosed except with this disclaimer intact.
51