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
© Copyright 2024 ExpyDoc