コードレビューの後に、デザインの選択について話し合いました。あなたの意見は何ですか。
このPreferences
クラスがあり、これはキーと値のペアのバケットです。 null値は有効です(これは重要です)。特定の値がまだ保存されていない可能性があるため、要求されたときに事前定義されたデフォルト値で初期化することにより、これらのケースを自動的に処理したいと考えています。
議論された解決策は次のパターンを使用しました(注:これは実際のコードではなくnotです-明らかに-説明のために簡略化されています):
_public class Preferences {
// null values are legal
private Map<String, String> valuesFromDatabase;
private static Map<String, String> defaultValues;
class KeyNotFoundException extends Exception {
}
public String getByKey(String key) {
try {
return getValueByKey(key);
} catch (KeyNotFoundException e) {
String defaultValue = defaultValues.get(key);
valuesFromDatabase.put(key, defaultvalue);
return defaultValue;
}
}
private String getValueByKey(String key) throws KeyNotFoundException {
if (valuesFromDatabase.containsKey(key)) {
return valuesFromDatabase.get(key);
} else {
throw new KeyNotFoundException();
}
}
}
_
それはアンチパターンとして批判されました- 例外を悪用してフローを制御する 。 KeyNotFoundException
-その1つのユースケースのみで実現-このクラスのスコープ外で見られることは決してありません。
それは本質的に、単に何かを互いに通信するためにそれを使ってフェッチを再生する2つの方法です。
データベースに存在しないキーは、驚くようなものでも例外的なものでもありません。新しい設定が追加されると必ず発生するため、必要に応じてデフォルト値で正常に初期化するメカニズムです。
反対論は、getValueByKey
-プライベートメソッド-現在定義されているとおり、パブリックメソッドに値の両方と、キーががあった。 (そうでない場合は、値を更新できるように追加する必要があります)。
null
は完全に正当な値であるため、null
を返すことはあいまいです。そのため、キーがないこと、またはnull
があったかどうかはわかりません。
getValueByKey
は、何らかの_Tuple<Boolean, String>
_を返す必要があります。キーがすでに存在する場合は、ブール値をtrueに設定して、_(true, null)
_と_(false, null)
_を区別できるようにします。 (out
パラメーターはC#で使用できますが、これはJavaです)。
これはより良い代替手段ですか?はい、_Tuple<Boolean, String>
_の効果に合わせて1回限りのクラスを定義する必要がありますが、その場合、KeyNotFoundException
が取り除かれるので、そのようなバランスが取れます。例外処理のオーバーヘッドも回避していますが、実際にはそれほど重要ではありません。パフォーマンスに関する考慮事項はなく、クライアントアプリであり、ユーザー設定が1秒あたり数百万回取得されるようなものではありません。
このアプローチのバリエーションでは、一部のカスタム_Optional<String>
_の代わりにGuavaの_Tuple<Boolean, String>
_(Guavaはすでにプロジェクト全体で使用されています)を使用できます。これにより、Optional.<String>absent()
と "proper" null
を区別できます。ただし、見やすい理由からハックに感じられます。「ヌルネス」の2つのレベルを導入すると、最初にOptional
sを作成する背後にある概念を乱用しているようです。
もう1つのオプションは、キーが存在するかどうかを明示的にチェックすることです(boolean containsKey(String key)
メソッドを追加し、すでに存在することを表明している場合にのみgetValueByKey
を呼び出します)。
最後に、privateメソッドをインライン化することもできますが、実際のgetByKey
はコードサンプルよりもやや複雑であるため、インライン化すると非常に厄介に見えます。
私はここで髪の毛を分割しているかもしれませんが、この場合のベストプラクティスに最も近づくためにあなたが何に賭けるのか知りたいです。 OracleやGoogleのスタイルガイドに答えは見つかりませんでした。
コードサンプルのような例外の使用はアンチパターンですか、それとも代替手段があまりクリーンでないことを考えると許容できますか?そうであれば、どのような状況で問題ないでしょうか?およびその逆?
はい、あなたの同僚は正しいです:それは悪いコードです。エラーをローカルで処理できる場合は、すぐに処理する必要があります。例外をスローしてすぐに処理しないでください。
これはあなたのバージョンよりもずっときれいです(getValueByKey()
メソッドは削除されています):
public String getByKey(String key) {
if (valuesFromDatabase.containsKey(key)) {
return valuesFromDatabase.get(key);
} else {
String defaultValue = defaultValues.get(key);
valuesFromDatabase.put(key, defaultvalue);
return defaultValue;
}
}
ローカルでエラーを解決する方法がわからない場合にのみ、例外がスローされます。
私は、この例外の使用をアンチパターンとは呼びません。複雑な結果を伝える問題の最善の解決策ではありません。
最善の解決策(あなたがJava 7)を使用していると仮定した場合)は、Guavaのオプションを使用することです。 GuavaのOptionalの拡張説明 、これはいつそれを使用するかの完璧な例であることを意味します。「値が見つかりません」と「nullの値が見つかりました」を区別しています。
パフォーマンスに関する考慮事項はなく、実装の詳細であるため、最終的にどのソリューションを選択するかは問題ではありません。しかし、私はそれが悪いスタイルであることに同意する必要があります。キーが存在しないことはあなたが知っていることであり起こりますそして、あなたはそれを処理してスタックを呼び出すことさえしません、それは例外です最も便利です。
ブールがfalseの場合、2番目のフィールドは無意味であるため、タプルアプローチは少しハックです。マップがキーを2回検索しているため、事前にキーが存在するかどうかを確認するのはばかげています。 (まあ、あなたはすでにそうしているので、この場合は3回です。)Optional
ソリューションは、問題に完全に適合します。 null
をOptional
に保存するのは少し皮肉なことのように思えるかもしれませんが、それがユーザーの希望する場合は、いいえとは言えません。
コメントでマイクが述べたように、これには問題があります。 GuavaもJava= 8's Optional
はnull
sの保存を許可していません。したがって、あなたは自分でロールする必要があります。ボイラープレートなので、内部で1度しか使用されないものにはやりすぎかもしれません。マップのタイプをMap<String, Optional<String>>
に変更することもできますが、Optional<Optional<String>>
sを処理するのは面倒です。
妥当な妥協案は例外を維持することかもしれませんが、その役割を「代替的利益」として認めます。スタックトレースを無効にして新しいチェック済み例外を作成し、静的変数に保持できる事前作成されたそのインスタンスをスローします。これは安価です- おそらくローカルブランチと同じくらい安いです -そして、それを処理することを忘れることができないので、スタックトレースの欠如は問題ではありません。
キーと値のペアのバケットであるこのPreferencesクラスがあります。 Null値は有効です(これは重要です)。特定の値がまだ保存されていない可能性があるため、要求されたときに事前定義されたデフォルト値で初期化することにより、これらのケースを自動的に処理したいと考えています。
問題はまさにこれです。しかし、あなたはすでに自分で解決策を投稿しました:
このアプローチのバリエーションでは、カスタムタプルの代わりにGuavaのオプションを使用できます(Guavaはプロジェクト全体で既に使用されています)これで、Optional.absent()と "正しい "null。ただし、見やすい理由から、依然としてハックのように感じられます。2つのレベルの「nullness」を導入すると、そもそもOptionalの作成の背後にある概念が乱用されるようです。
ただし、null
またはOptional
は使用しないでください。 Optional
のみを使用できます。 「ハッキー」な感覚の場合は、ネストして使用するだけで、データベースにキー(最初のオプションレイヤー)があり、事前定義された値(2番目のオプション)が含まれる可能性があることを明示する_Optional<Optional<String>>
_になります。層)。
このアプローチは、例外を使用するよりも優れており、Optional
が初めての人でない限り、非常に簡単に理解できます。
また、Optional
には快適な関数がいくつかあるため、すべての作業を自分で行う必要がないことに注意してください。これらには以下が含まれます:
static static <T> Optional<T> fromNullable(T nullableReference)
は、データベース入力をOptional
タイプに変換しますabstract T or(T defaultValue)
は、内部オプションレイヤーのキー値を取得するか、または(存在しない場合)デフォルトのキー値を取得します私はパーティーに遅れていることを知っていますが、とにかくあなたのユースケースはどのように似ています JavaのProperties
はデフォルトのプロパティのセットも定義でき、対応するキーがない場合にチェックされますインスタンスによってロードされます。
Properties.getProperty(String)
の実装方法を見る(Java 7から)):
Object oval = super.get(key);
String sval = (oval instanceof String) ? (String)oval : null;
return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
あなたが引用したように、「フローを制御するために例外を悪用する」必要は本当にありません。
@BЈовићの回答への同様の、しかし少し簡潔なアプローチも次のようになります。
public String getByKey(String key) {
if (!valuesFromDatabase.containsKey(key)) {
valuesFromDatabase.put(key, defaultValues.get(key));
}
return valuesFromDatabase.get(key);
}
@B [овићの答えはgetValueByKey
が他のどこにも必要ない場合には問題ないと思いますが、プログラムに両方のユースケースが含まれている場合、あなたのソリューションは悪いとは思いません:
事前にキーが存在しない場合の自動作成によるキーによる検索、および
データベース、リポジトリ、またはキーマップの何も変更せずに、その自動化なしで取得する(getValueByKey
は非公開ではなく公開されていると考えてください)
これがあなたの状況であり、パフォーマンスヒットが許容範囲内である限り、提案されたソリューションは完全に問題ないと思います。検索コードの重複を回避できるという利点があり、サードパーティのフレームワークに依存せず、非常にシンプルです(少なくとも私の目には)。
実際、そのような状況では、欠落しているキーが「例外的な」状況であるかどうかはコンテキストに依存します。それが存在するコンテキストでは、getValueByKey
のようなものが必要です。自動キー作成が予想されるコンテキストでは、すでに利用可能な機能を再利用するメソッドを提供し、例外を飲み込み、異なる失敗動作を提供することは、完全に理にかなっています。これはgetValueByKey
の拡張または「デコレータ」として解釈できます。「制御のフローのために例外が悪用される」関数ではありません。
もちろん、3つ目の選択肢があります。提案したように、Tuple<Boolean, String>
を返す3つ目のプライベートメソッドを作成し、getValueByKey
とgetByKey
の両方でこのメソッドを再利用します。確かに優れた代替案となる可能性のあるより複雑なケースの場合、ここに示すような単純なケースの場合、これはIMHOに過剰なエンジニアリングの匂いがするため、コードがそのように実際に保守しやすくなるとは思えません。私はここにいます カールビーレフェルトによるここでの一番上の答え :
「コードを単純化するときに、例外を使用することについて気を悪くすべきではありません」。
Optional
が正しい解決策です。ただし、必要に応じて、「2つのヌル」の感じが少ない代替案があります。歩哨を検討してください。
プライベート静的文字列keyNotFoundSentinel
を値new String("")
。*で定義します
これで、プライベートメソッドはthrow new KeyNotFoundException()
ではなく_return keyNotFoundSentinel
_を使用できます。 publicメソッドはその値を確認できます
_String rval = getValueByKey(key);
if (rval == keyNotFoundSentinel) { // reference equality, not string.equals
... // generate default value
} else {
return rval;
}
_
実際には、null
は番兵の特殊なケースです。これは言語によって定義されたセンチネル値であり、いくつかの特別な動作(つまり、null
でメソッドを呼び出す場合の明確な動作)があります。それは、ほとんどの言語がそれを使用するそのような歩哨を持っていることは偶然にとても偶然です。
*単に_""
_ではなくnew String("")
を意図的に使用して、Javaが文字列を「インターン」することを防ぎます。これにより、他の空の文字列と同じ参照が与えられます。この追加の手順を実行したため、keyNotFoundSentinel
によって参照される文字列が一意のインスタンスであることが保証されます。これは、マップ自体に表示されないようにする必要があります。
Javaのすべての問題点から学んだフレームワークから学びます。
.NETは、この問題に対して2つのはるかに洗練されたソリューションを提供します。
後者はJavaで書くのが非常に簡単で、前者は「強力な参照」ヘルパークラスを必要とするだけです。