こんにちは。コミュニケーションアプリ「LINE」のモバイルクライアントを開発している高梨です。
この記事は、毎週木曜の定期連載 "Weekly Report" 共有の第 65 回です。 LINEヤフー社内には、高い開発生産性を維持するための Review Committee という活動があります。ここで集まった知見を、Weekly Report と称して毎週社内に共有しており、その一部を本ブログ上でも公開しています。(Weekly Report の詳細については、過去の記事一覧を参照してください)
Collection は List だけにして成らず
ユーザーデータをまとめて取得する bulkQuery()
という関数を実装しようとしています。 ただし、各ユーザーは FRIEND
や RECOMMENDED
というカテゴリに属していて、そのカテゴリごとにユーザーデータを取得できるようにしたいです。以下の bulkQuery()
の実装では、カテゴリのリストを引数として受け取り、戻り値としてユーザーのセットのリストを返します。引数のリストのインデックスと戻り値のリストのインデックスは対応付けられています。
fun bulkQuery(categories: List<UserCategory>): List<Set<User>> = TODO()
enum class UserCategory { FRIEND, RECOMMENDED, BLOCKED }
data class User(val id: Int)
以下のコードは、bulkQuery()
の使用例です。ここでは、FRIEND
と RECOMMENDED
のカテゴリーのユーザーを取得し、それらを表示しています。
// Caller
val targetCategories = listOf(UserCategory.FRIEND, UserCategory.RECOMMENDED)
val result = bulkQuery(targetCategories)
targetCategories.zip(result).forEach { (category, users) ->
val sortedUsers = users.sortedBy(User::id)
when (category) {
UserCategory.FRIEND -> println("Friends: $sortedUsers")
UserCategory.RECOMMENDED -> println("Recommended: $sortedUsers")
UserCategory.BLOCKED -> error("error")
}
}
このコードに改善の余地はありますか?
コレクションの特性を活用する
コレクションには、List
や Set
といった種類があり、それぞれ異なる特性を持っています。しかし、現状の bulkQuery()
では、それらの特性がうまく 使えていないのが問題です。仕様にも依存しますが、引数と戻り値の型を以下のように変更することで、うまくコレクションの特性を利用することができます。
- fun bulkQuery(categories: List<UserCategory>): List<Set<User>>
+ fun bulkQuery(categories: Set<UserCategory>): Map<UserCategory, List<User>>
引数の変更(List<UserCategory>
-> Set<UserCategory>
)
同じ UserCategory
を含むリストを引数として渡すことは無意味です。 Set
を使用することで、重複が意味を持たないことを強調できます。
戻り値の変更(List<Set<User>>
-> Map<UserCategory, List<User>>
)
もともとの実装では、各カテゴリ内のユーザーデータは Set
でまとめて返されていました。呼び出し元のコードでは、この Set
を ID でソートした後に利用しています。このように、順序が意味を持つ場合、List
を使用する方が良いです。特に、データレイヤーで DB を使用している場合は、そこでソートする方が、パフォーマンスが良いことが多いです。このとき、ソートされたリストの順序を説明するコメントを記述すると、よりコードが読みやすくなります。
もう一つの改善として、カテゴリごとのまとまりを List
から Map
に変更することで、引数と戻り値の間の暗黙の関係を解消しています。List
の実装では、どのユーザーがどのカテゴリに属するかを知るためには、引数のインデックスとの対応を調べなければなりません。呼び出し元のコードでは、クエリ結果に zip()
を使って対応を取っていますが、この構造は後で結果を再利用するのが難しいです。このような暗黙的な関係は分かりにくいだけではなく、壊れやすいため、その関係を説明する詳細なコメントを追加する必要があります。クエリと結果の関係を明示的に示すために Map
を使用する方が理解しやすく、壊れにくいコードにすることができます。
一言まとめ
コレクションを使うときは、特徴の違いを意識して適切な種類を選択するべき。
キーワード: collection
, implicit relationship