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

Delegatesと拡張関数・拡張プロパティその合わせ技

 Delegatesと拡張関数・拡張プロパティその合わせ技

2021/09/22の Server-Side Kotlin Study #2 にて発表した、KotlinのDelegatesと拡張関数・拡張プロパティの組み合わせに関する資料です。

https://server-sider-kotlin.connpass.com/event/224077/

8a43d544df4886ea5ef714e78f3420a7?s=128

Hideyuki Takeuchi

September 22, 2021
Tweet

Transcript

  1. たけうち ひでゆき @chimerast Delegatesと 拡張関数・拡張プロパティ 利用ライブラリの、かゆいところに手を届かせる 2021/09/22 Server-Side Kotlin Study

    #2
  2. 株式会社イエソド 代表取締役 エンジニア • 静的型付け言語以外でのプロダクト開発を認めない系ITエンジニア • JVM以外信じない系ITエンジニア • パソコンの大先生 •

    物理レイヤーからフロントエンドまで • 全業種・全職種の人とだいたい話が合わせられる • 元 株式会社ユーザベース チーフテクノロジスト 「SPEEDA」「NewsPicks」「FORCAS」を作った 東証の鐘を鳴らしたことがある たけうち ひでゆき @chimerast きれいなたけうちさん マサカリを投げるたけうちさん
  3. None
  4. 企業の人・組織・情報に まつわる非効率をなくす

  5. None
  6. None
  7. None
  8. None
  9. 要するに次世代の 時系列の人組織データベース と、それを起点とした 
 各種サービスの権限管理サービス 作ってます!

  10. 言語 Kotlin / TypeScript フロントエンド HTML / CSS / Sass(SCSS)

    / TypeScript / Vue.js / Vuex バックエンド Ktor / Exposed / Express データベース PostgreSQL (Cloud SQL) インフラ GCP / Docker / Kubernetes / Istio バージョン管理等 GitHub / JIRA CI/CD CloudBuild / GitHub Actions / Kustomize / Argo CD コミュニケーション Slack / Notion / Zoom / Google Meet / Miro / Figma 技術スタック
  11. 本題

  12. お品書き "Open Classes"について 拡張関数・拡張プロパティ おさらい Delegates おさらい 合わせ技と実際のユースケース 
  「Exposedがちょっとつらかった件」

  13. "Open Classes" について

  14. "Open Classes" について • いにしえから存在する、 
 「俺、このクラス or インターフェースに横から機能を追加・変更したいんだよね」 


    というプログラマのエゴを満たすための機能 • "Extension Classes" とも言う • 情報科学の世界では古くからの研究分野 (論文いっぱいある)
  15. "Open Classes" 全般について • あんま使うな • ライブラリ開発者の設計思想を破壊する可能性がある • モジュラリティを破壊する可能性がある •

    でもかゆいところに手が届いて便利だよね • ライブラリ開発者が想定しきれなかった所をモンキーパッチ • 変換関数は、拡張関数使った方が収まりが良いよね
  16. Kotlinでの"Open Classes" • 拡張関数・拡張プロパティ • 元のクラスの挙動を変更することはなく機能の追加のみ • スコープの概念がある ← 結構重要

    • ほどよくできることが制限されているので、ちょうどよい案配 • 完全な無法地帯にはならない ※ 個人の感想です
  17. 拡張関数・拡張プロパティ 
 おさらい

  18. 拡張関数 fun ByteArray.uuidv5(namespace: UUID): UUID { return UUIDv5(namespace, this) }

    fun <拡張したいクラス>.<新しく追加したい関数名>(<引数>): <返り値の型> { // 拡張したクラスのインスタンスが this としてアクセスできるので // 何かしらの処理をして返す } 書き方 実際の例
  19. 拡張プロパティ val編 val <拡張したいクラス>.<新しく追加したいプロパティ名> get(): <プロパティの型> { // 拡張したクラスのインスタンスが this

    としてアクセスできるので // インスタンスの状態を返す } 書き方 実際の例 val String.uuid get() = when (this.length) { 22 -> UUIDConverter.decodeBase64(this) 36 -> requireNotNull(UUID.fromString(this)) else -> throw IllegalUUIDFormatException(this) }
  20. 拡張プロパティ var編 var <拡張したいクラス>.<新しく追加したいプロパティ名> get(): <プロパティの型> { // 拡張したクラスのインスタンスが this

    としてアクセスできるので // インスタンスの状態を返す } set(value: <プロパティの型>){ // 拡張したクラスのインスタンスが this としてアクセスできるので // インスタンスの状態を変更する } 書き方
  21. 拡張関数・プロパティのスコープ制御 • クラス内で、 private や protected で拡張関数・プロパティを定義すると、 
 そのクラス内もしくはサブクラス内でしか利用できない拡張関数・プロパティが 


    宣言できる class Foo { fun say(): String = "foo".bar() // Fooの中でしか使えない private fun String.bar(): String = this + "bar" } ゴミみたいな例
  22. 拡張関数・プロパティの悩みどころ • 引数をとらないような拡張をする場合に、 
 拡張関数にするのか、拡張プロパティ(val)にするのか悩む • 重たい処理は関数、簡単な変換処理はプロパティにするのが無難? • 拡張フィールド的なものは存在しない (→

    後ほどのネタ) • バッキングフィールド( fi eld 識別子)も使えない
  23. Delegates おさらい

  24. Delegates Delegates って? val lazyValue: String by lazy { "Hello"

    } 上のようなフィールド定義の lazy関数 が Delegateインスタンスを返す プロパティのsetterやgetterで行う処理を共通化できる よくある用途としては、 lazy (遅延初期化)、 Delegates.observable() (監視) val <プロパティ名>: <プロパティの型> by <Delegateインスタンス> Delegatesを使ったプロパティ定義の構造
  25. Delegates の作り方 Delegates の定義のしかた class <Delegateのクラス名> { operator fun getValue(thisRef:

    Any?, property: KProperty<*>): String { // プロパティのgetterの代わりに実行される処理 } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { // プロパティのsetterの代わりに実行される処理 } }
  26. Delegates の作り方 その二 とりあえず、下記のインターフェースを実装すればよい interface ReadWriteProperty<in T, V> interface ReadOnlyProperty<in

    T, out V> val 用 var 用
  27. 拡張関数・拡張プロパティとDelegates 合わせ技と実際のユースケース 
 Exposedがちょっとつらかった件

  28. Exposed がちょっとつらかった部分 課題編 1. Exposed のデータソースとして HikariCP を利用 2. 開発時にコードの更新をして

    Ktor の Auto-reload が走ると、 Exposed と HikariCP は再作成されるが、 更新前の HikariCP のコネクションプールは破棄 されず残ったまま 3. 何度かコードを更新すると、ローカルのDB側の最大受け入れコネクション数を 超えてしまい、JVMごと再起動する必要が出てくる
  29. Exposed がちょっとつらかった部分 解決できなかった編 1. Ktor 側には、 Auto-reload が走ったときに、環境の破棄をフックする為の environment.onShutdown() というメソッドが用意されていた

    2. Exposed 側は、コネクションを管理する Database インスタンスのクローズ処 理をする方法はあったが、紐付く HikariCP を破棄するためのフック等が全く用 意されていなかった。 3. そうだ、拡張関数でDatabase クラスに HikariCP を閉じるためのメソッドを追 加すればいいじゃないか。 → 失敗
  30. 拡張関数・プロパティの悩みどころから抜粋 拡張フィールド的なものは存在しない 拡張関数および拡張プロパティは あくまで元のクラスファイルは変更せず 関数を生成しうまくコードに差し込んでいるだけ そのため、 Database インスタンスに HikariCP のインスタンスを

    保持するためのフィールドの追加ができなかった。
  31. 拡張プロパティとDelegatesでなんとかする fun connect(): Database { val hikariDataSource = HikariDataSource() return

    Database.connect(hikariDataSource).apply { dataSource = hikariDataSource } } private var Database.dataSource: HikariDataSource by ExtensionInstanceVariable() fun Database.shutdown() { this.dataSource.close() TransactionManager.closeAndUnregister(this) } Database.dataSource プロパティへの操作を 
 ExtensionInstanceVariable() に移譲
  32. 拡張プロパティの値を保持するDelegate 無理やり作られた解決策 private class ExtensionInstanceVariable<T : Any> : ReadWriteProperty<Any, T>

    { private val valueMap: MutableMap<Any, T> = Collections.synchronizedMap(WeakHashMap()) override fun getValue(thisRef: Any, property: KProperty<*>): T { return valueMap[thisRef] ?: throw IllegalStateException("Property ${property.name} should } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { valueMap[thisRef] = value } } WeakHashMap で thisRef に紐付けて値を保持 速度的な性能は保証できない
  33. ダメな拡張プロパティのためのDelegates private class BadExtensionInstanceVariable<T : Any> : ReadWriteProperty<Any, T> {

    private var value: T? = null override fun getValue(thisRef: Any, property: KProperty<*>): T { return value ?: throw IllegalStateException("Property ${property.name} should be initial } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { this.value = value } } 拡張プロパティへのDelegateは、インスタンス毎ではなく、 一つの object に保持されるため、全てのインスタンスで 
 同じ値が返るようになるし上書きされる
  34. 拡張関数・拡張プロパティでうまくいった Delegatesを添えて environment.onShutdown { db.shutdown() } スッキリ!快適! DBを利用したシナリオテストも 挙動不審な動作が無くなった

  35. 本日のまとめ

  36. まとめ • 拡張関数・プロパティとDelegatesを合わせると 
 なんか色々マジカルなことができるよ • Delegatesも自分で一回作ってみると面白い使いどころがわかるよ • 拡張関数・プロパティは、節度を持って利用しましょう

  37. ご静聴ありがとうございました 株式会社イエソドでは、 Kotlinを使ってサーバサイドを開発したいエンジニアを募集しています。 TypeScriptとVue.jsを使って開発したいエンジニアも募集してます。