覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Kotlin - ローカル関数

例えば以下のような User クラスとそのUserのオブジェクトを登録する関数が存在するとします。

class User(val id: Int, val firstName: String, val lastName: String)

fun registerUser(user: User) {

    if (user.firstName.isEmpty()) {
        throw IllegalArgumentException("Invalid User id=${user.id} : firstName is Empty")
    }

    if (user.lastName.isEmpty()) {
        throw IllegalArgumentException("Invalid User id=${user.id} : lastName is Empty")
    }

    // Register to persistence area ...
}

登録関数の registerUser は実際の登録処理前に、Userの firstName と lastName が空文字列でないかのバリデーションを行い、
空であれば IllegalArgumentException をスローします。


一応実行させてみます

fun main() {
    registerUser(User(100, "", "Yamada"))
}

結果は以下の通りです

Exception in thread "main" java.lang.IllegalArgumentException: Invalid User id=100 : firstName is Empty
    at net.yyuki.sandbox.localfunc.LocalFuncKt.registerUser(LocalFunc.kt:10)
    at net.yyuki.sandbox.localfunc.LocalFuncKt.main(LocalFunc.kt:21)
    at net.yyuki.sandbox.localfunc.LocalFuncKt.main(LocalFunc.kt)


firstName と lastName のチェックを行っている処理は重複していて、DRY原則に従うのであれば、関数化して重複を取り除く必要があるでしょう。

こういった場合に、バリデーションの処理部分をローカル関数として配置する事で重複を取り除く事ができます。
validation というローカル関数を、 registerUser関数の中に配置する事でバリデーション処理の重複を取り除く事ができました。

class User(val id: Int, val firstName: String, val lastName: String)

fun registerUser(user: User) {

    fun validation(user: User, value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Invalid User id=${user.id} : $fieldName is Empty")
        }
    }

    validation(user, user.firstName, "firstName")
    validation(user, user.lastName, "lastName")

    // Register to persistence area ...
}

先ほどと同様の呼び出しで実行してみると結果は以下の通りで、予定通りに例外がスローされています

Exception in thread "main" java.lang.IllegalArgumentException: Invalid User id=100 : firstName is Empty
    at net.yyuki.sandbox.localfunc.LocalFuncKt$registerUser$1.invoke(LocalFunc.kt:11)
    at net.yyuki.sandbox.localfunc.LocalFuncKt.registerUser(LocalFunc.kt:15)
    at net.yyuki.sandbox.localfunc.LocalFuncKt.main(LocalFunc.kt:22)
    at net.yyuki.sandbox.localfunc.LocalFuncKt.main(LocalFunc.kt)


ローカル関数は、外側の関数の全ての引数や変数にアクセスする事ができるため、validation 関数に Userのオブジェクトを渡す必要はありません。
そのため、以下のように書き換え可能です。

class User(val id: Int, val firstName: String, val lastName: String)

fun registerUser(user: User) {

    fun validation(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Invalid User id=${user.id} : $fieldName is Empty")
        }
    }

    validation(user.firstName, "firstName")
    validation(user.lastName, "lastName")

    // Register to persistence area ...
}

ローカル関数へ Userのオブジェクトを渡さなくなったので、だいぶすっきりしました。

さらに、validation 関数をUserクラスの拡張関数へと移動する事もできます

class User(val id: Int, val firstName: String, val lastName: String)

private fun User.validation() {
    fun validation(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException("Invalid User id=$id : $fieldName is Empty")
        }
    }

    validation(firstName, "firstName")
    validation(lastName, "lastName")
}

fun registerUser(user: User) {
    user.validation()

    // Register to persistence area ...
}

拡張関数にすることで、余分な修飾もなくUserオブジェクトのメンバにアクセスできます。