Kotlin Multiplatformなプロジェクトで便利だった小技いくつか

## はじめに

ここ半年ほどKotlin Multiplatformなコードを書く中で、使って便利だった小技がいくつかあったため共有します。

## ログ出力時にそのログの出力元のクラス名を利用する

現在のプロジェクトではロガーについてシンプルなインタフェースを作り、実装をDIで注入するという方式を取っています。

ログの各行にはそれを出力したクラス名くらいは載っていてほしいものですが、ログ出力のたびに出力関数の引数に `this::class.simpleName` のようなボイラープレートを渡すのも少々面倒です。

そのため、以下のようなちょっとした工夫をおこない、ログ出力コードの簡便さとログの情報量を両立させています。

まず、ロガーのインタフェースのなかに以下の `Attatched` のような、ロガーをプロパティとして持っていることを表すインタフェースを入れ子で用意しておきます。

```kotlin
internal interface Logger {
    enum class Severity {
        TRACE, DEBUG, INFO, WARN, ERROR, FATAL
    }
    fun log(severity: Severity, content: () -> String)

    interface Attached {
        val logger: Logger
    }
}
```

また、適当な場所に以下のようなユーティリティを生やしておきます。

```kotlin
private inline fun log(
    logger: Logger,
    severity: Logger.Severity,
    className: String?,
    crossinline content: () -> String
) {
    logger.log(severity){"<${className?:"unknown"}>: ${content()}"}
}

internal fun Logger.Attached.putTrace(content: () -> String) {
    log(logger, Logger.Severity.TRACE, this::class.simpleName, content)
}
internal fun Logger.Attached.putDebug(content: () -> String) {
    log(logger, Logger.Severity.DEBUG, this::class.simpleName, content)
}
// :
// :
```

こうすることで、以下のように、`Logger.Attatched` を実装したクラスの中では、 `putDebug {（出力する内容）}` のような記述をするだけで、ログの内容にクラス名を自動的に含められるようになります。

```kotlin
internal class Klass(di: DI): Logger.Attatched {
    override val logger: Logger by di.instance()

    fun f() {
        putDebug { "Hello World!" }
    }
}
```

Kotlin/JVMのリフレクションなどならもう少しスマートな方法があるかもしれませんが、とりあえずこのような小手先のテクニックでも十分役に立っています。

気になる点としては、`logger` がクラス外に漏れてしまっている点が挙げられます。 `internal` なクラスで使う分には、そのクラスの外側であえてそのクラスの文脈であることを明示したいときに 、`Klass().logger.putDebug{}` のように使えて便利な場面もありますが、ライブラリ等の一部としてモジュール外に公開するクラスには使用しない方がいいかもしれません。

## `Result` 周りを少し書きやすくする

kotlinx.coroutineではコルーチン間を跨いだ例外を扱うのが面倒な場合が多いため、自然と（Rustなどでよくやるように）例外も関数の返値として陽に扱うケースが出てくるかと思います。その際に、正常系の値や例外をラップする型として、Kotlin標準の[`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/)[^result] や Arrowの[`Either`](https://arrow-kt.io/learn/typed-errors/either-and-ior/)を選択する場合が多いかと思います。

現在のプロジェクトでは、一部を除いて `Result` を使用することを決めたため、値や例外を `Result` に変換するために、`Result.success(v)` や `Result.failure(e)` というコードが頻出するようになりました。

これは長くて冗長であるため、適当な場所に

```kotlin
@Suppress("NOTHING_TO_INLINE")
inline fun <T> T.success(): Result<T> = Result.success(this)
@Suppress("NOTHING_TO_INLINE")
inline fun <T, F> F.failure(): Result<T> where F: Throwable = Result.failure(this)
```

のようなユーティリティ関数を書いておき、`v.success()` や `e.failure()` と書けるようにしました。

今のところこの程度の機能で十分ではありますが、 `Result` をよりガッツリ扱いたい場合は[kotlin-result](https://github.com/michaelbull/kotlin-result)などのライブラリを使ってみるのも手かもしれません。

[^result]: ちょっと前までは関数の返値に使えない不便な型で、自力で似たようなものを実装したりしましたが、今では他の言語の `Result` や `Either` と同じような感覚で扱えるようになりました。

## `Deffered` な値にお手軽にログを仕込む

kotlin.coroutineを使った非同期処理を行う場合、`async` などの返値として`Deferred` 型の値を扱うことがあるかと思います。

またデバッグのために、こうした値のキャンセル時や例外送出時にログを吐かせたいケースが多々あるかと思います[^debugger]。

そのような場合、下記のようなコードを適当な場所に置いておき、

```kotlin
internal fun <T> Deferred<T>.attachTracer(
    onComplete: () -> Unit,
    onCancelled: () -> Unit,
    onThrown: (Throwable) -> Unit
): Deferred<T> = apply {
    invokeOnCompletion {
        when (it) {
            null -> onComplete()
            is CancellationException -> onCancelled()
            else -> onThrown(it)
        }
    }
}
```

```kotlin
val v = async { f() }.attachTracer(
    onComplete = { putTrace { "f() completed" } },
    onThrown = { putError { "error on f(): $it" } },
    onCancelled = { putTrace { "f() cancelled" } }
)
```

上記コードのように `attachTracer` をつけ足してやると、コードをあまり変更せずに簡単にログを仕込めて便利です。

[^debugger]: 特にKMPではデバッガにあまり恵まれないため