たとえば、lastChangePasswordTimeを持つクラスMember
があるとします。
class Member{
.
.
.
constructor(){
this.lastChangePasswordTime=null,
}
}
一部のメンバーはパスワードを変更できないため、lastChangePasswordTimeは意味がない場合があります。
しかし、nullが悪である場合、値が実質的に存在しない場合に何を使用する必要がありますか?およびhttps://softwareengineering.stackexchange.com/a/12836/248528、nullを使用して意味のない値を表すことはできません。だから私はブールフラグを追加しようとします:
class Member{
.
.
.
constructor(){
this.isPasswordChanged=false,
this.lastChangePasswordTime=null,
}
}
しかし、私はそれがかなり時代遅れだと思います:
IsPasswordChangedがfalseの場合、lastChangePasswordTimeはnullでなければならず、lastChangePasswordTime == nullの確認はisPasswordChangedがfalseの確認とほぼ同じであるため、lastChangePasswordTime == nullを直接確認することをお勧めします。
ここでロジックを変更すると、bothフィールドの更新を忘れる可能性があります。
注:ユーザーがパスワードを変更すると、次のように時間を記録します。
this.lastChangePasswordTime=Date.now();
追加のブール値フィールドは、ここでnull参照よりも優れていますか?
どうしてかわかりません。もしあなたが意味のない値を持っているなら、それについて慎重で注意深いならnull
は使われるべきではありません。
Null値を誤って参照しないようにするためにnull許容値を囲むことが目的である場合は、nullチェックの結果を返す関数またはプロパティとしてisPasswordChanged
値を作成することをお勧めします。次に例を示します。
class Member {
DateTime lastChangePasswordTime = null;
bool isPasswordChanged() { return lastChangePasswordTime != null; }
}
私の意見では、これを次のようにしています:
isPasswordChanged
値を維持することを実際に心配する必要がないようにします。データを(おそらくデータベースに)保持する方法は、nullが確実に保持されるようにする責任があります。
nulls
は悪ではありません。考えずに使うのです。これは、null
が正解である場合です。日付はありません。
ソリューションによってさらに問題が発生することに注意してください。日付が何かに設定されている意味は何ですか、isPasswordChanged
はfalseですか? null
値には明確に定義された明確な意味があり、他の情報と競合することはありませんが、特別にキャッチして処理する必要がある競合する情報のケースを作成しました。
だから、いいえ、あなたのソリューションは良くありません。 null
値を許可することは、ここでの正しいアプローチです。 null
が存在する理由がコンテキストに理解されていなくても、null
は常に邪悪であると主張する人々。
プログラミング言語によっては、optional
データ型などの適切な代替手段がある場合があります。 C++(17)では、これは std :: optional になります。存在しないか、基になるデータ型の任意の値を持つことができます。
null
の使用方法はいくつかあります。最も一般的で意味的に正しい方法は、single値がある場合とない場合に使用することです。この場合、値はnull
に等しいか、データベースのレコードなどの意味のあるものです。
これらの状況では、ほとんどの場合、(擬似コードで)次のように使用します。
_if (value is null) {
doSomethingAboutIt();
return;
}
doSomethingUseful(value);
_
そして、それは非常に大きな問題を抱えています。問題は、doSomethingUseful
を呼び出すまでに、値がnull
に対してチェックされていない可能性があることです。そうでなければ、プログラムはおそらくクラッシュします。また、ユーザーには「恐ろしいエラー:値が必要ですがnullになりました!」などのエラーメッセージが表示されない場合もあります。 (更新後:_Segmentation fault. Core dumped.
_などの有益なエラーはさらに少なくなる可能性がありますが、さらに悪いことに、エラーが発生せず、場合によってはnullが正しく処理されません)
null
のチェックを記述し、null
の状況を処理するのを忘れることは、非常に一般的なバグです。これが、null
を発明したTony Hoareが2009年にQCon Londonと呼ばれるソフトウェア会議で1965年に10億ドルの間違いを犯したと述べた理由です https://www.infoq.com/presentations/ Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
一部のテクノロジーと言語では、null
のチェックをさまざまな方法で忘れることができなくなり、バグの量が減少します。
たとえば、Haskellにはnullの代わりにMaybe
モナドがあります。 DatabaseRecord
がユーザー定義型であるとします。 Haskellでは、タイプ_Maybe DatabaseRecord
_の値は_Just <somevalue>
_と等しいか、またはNothing
と等しい場合があります。その後、さまざまな方法で使用できますが、どのように使用しても、知らないうちにNothing
に操作を適用することはできません。
たとえば、zeroAsDefault
と呼ばれるこの関数は、_Just x
_の場合はx
を、Nothing
の場合は_0
_を返します。
_zeroAsDefault :: Maybe Int -> Int
zeroAsDefault mx = case mx of
Nothing -> 0
Just x -> x
_
Christian HacklはC++ 17とScalaは独自の方法を持っていると言います。そのため、あなたの言語がそのようなものを持っているかどうかを調べてそれを使用したいと思うかもしれません。
何もない場合は、null
を使用することをお勧めします。気をつけてください。関数の型宣言は、とにかくいくらか役立ちます。
また、それほど進歩的ではないように聞こえるかもしれませんが、同僚がnull
などを使用したいかどうかを確認する必要があります。それらは保守的であり、何らかの理由で新しいデータ構造を使用したくない場合があります。たとえば、古いバージョンの言語をサポートしています。このようなことは、プロジェクトのコーディング標準で宣言し、チームと適切に話し合う必要があります。
別のブールフィールドを使用することをお勧めします。しかし、とにかくそれをチェックする必要があり、それでもチェックするのを忘れる可能性があります。だからここで勝ったものはありません。毎回両方の値を更新するなど、他のことを忘れることさえできれば、それはさらに悪いことです。 null
のチェックを忘れる問題が解決しない場合は、意味がありません。 null
を回避することは困難であり、悪化させるような方法で回避することはできません。
最後に、null
を誤って使用する一般的な方法があります。そのような方法の1つは、配列や文字列などの空のデータ構造の代わりに使用することです。空の配列は、他の配列と同様に適切な配列です!複数の値を収めることができる、つまり空にすることができる、つまり長さが0であることがデータ構造にとってほとんど常に重要かつ有用です。
代数の観点からは、文字列の空の文字列は数値の0に非常に似ています。つまり、identity:
_a+0=a
concat(str, '')=str
_
空の文字列を使用すると、一般的に文字列をモノイドにすることができます。 https://en.wikipedia.org/wiki/Monoid 取得できない場合は、それほど重要ではありません。
次に、この例でプログラミングすることが重要である理由を見てみましょう。
_for (element in array) {
doSomething(element);
}
_
ここで空の配列を渡すと、コードは正常に動作します。何もしません。ただし、ここでnull
を渡すと、「nullをループできません、申し訳ありません」などのエラーでクラッシュする可能性があります。 if
でラップすることもできますが、これはあまりクリーンではありません。あなたはチェックするのを忘れているかもしれません
doSomethingAboutIt()
がすべきこと、特に例外をスローすべきかどうかは、別の複雑な問題です。要するに、null
が特定のタスクの受け入れ可能な入力値であったかどうか、および応答で何が期待されるかによって異なります。例外は、予期されなかったイベントです。私はそのトピックにはこれ以上触れません。この答えは非常に長いです。
以前に与えられた非常に良い答えのすべてに加えて、フィールドをnullにしようとするたびに、リストタイプであるかどうか慎重に検討します。 null許容型は、同等に0または1要素のリストであり、多くの場合、これはN要素のリストに一般化できます。特にこの場合、lastChangePasswordTime
をpasswordChangeTimes
のリストと見なすことができます。
これを自問してみてください。どの動作にフィールドlastChangePasswordTimeが必要ですか?
メソッドIsPasswordExpired()にそのフィールドが必要で、メンバーにパスワードの変更を頻繁に要求するかどうかを判断する必要がある場合は、メンバーを最初に作成した時間にフィールドを設定します。 IsPasswordExpired()の実装は、新規メンバーと既存のメンバーで同じです。
class Member{
private DateTime lastChangePasswordTime;
public Member(DateTime lastChangePasswordTime) {
// set value, maybe check for null
}
public bool IsPasswordExpired() {
DateTime limit = DateTime.Now.AddMonths(-3);
return lastChangePasswordTime < limit;
}
}
新しく作成されたメンバーhaveがパスワードを更新するという別の要件がある場合は、passwordShouldBeChangedと呼ばれる別のブールフィールドを追加し、作成時にtrueに設定します。次に、IsPasswordExpired()メソッドの機能を変更して、そのフィールドのチェックを含めます(メソッドの名前をShouldChangePasswordに変更します)。
class Member{
private DateTime lastChangePasswordTime;
private bool passwordShouldBeChanged;
public Member(DateTime lastChangePasswordTime, bool passwordShouldBeChanged) {
// set values, maybe check for nulls
}
public bool ShouldChangePassword() {
return PasswordExpired(lastChangePasswordTime) || passwordShouldBeChanged;
}
private static bool PasswordExpired(DateTime lastChangePasswordTime) {
DateTime limit = DateTime.Now.AddMonths(-3);
return lastChangePasswordTime < limit;
}
}
コードで意図を明示します。
まず、ヌルが悪であることはドグマであり、ドグマではいつものように、合格/不合格テストとしてではなく、ガイドラインとして最適に機能します。
第2に、値をnullにできないようにすることで、状況を再定義できます。 InititialPasswordChangedはブール値で、最初はfalseに設定されています。PasswordSetTimeは、現在のパスワードが設定された日時です。
これには多少のコストがかかりますが、パスワードを最後に設定してからの経過時間をいつでも計算できることに注意してください。
呼び出し元が使用前にチェックする場合、どちらも「安全/正気/正しい」です。問題は、発信者がチェックしないとどうなるかです。 nullエラーのいくつかのフレーバーまたは無効な値の使用はどちらが良いですか?
正解は1つではありません。それはあなたが心配していることに依存します。
クラッシュが本当に悪いが、答えが重要ではないか、受け入れられるデフォルト値がある場合は、フラグとしてブール値を使用することをお勧めします。間違った答えを使用することがクラッシュよりも悪い問題である場合は、nullを使用することをお勧めします。
「典型的な」ケースの大部分では、高速な失敗と呼び出し側にチェックを強制することは、コードを正気にするための最速の方法です。 「Xはすべての悪の根である」伝道者をあまり信用しません。彼らは通常、すべてのユースケースを想定していません。