Slide 1

Slide 1 text

FRESH! Kotlin Style Guide AAkira

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

@_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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

私と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

Slide 7

Slide 7 text

Kotlin採用の話 IUUQTHPPHMX9UFI; IUUQTHPPHMQK$T

Slide 8

Slide 8 text

FRESH! Kotlin style guide

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

思想

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Rules

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Rules ӈʹ͋Δബ͍ઢ

Slide 16

Slide 16 text

Idiom

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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]

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

型推論をなるべく使う 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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Slide 28

Slide 28 text

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 }

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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"

Slide 32

Slide 32 text

Null

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Property

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

定数 • Javaでいうstatic finalはconst valを使う
 ※ constはprimitive型にしか使えない • Java変換をすると下にくるが 、companion objectは
 class宣言直下(一番上)に書く

Slide 40

Slide 40 text

Scope function

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

スコープ関数をなるべく使う 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

Slide 43

Slide 43 text

一度しか使わない変数は宣言しない 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) } } スコープ関数を使って回避可能だが、過度にやりすぎると読みづらくなるデメリットもある
 思考しないと読めない場合は変数宣言しても良い

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Function

Slide 46

Slide 46 text

戻り値の型の省略 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" } 変数の型推論同様、基本的に戻り値の型は省略している
 戻り値はメソッド名から推測不能な場合に書くことを推奨する
 もちろん書いても良い

Slide 47

Slide 47 text

setHoge, getHogeは使わない // bad fun setHoge() { } Kotlinのgetter,setterと被るので
 プロパティ、関数にsetHoge, getHogeは使わない

Slide 48

Slide 48 text

関数の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") } }

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

拡張関数 • javaとかでいうUtil系の関数と相性が良い • Stringとかに生やすとスコープが広くなるので、無理に拡張関数にする必要はない 
 (その場合後述のprivate拡張関数を検討する) • 拡張関数のクラス名はHogeExt.kt • package main 
 |—data 
 |—ui
 |—util 
 | |—ext 
 |—util 
 |—ext

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Others

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Conclusion

Slide 57

Slide 57 text

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