blog/content/posts/2025-01-27/index.md

3.6 KiB
Raw Blame History

author draft categories date tags keywords title relpermalink url decription
usbharu false
技術
2025-01-12T14:08:58+09:00
Kotlin
ActivityPub
Kotlin
ActivityStreams
ActivityVocabulary
Kotlin
ActivityPub
Kotlin
ActivityStreams
ActivityVocabulary
Kotlinで作るDSL posts/2025-01-12/ posts/2025-01-12/ KotlinでデータとDSLが分離されたタイプのDSLの作成

KotlinでひたすらDSLを書いてたらだいぶウハウが溜まってきたので共有

KotlinのDSL作成でググるとこんな記事が出てくると思いますが、大半の人は下の記事を読むだけで作りたいものが作れると思うのでぜひ読んでください。

Kotlin で書く DSL

Type-safe builders | Kotlin Documentation

データの実体とDSLが分離されたタイプのDSL

上で紹介した記事では全部データの実体となるクラスに直接DSLを生やしています。これはこれで綺麗にまとまっていいのかもしれませんが、僕は頭Javaな人なのでBuilderに相当する部分は分けられていてほしいです。特に内部でStringBuilderなどを使うようになってくるとややこしくなってきます。

分離すると何が嬉しいかというと

  • DSL上の構造と実際のデータ構造が大幅に乖離しているときにDSLを書きやすい
  • StringBuilderなどが使いやすい
  • データ保持側に手を入れなくていい(責務の分離)
  • クソややこしいDSLを書いたときにデータ保持側を読みやすい
  • パフォーマンスが向上する場合がある(とりあえずListに溜め込んでbuild時に生成するStringBuilder的なアプローチがとれる)

逆にデメリットは

  • 単純に考えて必要なクラス数が増える(どこまで凝るかによるが2倍以上)
  • それに伴う保守性の低下
  • Kotlinのうんこ仕様によるプロパティの露出

メリット

DSL上の構造と実際のデータ構造が大幅に乖離しているときにDSLを書きやすい

実際のデータにDSLを生やした場合全然関係ないインスタンスを操作することは可能ですが不自然です。しかし、DSL部分を分離するとそのへんの制約は完全になくなって自由になります。

StringBuilderなどが使いやすい

上と同じで、データ側がStringBuilderをもともと持っているなら自然に使えますが、持ってない場合StringBuilderの意味がなくなります。

データ保持側に手を入れなくていい

そう、責務の分離ってやつです。わざわざ説明するまでもない

クソややこしいDSLを書いたときにデータ保持側を読みやすい

例えば一つのオブジェクトのDSLに300行以上あったらすごいややこしくないですか? そういうときに読みやすいです。

パフォーマンスが向上する場合がある

StringBuilderがいい例なんですが、一旦Listに溜め込んで、build時にインスタンスの生成するっていうアプローチを取れます。

これはKotlin標準のDSL

val buildString = buildString {
        append("hello")
        append(" ")
        append("world")


        for (i in 1..10) {
            append(i)
        }
    }