web-dev-qa-db-ja.com

null参照例外の回避

どうやら、コード内のエラーの大部分はnull参照例外です。 null参照エラーの発生を回避するための一般的な手法はありますか?

間違えない限り、F#などの言語ではnull値を設定できないことを認識しています。しかし、それは問題ではありません。C#などの言語でnull参照エラーを回避する方法を尋ねています。

23
Nippysaurus

Null参照例外がユーザーに表示される場合、これは開発者側のエラーに起因するコードの欠陥を示しています。これらのエラーを防ぐ方法に関するいくつかのアイデアがあります。

ソフトウェアの品質に関心があり、.netプログラミングプラットフォームも使用している人への私の一番の推奨事項は、Microsoftコードコントラクトをインストールして使用することです( http://msdn.Microsoft.com/en-us/devlabs/ dd491992.aspx )。これには、実行時チェックと静的検証を行う機能が含まれています。これらのコントラクトをコードに組み込むための基本的な機能は、4.0バージョンの.netフレームワークに含まれています。コードの品質に関心があり、そう思われる場合は、Microsoftコードコントラクトの使用を本当に楽しんでください。

Microsoftコードコントラクトでは、「Contract.Requires(customer!= null);」のような前提条件を追加することで、メソッドをnull値から保護できます。このような前提条件を追加することは、上記のコメントで他の多くの人が推奨する方法と同等です。コード契約の前に、私はあなたがこのようなことをすることを勧めたでしょう

if (customer == null) {throw new ArgumentNullException("customer");}

今私はお勧めします

Contract.Requires(customer != null);

次に、これらの欠陥をできるだけ早くキャッ​​チするランタイムチェックシステムを有効にして、欠陥コードの診断と修正に導きます。ただし、コードコントラクトは、引数のnull例外を置き換えるための単なる空想的な方法であるという印象を与えないでください。それらはそれよりはるかに強力です。 Microsoftコードコントラクトを使用すると、静的チェッカーを実行して、null参照例外が発生する可能性のあるコード内の可能性のあるサイトを調査するように依頼することもできます。静的チェッカーを簡単に使用するには、もう少し経験が必要です。初心者にはお勧めしません。しかし、気軽に試してみてください。

NULL参照エラーの有病率に関する研究

このスレッドでは、null参照エラーが重大な問題であるかどうかについていくつかの議論がありました。長い答えは以下の通りです。それを乗り越えたくない人のために、要約します。

  • Spec#およびコード契約プロジェクトのプログラムの正当性に関するマイクロソフトの主要な研究者は、これは対処する価値のある問題であると考えています。
  • Eiffelプログラミング言語を開発およびサポートしたBertrandMeyer博士とISEのソフトウェアエンジニアのチームも、これは対処する価値のある問題であると考えています。
  • 通常のソフトウェアを開発している私自身の商業経験では、null参照エラーを何度も目にしているので、自分の製品やプラクティスの問題に対処したいと思います。

マイクロソフトは何年もの間、ソフトウェアの品質を向上させるために設計された研究に投資してきました。彼らの取り組みの1つは、Spec#プロジェクトでした。 .net 4.0フレームワークに関する私の意見で最もエキサイティングな開発の1つは、Microsoftコードコントラクトの導入です。これは、Spec#研究チームによって行われた以前の作業から派生したものです。

「コード内のエラーの大部分はnull参照の例外です」というあなたの発言に関して、いくつかの不一致を引き起こすのは「大多数」の修飾子であると私は信じています。 「大多数」というフレーズは、おそらく70〜90%の障害が根本原因としてnull参照例外を持っていることを示唆しています。これは私には高すぎるようです。 Microsoft Spec#の調査から引用したいと思います。彼らの記事の中で、Spec#プログラミングシステム:概要、Mike Barnett、K。RustanM. Leino、およびWolframSchulteによる。 CASSIS 2004では、LNCSvol。 3362、Springer、2004、彼らは書いた

1.0非ヌル型最近のプログラムの多くのエラーは、ヌル逆参照エラーとして現れます。これは、ヌルと評価される可能性のある式とそうでない式を区別する機能を提供するプログラミング言語の重要性を示唆しています(いくつかの実験的証拠については、 [24、22]を参照)。実際、すべてのnull逆参照エラーを根絶したいと考えています。

これは、この調査に精通しているMicrosoftの人々にとっておそらく情報源です。この記事はSpec#サイトで入手できます。

以下の参考文献22と24をコピーし、便宜上ISBNを含めました。

  • マニュエルファーンドリッチとK.ルスタンM.レイノ。オブジェクト指向言語でのnull以外の型の宣言とチェック。オブジェクト指向プログラミング、システム、言語、およびアプリケーションに関する2003 ACM会議の議事録、OOPSLA 2003、第38巻、SIGPLAN通知の第11号、302〜312ページ。 ACM、2003年11月。isbn= {1-58113-712-5}、

  • コーマックフラナガン、K。ルスタンM.レイノ、マークリリブリッジ、グレッグネルソン、ジェームスB.サックス、レイミースタタ。 Javaの拡張静的チェック。プログラミング言語の設計と実装に関する2002ACM SIGPLAN会議(PLDI)の議事録、第37巻、SIGPLAN通知の第5号、234〜245ページ。 ACM、2002年5月。

これらの参考資料を確認しました。最初の参照は、null参照の欠陥の可能性について独自のコードを確認したいくつかの実験を示しています。彼らはいくつかを見つけただけでなく、多くの場合、潜在的なnull参照の識別は、設計に関するより広範な問題を示していました。

2番目の参照は、null参照エラーが問題であるというアサーションの具体的な証拠を提供しません。しかし、著者は、彼らの経験では、これらのnull参照エラーがソフトウェアの欠陥の重大な原因であると述べています。次に、このペーパーでは、これらの欠陥を根絶しようとする方法について説明します。

また、エッフェル塔の最近のリリースに関するISEからの発表で、これについて何かを見たことを思い出しました。彼らはこの問題を「ボイドセーフティ」と呼んでおり、バートランドメイヤー博士に触発または開発された多くのものと同様に、問題について雄弁で教育的な説明があり、言語やツールで問題を防ぐ方法を説明しています。詳細については、彼らの記事 http://doc.eiffel.com/book/method/void-safety-background-definition-and-tools を読むことをお勧めします。

Microsoftのコード契約についてもっと知りたい場合は、最近出てきた記事がたくさんあります。また、http:SLASH SLASH codecontracts.infoで私のブログを確認することもできます。このブログは、主に契約によるプログラミングの使用によるソフトウェア品質に関する会話に専念しています。

31
David Allen

上記(Nullオブジェクト、空のコレクション)に加えて、いくつかの一般的な手法があります。つまり、リソースの取得はC++の初期化(RAII)であり、Eiffelの契約による設計です。これらは要約すると次のようになります。

  1. 有効な値で変数を初期化します。
  2. 変数がnullになる可能性がある場合は、nullをチェックして特殊なケースとして扱うか、null参照例外を予期します(そしてそれに対処します)。アサーションは、開発ビルドでのコントラクト違反をテストするために使用できます。

私はこのようなコードをたくさん見ました:

if((value!= null)&&(value.getProperty()!= null)&& ... &&(... doSomethingUseful())

多くの場合、これは完全に不要であり、ほとんどのテストは、より厳密な初期化とより厳密なコントラクト定義で削除できます。

これがコードベースの問題である場合は、それぞれの場合にnullが何を表すかを理解する必要があります。

  1. Nullが空のコレクションを表す場合は、空のコレクションを使用します。
  2. Nullが例外的なケースを表す場合は、例外をスローします。
  3. Nullが誤って初期化されていない値を表す場合は、明示的に初期化してください。
  4. Nullが正当な値を表す場合は、それをテストします。または、null操作を実行するNullObjectを使用することをお勧めします。

実際には、設計レベルでのこの明確さの基準は重要であり、コードベースに一貫して適用するには労力と自己規律が必要です。

21
richj

あなたはしません。

むしろ、C#でNREを「防止」しようとするために特別なことは何もありません。ほとんどの場合、NREはある種の論理エラーです。パラメータをチェックし、次のようなコードをたくさん持つことで、インターフェイスの境界でこれらをファイアウォールで保護できます

void Foo(Something x) {
    if (x==null)
        throw new ArgumentNullException("x");
    ...
}

いたるところに(.Net Frameworkの多くがこれを実行します)、失敗したときに、もう少し有益な診断が得られます(ただし、スタックトレースはさらに価値があり、NREもそれを提供します)。しかし、それでも例外が発生します。

(余談ですが、これらのような例外-NullReferenceException、ArgumentNullException、ArgumentException、...-は通常、プログラムによってキャッチされるべきではなく、単に「このコードの開発者、バグがあります。修正してください」という意味です。私はこれらを参照します。 「設計時」の例外として。これらを、ランタイム環境(FileNotFoundなど)の結果として発生し、プログラムによってキャッチおよび処理される可能性のある真の「ランタイム」例外と比較してください。)

しかし、結局のところ、正しくコーディングする必要があります。

'null'は多くの型/変数の無意味な値であるため、理想的にはNREの大部分は発生しません。理想的には、静的型システムはそれらの特定の型/変数の値として 'null'を許可しません。そうすれば、コンパイラーは、このタイプの偶発的なエラーの発生を防ぎます(特定のクラスのエラーを除外することが、コンパイラーと型システムが得意とするものです)。これは、特定の言語と型システムがExcelである場所です。

ただし、これらの機能がない場合は、コードをテストして、このタイプのエラーのあるコードパスがないことを確認します(または、追加の分析を実行できる外部ツールを使用することもできます)。

7
Brian

ここでは、 Null Object Patterns を使用することが重要です。

コレクションが入力されていない場合は、コレクションをnullではなく空にする必要があることを確認してください。空のコレクションが必要なときにnullコレクションを使用することは混乱を招き、多くの場合不要です。

最後に、可能な限り、構築時にオブジェクトにnull以外の値をアサートさせます。そうすれば、後で値がnullであるかどうかに疑いの余地はなく、必要な場合はnullチェックを実行するだけで済みます。私のフィールドとパラメーターのほとんどについて、以前のアサーションに基づいて値がnullではないと想定できます。

5
Brian Agnew

例外が発生する前にnull参照を簡単にチェックできますが、通常はそれが実際の問題ではないため、データがないとコードを続行できないため、とにかく例外をスローすることになります。

多くの場合、主な問題は、null参照があるという事実ではなく、そもそもnull参照を取得しているという事実です。参照がnullであると想定されていない場合は、適切な参照がなくても、参照が初期化されるポイントを超えてはなりません。

5
Guffa

実際、あなたの言語にnull値がある場合、それは必ず発生します。 null参照エラーは、アプリケーションロジックのエラーに起因します。したがって、これらすべてを回避できない限り、いくつかのエラーが発生する可能性があります。

4
Jeremy Raymond

私が見た中で最も一般的なnull参照エラーの1つは、文字列からのものです。チェックがあります:

if(stringValue == "") {}

しかし、文字列は実際にはnullです。そのはず:

if(string.IsNullOrEmpty(stringValue){}

また、オブジェクトのメンバー/メソッドにアクセスする前に、過度に注意して、オブジェクトがnullでないことを確認することもできます。

4
Jim Schubert

1つの方法は、可能な場合はNull値オブジェクト(別名 Nullオブジェクトパターン )を使用することです。あります 詳細はこちら

3
Preet Sangha

NullReferenceExceptionsを回避する最も簡単な方法の1つは、クラスコンストラクター/メソッド/プロパティセッターでnull参照を積極的にチェックし、問題に注意を引くことです。

例えば。

public MyClass
{
   private ISomeDependency m_dependencyThatWillBeUsedMuchLater 

   // passing a null ref here will cause 
   // an exception with a meaningful stack trace    
   public MyClass(ISomeDependency dependency)
   {
      if(dependency == null) throw new ArgumentNullException("dependency");

      m_dependencyThatWillBeUsedMuchLater = dependency;
   }

   // Used later by some other code, resulting in a NullRef
   public ISomeDependency Dep { get; private set; }
}

上記のコードでnullrefを渡すと、呼び出し元のコードがタイプを誤って使用していることがすぐにわかります。 null参照チェックがなかった場合、エラーはさまざまな方法で隠される可能性があります。

.NET Frameworkライブラリは、ほとんどの場合、早期に失敗し、無効な場所にnull参照を指定すると、失敗することがよくあります。スローされた例外は明示的に「めちゃくちゃだ!」と言っているので。理由を説明します。欠陥のあるコードの検出と修正は簡単な作業になります。

NullReferenceExceptionが必要なすべてであるため、このプラクティスは過度に冗長で冗長であるという苦情を一部の開発者から聞いていますが、実際には大きな違いがあります。これは特に、呼び出しスタックが深い場合やパラメータが保存されていて、その使用が後になるまで延期される場合に当てはまります(おそらく、別のスレッドで、または他の方法で隠されています)。

どちらかと言えば、エントリメソッドでのArgumentNullException、またはその内臓のあいまいなエラーは何ですか?エラーの原因から離れるほど、エラーを追跡するのが難しくなります。

2
Mark Simpson

優れたコード分析ツールがここで役立ちます。優れた単体テストは、コード内の可能なパスとしてnullを考慮するツールを使用している場合にも役立ちます。 「警告をエラーとして扱う」というスイッチをビルド設定に入れて、プロジェクトの警告の数を0に保つことができるかどうかを確認してください。警告が多くのことを示していることに気付くかもしれません。

覚えておくべきことの1つは、nullをスローしているのはgoodものである可能性があるということです-参照例外。どうして? shouldが実行したはずのコードが実行しなかったことを意味する場合があるためです。デフォルト値に初期化することは良い考えですが、問題を隠してしまうことがないように注意する必要があります。

List<Client> GetAllClients()
{
    List<Client> returnList = new List<Client>;
    /* insert code to go to data base and get some data reader named rdr */
   for (rdr.Read()
   {
      /* code to build Client objects and add to list */
   }

   return returnList;
}

了解しました。これで問題ないように見えるかもしれませんが、ビジネスルールによっては、これが問題になる場合があります。確かに、null参照をスローすることは決してありませんが、Userテーブルが空になることはありませんか?アプリを適切に回転させて、「空白の画面です」というユーザーからのサポートコールを生成しますか、それともどこかにログが記録される可能性のある例外を発生させて、アラートをすばやく発生させますか?実行していることと「処理」例外を検証することを忘れないでください。これが、一部の言語からnullを削除することを嫌う理由の1つです...新しいバグが発生する可能性がある場合でも、バグを見つけやすくなります。

注意:例外を処理し、非表示にしないでください。

2
Jim L

構造化された例外処理を適切に使用すると、このようなエラーを回避できます。

また、単体テストは、コードが期待どおりに動作することを確認するのに役立ちます。たとえば、値がnullでないはずのときにnullにならないようにすることもできます。

2
Andy West

プレーンコードソリューション

変数、プロパティ、およびパラメータを「null不可」としてマークすることにより、null参照エラーを早期にキャッチするのに役立つ構造体をいつでも作成できます。 Nullable<T>が機能する方法を概念的にモデル化した例を次に示します。

[System.Diagnostics.DebuggerNonUserCode]
public struct NotNull<T> where T : class
{
    private T _value;

    public T Value
    {
        get
        {
            if (_value == null)
            {
                throw new Exception("null value not allowed");
            }

            return _value;
        }
        set
        {
            if (value == null)
            {
                throw new Exception("null value not allowed.");
            }

            _value = value;
        }
    }

    public static implicit operator T(NotNull<T> notNullValue)
    {
        return notNullValue.Value;
    }

    public static implicit operator NotNull<T>(T value)
    {
        return new NotNull<T> { Value = value };
    }
}

Nullable<T>を使用するのとまったく同じ方法を使用しますが、nullを許可しないというまったく逆のことを達成することを目的としています。ここではいくつかの例を示します。

NotNull<Person> person = null; // throws exception
NotNull<Person> person = new Person(); // OK
NotNull<Person> person = GetPerson(); // throws exception if GetPerson() returns null

NotNull<T>は暗黙的にTとの間でキャストされるため、必要な場所で使用できます。たとえば、PersonオブジェクトをNotNull<Person>を受け取るメソッドに渡すことができます。

Person person = new Person { Name = "John" };
WriteName(person);

public static void WriteName(NotNull<Person> person)
{
    Console.WriteLine(person.Value.Name);
}

Nullableの場合と同様に上記で確認できるように、Valueプロパティを介して基になる値にアクセスします。または、明示的または暗黙的なキャストを使用することもできます。以下の戻り値の例を参照してください。

Person person = GetPerson();

public static NotNull<Person> GetPerson()
{
    return new Person { Name = "John" };
}

または、キャストを実行してメソッドがT(この場合はPerson)を返す場合にも使用できます。たとえば、次のコードは上記のコードとまったく同じです。

Person person = (NotNull<Person>)GetPerson();

public static Person GetPerson()
{
    return new Person { Name = "John" };
}

拡張機能と組み合わせる

NotNull<T>を拡張メソッドと組み合わせると、さらに多くの状況に対応できます。拡張メソッドがどのように見えるかの例を次に示します。

[System.Diagnostics.DebuggerNonUserCode]
public static class NotNullExtension
{
    public static T NotNull<T>(this T @this) where T : class
    {
        if (@this == null)
        {
            throw new Exception("null value not allowed");
        }

        return @this;
    }
}

そして、これがどのように使用できるかの例です:

var person = GetPerson().NotNull();

GitHub

参考までに、上記のコードをGitHubで利用できるようにしました。次の場所にあります。

https://github.com/luisperezphd/NotNull

1
Luis Perez

Nullを置き換えることができる正当なオブジェクトが存在する可能性がある場合は、 Nullオブジェクトパターン および 特殊なケースパターン を使用できます。

そのようなオブジェクトを構築できない場合、その必須操作を実装する方法がないため、 Map-Reduce Queries のように空のコレクションに依存できます。

もう1つの解決策は、 オプション機能タイプ です。これは、0個または1個の要素を持つコレクションです。そうすることで、実行できない操作をスキップする機会が得られます。

これらは、null参照やnullチェックなしでコードを書くのに役立つオプションです。

0
Zoran Horvat

役立つツール

役立つライブラリもいくつかあります。 Microsoftコード契約は上記のとおりです。

他のいくつかのツールには、Resharperが含まれます。これは、特に属性を使用する場合に、コードの記述中に警告を提供できます: NotNullAttribute

PostSharpもあり、次のような属性を使用できます。

public void DoSometing([NotNull] obj)

これを実行し、PostSharpをビルドプロセスの一部にすることで、実行時にobjがnullであるかどうかがチェックされます。参照: PostSharp nullチェック

Fodyコードウィービングプロジェクトには、ヌルガードを 実装するためのプラグインがあります。

0
Luis Perez