すべての間接参照されたポインタをヌルガードするのは合理的ですか?


65

新しい仕事で、次のようなコードのコードレビューでフラグを立てられました。

PowerManager::PowerManager(IMsgSender* msgSender)
  : msgSender_(msgSender) { }

void PowerManager::SignalShutdown()
{
    msgSender_->sendMsg("shutdown()");
}

最後のメソッドは次のように読むべきだと言われています:

void PowerManager::SignalShutdown()
{
    if (msgSender_) {
        msgSender_->sendMsg("shutdown()");
    }
}

つまり、変数がプライベートデータメンバーであっても、変数をガードする必要があります。この「知恵」についてどのように感じているかを説明するためにexp辞を使うのを抑えるのは難しいです。説明を求めると、ある年のジュニアプログラマーがクラスがどのように機能するかについて混乱し、必要のないメンバーを誤って削除した(そしてその後に設定する)ことについてのホラーストーリーを大量に受け取ります、どうやら)、そして製品リリース直後に現場で物事が爆発しました。すべてをチェックする方が良いという「難しい方法を学び、信頼してください」。NULLmsgSender_NULLNULL

私にとって、これは貨物カルトのプログラミングのように感じます。いくつかの善意の同僚は、私が「それを得る」のを助け、これが私がより堅牢なコードを書くのにどのように役立つかを真剣に助けようとしていますが、...私は彼らがそれを手に入れていないように感じるのを助けることができません。

コーディング標準では、関数内で間接参照されているすべてのポインターをNULL最初にチェックすることを要求するのは妥当ですか?プライベートデータメンバーであっても?(注:コンテキストを提供するために、航空交通管制システムやその他の「障害と同等の人々が死ぬ」製品ではなく、家電製品を製造しています。)

編集:上記の例では、msgSender_共同編集者はオプションではありません。これまでNULLに発生した場合は、バグを示しています。コンストラクターに渡される唯一の理由はPowerManager、モックIMsgSenderサブクラスでテストできることです。

要約:この質問には本当に素晴らしい回答がいくつかありました。みなさんありがとう。私は主にその簡潔さのために@aaronpsからのものを受け入れました。以下のかなり一般的な合意があるようです。

  1. すべての間接参照されたポインタにNULLガードを強制するのはやり過ぎですが、
  2. 代わりに参照(可能な場合)またはconstポインターを使用して、議論全体を回避できます。
  3. assertステートメントは、NULL関数の前提条件が満たされていることを検証するための、ガードに対するより賢明な代替手段です。

14
間違いはジュニアプログラマーの領域だと思い込まないでください。あらゆるレベルの開発者の95%が、たまに何かをやった。残りの5%は歯を介して横たわっています。
Blrfl

56
nullをチェックし、静かに失敗するコードを誰かが書いているのを見た場合、その場でそれらを起動したいと思います。
ウィンストンイーバート

16
静かに失敗するものは、ほとんど最悪です。少なくとも爆発したとき、どこで爆発が起こっているかを知っています。チェックしnullて何もしないことは、バグを実行に移すだけの方法であり、ソースに戻るのをはるかに困難にします。
MirroredFate

1
+1、非常に興味深い質問。私の答えに加えて、ユニットテストに対応することを目的とするコードを作成する必要がある場合、あなたが本当にしていることは、誤ったセキュリティのリスクを増やすことです(コードの行数=バグの可能性が増える)。参照を使用するように再設計すると、コードの可読性が向上するだけでなく、コードが機能することを証明するために記述する必要があるコード(およびテスト)の量も削減されます。
セス

6
@Rig、サイレントフェイルの適切なケースがあると確信しています。しかし、誰かが(それが意味をなす分野で働いていない限り)一般診療として失敗したサイレントを置くならば、私は本当に彼らがコードを入れているプロジェクトで作業することにしたくない。
ウィンストン・エバート

回答:


66

それは「契約」に依存します:

有効ながPowerManager なけれならない場合IMsgSender、nullをチェックせず、すぐに消滅させます。

一方で、それは場合かもしれない持っているIMsgSender、あなたはそのような単純なように、使用するたびにチェックする必要があります。

ジュニアプログラマーの話についての最後のコメント、問題は実際にはテスト手順の欠如です。


4
途中までかなりの和が、私は感じて非常に簡潔な答えのための+1:あなたはまた、(「ヌルをチェックしたことがない」と言って根性を持っていた「それは...依存」場合はnullが無効です)。assert()ポインターを間接参照するすべてのメンバー関数に配置することは、多くの人をなだめる可能性が高い妥協ですが、それでも私にとっては「投機的妄想」のように感じます。私がコントロールする能力を超えたもののリストは無限であり、それらを心配し始めると、それは本当に滑りやすい斜面になります。私のテスト、同僚、そしてデバッガーを信頼することを学んだことで、夜の眠りが良くなりました。
evadeflow

2
コードを読んでいるとき、これらのアサートは、コードがどのように機能するかを理解するのに非常に役立ちます。私は「これらのすべての主張があちこちにあることを望んでいません」と言ったコードベースに一度も励ましませんでしたが、反対のことを言ったところをたくさん見ました。
クリストフプロボスト

1
単純明快、これは質問に対する正しい答えです。
マシューアズキモフ

2
これは最も近い正解ですが、「nullをチェックしない」ことに同意することはできません。メンバー変数に割り当てられるポイントで常に無効なパラメーターをチェックしてください。
ジェームズ

コメントをありがとう。誰かが仕様を読んでいない場合はすぐにクラッシュさせ、そこに何か有効なものが必要な場合はnull値で初期化することをお勧めします。
aaronps

77

私はコードを読むべきだと感じています:

PowerManager::PowerManager(IMsgSender* msgSender)
  : msgSender_(msgSender)
{
    assert(msgSender);
}

void PowerManager::SignalShutdown()
{
    assert(msgSender_);
    msgSender_->sendMsg("shutdown()");
}

これは実際には、NULLをガードするよりも優れています。NULLの場合、関数が呼び出されるべきではないことが非常に明確になるためmsgSender_です。また、これが発生した場合に気付くようになります。

同僚が共有する「知恵」は、このエラーを静かに無視し、予測できない結果をもたらします。

一般的に、バグは原因に近いと検出された場合、修正が簡単です。この例では、提案されているNULLガードによりシャットダウンメッセージが設定されず、顕著なバグが発生する場合と発生しない場合があります。SignalShutdownアプリケーション全体が停止した場合に比べて、関数を逆方向に操作するのに苦労し、便利なバックトレースまたはを直接指すコアダンプを生成しSignalShutdown()ます。

少し直感に反しますが、何か問題があるとすぐにクラッシュすると、コードがより堅牢になります。それはあなたが実際に問題を見つけ、そして同様に非常に明白な原因を持つ傾向があるからです。


10
assertの呼び出しとOPのコード例の大きな違いは、assertはデバッグビルド(#define NDEBUG)でのみ有効になっていることです。問題の関数が実行されてもポインタがnullの場合、アサートは保護を提供しません。
atk

17
おそらく、実際に顧客に送信する前にビルドをテストしたと思いますが、はい、これはリスクの可能性があります。ただし、ソリューションは簡単です:アサートを有効にして出荷するか(テストしたバージョンです)、デバッグモードでアサートし、リリースモードでログ、ログ+バックトレース、終了、...をアサートする独自のマクロに置き換えます。
クリストフプロボスト

7
@James-90%の場合、失敗したNULLチェックからも回復しません。しかし、そのような場合、コードは黙って失敗し、エラーはずっと後に表示される可能性があります-たとえば、ファイルに保存したと思ったが、実際にはnullチェックが失敗して賢くないことがあります。起こるべきではないすべての状況から回復することはできません。それらが発生しないことを確認できますが、NASA衛星または原子力発電所のコードを記述しない限り、予算があるとは思いません。「何が起こっているのかわからない-プログラムがこの状態になるとは思わない」と言う方法が必要です。
マチェイピエチョトカ

6
@ジェームス私は投げることに同意しません。それは実際に起こりうる何かのように見えます。アサーションは、不可能と思われるものに対するものです。言い換えれば、コードの実際のバグについて。例外は、予期しない、可能性のある事態が発生した場合です。例外は、考えられる処理と回復が可能なものです。回復できないコードの論理エラー。どちらも試してはいけません。
クリストフプロボスト

2
@James:組み込みシステムに関する私の経験では、ソフトウェアとハ​​ードウェアは常に設計されているため、デバイスを完全に使用できなくすることは非常に困難です。ほとんどの場合、ソフトウェアの実行が停止した場合にデバイスの完全なリセットを強制するハードウェアウォッチドッグがあります。
バートヴァンインゲンシェナウ

30

msgSender が決してであってnullならない場合null、コンストラクターのみにチェックを入れる必要があります。これは、クラスへの他の入力にも当てはまります-クラス、関数など、「モジュール」へのエントリポイントに整合性チェックを配置します。

私の経験則では、モジュール境界(この場合はクラス)間で整合性チェックを実行します。さらに、クラスは、クラスメンバーのライフタイムの整合性をすばやく精神的に検証できるように、十分に小さくする必要があります。不適切な削除やnullの割り当てなどの間違いを防ぐことができます。投稿のコードで実行されるnullチェックは、無効な使用が実際にポインターにnullを割り当てることを前提としています-常にそうであるとは限りません。「無効な使用法」は本質的に通常のコードに関する仮定が適用されないことを意味するため、すべてのタイプのポインターエラー(無効な削除、増分など)を確実にキャッチすることはできません。

また、引数がnullにならないことが確実な場合は、クラスの使用法に応じて参照の使用を検討してください。それ以外の場合は、生のポインタを使用するstd::unique_ptrstd::shared_ptr、代わりに使用することを検討してください。


11

いいえ、ポインターであるすべてのポインター逆参照をチェックすることは合理的ではありませんNULL

NULLポインターチェックは、前提条件が満たされていることを確認するため、またはオプションのパラメーターが提供されていない場合に適切なアクションを実行するために、関数の引数(コンストラクターの引数を含む)で役立ちます。また、クラスの内部を公開した後、クラスの不変式をチェックするのに役立ちます。しかし、ポインターがNULLになった唯一の理由がバグの存在である場合、チェックする意味はありません。そのバグは、ポインターを別の無効な値に簡単に設定できた可能性があります。

あなたのような状況に直面した場合、2つの質問をします。

  • なぜそのジュニアプログラマーの間違いがリリース前にキャッチされなかったのですか?たとえば、コードレビューやテスト段階ですか?障害のある家電製品を市場に持ち込むことは、安全性が重要なアプリケーションで使用されている障害のあるデバイスをリリースするのと同じくらい費用がかかります(市場シェアとのれんを考慮すると)。その他のQAアクティビティ。
  • nullチェックが失敗した場合、どのようなエラー処理を行うことを期待しますか、またはassert(msgSender_)nullチェックの代わりに単に書くことができますか?nullチェックを入れるだけでクラッシュを防ぐことができたかもしれませんが、実際には操作がスキップされている間に操作が行われたという前提でソフトウェアが続行するため、状況が悪化する可能性があります。これにより、ソフトウェアの他の部分が不安定になる可能性があります。

9

この例は、入力パラメーターがnull†であるかどうかよりも、オブジェクトの有効期間に関するもののようです。あなたPowerManager常に有効IMsgSenderでなければならないことに言及しているので、引数をポインタで渡すことにより(それによってnullポインタの可能性を可能にします)、設計上の欠陥として私を襲います††。

このような状況では、インターフェイスを変更して、呼び出し元の要件が言語によって強制されるようにすることをお勧めします。

PowerManager::PowerManager(const IMsgSender& msgSender)
  : msgSender_(msgSender) {}

void PowerManager::SignalShutdown() {
    msgSender_->sendMsg("shutdown()");
}

このように書き直すことは、そのライフタイム全体にわたってPowerManager参照を保持する必要があると言いますIMsgSender。また、これは、にIMsgSender比べてより長くPowerManager存続しなければならない暗黙の要件を確立し、内部のNULLポインターチェックまたはアサーションの必要性を否定しますPowerManager

また、スマートポインターを使用して(boostまたはc ++ 11を介して)同じことを記述し、明示的に次のIMsgSender期間よりも長く生きることができPowerManagerます。

PowerManager::PowerManager(std::shared_ptr<IMsgSender> msgSender) 
  : msgSender_(msgSender) {}

void PowerManager::SignalShutdown() {
    // Here, we own a smart pointer to IMsgSender, so even if the caller
    // destroys the original pointer, we still have a valid copy
    msgSender_->sendMsg("shutdown()");
}

このメソッドは、IMsgSenderのライフタイムがのライフタイムより長いことが保証できない場合PowerManager(つまりx = new IMsgSender(); p = new PowerManager(*x);)に推奨されます。

†ポインターに関して:横行するnullチェックはコードを読みにくくし、安定性を向上させません(安定性の外観を改善しますが、これははるかに悪いです)。

どこかに、誰かがメモリを保持するためのアドレスを取得しましたIMsgSenderstd::bad_alloc無効なポインタを渡さないように、割り当てが成功したことを確認する(ライブラリの戻り値をチェックするか、例外を適切に処理する)のは、関数の責任です。

PowerManagerはを所有していないためIMsgSender(しばらく借用しているだけです)、そのメモリの割り当てまたは破壊については責任を負いません。これが私がリファレンスを好むもう一つの理由です。

††この仕事は初めてなので、既存のコードをハッキングしていると思います。したがって、設計上の欠陥とは、使用しているコードに欠陥があることを意味します。したがって、nullポインターをチェックしていないためにコードにフラグを立てている人は、ポインターを必要とするコードを書くために実際にフラグを立てています:)


1
実際には、生のポインタを使用することは実際には問題ありません。Std :: shared_ptrは特効薬ではなく、引数リストで使用することはずさんなエンジニアリングの治療法ですが、可能な限りそれを使用することは「リート」と考えられています。そのように感じたらjavaを使用してください。
ジェームズ

2
生のポインターが悪いとは言わなかった!彼らは間違いなく彼らの場所を持っています。しかし、これはC + +、参照(共有ポインタは、今)理由のための言語の一部です。それらを使用してください、彼らはあなたを成功させるでしょう。
セス

私はスマートポインターのアイデアが好きですが、この特定のギグではオプションではありません。参照の使用を推奨するための+1(@Maxおよび他のいくつかが持っているように)。ただし、鶏と卵の依存関係の問題のため、参照を注入できない場合があります。つまり、注入の候補となるオブジェクトに、そのホルダーへのポインター(または参照)を注入する必要がある場合です。これは、密結合設計を示している場合があります。しかし、オブジェクトとその反復子の間の関係のように、前者なしで後者を使用することは理にかなっていない場合、それはしばしば受け入れられます。
evadeflow

8

例外と同様に、ガード条件は、エラーから回復するための対処方法がわかっている場合、またはより意味のある例外メッセージを表示する場合にのみ役立ちます。

エラーを飲み込むこと(例外またはガードチェックとしてキャッチされるかどうか)は、エラーが重要でない場合にのみ行うべき正しいことです。飲み込まれているエラーを確認する最も一般的な場所は、エラーログコードです。ステータスメッセージをログに記録できなかったため、アプリをクラッシュさせたくありません。

関数が呼び出され、オプションの動作ではないときはいつでも、静かにではなく大声で失敗するはずです。

編集:ジュニアプログラマーの話を考えてみると、プライベートメンバーがnullに設定されていたのに、それが許可されていないことが起こったようです。彼らは不適切な文章に問題があり、読んで検証することでそれを修正しようとしています。これは逆向きです。特定するまでに、エラーはすでに発生しています。そのためのコードレビュー/コンパイラの強制可能なソリューションは、ガード条件ではなく、ゲッターとセッターまたはconstメンバーです。


7

他の人が指摘したように、これは合法的にmsgSenderできるかどうかに依存します。以下は、決して NULL であってはならないと想定しています。 NULL

void PowerManager::SignalShutdown()
{
    if (!msgSender_)
    {
       throw SignalException("Shut down failed because message sender is not set.");
    }

    msgSender_->sendMsg("shutdown()");
}

チームの他の人が提案した「修正」は、デッドプログラムの嘘をつかないという原則に違反しています。バグを見つけるのは本当に難しいです。以前の問題に基づいて動作をサイレントに変更するメソッドは、最初のバグを見つけるのを難しくするだけでなく、独自の2番目のバグも追加します。

ジュニアはヌルをチェックしないことで大混乱を引き起こしました。このコードが未定義の状態で実行を継続することで大混乱を招くとしたらどうでしょう(デバイスはオンですが、プログラムはオフだと「考えている」)。おそらく、プログラムの別の部分は、デバイスがオフの場合にのみ安全なことを行います。

これらのアプローチのいずれかは、サイレント障害を回避します。

  1. この回答で提案されているようにアサートを使用しますが、実稼働コードでアサートされていることを確認してください。もちろん、他のアサーションが本番環境ではオフになるという前提で記述されている場合、これは問題を引き起こす可能性があります。

  2. nullの場合、例外をスローします。


5

コンストラクターでnullをトラップすることに同意します。さらに、メンバーがヘッダーで次のように宣言されている場合:

IMsgSender* const msgSender_;

その場合、初期化後にポインタを変更することはできません。そのため、構築に問題がなければ、それを含むオブジェクトの存続期間は問題ありません。(オブジェクトが指摘するだろうない CONSTです。)


それは素晴らしいアドバイスです、ありがとう!実際、非常に良いので、それ以上は投票できません。これは、述べられた質問に対する直接的な答えではありませんが、ポインターが「偶然」になる可能性を排除する素晴らしい方法NULLです。
evadeflow

@evadeflow「私は他のすべての人に同意します」と考えて、質問の主な目的に答えました。乾杯!;-)
グリムオピナー

質問の表現方法を考えると、この時点で受け入れられた答えを変更することは少し失礼になります。でも、このちょっとしたアドバイスは本当に素晴らしいと思うし、自分で考えていなかったのが残念だ。ではconst、ポインタ、する必要はありませんassert()コンストラクタの外では、ので、この答えは@Kristof学長のものよりも、少し良さそうです(やや間接的に同様の質問にも優れあったが、対処。)私はそれ以来、他の人がこれを投票を願っていますassertポインタを作ることができるだけである場合、すべてのメソッドでは本当に意味がありませんconst
evadeflow

@evadeflow人々は担当者のためにQuestinsにのみアクセスしますが、今では誰もそれを見ることはないと答えられています。;-)ポインタについての質問だったので、私は飛び出しただけで、血なまぐさい「常にすべてにスマートポインタを使用する」旅団に目を光らせています。
グリムオピナー

3

これはまったく危険です!

私は、Cコードベースの上級開発者の下で、同じことを求めて、すべてのポインターのNULLを盲目的にチェックする、最も粗末な「標準」を使用して作業しました。開発者は次のようなことをすることになります。

// Pre: vertex should never be null.
void transform_vertex(Vertex* vertex, ...)
{
    // Inserted by my "wise" co-worker.
    if (!vertex)
        return;
    ...
}

私はかつて、そのような関数で前提条件のそのようなチェックを一度削除して、assert何が起こるかを見るためにそれを置き換えることを試みました。

恐ろしいことに、この関数にnullを渡すコードベースで数千行のコードを見つけましたが、開発者は混乱している可能性があり、問題が解決するまでコードを追加しました。

さらに恐ろしいことに、この問題はコードベースのあらゆる場所でヌルをチェックしていることがわかりました。コードベースは数十年にわたって成長し、最も明確に文書化された前提条件でさえ静かに違反できるように、これらのチェックに依存するようになりました。これらの致命的なチェックを削除することによりasserts、コードベースでの数十年にわたるすべての論理的な人為的エラーが明らかになり、それらにinれ込んでしまいます。

このように一見無害なコードの2行+時間と1つのチームで、蓄積された1000個のバグをマスクできました。

これらは、ソフトウェアが機能するために存在する他のバグに依存するバグを作成する種類のプラクティスです。これは悪夢のシナリオです。また、こうした前提条件の違反に関連するすべての論理エラーにより、ミスが発生した実際のサイトから100万行のコードが神秘的に表示されます。これらのnullチェックはすべて、バグを非表示にし、バグを隠します。

ヌルポインターが前提条件に違反するすべての場所で盲目的にヌルを単純にチェックすることは、私にとっては完全に狂気です。

コーディング標準では、関数で間接参照されているすべてのポインターを最初にNULLでチェックすることを要求することは合理的ですか?プライベートデータメンバーであっても?

ですから、絶対にそうではないと言います。「安全」でもありません。それは非常によく反対であり、コードベース全体のあらゆる種類のバグを隠します。これは、長年にわたって最も恐ろしいシナリオにつながる可能性があります。

assertここに行く方法です。前提条件の違反は、気付かれることなく許可されるべきではありません。さもないと、マーフィーの法則が簡単に発動します。


1
「アサート」は進むべき方法ですが、最新のシステムでは、ポインターを介した各メモリアクセスにはハードウェアに組み込まれたヌルポインターアサートがあることに注意してください。そのため、「assert(p!= NULL); return * p;」では、アサートでさえほとんど意味がありません。
gnasher729

@ gnasher729ああ、それは非常に良い点ですが、私assertは、単にアボートを強制する方法ではなく、前提条件が何であるかを確立するための文書化メカニズムの両方と考える傾向があります。しかし、どのコード行が失敗を引き起こし、アサート条件が失敗したかを正確に示すアサーション失敗の性質も好きです(「クラッシュの文書化」)。デバッガーの外部でデバッグビルドを使用することになり、不意を突かれた場合に役立ちます。

@ gnasher729または、その一部を誤解した可能性があります。これらの最新のシステムは、アサーションが失敗したソースコードの行を示していますか?私は通常、彼らがその時点で情報を失い、単にセグメンテーション違反/アクセス違反を示すかもしれないと思いますが、私は「モダン」とはほど遠いです-私の開発のほとんどでまだWindows 7で頑固です。

たとえば、MacOS XおよびiOSでは、開発中にnullポインターアクセスが原因でアプリがクラッシュした場合、デバッガーはそれが発生した正確なコード行を通知します。別の奇妙な側面があります。疑わしい操作を行うと、コンパイラは警告を表示しようとします。ポインターを関数に渡してアクセスすると、コンパイラーはポインターがNULLである可能性があるという警告を出しません。これは頻繁に発生するため、警告にdrれるからです。ただし、if(p == NULL)...と比較すると、コンパイラはpがNULLである可能性が高いと想定します。
gnasher72916年

なぜそうしないとテストするのか、テストせずに使用すると警告が表示されるからです。独自のアサートのようなマクロを使用する場合、「my_assert(p!= NULL、 "それはヌルポインターです、愚か!"); * p = 1; "警告はしません。
gnasher729

2

たとえば、Objective-Cは、nilオブジェクトに対するすべてのメソッド呼び出しを、ゼロに近い値に評価されるノーオペレーションとして扱います。質問で提案された理由により、Objective-Cの設計決定にはいくつかの利点があります。すべてのメソッド呼び出しをヌルガードするという理論的な概念は、それが十分に公表され、一貫して適用されている場合、ある程度のメリットがあります。

とはいえ、コードは真空ではなくエコシステム内に存在します。ヌルガードの動作は、C ++では非慣用的で驚くべきものであるため、有害であると見なされる必要があります。要約すると、C ++コードをそのように書く人はいないので、やらないでください!ただし、反例として、CおよびC ++での呼び出しfree()またはdeleteonはNULL、no-opであることが保証されていることに注意してください。


あなたの例では、コンストラクターにmsgSenderヌルでないアサーションを配置する価値があるでしょう。コンストラクターがすぐにメソッド呼び出した場合、msgSenderとにかくそこでクラッシュするため、そのようなアサーションは必要ありません。ただし、将来の使用のために保存する だけmsgSenderなのでSignalShutdown()、値がどのようになったかのスタックトレースを見ても明らかではないNULLため、コンストラクターでのアサーションはデバッグを大幅に容易にします。

さらに良いことに、コンストラクタはconst IMsgSender&参照を受け入れる必要がありますNULL


1

null参照を避けるように求められる理由は、コードが堅牢であることを保証するためです。ずっと前のジュニアプログラマーの例は単なる例です。誰でもコードを誤って壊して、特にグローバルおよびクラスグローバルの場合、null参照解除を引き起こす可能性があります。CおよびC ++では、直接メモリ管理機能により、偶発的にさらに可能性が高くなります。驚くかもしれませんが、この種のことは非常に頻繁に起こります。非常に知識があり、経験豊富で、非常に上級の開発者でも。

すべてをnullチェックする必要はありませんが、nullになる可能性が十分にある逆参照から保護する必要があります。これは通常、それらが異なる関数で割り当てられ、使用され、逆参照される場合です。他の関数の1つが変更され、関数が破損する可能性があります。また、他の関数の1つが順不同で呼び出される可能性があります(デストラクタとは別に呼び出すことができるデアロケータがある場合など)。

私は、同僚がassertの使用と組み合わせてあなたに言っているアプローチが好きです。テスト環境でクラッシュするため、本番環境で問題を修正して正常に失敗することは明らかです。

また、coverityやfortifyなどの堅牢なコード正当性ツールを使用する必要があります。そして、すべてのコンパイラの警告に対処する必要があります。

編集:他の人が述べたように、いくつかのコード例のように、静かに失敗することも一般的に間違っています。関数がnull値から回復できない場合、呼び出し元にエラーを返す(または例外をスローする)必要があります。呼び出し元は、呼び出し順序を修正するか、回復するか、呼び出し元にエラーを返す(または例外をスローする)などの責任を負います。最終的に、関数は正常に回復して続行するか、正常に回復して失敗するか(1人のユーザーの内部エラーのためにデータベースがトランザクションに失敗するが、実際には終了しない)、または関数がアプリケーションの状態が破損していると判断する回復不能で、アプリケーションが終了します。


(一見)不人気な立場を支える勇気を持っているために+1。これについて私の同僚を誰も擁護していなければ、私は失望していたでしょう(彼らは長年にわたって高品質の製品を出し続けてきた非常に賢い人々です)。また、アプリは「テスト環境でクラッシュし、本番環境で正常に失敗する」というアイデアも気に入っています。私が義務づけ「丸暗記」ことを確信していないNULLチェックはそれを行うための方法ですが、私は基本的に言って、私の職場外の人から意見を聞いて感謝:「Aighは思えませんあまりにも。無理」
evadeflow

malloc()の後にNULLをテストする人がいるのを見るとイライラします。最新のOSがメモリを管理する方法を理解していないことがわかります。
クリストフプロボスト

2
私の推測では、@ KristofProvostは、オーバーコミットを使用するOSについて話しているため、mallocは常に成功します(ただし、実際に十分なメモリが使用できない場合、OSは後でプロセスを強制終了する場合があります)。ただし、これはmallocでnullチェックをスキップする正当な理由ではありません:オーバーコミットはプラットフォーム間で普遍的ではなく、有効になっている場合でも、mallocが失敗する他の理由があるかもしれません(たとえば、Linuxでは、ulimitで設定されたプロセスアドレススペース制限)。
ジョンバーソロミュー

1
ジョンが言ったこと。私は確かにオーバーコミットについて話していました。Linuxでは、デフォルトでオーバーコミットが有効になっています(これが私の支払いです)。わかりませんが、Windowsでも同じであると思われます。最もあなたが書くために準備している場合を除き、とにかくクラッシュ終わるつもりケース入りでたくさん ...決して、今までにほぼ確実に起こるだろうことはありませんエラーを処理するためにテストすることになるだろうコードのを
クリストフ学長

2
メモリ不足の状態を実際に正しく処理するコード(Linuxカーネル以外、実際にはメモリ不足が実際に発生する可能性がある)を見たことがないことも付け加えてください。私が試したすべてのコードは、とにかく後でクラッシュします。達成されたのは、時間を無駄にし、コードを理解しにくくし、実際の問題を隠すことでした。Null参照解除はデバッグが簡単です(コアファイルがある場合)。
クリストフプロボスト

1

参照の代わりにポインターを使用すると、それは単なるオプションであり、nullチェックが正しいことがわかります。コードスニペットは、それを決定するには遅すぎます。たぶん、価値のある(またはテスト可能な)他の要素があります...msgSenderPowerManager

ポインターと参照を選択するときは、両方のオプションを徹底的に検討します。メンバー(プライベートメンバーであっても)にポインターを使用する必要がある場合は、if (x)参照を解除するたびに手順を受け入れる必要があります。


別のコメントで述べたように、参照を挿入できない場合があります。よくある例は、保持されたオブジェクト自体がホルダーへの参照を必要とする場合です
。– evadeflow

@evadeflow:OK、私は告白します、それはクラスのサイズとポインターメンバーの可視性(参照することができない)、逆参照する前にチェックするかどうかに依存します。クラスは小さく、無地で、ポインタ(s)はプライベートでのケースでは、私にはありません...
ウルフ

0

それはあなたが何をしたいかによって異なります。

あなたは「家電製品」に取り組んでいると書きました。、どういうわけか、バグが設定することにより、導入された場合msgSender_NULL、あなたがしたいですか

  • SignalShutdown続行するデバイスで、スキップして残りの操作を継続する、または
  • デバイスをクラッシュさせ、ユーザーに強制的に再起動させますか?

シャットダウン信号が送信されない場合の影響によっては、オプション1 実行可能な選択肢になる場合があります。ユーザーが彼の音楽に耳を傾け続けることができますが、表示はまだ前のトラックのタイトルが表示されている場合、それは可能性があるデバイスの完全なクラッシュに好ましいこと。

もちろん、オプション1を選択した場合、assert(他の人が推奨するように)開発中に気付かれずにこのようなバグが忍び寄る可能性を減らすために不可欠です。ifヌルガードは、生産、使用中の障害の緩和のためだけに存在しています。

個人的には、プロダクションビルドには「早期クラッシュ」アプローチも好みますが、バグが発生した場合に簡単に修正および更新できるビジネスソフトウェアを開発しています。家電機器の場合、これはそれほど簡単ではないかもしれません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.