$30 off During Our Annual Pro Sale. View Details »

FRESH!_Kotlin_StyleGuide

AAkira
July 24, 2017

 FRESH!_Kotlin_StyleGuide

Kotlin style guide of FRESH!

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

AAkira

July 24, 2017
Tweet

More Decks by AAkira

Other Decks in Programming

Transcript

  1. FRESH!
    Kotlin Style Guide
    AAkira

    View Slide

  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}")

    View Slide

  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

    View Slide

  4. Kotlin助走読本
    ,PUMJOॿ૸ಡຊ΋গ͠ॻ͍ͨ
    IUUQTHPPHM"$K')

    View Slide

  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)
    • コルーチンも一部で導入済み

    View Slide

  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

    View Slide

  7. Kotlin採用の話
    IUUQTHPPHMX9UFI;
    IUUQTHPPHMQK$T

    View Slide

  8. FRESH! Kotlin style guide

    View Slide


  9. 本日の内容はチームとして用意しているStyle Guideの為、
    必ずしも正しい書き方とは限りません。
    必ずチームメンバと話し合ってから適用して下さい。

    View Slide

  10. Agenda
    • 思想
    • Rules
    • Idiom
    • null
    • Property
    • Scope function
    • Function
    • Others

    View Slide

  11. 思想

    View Slide

  12. 思想
    • Kotlinらしく書く(Lambda, Collections, 拡張関数)
    • Lambda式を積極的に使う
    • Scope関数等の言語で用意されている拡張関数を積極的に使う
    • (読みやすい範囲で) 短く書く
    • 短くしようとして読めないコードは意味がない
    • 考える必要のあるコードは汚いコード

    View Slide

  13. Rules

    View Slide

  14. Rules
    • /.idea/codeStyleSttings.xml のフォーマッタを使用する
    • 右端はandroid studioの線まで(大体130文字程度)

    View Slide

  15. Rules
    ӈʹ͋Δബ͍ઢ

    View Slide

  16. Idiom

    View Slide

  17. 定義
    • bad
    • 非推奨
    • not good
    • 書き方として問題はない、FRESHとしてのコード規約に沿ってはいない
    • good
    • FRESHとして推奨している書き方
    ※ bad, not goodいずれも言語としては正しい書き方でコンパイルは通る

    コーディング規約上のbad, not good, good

    View Slide

  18. IDEのSuggestionに従う
    class FeedFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // bad
    getActivity().finish()
    // good
    activity.finish()
    }
    }
    val array = ArrayList()
    // bad
    array.get(0)
    // good
    array[0]

    View Slide

  19. Smart castを有効利用する
    fun hoge(value: Boolean?) {
    value ?: return
    if (value) {
    }
    }
    fun hoge(context: Context) {
    context as Activity
    context.finish() // finish activity
    }

    View Slide

  20. Smart castを有効利用する
    // bad
    if (hoge !is Hoge) {
    throw IllegalArgumentException("not Hoge!")
    }
    hoge.foo()
    // good
    hoge as? Hoge ?: throw IllegalArgumentException("none Hoge!")
    hoge.foo()

    View Slide

  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の線に被った時に , : { = 等の記号で改行する

    View Slide

  22. Lambda式の中のitはスコープの広い方にする
    // bad
    { hoge ->
    hoge?.let { it.foo() }
    }
    // good
    {
    it?.let { hoge -> hoge.foo() }
    }

    View Slide

  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

    View Slide

  24. 型推論をなるべく使う
    fun Display.getSize(): Point = Point().apply { getSize(this) }
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    fun Display.getRealSize():Point = Point().apply { getRealSize(this) }
    分かりづらいと判断した場合は型を付ける

    View Slide

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

    View Slide

  26. Pairはtoで使う
    // bad
    Pair(foo, bar)
    // good
    foo to bar

    View Slide

  27. Pairはtoで使う
    public infix fun A.to(that: B): Pair = Pair(this, that)
    正体はinfix

    View Slide

  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
    }

    View Slide

  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 {
    }

    View Slide

  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

    View Slide

  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"

    View Slide

  32. Null

    View Slide

  33. !!は使わない
    • Null pointer exception を明示的に起こしたい時のみ
    • それ以外はKotlinのメリットを消すので絶対に使わない

    View Slide

  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()
    }
    }

    View Slide

  35. Property

    View Slide

  36. Property
    • Propertyにm Prefixは使用しない
    • 極力non-nullにする
    • 優先順位
    • non-null & val
    • non-null & var
    • nullable & var

    View Slide

  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

    View Slide

  38. Property
    • Non-nullに出来ない
    • lateinitで対処
    • Delegates.notNull()で対処
    • Delegates.notNul()はreflection使ってるので基本的にはlateinitを使う
    ※ただし、lateinitはprimitive型は対応していない 

    Int, Long, Boolean等, Stringは使用可

    View Slide

  39. 定数
    • Javaでいうstatic finalはconst valを使う

    ※ constはprimitive型にしか使えない
    • Java変換をすると下にくるが 、companion objectは

    class宣言直下(一番上)に書く

    View Slide

  40. Scope function

    View Slide

  41. スコープ関数をなるべく使う
    class Hoge {
    fun fun1() {}
    fun fun2() {}
    }
    // bad
    val hoge = Hoge()
    hoge.fun1()
    hoge.fun2()
    // good
    val hoge = Hoge().apply {
    fun1()
    fun2()
    }

    View Slide

  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

    View Slide

  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)
    }
    }
    スコープ関数を使って回避可能だが、過度にやりすぎると読みづらくなるデメリットもある

    思考しないと読めない場合は変数宣言しても良い

    View Slide

  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を使う

    View Slide

  45. Function

    View Slide

  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"
    }
    変数の型推論同様、基本的に戻り値の型は省略している

    戻り値はメソッド名から推測不能な場合に書くことを推奨する

    もちろん書いても良い

    View Slide

  47. setHoge, getHogeは使わない
    // bad
    fun setHoge() {
    }
    Kotlinのgetter,setterと被るので

    プロパティ、関数にsetHoge, getHogeは使わない

    View Slide

  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")
    }
    }

    View Slide

  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式で書ける

    View Slide

  50. 拡張関数
    • javaとかでいうUtil系の関数と相性が良い
    • Stringとかに生やすとスコープが広くなるので、無理に拡張関数にする必要はない 

    (その場合後述のprivate拡張関数を検討する)
    • 拡張関数のクラス名はHogeExt.kt
    • package main 

    |—data 

    |—ui

    |—util 

    | |—ext 

    |—util 

    |—ext

    View Slide

  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 }
    }
    }
    引数にそのオブジェクトしか取らない関数は拡張関数にした方が見やすい

    (必ずではない)

    View Slide

  52. 同Classの中に拡張関数作らない
    class Hoge {
    // bad
    fun Hoge.foo() {
    }
    // good
    fun foo() {
    }
    }

    View Slide

  53. Others

    View Slide

  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)
    })

    View Slide

  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)
    })

    View Slide

  56. Conclusion

    View Slide

  57. Conclusion
    • Javaよりもコード規約を定める必要性が高い
    • 好みの部分も多いのでチームで話し合って決める
    • Github(FRESHのリポジトリ)に公開した(English)
    https://github.com/openfresh/android-kotlin-style-guide

    View Slide