LINEヤフー Tech Blog

LINEヤフー株式会社のサービスを支える、技術・開発文化を発信しています。

コード品質向上のテクニック:第63回 値の裏切り

こんにちは。コミュニケーションアプリ「LINE」のモバイルクライアントを開発している高梨です。

この記事は、毎週木曜の定期連載 "Weekly Report" 共有の第 63 回です。 LINEヤフー社内には、高い開発生産性を維持するための Review Committee という活動があります。ここで集まった知見を、Weekly Report と称して毎週社内に共有しており、その一部を本ブログ上でも公開しています。(Weekly Report の詳細については、過去の記事一覧を参照してください)

値の裏切り

最近の記事のデータを UI に表示するプログラムを実装しているとします。 以下の ArticleRepository.getRecentArticles() 関数は、データソースから受け取った最近の記事データを、Article モデルに変換して返します。

data class Article(
    val title: String,
    val summary: String,
    val category: Category,
    val pageNumber: Int,
    // ...
)

class ArticleRepository(private val dataSource: DataSource) {
    fun getRecentArticles(): List<Article> = dataSource
        .getArticles(since = LocalDate.now().minusDays(7))
        .mapIndexed { index, articleEntity ->
            Article(
                title = articleEntity.title,
                summary = articleEntity.summary,
                category = articleEntity.category,
                pageNumber = index + 1
                // ...
            )
        }
}

ArticleViewModelArticleRepository から受け取った Article のリストを、より UI 表示に特化したモデルクラスの ArticleUiState に変換してから、UI にデータを受け渡します。

class ArticleViewModel(private val articleRepository: ArticleRepository) {
    fun getRecentArticles(): List<ArticleUiState> = articleRepository
        .getRecentArticles()
        .map { article ->
            ArticleUiState(
                title = article.title,
                summary = article.summary,
                pageNumber = article.pageNumber
                // ...
            )
        }

    data class ArticleUiState(
        val title: String,
        val summary: String,
        val pageNumber: Int,
        // ...
    )
}

このようなコードのレビュー依頼を受けたときに、注意するべき点はありますか?

重複データによる不整合

以下のように、記事を Category でフィルタリングするコードが追加された場合、何が起こるでしょうか?

fun getRecentArticles(targetCategory: Category): List<ArticleUiState> = articleRepository
    .getRecentArticles()
    .filter { it.category == targetCategory } // Added
    .map { article ->
        ArticleUiState(
            title = article.title,
            summary = article.summary,
            pageNumber = article.pageNumber // This should be incorrect
            // ...
        )
    }

ArticleRepository.getRecentArticles() 関数では、Article.pageNumber が 1 から連番が振られているにもかかわらず、フィルタリングによって飛び飛びの値になってしまいます。このように、呼び出し元が記事リストを変更(フィルタリング、並べ替えなど)すると、pageNumber の値は簡単に壊れてしまいます。

重複データは悪なのか?

インデックス(pageNumber)は要素の情報ではなく、コレクションクラスの情報であるため、要素の外に保持すると不整合が生じます。そのため、このようなプロパティをそもそも作成すべきではありません。
しかし、このようなデータは UI をレンダリングするために必要なケースも存在します。このような情報をUIデータ(ArticleUiState)内にのみ保持することは理にかなっています。

この例以外にも、便利さのために重複データを持つ方が良い場合があります。キャッシュデータは典型的な例です。このようなデータの重複を使用する際は、以下の点に注意してください。

  • どれが主要なデータであるかを明確にする。
  • 使用する直前にそのようなデータを作成する。

一言まとめ

データの不整合のリスクを避けるために、重複データは使用する直前に作成すべき。

キーワード: data model, orthogonal relationship, collection, index

コード品質向上のテクニックの他の記事を読む

コード品質向上のテクニックの記事一覧