FRESH!_Kotlin_StyleGuide

Daec7e5cd5fae384eda88037d937343b?s=47 AAkira
July 24, 2017

 FRESH!_Kotlin_StyleGuide

Kotlin style guide of FRESH!

Github : https://github.com/openfresh/android-kotlin-style-guide

Daec7e5cd5fae384eda88037d937343b?s=128

AAkira

July 24, 2017
Tweet

Transcript

  1. 2.

    $ whois private lateinit var aakira: User data class User(val

    name: String, val twitterId: String, val githubId: String, val company: String) println("Name : ${aakira.name}") println("Twitter Id : ${aakira.twitterId}") println("Github Id : ${aakira.githubId}") println("Company : ${aakira.company}")
  2. 3.

    @_a_akira AAkira CyberAgent, Inc. Akira Aratani private lateinit var aakira

    : User data class User(val name: String, val twitterId: String, val githubId: String, val company: String) print("Name : ${aakira.name}”) println("Github Id : ${aakira.githubId}") print("Twitter Id : ${aakira.twitterId}") println("Company : ${aakira.company}") $ whois
  3. 5.

    About • 生放送配信プラットフォーム ≠ AbemaTV • 最近 アプリを縦化! FRESH! AndroidアプリのUI/UX :

    https://developers.cyberagent.co.jp/blog/archives/7177 • M11の頃からFull Kotlinで開発 • 使ってるLibraryは大体最新
 (Kotlin1.1, Rx2, Dagger2, Retrofit2, Ok3, ExoPlayer2 etc) • コルーチンも一部で導入済み
  4. 6.

    私とKotlin M1 2012-04-12 M11 2015-03-19 M14 2015-10-01 1.0-beta4 2015-12-22 M13

    2015-09-16 1.0 2016-02-16 1.0-RC 2016-02-04 2016-01-21 Release 2015-04 開発開始 kotlin FRESH 1.1 2017-03-01 2017-05-15 Renewal
  5. 10.

    Agenda • 思想 • Rules • Idiom • null •

    Property • Scope function • Function • Others
  6. 11.
  7. 12.

    思想 • Kotlinらしく書く(Lambda, Collections, 拡張関数) • Lambda式を積極的に使う • Scope関数等の言語で用意されている拡張関数を積極的に使う •

    (読みやすい範囲で) 短く書く • 短くしようとして読めないコードは意味がない • 考える必要のあるコードは汚いコード
  8. 13.
  9. 16.
  10. 17.

    定義 • bad • 非推奨 • not good • 書き方として問題はない、FRESHとしてのコード規約に沿ってはいない

    • good • FRESHとして推奨している書き方 ※ bad, not goodいずれも言語としては正しい書き方でコンパイルは通る
 コーディング規約上のbad, not good, good
  11. 18.

    IDEのSuggestionに従う class FeedFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) // bad getActivity().finish() // good activity.finish() } } val array = ArrayList<Int>() // bad array.get(0) // good array[0]
  12. 19.

    Smart castを有効利用する fun hoge(value: Boolean?) { value ?: return if

    (value) { } } fun hoge(context: Context) { context as Activity context.finish() // finish activity }
  13. 20.

    Smart castを有効利用する // bad if (hoge !is Hoge) { throw

    IllegalArgumentException("not Hoge!") } hoge.foo() // good hoge as? Hoge ?: throw IllegalArgumentException("none Hoge!") hoge.foo()
  14. 21.

    改行 fun hoge(aaa: Int, bbb: Int, ccc: Int, ddd: Int,

    eee: Int, fff: Int, ggg: Int) { } fun hoge(aaa: Int, bbb: Int, ccc: Int, ddd: Int, eee: Int) = hoge().apply { } data class Hoge(private val aaa: Int, private val bbb: Int) : AbstractHoge() { } 基本的にはAndroid studioに表示されている線の箇所でする
 IDEの線に被った時に , : { = 等の記号で改行する
  15. 23.

    型推論をなるべく使う val hoge = 0 // Int val foo =

    10L // Long val bar = 100f // Float fun Context.isConnectToWifi() = (getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager) .activeNetworkInfo?.type == ConnectivityManager.TYPE_WIFI
  16. 24.

    型推論をなるべく使う fun Display.getSize(): Point = Point().apply { getSize(this) } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)

    fun Display.getRealSize():Point = Point().apply { getRealSize(this) } 分かりづらいと判断した場合は型を付ける
  17. 25.

    for文は書かない // not good for (i in 0..9) { }

    // good (0..9).forEach { } // good (index͕ཉ͍࣌͠) (0..9).forEachIndexed { index, value -> } Collections パッケージにあるforEachがあるのでfor文を書く必要はない
  18. 28.

    Rangeを利用 val char = 'K' // bad if (char >=

    'A' && 'c' <= 'Z') print("Hit!") // good if (char in 'A'..'Z') print("Hit!") when (char) { in 'A'..'Z' -> print("Hit!") else -> return }
  19. 29.

    if - elseの分岐が2つ以上ある場合にはwhenを使う // good when { hoge > 10

    -> print("10") hoge > 5 -> print("0") hoge > 0 -> print("0") else -> print("else") } // bad val hoge = 10 if (hoge > 10) { } else if (hoge > 5) { } else if (hoge > 0) { } else { }
  20. 30.

    when val hoge: Hoge = Hoge() when (hoge) { is

    Hoge -> { } else -> { } } val hoge = 10 when (hoge) { in 0..4 -> print("0..4") in 5..10 -> print("5..10") } is range
  21. 31.

    val if-elseは一行で書く val foo: Int = 5 // bad val

    bar = if (foo > 10) { "Kotlin" } else { "Java" } // good val bar = if(foo > 10) "Kotlin" else "Java"
  22. 32.
  23. 34.

    nullの比較は2つ以上の評価値がある場合の時に使う class Hoge { fun fun1() {} fun fun2() {}

    fun fun3() = true } var hoge: Hoge? = null // not good if (hoge != null) { hoge.fun1() } else { val hoge = Hoge() hoge.fun1() hoge.fun2() } // good hoge?.run { fun1() } ?: run { hoge = Hoge().apply { fun1() fun2() } } // good if (hoge != null && hoge.fun3()) { hoge.fun1() } else { hoge = Hoge().apply { fun1() fun2() } }
  24. 35.
  25. 37.

    Property // non-null & val private val hoge: Hoge =

    Hoge() private val drawablePadding: Int by lazy { activity.resources.getDimensionPixelSize(R.dimen.drawable_padding) } // non-null & var private lateinit var hoge: Hoge private var hoge: Hoge = Delegates.notNull() // nullable & var private var hoge: Hoge? = null
  26. 41.

    スコープ関数をなるべく使う class Hoge { fun fun1() {} fun fun2() {}

    } // bad val hoge = Hoge() hoge.fun1() hoge.fun2() // good val hoge = Hoge().apply { fun1() fun2() }
  27. 42.

    スコープ関数をなるべく使う class Hoge { fun fun1() {} fun fun2() {}

    } // bad val hoge = Hoge() hoge.fun1() hoge.fun2() // good val hoge = Hoge().apply { fun1() fun2() } ※ withは他で代用出来るので使わない run, let, apply, also
  28. 43.

    一度しか使わない変数は宣言しない class Foo class Bar(val foo: Foo) class Hoge {

    fun fun1() {} fun fun2(bar: Bar) {} fun fun3(foo: Foo) {} } // bad val hoge = Hoge() val foo = Foo() val bar = Bar(foo) hoge.fun1() hoge.fun2(bar) hoge.fun3(foo) // good Hoge().run { fun1() Foo().let { fun2(Bar(it)) fun3(it) } } スコープ関数を使って回避可能だが、過度にやりすぎると読みづらくなるデメリットもある
 思考しないと読めない場合は変数宣言しても良い
  29. 44.

    runとletの使い分け class Foo class Bar(val foo: Foo) class Hoge {

    fun fun1() {} fun fun2(bar: Bar) {} fun fun3(foo: Foo) {} } // not good Hoge().let { it.fun1() Foo().run { it.fun2(Bar(this)) it.fun3(this) } } // good Hoge().run { fun1() Foo().let { fun2(Bar(it)) fun3(it) } } 基本的に役割は一緒なので好みではあるが、
 関数の中に代入する場合はlet、関数の外で使う場合はrunを使う
  30. 45.
  31. 46.

    戻り値の型の省略 fun createIntent(context: Context, foo: Int, bar: Boolean) = Intent(context,

    HogeActivity::class.java).apply { putExtra("foo", foo) putExtra("bar", bar) } fun hoge(value: Int): String = when (value) { in 0..10 -> "foo" in 100..500 -> "bar" else -> "else" } 変数の型推論同様、基本的に戻り値の型は省略している
 戻り値はメソッド名から推測不能な場合に書くことを推奨する
 もちろん書いても良い
  32. 48.

    関数のoverloadは名前付き引数で書く // bad class Hoge { fun hoge() { print("hoge")

    } fun hoge(prefix: String) { print(prefix + "hoge") } } // good class Hoge { fun hoge(prefix: String = "") { print(prefix + "hoge") } }
  33. 49.

    typealias // bad interface CallBackListener { fun onHoge(foo: String, bar:

    Int) } // caller var callback: CallBackListener? = null callback?.onHoge("foo", 100) // callee val callback = object : CallBackListener { override fun onHoge(foo: String, bar: Int) { print("$foo : $bar") } } // good typealias CallBackListener = (foo: String, bar: Int) -> Unit // caller var callback: CallBackListener? = null callback?.invoke("foo", 100) // callee val callback = { foo, bar -> print("$foo : $bar") } typealiasを使えばLambda式で書ける
  34. 51.

    Private拡張関数 enum class Hoge() { FOO, BAR, NONE } //

    not good fun toHoge(arg: String): Hoge { if (!arg.startsWith("hoge")) { return Hoge.NONE } return when (arg) { "hogeFoo" -> Hoge.FOO "hogeBar" -> Hoge.BAR else -> Hoge.NONE } } // good fun String.toHoge(): Hoge { if (!startsWith("hoge")) { return Hoge.NONE } return when (this) { "hogeFoo" -> Hoge.FOO "hogeBar" -> Hoge.BAR else -> Hoge.NONE } } } 引数にそのオブジェクトしか取らない関数は拡張関数にした方が見やすい
 (必ずではない)
  35. 53.
  36. 54.

    Rx // bad Observable.just(10) .map { it * 3 }

    .filter { val rand = Random() it % rand.nextInt(10) == 0 } .map { it.toFloat() } .subscribe ({ print(it) }, { print(it) }) // bad
 Observable.just(10).map { it * 3 } .filter { val rand = Random() it % rand.nextInt(10) == 0 }.map { it.toFloat() } .subscribe ({ print(it) }, { print(it) })
  37. 55.

    Rx // good // Operatorຖʹվߦ͢Δ Observable.just(10) .map { it *

    3 } .filter {
 // ෳ਺ߦ͸վߦ͢Δ
 val rand = Random() it % rand.nextInt(10) == 0 }
 .map(Int::toFloat) // ϝιουࢀর
 .subscribe ({ // subscribeͷத͸onErrorΛ࣮૷͢Δ৔߹վߦ print(it) }, { print(it) })