フロー制御に例外を使用しないようにするにはどうすればよいですか?
-
19-08-2019 - |
質問
私は、ストレージシステムへのインターフェイスとして機能するクラスのセットを開発するプロジェクトを割り当てられました。要件は、クラスが次のシグネチャを持つgetメソッドをサポートすることです:
public CustomObject get(String key, Date ifModifiedSince)
基本的に、メソッドは、オブジェクトがCustomObject
の後に変更された場合にのみ、key
に関連付けられたifModifiedSince
を返すことになっています。ストレージシステムにcontains
が含まれていない場合、メソッドはnullを返します。
私の問題はこれです:
キーは存在するがオブジェクトは変更されていないシナリオをどのように処理しますか?
このクラスを使用する一部のアプリケーションはWebサービスおよびWebアプリケーションになるため、これは重要です。これらのアプリケーションは、404(見つかりません)、304(変更なし)、または200(OK、ここにデータがあります)を返すかどうかを知る必要があります。
私が計量しているソリューションは次のとおりです。
- 次の場合にカスタム例外をスローします
ストレージシステムには
get(key, ifModifiedSince)
- 次の場合にカスタム例外をスローします
UNMODIFIED, KEY_DOES_NOT_EXIST
失敗します。 - CustomObjectにステータスプロパティを追加します。呼び出し元にプロパティの確認を要求します。
これら3つのオプションのいずれにも満足できません。フロー制御に例外を使用するのは好きではないので、オプション1と2は好きではありません。また、値がなかったことを示すことを目的とする場合、値を返すのも好きではありません。
それでも、オプション3に傾いています
検討していないオプションはありますか?これらの3つのオプションのいずれかに強い思いを持っている人はいますか?
この質問への回答、言い換え:
- <=>を提供する メソッドを呼び出し、呼び出し元にそれを呼び出すことを要求する <=>を呼び出す前に、スローする キーが存在しない場合は例外、 オブジェクトがされていない場合はnullを返します 変更されました。
- 応答とデータをラップします(存在する場合) 複合オブジェクト内。
- 事前定義済みの定数を使用して、何らかの状態を示します(<=>)。
- 発信者は、 コールバックとして使用されます。
- デザインが悪い。
回答#1を選択できない理由
これが理想的な解決策であることに同意しますが、それは私がすでに(しぶしぶ)却下したものです。このアプローチの問題は、これらのクラスが使用されるほとんどの場合、バックエンドストレージシステムがAmazon S3のようなサードパーティのリモートシステムになることです。これは、<=>メソッドがストレージシステムへのラウンドトリップを必要とし、ほとんどの場合、別のラウンドトリップが続くことを意味します。これは時間とお金の両方がかかるため、オプションではありません。
その制限がない場合、これが最良のアプローチです。
(質問でこの重要な要素に言及しなかったことに気づきましたが、簡潔にするように努めました。明らかに関連性がありました。)
結論:
すべての回答を読んだ後、この場合はラッパーが最善のアプローチであるという結論に達しました。基本的に、応答コードやコンテンツ本文(メッセージ)を含むメタデータ(ヘッダー)でHTTPを模倣します。
解決
実際には、応答コードと見つかったオブジェクトの2つの項目を返したいようです。両方を保持し、それらを一緒に返す軽量のラッパーを作成することを検討してください。
public class Pair<K,V>{
public K first;
public V second;
}
その後、応答コードとデータを保持する新しいペアを作成できます。ジェネリックを使用する副作用として、実際に必要なペアにこのラッパーを再利用できます。
また、データの有効期限が切れていない場合でも、データを返すことができますが、303コードを与えて、データが変更されていないことを知らせます。 4xxシリーズはnull
とペアになります。
他のヒント
指定された要件では、これを行うことはできません。
コントラクトを設計した場合、条件を追加し、呼び出し元に呼び出させる
exists(key): bool
サービスの実装は次のようになります。
if (exists(key)) {
CustomObject o = get(key, ifModifiedSince);
if (o == null) {
setResponseCode(302);
} else {
setResponseCode(200);
push(o);
}
} else {
setResponseCode(400);
}
クライアントは変更されないままであり、事前に検証されたことに気付くことはありません。
契約を設計しなかった場合おそらくそれには十分な理由があるか、おそらくそれは設計者(または設計者)のせいです。ただし、変更することはできないため、心配する必要もありません。
次に、仕様を順守し、次のように進めます。
CustomObject o = get(key, ifModifiedSince);
if (o != null) {
setResponseCode(200);
push(o);
} else {
setResponseCode(404); // either not found or not modified.
}
OK、この場合は302を送信していませんが、おそらくそれが設計された方法です。
つまり、セキュリティ上の理由から、サーバーはそれ以上の情報を返すべきではありません [プローブはget(key、date)はnullまたはオブジェクトのみを返します]
したがって、心配する必要はありません。上司に相談して、この決定を知らせてください。この決定もコードにコメントしてください。そして、あなたがアーキテクトを手に持っているなら、この奇妙な制限の背後にある理論的根拠を確認してください。
チャンスは、あなたはこれが来るのを見ていなかったということであり、彼らはあなたの提案後に契約を変更することができます。
場合によっては正しく処理を進めたいと思う一方で、処理が間違っていてアプリのセキュリティが侵害される可能性があります。
チームと通信します。
特別な最終CustomObjectを<!> quot; marker <!> quot;として作成できます。変更されていないことを示す:
static public final CustomObject UNCHANGED=new CustomObject();
および<!> quot; == <!> quotとの一致をテストします。 .equals()の代わりに。
変更されていないときにnullを返し、存在しないときに例外をスローすることもできますか?あなたの3つのうちの1つを選択しなければならなかった場合、1を選択します。これは最も例外的なケースだからです。
存在しないオブジェクトを探すことは、私にとって例外的なケースのようです。呼び出し元がオブジェクトが存在するかどうかを判断できるメソッドと組み合わせて、存在しない場合に例外をスローすることは問題ないと思います。
public bool exists( String key ) { ... }
発信者ができること:
if (exists(key)) {
CustomObject modified = get(key,DateTime.Today.AddDays(-1));
if (modified != null) { ... }
}
or
try {
CustomObject modified = get(key,DateTime.Today.AddDays(-1));
}
catch (NotFoundException) { ... }
例外の問題は、<!> quot; fail fast <!> quot;例外的なおよび異常な動作により、シナリオ(つまり、処理されない場合、例外はアプリケーションを 停止 します)
<!> quot;キーは存在するがオブジェクトが変更されていないシナリオ<!> quot;例外的なものであり、確かに異常なものではありません。
したがって、例外は使用しませんが、結果(プロパティまたは特別なオブジェクト)を正しく解釈するために呼び出し元が行う必要があるアクションを文書化します。
そのメソッドシグネチャの要件はどの程度厳格ですか?
まだ進行中のプロジェクトに取り組んでいるようです。クラスの消費者が他の開発者である場合、彼らが要求したメソッドシグネチャが不十分であると納得させることができますか?おそらく、2つの固有の障害モードが存在することにまだ気付いていません(キーは存在せず、オブジェクトは変更されていません)。
それがオプションである場合は、上司と話し合います。
まだnullを返します。
このプロパティの目的は、指定された日付以降に変更されたオブジェクトを返すことです。オブジェクトがない場合にnullを返すことが問題ない場合、変更されていないオブジェクトにnullを返すことも確かに問題ありません。
個人的には、変更されていないオブジェクトに対してはnullを返し、存在しないオブジェクトに対しては例外をスローします。それはもっと自然に思えます。
フロー制御BTWに例外を使用しないのは非常に正しいので、これらの3つのオプションしか持っていない場合、あなたの直感は正しいです。
CustomObject (string。空およびGuid.Empty)。オブジェクトが変更されていない場合、これを返すことができます(関数コンシューマはそれと比較する必要があります)。
編集:あなたはJavaで作業していることを発見しただけですが、原則は引き続き適用されます
これにより、次のオプションが提供されます
-
キーが存在しない場合はnullを返します。
-
キーは存在するがオブジェクトが変更されていない場合は、 CustomObject.Empty を返します。
欠点は、null戻り値とCustomObject.Empty戻り値の違いを消費者が知る必要があることです。
おそらく、プロパティは CustomObject.NotModified と呼ばれます。これは、値型がnullにできないため、Emptyは実際に値型を対象としているためです。また、 NotModified は、フィールドの意味をより簡単に消費者に伝えます。
要件に関する(意図した)インターフェースが深刻に壊れています。 1つのメソッド内で無関係なことをしようとします。これがソフトウェアの地獄への道です。
コールバッククラスがイベント駆動型またはセッター駆動型のいずれかである引数として、コールバックを提供します。
クラスのインターフェイスで発生する可能性のあるさまざまなエラーを定義し、必要に応じてイベントのパラメーターとしてCustomObjectを渡します。
public interface Callback {
public void keyDoesNotExist();
public void notModified(CustomObject c);
public void isNewlyModified(CustomObject c);
.
.
.
}
この方法では、Callbackインターフェースの実装者がイベントが発生したときの処理を定義できるようにし、その条件が取得されたオブジェクトの受け渡しを必要とするかどうかをインターフェースで選択できます。最後に、返品時のロジックの複雑さを軽減します。メソッドはそれを一度行います。 APIの実装者は、APIに対して行われているように、それを行う必要はまったくありません。
受け入れられる場合、増幅されたCustomObject(ラッパー)を返すことができます。このオブジェクトには、オブジェクトとその変更状態(ある場合)などを表す値が含まれています。