LoginSignup
7

posted at

updated at

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

要約

  • プリミティブ型に拡張関数を書くと、必要以上に広いスコープが対象になってしまう
  • 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に生やす方向で事足りそうなので、一見の価値あり

参考文献

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
What you can do with signing up
7