【オブジェクト指向設計】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# reference 既定の C# 言語バージョンを手動でオーバーライドする方法について説明します。 C# コンパイラでは、インストールされている SDK のバージョンまでの任意の言語バージョン...

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をコピーしました!

コメント

コメントする

目次