ここで例外をスローするのはアンチパターンですか?


33

コードレビューの後、デザインの選択について議論したところです。あなたの意見は何でしょうか。

このPreferencesクラスは、キーと値のペアのバケットです。NULL値は正当です(重要です)。特定の値がまだ保存されていない可能性があり、要求されたときに事前定義されたデフォルト値で初期化することにより、これらのケースを自動的に処理したいと考えています。

説明したソリューションでは、次のパターンを使用しました(注:これは実際のコードではなく、明らかに-説明のために単純化されています)。

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

getValueByKeyTuple<Boolean, String>キーが既に存在する場合、ブール値をtrueに設定して、とを区別できるように、何らかのaを返す必要が(true, null)あり(false, null)ます。(outパラメーターはC#で使用できますが、それはJavaです)。

これはより良い代替手段ですか?はい、の効果に対していくつかの使い捨てクラスを定義する必要がありますTuple<Boolean, String>が、その後、を取り除きKeyNotFoundException、そのようなバランスを取ります。また、例外を処理するオーバーヘッドも回避していますが、実際的な意味では重要ではありません-パフォーマンスに関する考慮事項はありません、それはクライアントアプリであり、ユーザー設定が毎秒数百万回取得されるというわけではありません。

このアプローチのバリエーションでOptional<String>は、カスタムの代わりにGuavaを使用できます(Guavaはプロジェクト全体で既に使用されています)。Tuple<Boolean, String>次にOptional.<String>absent()、「proper」と区別することができnullます。しかし、見やすい理由でまだハック感があります-2つのレベルの「ヌル」を導入するOptionalと、そもそもsの作成の背後にあった概念が乱用されるようです。

もう1つのオプションは、キーが存在するかどうかを明示的にチェックすることです(boolean containsKey(String key)メソッドを追加し、getValueByKey既に存在することをアサートしている場合のみ呼び出します)。

最後に、プライベートメソッドをインライン化することもできますが、実際のgetByKeyコードサンプルよりもやや複雑なので、インライン化すると非常に見苦しくなります。

私はここで髪を分割しているかもしれませんが、この場合のベストプラクティスに最も近くなるためにあなたが何に賭けるか興味があります。OracleまたはGoogleのスタイルガイドに答えが見つかりませんでした。

コードサンプルのように例外を使用しているのはアンチパターンですか、それとも代替物があまりきれいでないことを考えると許容可能ですか?そうである場合、どのような状況で問題ないでしょうか?およびその逆?


1
@ GlenH7の受け入れられた(そして最も高く投票された)答えは、「コードの乱雑が少なく、エラー処理を容易にする場合に[例外]を使用すべきです」と要約しています。ここで、私がリストした代替案を「コードの乱雑さを減らす」と見なすべきかどうかを尋ねていると思います。
コンラッドモラウスキー

1
VTCを撤回しました。関連するものを求めていますが、私が提案した複製とは異なります。

3
質問がgetValueByKey公開されている場合は、より興味深いものになると思います。
ドックブラウン

2
将来の参考のために、あなたは正しいことをしました。これはサンプルコードであるため、Code Reviewのトピックから外れていました。
ラバーダック

1
ちょうどチャイムに---これは完全に慣用的なPythonであり、その言語でこの問題を処理するための好ましい方法だと思います。ただし、明らかに、異なる言語には異なる規則があります。
パトリックコリンズ

回答:


75

はい、あなたの同僚は正しいです。それは悪いコードです。エラーをローカルで処理できる場合は、すぐに処理する必要があります。例外をスローしてすぐに処理しないでください。

これはあなたのバージョンよりずっときれいです(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;
    }
}

エラーをローカルで解決する方法がわからない場合にのみ、例外をスローする必要があります。


14
答えてくれてありがとう。記録のために、私はこの同僚だ(私は元のコード好きではなかった)、私はちょうど:)それがロードされていなかったので、中立的に疑問を言葉で表現
コンラッドMorawski

59
狂気の最初の兆候:あなたは自分自身と話し始めます。
ロバートハーヴェイ

1
@BЈовић:まあ、その場合は大丈夫だと思いますが、2つのバリアントが必要な場合はどうなりますか?では、最もクリーンな(そして乾燥した)代替品は何だと思いますか?
ドックブラウン

3
@RobertHarvey :)他の誰かがこのコードを作成しました。実際には別の人です。私はレビュアーでした
Konrad Morawski

1
@Alvaro、例外を頻繁に使用することは問題ではありません。言語に関係なく、例外を不適切に使用することは問題です。
kdbanman

11

私はこの例外の使用をアンチパターンとは呼ばず、単に複雑な結果を伝える問題の最良の解決策ではありません。

最善の解決策(まだJava 7を使用している場合)は、Guavaのオプションを使用することです。この場合の使用がハックになることに同意しません。私には、グアバのOptionalの拡張説明に基づいて、これがいつそれを使用するかの完璧な例であるように思えます。「値が見つかりません」と「nullの値が見つかりました」を区別しています。


1
Optionalnullを保持できるとは思わない。Optional.of()null以外の参照のみを受け取り、Optional.fromNullable()nullを「value not present」として扱います。
ナビン

1
@Navinはnullを保持できませんが、自由に使用できOptional.absent()ます。したがって、null であった場合、またはnullでなかった場合にOptional.fromNullable(string)等しくなります。Optional.<String>absent()stringOptional.of(string)
コンラッド・モラウスキー

@KonradMorawski OPの問題は、例外をスローしないとヌル文字列と存在しない文字列を区別できないことだと思いました。Optional.absent()これらのシナリオの1つを扱います。他の人をどのように表現しますか?
ナビン

@Navinと実際のnull
コンラッド・モラウスキー

4
@KonradMorawski:それは本当に悪い考えのように聞こえます。「このメソッドは決して戻りませんnull!」と言うより明確な方法は考えられません。戻ると宣言するよりもOptional<...>。。。
ルアック

8

パフォーマンスに関する考慮事項はなく、実装の詳細であるため、最終的にはどのソリューションを選択しても問題ありません。しかし、私はそれが悪いスタイルであることに同意する必要があります。キーが存在しないことは、発生することわかっていることであり、スタックを複数回呼び出すことさえしないため、例外が最も役立ちます。

ブールがfalseの場合、2番目のフィールドは無意味であるため、タプルアプローチは少しハックです。マップがキーを2回検索しているため、事前にキーが存在するかどうかを確認するのはばかげています。(まあ、あなたはすでにそれをしているので、この場合は3回です。)このOptional解決策は問題にぴったりです。をに格納するのnullは少し皮肉に見えるかもしれませんがOptional、ユーザーがそれをしたい場合、ノーと言うことはできません。

マイクがコメントで指摘したように、これには問題があります。GuavaもJava 8もsをOptional保存できませんnull。したがって、あなたは自分自身をロールする必要があります-それは単純ですが、かなりの量のボイラープレートを含むので、内部で一度しか使用されないものに対してはやりすぎかもしれません。マップのタイプをに変更することもできますがMap<String, Optional<String>>Optional<Optional<String>>sの処理は厄介になります。

合理的な妥協案は、例外を保持することですが、「代替リターン」としての役割を認めることです。スタックトレースを無効にして新しいチェック例外を作成し、静的変数に保持できる事前作成済みのインスタンスをスローします。これは安価で、おそらくローカルブランチ同じくらい安価であり、処理することを忘れることはできません。したがって、スタックトレースの欠如は問題ではありません。


1
実際Map<String, String>values列は1つのタイプでなければならないため、実際にはデータベーステーブルレベルでのみです。ただし、各キーと値のペアを厳密に型指定するラッパーDAOレイヤーがあります。しかし、明確にするためにこれを省略しました。(もちろん、代わりにn列の1行のテーブルを作成することもできますが、新しい値を追加するたびにテーブルスキーマを更新する必要があるため、それらの解析に関連する複雑さを取り除くには、別の複雑さを別の場所に導入する代償が伴います)。
コンラッド・モラウスキー

タプルについての良い点。実際、何(false, "foo")を意味するのでしょうか?
コンラッド・モラウスキー

少なくともGuava's Optionalでは、nullを挿入しようとすると例外が発生します。Optional.absent()を使用する必要があります。
マイクパートリッジ

@MikePartridge良い点。い回避策は、マップをに変更するMap<String, Optional<String>>ことですOptional<Optional<String>>。混乱を避けるための解決策はnull、を許可する独自のOptional や、許可しないnullが3つの状態(absent、null、またはpresent)を持つ同様の代数データ型をロールすることです。どちらも簡単に実装できますが、関連するボイラープレートの量は、内部でのみ使用されるものに対しては高くなります。
ドーバル

2
「パフォーマンスに関する考慮事項はなく、実装の詳細であるため、最終的にはどのソリューションを選択しても問題ありません」-例外を使用してC#を使用している場合、「例外がスローされたときにブレークする」 CLR例外に対してオンになります。
スティーブン

5

このPreferencesクラスがあります。これは、キーと値のペアのバケットです。 NULL値は正当です(重要です)。特定の値がまだ保存されていない可能性があり、要求されたときに事前定義されたデフォルト値で初期化することにより、これらのケースを自動的に処理したいと考えています。

問題はまさにこれです。しかし、あなたはすでに自分で解決策を投稿しています:

このアプローチのバリエーションでは、カスタムTupleの代わりにGuavaのOptional(プロジェクト全体でGuavaが既に使用されています)を使用できますその後、Optional.absent()と "proper" nullを区別できます。しかし、見やすい理由でまだハッキングのように感じられます。2つのレベルの「ヌル」を導入すると、そもそもオプションの作成の背後にある概念が乱用されるようです。

しかし、使用していませんnull Optional。使用でき、使用する必要がありますOptionalだけできます。あなたの「ハック」的な感覚のために、それをネストして使用するだけです。そのためOptional<Optional<String>>、データベースにキーがあり(最初のオプションレイヤー)、事前定義された値が含まれている可能性があることを明示します(2番目のオプションレイヤー)。

このアプローチは、例外を使用するよりも優れており、次の条件を満たしている限り理解するのは非常に簡単です。 Optionalないです。

またOptional、いくつかの快適機能があるため、すべての作業を自分で行う必要はありません。これらには以下が含まれます。

  • static static <T> Optional<T> fromNullable(T nullableReference) データベース入力を Optional
  • abstract T or(T defaultValue) 内側のオプションレイヤーのキー値を取得するか、(存在しない場合)デフォルトのキー値を取得します

1
Optional<Optional<String>>それは私が認めなければならないいくつかの魅力がありますが、悪そうに見えます:)
コンラッド・モラウスキー

なぜ悪そうに見えるのか、さらに説明できますか?実際にはと同じパターンList<List<String>>です。もちろん(言語が許すなら)DBConfigKeywhichをラップするような新しい型を作成しOptional<Optional<String>>、それを好むならより読みやすくすることができます。
ヴァレンテリー

2
@valenterryそれは非常に異なっています。リストのリストは、ある種のデータを保存する正当な方法です。オプションのオプションは、データが持つ可能性のある2種類の問題を示す非典型的な方法です。
Ypnypn

オプションのオプションはリストのリストのようなもので、各リストには要素が1つまたはまったくありません。基本的に同じです。Javaで頻繁使用されないからといって、本質的に悪いというわけではありません。
ヴァレンテリー

1
ジョン・スキートが意味する@valenterryの悪。まったく悪いわけではありませんが、少し邪悪な「賢いコード」です。私はそれがややバロックで、オプションのオプションであると思います。どのレベルのオプション性が何を表すかは明確に明確ではありません。誰かのコードで遭遇した場合、ダブルテイクになります。あなたは、それが珍しい、一風変わっているという理由だけで、それが奇妙に感じるのは正しいかもしれません。おそらく、モナドとすべてを備えた関数型言語のプログラマーにとっては空想ではありません。私はそれを自分ではやりませんが、面白いのであなたの答えをまだ+1しました
コンラッド・モラウスキー

5

私はパーティーに遅れていることを知っていますが、とにかくあなたのユースケースは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);
}

4

@BЈовићの答えはgetValueByKey、他にどこにも必要がない場合は問題ないと思いますが、プログラムに両方のユースケースが含まれている場合、あなたのソリューションが悪いとは思いません。

  • キーが事前に存在しない場合の自動作成によるキーによる検索

  • データベース、リポジトリ、またはキーマップの何も変更せずに、その自動化なしで取得(getValueByKeyプライベートではなく、パブリックであると考えてください)

これがあなたの状況であり、パフォーマンスヒットが許容できる限り、提案されたソリューションは完全に大丈夫だと思います。検索コードの重複を避けるという利点があり、サードパーティのフレームワークに依存せず、非常に単純です(少なくとも私の目には)。

実際、このような状況では、欠落しているキーが「例外的な」状況であるかどうかはコンテキストに依存します。あるコンテキストでは、次のようなものgetValueByKeyが必要です。キーの自動作成が予想されるコンテキストでは、すでに利用可能な機能を再利用し、例外を飲み込み、異なる障害動作を提供するメソッドを提供することは、完全に理にかなっています。これは、の拡張機能または「装飾」として解釈できますgetValueByKeyが、「制御フローのために例外が悪用される」機能としてはありません。

もちろん、3番目の選択肢があります:を返す3番目のプライベートメソッドを作成し、Tuple<Boolean, String>提案したように、との両方でこのメソッドを再利用getValueByKeygetByKeyます。実際にはより良い代替手段となるかもしれないより複雑なケースについては、ここに示すような単純なケースについては、これは私見過剰設計の匂いがあり、コードはそのように本当にもっと保守可能になるとは思いません。私はここにいるカール・Bielefeldtことで、ここで一番上の答え

「コードを簡素化するとき、例外を使用することを気にしないでください」。


3

Optional正しい解決策です。ただし、必要に応じて、「2つのヌル」の感覚が少ない代替手段があります。センチネルを検討してください。

keyNotFoundSentinelnew String("")。* でプライベート静的文字列を定義します

現在、プライベートメソッドではreturn keyNotFoundSentinelなくが可能ですthrow new KeyNotFoundException()。publicメソッドはその値を確認できます

String rval = getValueByKey(key);
if (rval == keyNotFoundSentinel) { // reference equality, not string.equals
    ... // generate default value
} else {
    return rval;
}

実際にnullは、センチネルの特別なケースです。これは、言語によって定義されたセンチネル値であり、いくつかの特別な動作があります(つまり、でメソッドを呼び出す場合の動作は明確に定義されていますnull)。ほぼすべての言語で使用されているようなセンチネルを持つことは、たまたま非常に便利です。

* Javaが文字列を「抑留」するのを防ぐためnew String("")だけでなく、""他の空の文字列と同じ参照を与えるために、意図的に使用します。この追加のステップを実行したため、によって参照されるString keyNotFoundSentinelは一意のインスタンスであることが保証されます。これは、マップ自体に表示されないようにする必要があります。


これが私見です。
fgp

2

Javaのすべての問題点から学んだフレームワークから学びます。

.NETは、この問題に対する2つのはるかに洗練されたソリューションを提供します。

後者はJavaで非常に簡単に記述でき、前者は「強参照」ヘルパークラスを必要とするだけです。


Dictionaryパターンの共分散に適した代替はになりますT TryGetValue(TKey, out bool)
-supercat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.