【オブジェクト指向設計】nullを返したいとき

本記事は、オブジェクト指向設計で nullを返したくなるときというテーマで書きます。

オブジェクト指向設計においては、nullを返してはいけない ということを聞いたことがあります。

↓の書籍にも明記されています。この書籍は、ぼくがすごく参考にしているものの1冊です。

・nullを渡さない/nullを返さない

現場で役立つシステム設計の原則 第5章 アプリケーション機能を組み立てる P. 166

それでも、nullを返したくなるときはあります。そのときどのように対応すればよいのでしょうか?

結論として、nullを返したくなったときは、値がないことを表す型を戻り値とすることです。これを約束事とすることで、未定義値を返すことができ、コードをすっきりさせることができます。

サンプルの言語はC#です。

目次

null を返したくなるとき

例えば、

ファーストクラスコレクションクラスにて、条件と一致する値を取得する

なる処理をつくる場合、一致するものがないときnullを返したくなります

以下の例で考えてみます。IDと名称を持った、地図記号(MapSymbol) 一覧クラスで、IDと一致する地図記号を取得するメソッドを実装します。

.NET Framework 4.8.1 を使用しています。

namespace NullReturnNG
{
    /// <summary>
    /// 地図記号
    /// </summary>
    class MapSymbol
    {
        /// <summary>ID</summary>
        private int id;

        /// <summary>名称</summary>
        public string name { get; }

        public MapSymbol(int id, string name)
        {
            this.id = id;
            this.name = name;
        }

        public bool EqualId( int id )
        {
            return this.id == id;
        }
    }
}
using System.Collections.Generic;
using System.Linq;

namespace NullReturnNG
{
    /// <summary>
    /// 地図記号一覧
    /// </summary>
    class MapSymbols
    {
        private List<MapSymbol> list;

        public MapSymbols(List<MapSymbol> list)
        {
            this.list = list;
        }
        
        /// <summary>
        /// IDと一致する地図記号の名称を返す
        /// </summary>
        /// <remarks>存在しない場合はnullを返す</remarks>
        /// <param name="id"></param>
        /// <returns>地図記号の名称</returns>
        public string Search(int id)
        {
            MapSymbol mapSymbol = list.Where(obj => obj.EqualId(id)).FirstOrDefault();
            if( mapSymbol == null)
            {
                return null;
            }
            return mapSymbol.name;
        }
    }
}

idと一致する地図記号がないときは、nullを返したくなります。

でも、存在しないときはnullを返すのが分かりやすいと思います。存在しない=nullとするのは同じプロジェクトメンバーにも伝わりやすいです

空文字だったり、None 文字を返すというのもありますが、余計なルールを追加することになり、あまりやりたくありません。他のクラスのメソッドで、別のルールが混在する可能性もあります。
例外を発生させるという対策もありますが、メソッド内で例外を発生させたくないこともあります。

Null許容型を使用する

1つ目の対策として、Null許容型を使用することが考えられます。基になる型に対し、未定義の値を表したいときに使います。

Null許容型の種類

Null許容参照型はC#8.0 以降で使用できます。
.NET 5.0以降。.NET Core3.0 以降。.NET Standard 2.1以降。.NET Frameworkは非対応。

あわせて読みたい
C# 言語のバージョン管理 - C# ガイド - C# C# 言語のバージョンがプロジェクトに基づいて決定されるしくみとその選択の背後にある理由について説明します。 既定値を手動でオーバーライドする方法について説明します...

Null許容型を使用して、先ほどのSearchメソッドの戻り値をstring 型→string Null許容型に変更します

Null許容参照型を使用するため、環境を.NET Framework 4.81 → .NET 6.0 に変更します。

namespace NullReturnNG2
{
    /// <summary>
    /// 地図記号一覧
    /// </summary>
    internal class MapSymbols
    {
        private List<MapSymbol> list;

        public MapSymbols(List<MapSymbol> list)
        {
            this.list = list;
        }

        /// <summary>
        /// IDと一致する地図記号の優先度を返す
        /// </summary>
        /// <param name="id"></param>
        /// <returns>地図記号の名称</returns>
        public string? Search(int id)
        {
            string? name = list.Where(obj => obj.EqualId(id)).FirstOrDefault()?.name;
            return name;
        }
    }
}

戻り値をNull許容型( string? ) とすることで、戻り値はnullの可能性があるということを表明します。

逆に言えば、戻り値がstring型ならnullの可能性がないです。

戻り値の型に値がないことを表すことができます

「存在しない場合はnullを返す」というコメントも消すことができました。 string?はこのコメントの内容を含んでいます。

契約による設計ということで、約束事として、プロジェクトメンバーと共有できていれば問題ないはずです。

C#言語の例ですが、多言語でも同じような方法ができるかもしれません。

Option 型を使用する

もう1つの対策として、Option型を使用する方法をご紹介します。Null許容型を使う方法と似ています。

C#8.0 が使用できないときに使えます。

Option型は、値がないことを表すことができるものです。

↓のOption型を使って、先ほどのSearch関数を実装します。

Qiita
C#でOption型(改良) - Qiita 以前C#でOption<T>型を作成しました。C#でOption<T>型C#でOption<T>型(再考)これはこれで非常に便利で多用していたのですが、使っていて1つ不満な点が...

環境を.NET Framework 4.81 に戻します。

using System.Collections.Generic;
using System.Linq;

namespace NullReturnNG
{
    /// <summary>
    /// 地図記号一覧
    /// </summary>
    class MapSymbols
    {
        private List<MapSymbol> list;

        public MapSymbols(List<MapSymbol> list)
        {
            this.list = list;
        }
        
        /// <summary>
        /// IDと一致する地図記号の優先度を返す
        /// </summary>
        /// <remarks>存在しない場合はnullを返す</remarks>
        /// <param name="id"></param>
        /// <returns>地図記号の名称</returns>
        public IOption<string> Search(int id)
        {
            MapSymbol mapSymbol = list.Where(obj => obj.EqualId(id)).FirstOrDefault();
            if( mapSymbol == null)
            {
                return new None<string>();
            }
            return new Some<string>(mapSymbol.name);
        }
    }
}

戻り値をIOption<string> 型とすることで、Null許容型と同じく、stringの値がないかもしれないということを表すことができます。

まとめ

オブジェクト指向設計で nullを返したくなるときというテーマで書きます。

結論として、nullを返したくなったときは、値がないことを表す型を戻り値とすることです。これを約束事とすることで、未定義値を返すことができ、コードをすっきりさせることができます。

本記事の続きは↓。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次