オブジェクトに無効な状態がある場合、ゲッターは例外をスローする必要がありますか?


55

一般的なOOPの問題だと思っていても、特にJavaでこの問題によく遭遇します。つまり、例外を発生させると、設計上の問題が明らかになります。

String nameフィールドとフィールドを持つクラスがあるとしString surnameます。

次に、これらのフィールドを使用して個人の完全な名前を作成し、ある種のドキュメント(請求書など)に表示します。

public void String name;
public void String surname;

public String getCompleteName() {return name + " " + surname;}

public void displayCompleteNameOnInvoice() {
    String completeName = getCompleteName();
    //do something with it....
}

次にdisplayCompleteNameOnInvoice、名前が割り当てられる前にが呼び出された場合にエラーをスローして、クラスの動作を強化したいと思います。いい考えですね。

getCompleteNameメソッドに例外発生コードを追加できます。しかし、この方法では、クラスユーザーとの「暗黙の」契約に違反しています。一般に、値が設定されていない場合、ゲッターは例外をスローすることは想定されていません。わかりました。これは単一のフィールドを返さないため、これは標準のゲッターではありませんが、ユーザーの観点からは区別が微妙すぎて考えられない場合があります。

または、の内部から例外をスローできdisplayCompleteNameOnInvoiceます。しかし、そうするためには、直接nameまたはsurnameフィールドをテストする必要がありますgetCompleteName。そうすることで、で表される抽象化に違反することになります。完全な名前を確認して作成するのは、このメソッドの責任です。他のデータに基づいて、場合によっては十分であると判断することさえできsurnameます。

だから、唯一の可能性は、メソッドのセマンティックを変更するようだgetCompleteNamecomposeCompleteName、それをより「アクティブ」の行動と、例外をスローする能力を示唆しています。

これはより良い設計ソリューションですか?私はいつもシンプルさと正確さの間で最高のバランスを探しています。この問題の設計参照はありますか?


8
「一般に、値が設定されていない場合、ゲッターは例外をスローすることになっていません。」-それで彼らは何をすることになっていますか?
ロテム14年

2
そして、そのロジックを以下@scriptin、displayCompleteNameOnInvoice場合だけ例外を投げることができるgetCompleteName戻りnull、それができないのですか?
ロテム14年

2
@Rotemできます。問題は、それが必要かどうかです。
scriptin

22
テーマに関して、Falsehoods Programmers Believe About Namesは、「名」と「姓」が非常に狭い概念である理由について非常に良い読み物です。
ラースヴィクルンド14年

9
オブジェクトが無効な状態になる可能性がある場合は、すでに台無しになっています。
user253751 14年

回答:


151

名前を割り当てずにクラスを構築することを許可しないでください。


9
面白いですが、非常に根本的な解決策です。多くの場合、クラスはより流動的である必要があり、特定のメソッドが呼び出された場合にのみ問題になる状態を可能にします。
AgostinoX 14年

71
これはコメントではありません。彼が答えにアドバイスを適用した場合、彼は説明された問題を抱えなくなります。それは答えです。
DeadMG

14
@AgostinoX:絶対に必要な場合にのみ、クラスを流動的にする必要があります。そうでなければ、それらは可能な限り不変でなければなりません。
DeadMG

25
@gnat:ここでは、PSEのすべての質問とすべての回答の質に疑問を投げかける素晴らしい仕事をしていますが、時々、私見がちょっと気味が悪いです。
ドックブラウン14年

9
それらが有効にオプションである場合、彼が最初に投げる理由はありません。
DeadMG

75

DeadMGが提供する答えはそれをほとんど否定していますが、少し違う言い方をしましょう:無効な状態のオブジェクトを持つことは避けてください。オブジェクトは、それが果たすタスクのコンテキストで「全体」である必要があります。または、存在しないはずです。

したがって、流動性が必要な場合は、Builderパターンのようなものを使用します。または、「実物」とは対照的に、構築中のオブジェクトの別個の具体化であるもの。後者は有効な状態を持つことが保証され、その状態で定義された操作を実際に公開します(例getCompleteName)。


7
So if you want fluidity, use something like the builder pattern-流動性、または不変性(それが何とか意味する場合を除く)。不変オブジェクトを作成したいが、いくつかの値の設定を延期する必要がある場合、従うべきパターンは、ビルダーオブジェクトに1つずつ設定し、正しいために必要なすべての値を収集した後にのみ不変オブジェクトを作成することです状態...
コンラートMorawski

1
@KonradMorawski:私は混乱しています。私の声明を明確にしたり、矛盾したりしていますか ;)
back2dos 14年

3
私は...それを補完しようとした
コンラートMorawski

40

無効な状態のオブジェクトはありません。

コンストラクターまたはビルダーの目的は、オブジェクトの状態を一貫性のある使いやすいものに設定することです。この保証がないと、オブジェクトの状態が不適切に初期化されると問題が発生します。この問題は、並行性を処理していて、オブジェクトの設定が完了する前に何かがオブジェクトにアクセスできる場合、さらに複雑になります。

したがって、この部分の質問は、「なぜ名前または姓をヌルにすることを許可しているのですか?」です。それが適切に機能するためにクラスで有効なものでない場合は、許可しないでください。オブジェクトの作成を適切に検証するコンストラクターまたはビルダーを用意し、正しくない場合はその時点で問題を提起します。また、存在する@NotNull注釈の 1つを使用して、「これはnullにできない」ことを伝え、コーディングと分析でそれを強制することもできます。

この保証が適切に設定されていると、コードとその動作について推論するのがはるかに簡単になり、奇妙な場所で例外をスローしたり、ゲッター関数を過度にチェックしたりする必要がなくなります。

より多くのことをするゲッター。

このテーマに関しては、かなり多くのことがあります。あなたは持っているどのくらいのゲッターのロジックゲッターとセッターの内側に許可する必要がありますか?ここから、ゲッターとセッターがスタックオーバーフローで追加のロジック実行します。これは、クラス設計で何度も発生する問題です。

このコアは次のものから来ています。

一般に、値が設定されていない場合、ゲッターは例外をスローすることになっていません

そしてあなたは正しい。彼らはすべきではありません。彼らは世界の残りの部分に対して「馬鹿げている」ように見え、期待されることをするべきです。そこにあまりにも多くのロジックを入れると、最小限の驚きの法則に違反する問題につながります。それに直面して、ゲッターをラッピングするのは本当に嫌です。try catchなぜなら、一般的にそれをするのがどれだけ好きか知っているからです。

また、JavaBeansフレームワークなどのメソッドを使用する必要ありgetFooEL呼び出しから何かを取得してBeanを取得する必要がある場合もあります(そのため、<% bar.foo %>実際にgetFoo()クラスを呼び出します。どちらかが明らかに正しい答えになりうる状況を簡単に思い付くことができるからです)

また、特定のゲッターがインターフェイスで指定されるか、リファクタリングされるクラスの以前に公開されたパブリックAPIの一部であった可能性があることを認識します2つのフィールドに分けます)。

一日の終わりに...

推論するのが最も簡単なことをしてください。このコードの維持に費やす時間は、設計に費やす時間よりも長くなります(ただし、設計に費やす時間は短くなるほど、維持に費やす時間は長くなります)。理想的な選択は、メンテナンスにそれほど時間がかからないように設計することです、数日間は考えずに座ってはいけません-これが実際に数日後に影響を与える設計の選択でない限り。

ゲッターのセマンティックな純度に固執しようとするとprivate T foo; T getFoo() { return foo; }、ある時点で問題が発生します。コードがそのモデルに適合しない場合や、そのように維持しようとする歪みが意味をなさない場合があり、最終的に設計が困難になります。

設計の理想は、コードで望んでいるようには実現できないことを時々受け入れます。ガイドラインはガイドラインであり、シャックルではありません。


2
明らかに、私の例は、ブロッキングの問題ではなく、推論することでコーディングスタイルを改善するための言い訳にすぎません。しかし、私はあなたと他の人がコンストラクタに与える重要性に非常に困惑しています。理論的には、彼らは約束を守ります。実際には、継承によって維持するのが面倒になり、クラスからインターフェイスを抽出するときに使用できず、javabeansや春のような他のフレームワークに採用されていない場合はうまくいきません。「クラス」アイデアの傘の下で、オブジェクトの多くの異なるカテゴリに住んでいます。値オブジェクトには検証コンストラクターがある場合がありますが、これは単なる1つのタイプです。
AgostinoX 14年

2
コードについて推論できることは、APIを使用してコードを記述できること、またはコードを維持できることの鍵です。クラスに無効な状態になることを許可するコンストラクターがある場合、「まあ、これは何をしますか?」を通して考えることをはるかに難しくします。なぜなら、クラスの設計者が考慮または意図したよりも多くの状況を考慮する必要があるからです。この余分な概念的負荷には、より多くのバグとコードの作成時間が長くなるというペナルティが伴います。一方、コードを見て、「名前と姓は決してnullにならない」と言うことができれば、それらを使用してコードを書くのがはるかに簡単になります。

2
不完全であることは、必ずしも無効であることを意味しません。領収書のない請求書が進行中である可能性があり、それが提示するパブリックAPIはそれが有効な状態であることを反映します。場合surnameまたはnameヌルであることは、オブジェクトのための有効な状態で、それに応じてそれを処理。しかし、それが有効な状態ではない場合-クラス内のメソッドが例外をスローする場合、初期化された時点からその状態にならないようにする必要があります。不完全なオブジェクトを渡すことはできますが、無効なオブジェクトを渡すことには問題があります。nullの姓が例外的である場合は、それより前にそれを防ぐようにしてください。

2
関連するブログ記事「オブジェクトの初期化の設計」で、インスタンス変数を初期化するための4つのアプローチに注目してください。それらの1つは、例外をスローすることに注意して、無効な状態にすることです。これを回避しようとしている場合、そのアプローチは有効なアプローチではなく、他のアプローチで作業する必要があります-常に有効なオブジェクトを確保する必要があります。ブログから:「無効な状態を持つことができるオブジェクトは、常に有効なオブジェクトよりも使いにくく、理解しにくい」それが鍵です。

5
「推論するのが最も簡単なことをしてください。」それ
ベン

5

私はJavaの男ではありませんが、これはあなたが提示した両方の制約を順守しているようです。

getCompleteName名前が初期化されていない場合、例外をスローしませんdisplayCompleteNameOnInvoice

public String getCompleteName()
{
    if (name == null || surname == null)
    {
        return null;
    }
    return name + " " + surname;
}

public void displayCompleteNameOnInvoice() 
{
    String completeName = getCompleteName();
    if (completeName == null)
    {
        //throw an exception.
    }
    //do something with it....
}

これが正しい解決策である理由を拡大するには:このように、呼び出し元はgetCompleteName()、プロパティが実際に保存されているか、その場で計算されるかを気にする必要はありません。
バートヴァンインゲンシェナウ14年

18
なぜこのソリューションがバカバカ狂っているのかを拡大するには、のすべての呼び出しでnullityをチェックする必要がありますgetCompleteName。これは恐ろしいことです。
DeadMG 14年

いいえ、@ DeadMG、getCompleteNameがnullを返す場合に例外をスローする必要がある場合にのみチェックする必要があります。それはあるべき姿です。この解決策は正しいです。
ダウッドイブンカリーム14年

1
@DeadMGはい、ただしgetCompleteName()、クラスの他の部分やプログラムの他の部分で、OTHERが使用するものがわからない。場合によっては、返品nullがまさに必要な場合があります。しかし、「この場合は例外をスローする」という仕様のメソッドが1つありますが、まさにそれをコーディングする必要があります。
ダウッドイブンカリーム14年

4
これが最善の解決策である場合もありますが、私には想像できません。ほとんどの場合、これは過度に複雑に思われます。1つのメソッドは不完全なデータをスローし、別のメソッドはnullを返します。これは発信者が誤って使用する可能性が高いと思います。
sleske

4

誰もあなたの質問に答えていないようです。
人々が信じたいものとは異なり、「無効なオブジェクトを避ける」ことは実際的な解決策ではないことがよくあります。

答えは:

はい、そうすべきです。

簡単な例:FileStream.LengthC#/。NETではNotSupportedException、ファイルがシーク可能でない場合にスローされます。


コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
maple_shaft

1

あなたは全体の逆さまのチェック名のものを持っています。

getCompleteName()どの機能がそれを利用するかを知る必要はありません。したがって、name呼び出しdisplayCompleteNameOnInvoice()がない場合はgetCompleteName()責任を負いません。

ただし、nameはの明確な前提条件ですdisplayCompleteNameOnInvoice()。したがって、状態をチェックする責任を負う必要があります(または、必要に応じて、チェックの責任を別のメソッドに委任する必要があります)。

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