17
7

More than 1 year has passed since last update.

String型やInt型に直接拡張関数を生やすのはいかがなものか(value classを検討しよう)

Last updated at Posted at 2023-01-28

要約

  • プリミティブ型に拡張関数を書くと、必要以上に広いスコープが対象になってしまう
  • value classを活用するとスコープと用途を限定できて良いかも
    • 絶対こうすべきという強い思想はまったくなく、検討する価値のある方法の1つという意

よくある場面

🙂<1500のようにIntで扱っている値を画面に表示する際に、1,500円のようにカンマ区切りにして「円」をつけたStringに変換したいな

val formatedPrice: String = "%,d".format(1500) + "円"
println(formatedPrice)
// -> 1,500円

🙂<この変換はいろいろな箇所で使うからInt.ktファイルに拡張関数として追加しておこう

kotlin Int.kt
fun Int.format(): String = "%,d".format(this) + "円"

😊<簡単に呼び出せるようになって、コードもスッキリした

val formatedPrice: String = 1500.format()
println(formatedPrice)
// -> 1,500円

問題点

  • 追加したい拡張関数がこれだけなら問題ない
    • (ほとんどの場合は追加したい処理がどんどん増えていく)
  • 用途が限定的なので、Intすべてが使える関数として定義するにはスコープが広すぎる
  • 今後同じようなことをやりたい場合Int.ktに続々と関数が追加され肥大化する
    • このままいくとIntはグローバルなので、金額の変換、日付の変換などすべてが一同に介してしまう

1ヶ月後...
🤮<Int.ktに拡張関数を生やしすぎてが1000行以上あって混沌としている!

kotlin Int.kt
// 円を末尾につける
fun Int.formatYen(): String = {...}
// ドルを末尾につける
fun Int.formatDoller(): String = {...}
// ユーロを末尾につける
fun Int.formatEuro(): String = {...}
// yyyy/MM/ddの形式に変換する
fun Int.formatYYYYMMDD(): String = {...}
...
...

解決策 -> value classの利用を検討しよう

value classとは

  • data classとだいたい同じ
  • コンパイル時にプリミティブな値としてバイトコードを生成するので、パフォーマンスはdata classよりも良い
  • 単一のプロパティしか持てない
  • @JvmInlineをつける必要がある
  • Value Objectとして利用することに適している
  • 参考:https://star-zero.medium.com/kotlin%E3%81%AEvalue-class-27e865696f35

value classにメソッドを生やすことで、スコープを限定できる

  • Intの拡張関数を使うのとパフォーマンス的にもほぼ変わらずにかける
  • Price, Dateのように扱う対象ごとに分けられる
  • プリミティブ値は値の渡し間違いなどがあるため、value classを使うと型安全に扱える(value object)
kotlin Price.kt
@JvmInline
value class Price(private val value: Int) {
    fun format(): String = "%,d".format(this.value) + "円"
}
val price: Price = Price(1500) // <- オリジナルの型として扱える
val formatedPrice: String = Price(1500).format()
println(formatedPrice)
// -> 1,500円

プリミティブに拡張関数を生やすべきでないか

  • まずはvalue objectで扱う範囲を限定して汎用関数にする方法を検討するとクリーンなコードになる
  • 上記では収まらず本当に汎用的に行う処理であれば、プリミティブ型の拡張関数を作るのは良さそう
    • プリミティブ型の汎用的な関数は、言語仕様として豊富に提供されている
    • 標準の関数で事足りない場合、そのプロジェクト特有の対象(ドメイン)を扱っている可能性が高い
    • であれば、グローバルなプリミティブ型に直接生やすのではなく、value objectを作ってドメインとして扱ったほうがクリーンな気がする
      • 大半の場合はvalue objectに生やす方向で事足りそうなので、一見の価値あり

参考文献

17
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
7