地図で広告掲載順序の問題を伝える方法は?


24

データベースから一連のタプルを取得して、マップに入れています。データベースクエリは高価です。

マップ内の要素の明らかな自然な順序付けはありませんが、それでも挿入順序は重要です。マップの並べ替えは重い操作になるため、クエリ結果が既に希望どおりに並べ替えられている場合は、それを避ける必要があります。したがって、クエリ結果をに保存LinkedHashMapし、DAOメソッドからマップを返します。

public LinkedHashMap<Key, Value> fetchData()

processDataマップ上で何らかの処理を行うメソッドがあります。いくつかの値を変更し、いくつかの新しいキー/値を追加します。次のように定義されます

public void processData(LinkedHashMap<Key, Value> data) {...}

ただし、いくつかのリンター(ソナーなど)は、「データ」のタイプは「LinkedHashMap」squid S1319)の実装ではなく、「Map」などのインターフェースであるべきだと文句を言います
だから基本的には私が持っているべきだと言っている

public void processData(Map<Key, Value> data) {...}

しかし、メソッドのシグネチャには、マップの順序が重要であると言うようにしたい-それはアルゴリズムにとって重要であるprocessData-したがって、私のメソッドはランダムなマップだけに渡されない。

を使用したくないのはSortedMap、(のjavadocからjava.util.SortedMap)「キーの自然な順序に従って、またはソートされたマップ作成時に通常提供されるComparatorによって順序付けられる」ためです。

私のキーには自然な順序がありません。コンパレータを作成して何もしないのは冗長です。

そして、putキーの重複などを回避するために活用するために、それがマップであることを依然として望んでdataいますList<Map.Entry<Key, Value>>

それで、私のメソッドはすでにソートされたマップを必要としていると言うにはどうすればいいですか 残念なことに、java.util.LinkedMapインターフェースはありません。そうでなければ、私はそれを使用していました。

回答:


56

を使用しますLinkedHashMap

はいMap可能な限り特定の実装で使用する必要があります。はい、これベストプラクティスです。

とはいえ、これはMap実際の実装が重要である奇妙な特定の状況です。これは、コードを使用するときにコードの99.9%の場合に当てはまりMapませんが、この0.1%の状況ではそうです。Sonarはこれを知ることができないため、ほとんどの場合正しいと考えられるため、Sonarは特定の実装の使用を避けるよう単に指示しています。

特定の実装を使用することを主張できる場合は、豚に口紅をつけようとしないでください。あなたは必要LinkedHashMapではありませんMap

これは、もしあなたがプログラミングに不慣れで、この答えにつまずくなら、そうしないのでベストプラクティスに反することができるとは思わないでください。しかし、ある実装を別の実装に置き換えることが受け入れられない場合、あなたができる唯一のことは、その特定の実装を使用することであり、Sonarにられます。


1
私が好きな実用的なアプローチ。
Vidar S. Ramdal

20
私は答えにほぼ完全に同意します。私はあなたがソナーにのろわれていないことを言うでしょう。特定のエラー/警告を無視するようにいつでも構成できます。stackoverflow.com/questions/10971968/を

11
if you are new to programming and stumble upon this answer, don't think this allows you to go against best practice because it doesn't.-「ベストプラクティス」などがあった場合の良いアドバイス。より良いアドバイス:適切な決定を下す方法を学びます。 理にかなっている場合は慣習に従いますが、ツールや当局に指示プロセスではなく指示プロセスを指示させます。
ロバートハーヴェイ

13
注:ソナーが何かを報告するとき、「解決できない」としてそれを閉じ、あなたがそうしない理由としてメモを残すことができます。そのため、ソナーはあなたを煩わせるために停止するだけでなく、あなたがそれをした理由のトレーサーがいるでしょう。
ウォルフラット

2
これを一般原則の例外にする側面は、LinkedHashMapがその実装に固有であり、インターフェースで表現されていないコントラクトを持っていることだと思います。これは通常のケースではありません。したがって、その契約への依存を表す唯一の方法は、実装タイプを使用することです。
ダナ

21

あなたは3つのことを戦っています:

1つは、Javaのコンテナライブラリです。その分類法には、クラスが予測可能な順序で反復するかどうかを判断する方法はありません。IteratesInInsertedOrderMapで実装できるインターフェイスはありませんLinkedHashMap。これにより、型チェック(および同じように動作する代替実装の使用)が不可能になります。それはおそらく設計によるものです。なぜなら、その精神は、抽象のように振る舞うオブジェクトを本当に扱うことができるはずだからですMap

2番目は、リンターの言うことは福音として扱われなければならず、言うことを何でも無視することは悪いことだという信念です。最近の優れた実践に合格するものとは反対に、リンター警告は、コードを適切に呼び出すための障壁となることは想定されていません。それらは、あなたが書いたコードについて推論するよう促され、あなたの経験と判断を使用して、警告が正当化されるかどうかを決定します。不当な警告は、ほとんどすべての静的分析ツールが、コードを検証したこと、実行していることは大丈夫であり、今後文句を言うべきではないことを伝えるメカニズムを提供する理由です。

第三に、これはおそらくそれの肉であり、LinkedHashMap仕事にとって間違ったツールかもしれません。マップは、順序付けされたアクセスではなく、ランダムなアクセスを目的としています。場合はprocessData()、単純には、順番に、レコードを反復処理し、キーによって他のレコードを検索する必要はありません、あなたは、特定の実装を強制しているMapの仕事をしますList。一方、両方を必要とする場合LinkedHashMapは、適切なツールです。これは、必要なことを行うことがわかっているため、それを必要とする正当な理由があるからです。


2
「LinkedHashMapは、このジョブにとって間違ったツールかもしれません」。はい、多分。私が必要であると言うときOrderedMap、私は同様に言うことができましたUniqueList。定義された反復順序を持つコレクションの一種である限り、挿入時に重複を上書きします。
Vidar S. Ramdal

2
@ VidarS.Ramdalデータベースクエリは、重複を取り除く理想的な場所です。データベースでそれができない場合Set、リストaを見つけてそれらを見つける方法として、常にキーだけの一時的なものを保持することができます。
Blrfl

ああ、私は混乱を引き起こしたようです。はい、データベースクエリの結果には重複が含まれていません。ただしprocessData、マップを変更し、一部の値を置き換え、いくつかの新しいキー/値を導入します。そのprocessDataため、それがa以外で動作している場合、重複を導入する可能性がありMapます。
Vidar S. Ramdal

7
@ VidarS.Ramdal:独自のUniqueList(またはOrderedUniqueList)を記述して使用する必要があるようです。それは非常に簡単で、意図した用途をより明確にします。
TMN

2
@TMNはい、その方向で考え始めました。あなたの提案を答えとして投稿したい場合、それは確かに私の賛成を得ます。
Vidar S. Ramdal

15

LinkedHashMap重複を上書きする機能しか得られない場合でも、実際にそれをとしてList使用している場合は、その使用方法を独自のカスタムList実装と通信することをお勧めします。既存のJavaコレクションクラスに基づいて、任意のメソッドaddremoveメソッドをオーバーライドするだけで、バッキングストアを更新し、キーを追跡して一意性を確保できます。これに特有の名前を付けるProcessingListと、processDataメソッドに提示される引数を特定の方法で処理する必要があることが明確になります。


5
とにかくこれは良い考えかもしれません。ちなみに、ProcessingListエイリアスとして作成する1行のファイルを作成LinkedHashMapすることもできます。パブリックインターフェイスを損なわない限り、いつでも別のファイルに置き換えることができます。
-CompuChip

11

「システムにはLinkedHashMapを生成する部分があり、システムの別の部分では、最初の部分で生成されたLinkedHashMapオブジェクトのみを受け入れる必要があります。他のプロセスで生成されたオブジェクトは、 tは正しく動作します。」

それは、ここでの問題は実際にあなたがLinkedHashMapを使用しようとしていることだと思うようになります。なぜなら、それはあなたが探しているデータにほぼ適合しているからですが、実際にはあなたが作成したもの以外のインスタンスで置き換えることはできません。実際にやりたいことは、最初の部分が作成し、2番目の部分が消費する独自のインターフェイス/クラスを作成することです。「実際の」LinkedHashMapをラップし、Mapゲッターを提供したり、Mapインターフェースを実装したりできます。

これはCandiedOrangeの答えとは少し異なります。実際のマップを拡張するのではなく、実際のマップをカプセル化する(および必要に応じて呼び出しを委任する)ことをお勧めします。それは時々これらのスタイルの聖戦の1つですが、それは「いくつかの追加のものを持つ地図」ではなく、「地図で内部的に表すことができる有用な状態情報の私のバッグ」ではないことは確かに聞こえます。

このように渡す必要のある変数が2つある場合は、おそらくそれについて考えずにクラスを作成しているでしょう。ただし、論理的には「値」ではなく「後で処理する必要がある操作の結果」であるという理由だけで、1つのメンバー変数であってもクラスがあると便利な場合があります。


私はこの考え方のように-私はありました:) MyBagOfUsefulInformationそれを移入する方法(またはコンストラクタ)が必要になりますMyBagOfUsefulInformation.populate(SomeType data)。ただしdata、並べ替えられたクエリ結果である必要があります。ではSomeType、そうでない場合はLinkedHashMapどうなりますか?私は確かに私はこのキャッチ22を破ることができるよないよ
Vidar S. Ramdal

なぜMyBagOfUsefulInformationDAOまたはシステムでデータを生成しているものによって作成できないのですか?Bagのプロデューサーとコンシューマー以外のコードの残りの部分に、基礎となるマップを公開する必要があるのはなぜですか?

アーキテクチャによっては、private / protected / package-onlyコンストラクターを使用して、目的のプロデューサーのみがオブジェクトを作成できるようにすることができます。あるいは、正しい「工場」でしか作成できないという慣習として、それを行う必要があるかもしれません。

はい、MyBagOfUsefulInformationDAOメソッドにパラメーターとして渡すことで、少し似たようなことをしました:softwareengineering.stackexchange.com/a/360079/52573
Vidar S. Ramdal

4

LinkedHashMapは、探している挿入順序機能を持つ唯一のJavaマップです。したがって、依存関係反転の原則を破棄することは魅力的であり、実用的ですらあります。しかし、最初に、それに従うには何が必要かを考えてください。これが、SOLIDから求められることです。

注:Ramdalこのインターフェースのコンシューマーがこのインターフェースの所有者であることを伝えるわかりやすい名前に名前を置き換えてください。これにより、挿入順序が重要であるかどうかを決定する権限になります。単にこれInsertionOrderMapを呼び出す場合、あなたは本当にポイントを逃しました。

public interface Ramdal {
    //ISP asks for just the methods that processData() actually uses.
    ...
}

public class RamdalLinkedHashMap extends LinkedHashMap implements Ramdal{} 

Ramdal<Key, Value> ramdal = new RamdalLinkedHashMap<>();

ramdal.put(key1, value1);
ramdal.put(key2, value2);

processData(ramdal);

これは前もって大きなデザインですか?たぶん、に加えて実装が必要になると思われる可能性に依存しますLinkedHashMap。しかし、DIPが大きな痛みであるという理由だけでDIPをフォローしていない場合、ボイラープレートがこれ以上痛みを伴うとは思いません。これは、触れられないコードに実装されていないインターフェイスを実装したいときに使用するパターンです。最も苦しい部分は、本当に良い名前を考えることです。


2
命名が好きです!
Vidar S. Ramdal

1

たくさんの良い提案と思考の糧をありがとう。

新しいマップクラスの作成を拡張してprocessData、インスタンスメソッドを作成しました。

class DataMap extends LinkedHashMap<Key, Value> {

   processData();

}

次に、マップを返さないようにDAOメソッドをリファクタリングしましたが、代わりにtargetマップをパラメーターとして受け取ります。

public void fetchData(Map<Key, Value> target) {
  ...
  // for each result row
  target.put(key, value);
}

そのDataMapため、データの取り込みとデータの処理は2段階のプロセスになりました。これは、他の場所から来るアルゴリズムの一部である他の変数があるため、問題ありません。

public DataMap fetchDataMap() {
  var dataMap = new DataMap();
  dao.fetchData(dataMap);
  return dataMap;
}

これにより、Mapの実装でエントリの挿入方法を制御し、順序付けの要件を隠すことができますDataMap。これはの実装の詳細です。


0

使用したデータ構造が理由があることを伝えたい場合は、メソッドの署名の上にコメントを追加します。将来、別の開発者がこのコード行に遭遇し、ツールの警告に気づいた場合、彼らもコメントに気づき、問題を「修正」することを控えるでしょう。コメントがない場合、署名を変更することを止めるものは何もありません。

抑制自体は警告が抑制された理由を述べていないため、警告を抑制することは私の意見でコメントするよりも劣っています。警告の抑制とコメントの組み合わせも同様に問題ありません。


0

だから、ここであなたのコンテキストを理解してみましょう:

...挿入順序が重要です...マップのソートは重い操作になります...

...クエリ結果は既に希望どおりにソートされます

さて、あなたが現在すでにしていること:

データベースからタプルのセットを取得し、マップに入れています...

そして、現在のコードは次のとおりです。

public void processData(LinkedHashMap<Key, Value> data) {...}

私の提案は次のことです。

  • 依存性注入を使用し、MyTupleRepositoryを処理メソッドに注入します(MyTupleRepositoryは、通常DBからタプルオブジェクトを取得するオブジェクトによって実装されるインターフェイスです)。
  • 内部的に処理メソッドに、特定のLinkedHashMapコレクションにリポジトリ(別名DB、既に順序付けられたデータを返す)からのデータを配置します。これは、処理アルゴリズムの内部詳細であるためです(データ構造でのデータの配置方法に依存するため) );
  • これはほとんどあなたがすでにやっていることですが、この場合、これは処理メソッド内で行われることに注意してください。リポジトリはどこか別の場所でインスタンス化されます(データを返すクラスが既にあります。これはこの例のリポジトリです)

コード例

public interface MyTupleRepository {
    Collection<MyTuple> GetAll();
}

//Concrete implementation of data access object, that retrieves 
//your tuples from DB; this data is already ordered by the query
public class DbMyTupleRepository implements MyTupleRepository { }

//Injects some abstraction of repository into the processing method,
//but make it clear that some exception might be thrown if data is not
//arranged in some specific way you need
public void processData(MyTupleRepository tupleRepo) throws DataNotOrderedException {

    LinkedHashMap<Key, Value> data = new LinkedHashMap<Key, Value>();

    //Represents the query to DB, that already returns ordered data
    Collection<MyTuple> myTuples = tupleRepo.GetAll();

    //Optional: this would throw some exception if data is not ordered 
    Validate(myTuples);

    for (MyTupleData t : myTuples) {
        data.put(t.key, t.value);
    }

    //Perform the processing using LinkedHashMap...
    ...
}

これでSonar警告が取り除かれ、処理方法に必要なデータの特定のレイアウトが署名で指定されると思います。


うーん、しかし、リポジトリはどのようにインスタンス化されますか?これはちょうどどこかに問題を移動することはないでしょう(ここにMyTupleRepository作成された?)
Vidar S. Ramdal

ピーター・クーパーの答えと同じ問題に遭遇すると思います。
ヴィダールS.ラムダル

私の提案には、依存性注入の原則の適用が含まれます。この例では; MyTupleRepositoryは、言及したタプル(DBを照会)を取得する機能を定義するインターフェースです。ここでは、このオブジェクトを処理メソッドに注入します。データを返すクラスが既にあります。これはインターフェースで抽象化するだけで、オブジェクトを 'processData'メソッドに注入します。これは本質的に処理の一部であるため、LinkedHashMap を内部的に使用します。
エマーソンカルドーソ

私は自分の提案をより明確にしようとして、答えを編集しました。
エマーソンカルドーソ

-1

この質問は、実際には、データモデルが1つにまとめられた一連の問題です。それらを1つずつ解き始める必要があります。パズルの各ピースを単純化しようとすると、より自然で直感的なソリューションがドロップアウトします。

問題1:DBの順序に依存できない

データの並べ替えの説明は明確ではありません。

  • 最大の潜在的な問題は、ORDER BY句を使用してデータベースで明示的な並べ替えを指定していないことです。コストが高すぎると思われるためではない場合、プログラムにバグがあります。データベースは、結果を指定しない場合、任意の順序で結果を返すことができます。クエリを数回実行し、そのように見えるからといって、偶然データを順番に返すことに依存することはできません。ディスク上で行が再配置されるか、一部が削除されて新しい行が代わりになるか、インデックスが追加されるため、順序が変わる場合があります。何らかの種類の句を指定する必要ありますORDER BY。速度は正確さなしでは価値がありません。
  • また、挿入順序が重要であることの意味も明確ではありません。あなたはデータベース自体の話をしている場合は、あなたがしなければならない実際にこれを追跡する欄があり、それがなければなりません、あなたの中に含まれるORDER BY句。そうでなければ、バグがあります。そのような列がまだ存在しない場合は、追加する必要があります。このような列の一般的なオプションは、挿入タイムスタンプ列または自動インクリメントキーです。自動インクリメントキーはより信頼性があります。

問題2:メモリ内ソートを効率的にする

期待どおりの順序でデータを返すことが保証されていることを確認したら、この事実を活用して、メモリ内のソートより効率的にすることができます。クエリの結果セットにrow_number()またはdense_rank()列(またはデータベースの同等のもの)を追加するだけです。これで、各行にはインデックスがあり、順序がどのようになるかを直接示すことができます。これをメモリ内で簡単にソートできます。インデックスに意味のある名前(などsortedBySomethingIndex)を付けてください。

ビオラ。これで、データベースの結果セットの順序に依存する必要がなくなりました。

問題3:この処理をコードで行う必要さえありますか?

SQLは実際には非常に強力です。これは、データに対して多くの変換と集約を行うことができる驚くべき宣言型言語です。ほとんどのDBは、今日では行間操作もサポートしています。それらは、ウィンドウ関数または分析関数と呼ばれます。

このようにデータをメモリに取り込む必要さえありますか?または、ウィンドウ関数を使用して、SQLクエリですべての作業を実行できますか?DBでの作業のすべて(または多分かなりの部分)を行うことができれば、素晴らしいです!コードの問題はなくなります(または、もっと簡単になります)!

問題4:あなたはそれに対して何をしているのdataですか?

DBですべてを行うことはできないと仮定して、これをまっすぐにさせてください。データをマップとして取得し(並べ替えたくないものによってキー設定されます)、挿入順にデータを反復処理し、いくつかのキーの値を置き換えて追加してマップをその場で変更します新しいもの?

申し訳ありませんが、一体何ですか?

呼び出し元は、これらすべてを心配する必要はありません。作成したシステムは非常に脆弱です。ちょっとした間違った変更を行うのに、たった1つの愚かな間違い(たぶん自分で行ったのかもしれません)があれば、すべてがカードのデッキのように崩壊します。

ここにもっと良いアイデアがあります:

  • 関数でを受け入れますList
  • 注文の問題に対処するには、いくつかの方法があります。
    1. フェイルファーストを適用します。リストが関数に必要な順序になっていない場合、エラーをスローします。(注:問題2のソートインデックスを使用して、ソートインデックスがあるかどうかを確認できます。)
    2. ソートされたコピーを自分で作成します(再び問題2のインデックスを使用します)。
    3. マップ自体を順番に構築する方法を見つけてください。
  • 関数の内部で必要なマップを作成して、呼び出し元が気にする必要がないようにします。
  • 今、あなたが持っている順序表現で何でも繰り返し、あなたがしなければならないことをします。
  • マップを返すか、適切な戻り値に変換します

可能性のあるバリエーションは、ソートされた表現を構築し、インデックスのキーのマップを作成することです。これにより、誤って複製を作成することなく、ソートされたコピーを適切に変更できます。

または、これはもっと理にかなっているかもしれません:dataパラメータを取り除き、processData実際に独自のデータをフェッチします。データを取得する方法に関して非常に特定の要件があるため、これを実行していることを文書化できます。言い換えれば、関数がプロセスの一部ではなく、プロセス全体を所有するようにします。相互依存関係が強すぎて、ロジックを小さなチャンクに分割できません。(プロセス内の関数の名前を変更します。)

たぶん、これらはあなたの状況では機能しません。私は問題の完全な詳細なしで知りません。しかし、私が聞いたとき、私は壊れやすく紛らわしいデザインを知っています。

概要

ここでの問題は、最終的に悪魔が細部にあることだと思います。このような問題が発生し始めたのは、通常、実際に解決しようとしている問題に対するデータの不適切な表現があるためです。最良の解決策は、より適切な表現を見つけることであり、その後、私の問題は簡単に(おそらく簡単ではないが、簡単に)解決できます。

そのポイントを得る誰かを見つけてください:あなたの仕事は、あなたの問題を単純で簡単な問題のセットに減らすことです。その後、堅牢で直感的なコードを構築できます。彼らと話してください。優れたコードと優れたデザインは、単純で簡単なため、どんな馬鹿でも考え抜かれたと思わせるでしょう。たぶんあなたが話すことができるそのような考え方を持っている上級開発者がいるでしょう。


「自然な順序はないが、挿入順序は重要だとはどういう意味ですか。データがDBテーブルに挿入された順序は重要ですが、挿入された順序を示す列はありません」-質問には次のように記載されています。「マップの並べ替えは重い操作になるので、クエリ結果が既に並べ替えられている場合は、それを避けたい」これは明らかに、データに計算可能な明確な順序あることを意味します。そうでなければ、ソートは重いというよりも不可能になるためですが、その定義された順序はキーの自然な順序とは異なります。
ジュール

2
言い換えると、OPはのようなクエリの結果に取り組んでおりselect key, value from table where ... order by othercolumn、処理の順序を維持する必要があります。挿入順序彼らが言及しているがあり、そのマップへの挿入順序そのクエリで使用順、しないことによって定義され、データベースへの挿入順序。これは、それらの使用によって明らかにされているLinkedHashMapの両方の特性を有するデータ構造である、MapとのListキーと値のペアを。
ジュール

@Julesそのセクションを少し整理します、ありがとう。(実際にそれを読んだことを思い出しましたが、質問を書いているときに物事をチェックしていたときに、それを見つけることができませんでした。笑。雑草にも行き過ぎました。)クエリと、明示的な並べ替えがあるかどうか。また、「挿入順序が重要」と言っています。ポイントは、ソートが重い場合でも、明示的に指示しない限り、DBに依存して魔法のように正しく物事を正しく順序付けることはできないということです。また、DBで実行しいる場合、「インデックス」を使用してコードを効率化できます。
jpmc26

*答えを書く(すぐに寝るべきだと
思う

はい、@ Julesは正しいです。そこorder byクエリ内の句は、それが(非自明でないだけでorder by column、私はJavaでソートを再実装避けたいので、)。SQL 強力ですが(ここではOracle 11gデータベースについて説明しています)、processDataアルゴリズムの性質により、Javaでの表現がはるかに簡単になります。はい、「挿入順序」は「マップ挿入順序」、つまりクエリ結果の順序を意味します。
ヴィダールS.ラムダル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.