Upgrade to Pro — share decks privately, control downloads, hide ads and more …

KotlinFest.pdf

Yuki Anzai
August 25, 2018
12k

 KotlinFest.pdf

Yuki Anzai

August 25, 2018
Tweet

Transcript

  1. ソフトウェアの品質 = 要因の組み合わせ • 外的品質要因 • ソフトウェア製品にその性質があるかないかをユーザーが認 識できる性質 • スピード、使いやすさ、…

    • 内的品質要因 • ソフトウェア製品について形容されるそれ以外の性質 • モジュール性、読みやすさ、…
  2. 外的品質要因の特に重要な4要因 • 正確さ(correctness) • 頑丈さ(robustness) • 信頼性 = 正確さ +

    頑丈さ • 拡張性(extendibility) • 再利⽤性(reusability) • モジュール性 = 拡張性 + 再利⽤性
  3. 項⽬2 数多くのコンストラクタパラメータに直⾯した時には ビルダーを検討する Kotlin class NutritionFacts( val servingSize: Int, val

    servings: Int, val calories: Int = 0, val fat: Int = 0, val sodium: Int = 0, val carbohydrate: Int = 0 ) NutritionFacts( 240, 8, calories = 100, sodium = 35, carbohydrate = 27 )
  4. AlertDialog( message = "削除しますか?", negativeButtonLabel = "はい", positiveButtonListener = {

    delete() } ) class AlertDialog( private val title: String? = null, private val message: String? = null, private val positiveButtonLabel: String? = null, private val positiveButtonListener: (() -> Unit)? = null, private val negativeButtonLabel: String? = null, private val negativeButtonListener: (() -> Unit)? = null ) NG Kotlin
  5. AlertDialog.Builder() .message("削除しますか?") .positiveButton("はい") { delete() } .build() class AlertDialog private

    constructor(…) { class Builder { … private var positiveButtonLabel: String? = null private var positiveButtonListener: (() -> Unit)? = null … fun positiveButton(label: String, listener: (() -> Unit)?): Builder { positiveButtonLabel = label positiveButtonListener = listener return this } … } } OK 不変式を構成するパラメータ全体をセッターで受け取る Kotlin
  6. 項⽬4 private のコンストラクタでインスタンス化不可能を強 制する オブジェクト宣⾔ or トップレベル関数 fun util1(a: Int,

    b: Int): Int { … } public class UtilityClass { private UtilityClass() { } public static int util1(int a, int b) { … } } Kotlin
  7. 項⽬8 equals をオーバーライドするときは⼀般契約にしたが う public class MyClass { public boolean

    equals(MyClass o) { … } } class MyClass { override fun equals(o: MyClass): Boolean { … } } Java では書けてしまう Kotlin ではコンパイルエラー
  8. 項⽬11 clone を注意してオーバーライドする public class Object { … protected native

    Object clone() throws CloneNotSupportedException; } 「オブジェクトのコピーを⾏う何らかの代替⼿段を提供するか、 オブジェクトの複製を単に提供しない⽅がおそらく賢明です。」 Kotlin の Any には clone() がない Kotlin
  9. 項⽬11 clone を注意してオーバーライドする 「オブジェクトのコピーに対する上⼿い⽅法は、コピーコンスト ラクタかコピーファクトリを提供することです。」 data class の copy() val

    donutsBook = Book("donuts", "Android") val eclairBook = donutsBook.copy(title = "eclair") Iterable の拡張関数 toList() val list = listOf("donuts", "eclair") val list2 = list.toList() Kotlin
  10. data class Person( val name: String, val age: Int )

    : Comparable<Person> { override fun compareTo(other: Person): Int { age.compareTo(other.age).let { if (it != 0) { return it } } return name.compareTo(other.name) } } 項⽬12 Comparable の実装を検討する Kotlin の数値型,Boolean,String,Charは compareTo() が ⽤意されている 年齢昇順 → 名前ABC順 Kotlin
  11. 項⽬12 Comparable の実装を検討する 標準関数を活⽤する data class Person( val name: String,

    val age: Int ) : Comparable<Person> { override fun compareTo(other: Person): Int { return compareValuesBy(this, other, { it.age }, { it.name }) } } 年齢昇順 → 名前ABC順 Kotlin
  12. 項⽬14 public のクラスでは、public のフィールドではな く、アクセサーメソッドを使う public class Person { private

    String name; private int age; … public String getName() { return name; } public void setName(String name) { this.name = name; } … } OK
  13. 項⽬15 可変性を最⼩限にする • 1. オブジェクトの状態を変更するためにいかなるメソッド も提供しない • 2. クラスが拡張できないことを保証する。 •

    3. すべてのフィールドを final にする • 4. すべてのフィールドを private にする • 5. 可変コンポーネントに対する独占的アクセスを保証す る。
  14. 項⽬15 可変性を最⼩限にする • 1. オブジェクトの状態を変更するためにいかなるメソッド も提供しない • 2. クラスが拡張できないことを保証する。 •

    3. すべてのプロパティを val にする • 4. 可変コンポーネントのプロパティを private にする • 5. 可変コンポーネントに対する独占的アクセスを保証す る。 Kotlin
  15. 項⽬16 継承よりコンポジションを選ぶ 「継承は強⼒ですが、カプセル化を破ってしまうので問題があ ります。」 public class InstrumentedSet<E> extends HashSet<E> {

    private int addCount = 0; @Override public boolean add(E e) { addCount++; return super.add(e); } @Override public boolean addAll(@NotNull Collection<? extends E> c) { addCount += c.size(); return super.addAll(c); } } NG
  16. public class InstrumentedSet<E> implements Set<E> { private final Set<E> s;

    private int addCount = 0; public InstrumentedSet(Set<E> s) { this.s = s; } @Override public boolean add(E e) { addCount++; return s.add(e); } @Override public boolean addAll(@NotNull Collection<? extends E> c) { addCount += c.size(); return s.addAll(c); } @Override public int size() { return s.size(); } … } OK
  17. public class InstrumentedSet<E> implements Set<E> { private final Set<E> s;

    private int addCount = 0; public InstrumentedSet(Set<E> s) { this.s = s; } @Override public boolean add(E e) { addCount++; return s.add(e); } @Override public boolean addAll(@NotNull Collection<? extends E> c) { addCount += c.size(); return s.addAll(c); } @Override public int size() { return s.size(); } @Override public boolean isEmpty() { return s.isEmpty(); } @Override public boolean contains(Object o) { return s.contains(o); } @NotNull @Override public Iterator<E> iterator() { return s.iterator(); } @NotNull @Override public Object[] toArray() { return s.toArray(); } @NotNull @Override public <T> T[] toArray(@NotNull T[] a) { return s.toArray(a); } @Override public boolean remove(Object o) { return s.remove(o); } @Override public boolean containsAll(@NotNull Collection<?> c) { return s.contains(c); } @Override public boolean retainAll(@NotNull Collection<?> c) { return s.retainAll(c); } @Override public boolean removeAll(@NotNull Collection<?> c) { return s.removeAll(c); } @Override public void clear() { s.clear(); } } 転送してるだけ
  18. class InstrumentedSet2<E>( private val s: MutableSet<E> ) : MutableSet<E> by

    s { private var addCount = 0 override fun add(element: E): Boolean { addCount++ return s.add(element) } override fun addAll(elements: Collection<E>): Boolean { addCount += elements.size return s.addAll(elements) } } Kotlin
  19. 項⽬17 継承のために設計および⽂書化する、でなければ継承 を禁⽌する public final class Point { public class

    Point { private Point(int x, int y) { … } public static Point of(int x, int y) { return new Point(x, y); } クラスを final と宣⾔する コンストラクタを private かパッケージプライベートにして、代 わりに static ファクトリーメソッドを追加する
  20. 項⽬19 型を定義するためだけにインタフェースを使⽤する 定数ユーティリティクラス + static インポート public class PhysicalConstants {

    private PhysicalConstants() {} // インスタンス化を防⽌止 public static final double AVOGADROS_NUMBER = 6.02214199e23; } import static net.yanzm.sample.PhysicalConstants.AVOGADROS_NUMBER; public class Atoms { public double atoms() { return AVOGADROS_NUMBER * mols; } … } OK
  21. 項⽬19 型を定義するためだけにインタフェースを使⽤する Interface に定数を定義できない interface PhysicalConstants { const val AVOGADROS_NUMBER

    = 6.02214199e23 } object PhysicalConstants { const val AVOGADROS_NUMBER = 6.02214199e23 } コンパイルエラー OK トップレベルに定数を定義できる const val AVOGADROS_NUMBER = 6.02214199e23 定数ユーティリティとして object を使えば不要なインスタンス化 を防げる OK Kotlin
  22. public class Figure { enum Shape {RECTANGLE, CIRCLE} final Shape

    shape; double length, width; // RECTANGLE の場合だけ使われる double radius; // CIRLE の場合だけ使われる Figure(double length, double width) { shape = Shape.RECTANGLE; this.length = length; this.width = width; } Figure(double radius) { shape = Shape.CIRCLE; this.radius = radius; } double area() { switch (shape) { case RECTANGLE: return length * width; case CIRCLE: return Math.PI * radius * radius; default: throw new AssertionError(); } } } NG
  23. abstract class Figure { abstract double area(); } public class

    Rectangle extends Figure { final double length, width; … @Override double area() { return length * width; } } public class Circle extends Figure { final double radius; … @Override double area() { return Math.PI * radius * radius; } } OK
  24. sealed class を使ったクラス階層で置き換える sealed class Figure { abstract fun area():

    Double } class Rectangle(val length: Double, val width: Double) : Figure() { override fun area(): Double { return length * width } } class Circle(val radius: Double) : Figure() { override fun area(): Double { return Math.PI * radius * radius } } Kotlin
  25. 項⽬21 戦略を表現するために関数オブジェクトを使⽤する public class Person { private final String name;

    private final int age; public Person(String name, int age) { this.name = name; this.age = age; } public static final Comparator<Person> NAME_LENGTH_ORDER = new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.name.length() - o2.name.length(); } }; } OK
  26. data class Person(val name: String, val age: Int) { companion

    object { val NAME_LENGTH_ORDER: Comparator<Person> = Comparator { o1, o2 -> o1.name.length - o2.name.length } } } list.sortedWith(Person.NAME_LENGTH_ORDER) data class Person(val name: String, val age: Int) { companion object { val NAME_LENGTH_ORDER: Comparator<Person> = compareBy { it.name.length } } } 標準関数を使⽤用 Kotlin
  27. 項⽬22 ⾮ static のメンバークラスより static のメンバーク ラスを選ぶ 「エンクロージングインスタンスへアクセスする必要がないメン バークラスを宣⾔言するのであれば、その宣⾔言に static

    修飾⼦子を常 に付ける」 Java メンバークラスの宣⾔言はデフォルトが⾮非 static → 明示的に static を付けないといけない メンバークラスの宣⾔言はデフォルトが not inner Kotlin
  28. 項⽬23 原型を使⽤しない List や Map ではなく、List<String>, Map<String,String> のよ うに常に型パラメータを指定する 移⾏互換性のために原型(List

    や Map)が残されている 原型は使⽤できず、コンパイルエラーになる Java Kotlin List list = new ArrayList<String>(); NG val list :List = ArrayList<String>() コンパイルエラー
  29. 項⽬46 従来の for ループより for-each ループを選ぶ for (int i =

    0; i < a.length; i++) { doSomething(a[i]); } NG OK for (Element e : elements) { doSomething(e); } for-each のみ Kotlin for (e in elements) { doSomething(e) }
  30. Java で for-each ループが使⽤できないよくある状況 1. フィルタリング 2. 変換 3. 並列イテレーション

    Kotlin val even = list.filter { it % 2 == 0 } val square = list.map { it * it } for (i in 0 until min(list1.size, list2.size)) { val a = list1[i] val b = list2[i] }
  31. fun main() { val a = "A" val b =

    "B" val c = a + b val d = "$a, $b" } public static final void main() { String a = "A"; String b = "B"; (new StringBuilder()).append(a).append(b).toString(); (new StringBuilder()).append(a).append(", ").append(b).toString(); } Decompile
  32. class MainActivity : AppCompatActivity() { private fun versionName(): String {

    return packageManager.getPackageInfo(packageName, 0) .versionName } } public class MainActivity extends AppCompatActivity { private String versionName() { try { return getPackageManager() .getPackageInfo(getPackageName(), 0) .versionName; } catch (PackageManager.NameNotFoundException e) { // ここには来ないはず return ""; } } } Java Kotlin
  33. まとめ • Effective Java の多くの項⽬について、Kotlin では⾔語仕 様で対応している • Kotlin に書き換えることで、「明瞭で、正しく、再利⽤可

    能で、頑強で、柔軟性があり、保守可能なプログラムを書 く」エッセンスが⾃然と取り⼊れられる
  34. おわり • blog : Y.A.M の雑記帳 • y-anz-m.blogspot.com • twitter

    : @yanzm (やんざむ) • uPhyca Inc. (株式会社ウフィカ)