0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【リファクタリング】Introduce Null Object(ヌルオブジェクトの導入)

Posted at

1. 概要(Overview)

Introduce Null Object は、null を返したりチェックする代わりに、
「何もしない安全なオブジェクト」を導入するリファクタリングです。

null チェックが散在すると、コードが複雑になり、バグ(NullPointerException)の原因にもなります。
その代わりに、振る舞いは最小限だがインターフェースを満たすオブジェクト(Null Object)を用意することで、
クライアント側の null 判定を不要にできます。

目的は以下の通りです:

  • null チェックの削減
  • コードを読みやすく、安全にする
  • 既存クラスと同じインターフェースで「空の振る舞い」を提供

2. 適用シーン(When to Use)

  • null チェックがあちこちに散らばっている
  • 「デフォルト動作が何もない」ケースが多い
  • クライアントコードで if (obj != null) のようなガードが頻発している
  • 例外やエラーではなく「何もしない」で十分な場面がある

よくある匂い:

  • Repeated Null Checks(繰り返される null チェック)
  • Primitive Obsession(プリミティブ執着、null で状態を表現している)

3. 手順(Mechanics / Steps)

  1. null を返している箇所を特定する
  2. 返り値の型のインターフェースを持つ Null Object クラス を作成
  3. Null Object に「何もしない実装」を追加
  4. 元の null を返す箇所を Null Object に置き換える
  5. クライアントコードから null チェックを削除する

4. Kotlin 例(Before → After)

Before:null を返していて、毎回チェックが必要

interface Customer {
    fun getName(): String
}

class RealCustomer(private val name: String) : Customer {
    override fun getName(): String = name
}

class CustomerRepository {
    private val customers = listOf(RealCustomer("Alice"))

    fun findByName(name: String): Customer? {
        return customers.find { it.getName() == name }
    }
}

fun main() {
    val repo = CustomerRepository()
    val customer = repo.findByName("Bob")

    if (customer != null) {
        println("Found: ${customer.getName()}")
    } else {
        println("No customer found")
    }
}
  • findByNamenull を返す可能性がある
  • クライアント側で毎回 if (customer != null) を書く必要がある

After:Null Object を導入

interface Customer {
    fun getName(): String
}

class RealCustomer(private val name: String) : Customer {
    override fun getName(): String = name
}

object NullCustomer : Customer {
    override fun getName(): String = "No customer"
}

class CustomerRepository {
    private val customers = listOf(RealCustomer("Alice"))

    fun findByName(name: String): Customer {
        return customers.find { it.getName() == name } ?: NullCustomer
    }
}

fun main() {
    val repo = CustomerRepository()
    val customer = repo.findByName("Bob")
    println("Found: ${customer.getName()}")
}
  • NullCustomer が「何もしない安全なオブジェクト」
  • クライアント側で null チェック不要
  • コードがシンプルになり、安全性も向上

5. 効果(Benefits)

  • NullPointerException のリスクを削減
  • null チェックを削除でき、コードがスッキリする
  • クライアントコードが安全で読みやすくなる
  • テストもしやすくなる(Null Object をそのまま使える)

6. 注意点(Pitfalls)

  • 「null を返すこと自体がエラー」のケースでは適用不適切(例:必須項目が存在しない場合)
  • Null Object の振る舞いを明確に定義しておく必要がある(例:ログを出すのか、完全に無視するのか)
  • 過度に使うと「バグを隠す」危険もある(例:本当はエラーなのに黙って Null Object が返る)

まとめ

  • Introduce Null Object は「null の代わりに安全なダミーオブジェクトを導入する」リファクタリング
  • 判断基準:そのケースで「何もしない」振る舞いが妥当か?
  • 基本思想null を返さず、オブジェクトとして振る舞うことでコードを安全かつシンプルにする

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?