引数について

ふと思ったのでメモ。

要約:インタフェースメソッドの引数はインタフェース設計の視点で決定されるべきであり実装の都合を持ち込んではいけないかもしれないしそうでないかもしれないがおいらはそう思うw。

※サンプルを見やすくするため getter/setter は使っていない。
※プリミティブ型ではなくバリデーション等を含めてクラス化すべきとか言う話も今回は省略。

引数としてオブジェクトを渡すコード例:

interface User {
    val id: Long? // <- キモいw
    val name: String
    val address: String
}

interface UserService {
    fun register(newUser: User): User
}

必要な値のみ渡す例:

interface User {
    val id: Long
    val name: String
    val address: String
}

interface UserService {
    fun register(name: String, address: String): User
}

上記の2例は必要な値のみ渡す場合のほうが完成度が高いような気がする。しかし、User のメンバが100個あったらオブジェクトを渡す方式を採用してしまうことが多いのではないだろうか。

おいらの現段階での所感としては、そもそもバラすかどうかという考え方自体が本末転倒で、インタフェースの視点で必要な情報だけ渡せばいいと思う。

  • ユーザー登録時に id が決定されるので id を渡すのはそもそもおかしい。非 null を入れられたら実行時エラーにしなきゃならんとか無駄に複雑。
  • 別に User という形で渡す必要が無い。UIからユーザーが入力した値を元にUserを作成する場合になどわざわざ User を作るというのがナンセンス。

User の要素が100個あるような場合に一部データのみ変更したい場合は setter/getter のある UserInfo を作りたくなる気もするが、kotlin なら以下のように default argument と named arguments で対応できる。*1

interface User {
    val id: Long
    val name: String
    val address: String
}

interface UserService {
    fun register(name: String, address: String): User
    fun update(id: Long, name: String? = null, address: String? = null): User
    // 他での変更を考慮するなら下記のようにしてもいいかも。
    fun update(user: User, name: String? = null, address: String? = null): User
}

では、UserService.update() を叩く前にバケツリレーが必要な場合はどうかというと、確かに User の要素が100個あったらそれをバケツリレーするのはつらいものがある。しかし、UserService の視点では、そんなことは全く関係ない。つまり、UserInfo とか作りたいなら勝手に UserService の外でやってくれという話。しかし、以下のようなコードがソース中に分散されても困る。

// name, address, ... と100個あったら結構辛いものがある(´・ω・`) 
userService.register(user.name, user.address)

だからと言って、UserService に実装のツケを払わせるのは本質的におかしい。

重複するコードを抑えたいのであれば、以下のような方法で対処が可能。

interface UserInfo {
    var name: String
    var address: String
    fun register(userService: UserService)
}

しかし、UserInfo が UserService に依存するのも変。ならば、以下のように extension を使えばよい。

fun UserService.register(userInfo: UserInfo) {
    register(userInfo.name, userInfo.address)
}

extension の置き場とかはどこでもいいけど、UserService に入れるのはインタフェースの抽象化が崩れるので本末転倒。例えば、UserService が domain model 用のモジュール*2に配置され、UserInfo が domain model の実装用モジュールに配置されているとしたら、上記 extension も domain model の実装モジュールに入るのが正解ですかね。

*1:全ての引数に対して適切なデフォルト値を設定する必要がある。null が常に適切とは限らない。

*2:kotlin 用語としての module。