LINEヤフー Tech Blog

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

コード品質向上のテクニック: 第 7 回(新事成語)

LINEヤフー Advent Calendar 2023の21日目の記事です。

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

この記事は、毎週木曜の定期連載 "Weekly Report" 共有の第 7 回です。Weekly Report については、第 1 回の記事を参照してください。

新事成語

「ユーザ」を示す以下のようなモデルクラスがあるとします。

class UserData(
    val id: UserId,
    val name: String,
    val profileImageUri: String,
    ...
)

この name というプロパティは、ユーザの「ログイン名」を示しているものとします。ここで、ログイン名を他のユーザに公開したくないという要望を実現するために、公開用の別名として「ニックネーム」を登録できるように仕様を変えることを想定します。実装の結果として、UserData は以下のように変更されたとしましょう。(ニックネーム未登録時に null を使うことは合意が取れていて、問題がないものとします。)

class UserData(
    val id: UserId,
    val name: String,
    val profileImageUri: String,
    ...
    /**
     * ...
     * もし `nickname` が明示的に `null` の場合は、ニックネームが登録されていないことを意味する。
     */
    val nickname: String?
)

さらに、「ユーザ名」を表示する UI は以下のように実装されたとします。

userNameView.text = userData.nickname
    ?: userData.name

これらのコードについて問題点はありますか?

新しきを以って古きを変える

新しいプロパティやメソッドを足しただけでも、既存のプロパティやメソッドの名前の変更が必要になる ことがあります。上記のコードでは、UserData.name のプロパティ名を変更するべきでした。

仮にログイン名を表示させず、ニックネームだけを表示させる UI nicknameView を以下のように実装したとします。このコードには「ニックネームではなくログイン名を使ってしまっている」というバグがあるのですが、一見しただけではそれに気が付きにくいです。

val nickname = userData.name
nicknameView.isVisible = nickname.isNotEmpty()
nicknameView.text = nickname

一目でバグに気が付きにくいのは、「"nickname" (ニックネーム) は "name" (名前) の一種である」ということに起因します。実際の UserData.name と UserData.nickname の値に包含関係はないものの、プロパティ名としては包含関係があるように見えてしまうのです。このような混乱を避けるためにも、UserData.nickname が追加されるときに UserData.name の名前を変更するべきでしょう。以下のコードでは name というプロパティ名を loginName に変えています。

class UserData(
    val id: UserId,
    /** ... */
    val loginName: String,
    /** ... */
    val nickname: String?,
    val profileImageUri: String,
    ...
) {
    // Also, we may put property selection logic
    /** ... */
    val displayName: String
        get() = nickName ?: loginName
}

このようにして、loginName と nickname の違いを明確にすることができます。このコードではさらに、「表示名 displayName ではニックネームを優先し、なければログイン名にフォールバックする」ことが明示的になっています。

類似例

似たような状況は、様々なケースで発生しえます。

Case 1

  • 既存の要素: ローカルに保存されたデータ fooData
  • 新しい要素: ネットワークを介して取得するデータ fooRemoteData
  • リネーム案: fooData -> fooLocalData

Case 2

  • 既存の要素: 作成時のタイムスタンプ timestamp
  • 新しい要素: 最終更新のタイムスタンプ lastUpdateTimestamp
  • リネーム案: timestamp -> creationTimestamp

Case 3

  • 既存の要素: ユーザのプロフィールに追加可能な外部リンク url
  • 新しい要素: ユーザのプロフィール画像のリンク profileImageUrl
  • リネーム案: url -> externalLinkUrl

一言まとめ: 新しい要素を追加したときは、既存コードの名前の変更を検討する。

キーワード: naming, new element, inclusive relation