Je récupère un ensemble de tuples dans la base de données et je le mets dans une carte. La requête de base de données est coûteuse.
Il n'y a pas d' ordre naturel évident des éléments dans la carte, mais l' ordre d'insertion est néanmoins important. Le tri de la carte serait une opération lourde, donc je veux éviter de le faire, étant donné que le résultat de la requête est déjà trié comme je le veux. Par conséquent, je stocke simplement le résultat de la requête dans un LinkedHashMap
et retourne la carte à partir d'une méthode DAO:
public LinkedHashMap<Key, Value> fetchData()
J'ai une méthode processData
qui devrait faire un certain traitement sur la carte - modifier certaines valeurs, ajouter de nouvelles clés / valeurs. Il est défini comme
public void processData(LinkedHashMap<Key, Value> data) {...}
Cependant, plusieurs linters (Sonar, etc.) se plaignent que le type de «données» devrait être une interface telle que «Map» plutôt que l'implémentation «LinkedHashMap» ( calmar S1319 ).
Donc, fondamentalement, il dit que j'aurais dû
public void processData(Map<Key, Value> data) {...}
Mais je veux que la signature de la méthode dise que l' ordre des cartes est important - cela compte pour l'algorithme processData
- afin que ma méthode ne soit pas transmise à n'importe quelle carte aléatoire.
Je ne veux pas l'utiliser SortedMap
, car il (à partir du javadoc dejava.util.SortedMap
) "est ordonné selon l' ordre naturel de ses clés, ou par un comparateur généralement fourni au moment de la création de la carte triée".
Mes clés n'ont pas d' ordre naturel , et la création d'un comparateur pour ne rien faire semble verbeuse.
Et je voudrais quand même que ce soit une carte, pour en profiter put
pour éviter les clés en double, etc. Sinon, cela data
aurait pu être a List<Map.Entry<Key, Value>>
.
Alors, comment dire que ma méthode veut une carte déjà triée ? Malheureusement, il n'y a pas d' java.util.LinkedMap
interface, ou je l'aurais utilisé.
la source
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.
- Bon conseil, s'il existait une "meilleure pratique". Un meilleur conseil: apprenez à prendre les bonnes décisions. Suivez la pratique si cela a du sens, mais laissez les outils et les autorités guider votre processus de réflexion, pas le dicter.Vous vous battez contre trois choses:
La première est la bibliothèque de conteneurs de Java. Rien dans sa taxonomie ne vous permet de déterminer si la classe itère ou non dans un ordre prévisible. Aucune
IteratesInInsertedOrderMap
interface ne peut être implémentéeLinkedHashMap
, ce qui rend impossible la vérification de type (et l'utilisation d'implémentations alternatives qui se comportent de la même manière). C'est probablement par conception, car l'esprit de celui-ci est que vous êtes vraiment censé pouvoir traiter des objets qui se comportent comme l'abstraitMap
.Deuxièmement, nous croyons que ce que dit votre interlocuteur doit être traité comme un évangile et qu'ignorer tout ce qu'il dit est mauvais. Contrairement à ce qui passe pour une bonne pratique ces jours-ci, les avertissements de linter ne sont pas censés être des barrières pour qualifier votre code de bon. Ils sont invités à raisonner sur le code que vous avez écrit et à utiliser votre expérience et votre jugement pour déterminer si l'avertissement est justifié ou non. Les avertissements injustifiés sont la raison pour laquelle presque tous les outils d'analyse statique fournissent un mécanisme pour leur dire que vous avez examiné le code, que vous pensez que ce que vous faites est correct et qu'ils ne devraient pas s'en plaindre à l'avenir.
Troisièmement, et c'est probablement la viande de celui-ci,
LinkedHashMap
peut être le mauvais outil pour le travail. Les cartes sont destinées à un accès aléatoire et non ordonné. Si vousprocessData()
parcourez simplement les enregistrements dans l'ordre et n'avez pas besoin de trouver d'autres enregistrements par clé, vous forcez une implémentation spécifiqueMap
à faire le travail d'unList
. D'un autre côté, si vous avez besoin des deux,LinkedHashMap
c'est le bon outil car il est connu pour faire ce que vous voulez et vous êtes plus que justifié de l'exiger.la source
OrderedMap
, je pourrais tout aussi bien direUniqueList
. Tant qu'il s'agit d'une sorte de collection avec un ordre d'itération défini, qui écrase les doublons lors de l'insertion.Set
les clés pendant que vous créez la liste a pour les repérer.processData
modifie la carte, en remplaçant certaines valeurs, en introduisant de nouvelles clés / valeurs. IlprocessData
pourrait donc introduire des doublons s'il fonctionnait sur autre chose qu'unMap
.UniqueList
(ouOrderedUniqueList
) et de l'utiliser. C'est assez facile et rend votre utilisation prévue plus claire.Si tout ce que vous obtenez
LinkedHashMap
est la possibilité d'écraser les doublons, mais que vous l'utilisez vraiment en tant queList
, alors je suggère qu'il est préférable de communiquer cette utilisation avec votre propreList
implémentation personnalisée . Vous pouvez la baser sur une classe de collections Java existante et simplement remplacer toutes les méthodesadd
etremove
pour mettre à jour votre magasin de sauvegarde et garder une trace de la clé pour garantir l'unicité. En lui donnant un nom distinctif, ilProcessingList
sera clair que les arguments présentés à votreprocessData
méthode doivent être traités d'une manière particulière.la source
ProcessingList
comme un alias pourLinkedHashMap
- vous pouvez toujours décider de le remplacer par quelque chose d'autre plus tard, tant que vous gardez l'interface publique intacte.Je vous entends dire "J'ai une partie de mon système qui produit un LinkedHashMap, et dans une autre partie de mon système, je dois accepter uniquement les objets LinkedHashMap qui ont été produits par la première partie, car ceux produits par un autre processus ont gagné" t fonctionne correctement. "
Cela me fait penser que le problème ici est en fait que vous essayez d'utiliser LinkedHashMap car il correspond principalement aux données que vous recherchez, mais en fait, il ne peut pas être remplacé par une autre instance que celles que vous créez. Ce que vous voulez réellement faire, c'est créer votre propre interface / classe qui est ce que votre première partie crée et votre deuxième partie consomme. Il peut envelopper le "vrai" LinkedHashMap, et fournir un getter de carte ou implémenter l'interface de carte.
Ceci est un peu différent de la réponse de CandiedOrange, en ce que je recommanderais d'encapsuler la vraie carte (et de lui déléguer des appels si nécessaire) plutôt que de l'étendre. C'est parfois une de ces guerres sacrées de style, mais il me semble que ce n'est pas "une carte avec des trucs supplémentaires", c'est "mon sac d'informations utiles sur l'état, que je peux représenter en interne avec une carte".
Si vous aviez deux variables que vous aviez besoin de faire circuler comme ceci, vous auriez probablement fait une classe pour elle sans y penser beaucoup. Mais parfois, il est utile d'avoir une classe même si ce n'est qu'une variable membre, simplement parce que c'est logiquement la même chose, pas une "valeur" mais "le résultat de mon opération avec lequel je dois faire des choses plus tard".
la source
MyBagOfUsefulInformation
aurait besoin d' une méthode (ou le constructeur) pour le remplir:MyBagOfUsefulInformation.populate(SomeType data)
. Maisdata
devrait être le résultat de la requête triée. Alors, que serait-ceSomeType
sinonLinkedHashMap
? Je ne suis pas sûr de pouvoir battre ce Catch 22.MyBagOfUsefulInformation
être créé par le DAO ou quoi que ce soit qui génère les données dans votre système? Pourquoi avez-vous besoin d'exposer la carte sous-jacente au reste de votre code en dehors du producteur et du consommateur du sac?MyBagOfUsefulInformation
comme paramètre à la méthode DAO: softwareengineering.stackexchange.com/a/360079/52573LinkedHashMap est la seule carte java qui possède la fonction d'ordre d'insertion que vous recherchez. Donc, écarter le principe d'inversion de dépendance est tentant et peut-être même pratique. Mais d'abord, réfléchissez à ce qu'il faudrait pour le suivre. Voici ce que SOLID vous demanderait de faire.
Remarque: remplacez le nom
Ramdal
par un nom descriptif qui indique que le consommateur de cette interface est le propriétaire de cette interface. Ce qui en fait l'autorité qui décide si l'ordre d'insertion est important. Si vous appelez simplement cela,InsertionOrderMap
vous avez vraiment manqué le point.Est-ce un gros design d'avance? Cela dépend peut-être de la probabilité que vous pensiez avoir besoin d'une implémentation
LinkedHashMap
. Mais si vous ne suivez pas DIP uniquement parce que ce serait une énorme douleur, je ne pense pas que la plaque de la chaudière soit plus douloureuse que cela. C'est le modèle que j'utilise lorsque je souhaite que le code intouchable implémente une interface qu'il ne fait pas. La partie la plus douloureuse est vraiment de penser à de bons noms.la source
Merci pour beaucoup de bonnes suggestions et de matière à réflexion.
J'ai fini par étendre la création d'une nouvelle classe de carte, en créant
processData
une méthode d'instance:Ensuite, j'ai refactorisé la méthode DAO afin qu'elle ne renvoie pas de carte, mais prend à la place une
target
carte en paramètre:Donc, remplir
DataMap
et traiter les données est maintenant un processus en deux étapes, ce qui est bien, car il existe d'autres variables qui font partie de l'algorithme, qui viennent d'autres endroits.Cela permet à mon implémentation Map de contrôler la façon dont les entrées y sont insérées, et masque les exigences de classement - c'est maintenant un détail d'implémentation de
DataMap
.la source
Si vous souhaitez indiquer que la structure de données que vous avez utilisée existe pour une raison, ajoutez un commentaire au-dessus de la signature de la méthode. Si un autre développeur à l'avenir rencontre cette ligne de code et remarque un avertissement d'outil, il peut également remarquer le commentaire et s'abstenir de "résoudre" le problème. S'il n'y a pas de commentaire, rien ne les empêchera de changer la signature.
Supprimer les avertissements est inférieur à commenter à mon avis, car la suppression elle-même n'indique pas la raison pour laquelle l'avertissement a été supprimé. Une combinaison de suppression d'avertissement et de commentaire conviendra également.
la source
Alors, laissez-moi essayer de comprendre votre contexte ici:
Maintenant, ce que vous faites actuellement:
Et voici votre code actuel:
Ma suggestion est de faire ce qui suit:
Exemple de code
Je suppose que cela éliminerait l'avertissement Sonar et spécifierait également dans la signature la disposition spécifique des données requises par la méthode de traitement.
la source
MyTupleRepository
est créé?)Cette question est en fait un tas de problèmes avec votre modèle de données regroupé en un seul. Vous devez commencer à les démêler, un à la fois. Des solutions plus naturelles et intuitives disparaissent lorsque vous essayez de simplifier chaque pièce du puzzle.
Problème 1: vous ne pouvez pas dépendre de l'ordre DB
Vos descriptions du tri de vos données ne sont pas claires.
ORDER BY
clause. Si ce n'est pas le cas, car cela semble trop cher, votre programme a un bug . Les bases de données sont autorisées à renvoyer les résultats dans n'importe quel ordre si vous n'en spécifiez pas; vous ne pouvez pas en dépendre par coïncidence en renvoyant des données dans l'ordre simplement parce que vous avez exécuté la requête plusieurs fois et que cela ressemble à cela. L'ordre peut changer car les lignes sont réorganisées sur le disque, ou certaines sont supprimées et de nouvelles prennent leur place, ou un index est ajouté. Vous devez spécifier uneORDER BY
clause quelconque. La vitesse ne vaut rien sans correction.ORDER BY
clause. Sinon, vous avez des bugs. Si une telle colonne n'existe pas encore, vous devez en ajouter une. Les options typiques pour des colonnes comme celle-ci seraient une colonne d'horodatage d'insertion ou une clé d'incrémentation automatique. La clé d'incrémentation automatique est plus fiable.Problème 2: Rendre le tri en mémoire efficace
Une fois que vous assurez - vous qu'il est garanti à retourner les données dans l'ordre que vous attendez, vous pouvez tirer parti de ce fait pour faire en mémoire trie beaucoup plus efficace. Ajoutez simplement une colonne
row_number()
oudense_rank()
(ou l'équivalent de votre base de données) à l'ensemble de résultats de votre requête. Maintenant, chaque ligne a un index qui vous donnera une indication directe de ce que l'ordre est censé être, et vous pouvez trier par ceci en mémoire trivialement. Assurez-vous simplement de donner à l'index un nom significatif (commesortedBySomethingIndex
).Alto. Désormais, vous n'avez plus à dépendre de l'ordre du jeu de résultats de la base de données.
Problème 3: Avez-vous même besoin de faire ce traitement en code?
SQL est en fait vraiment puissant. C'est un langage déclaratif incroyable qui vous permet de faire beaucoup de transformations et d'agrégations sur vos données. De nos jours, la plupart des bases de données prennent même en charge les opérations entre lignes. On les appelle des fonctions de fenêtre ou d'analyse:
OVER
Clause SQL Server pour les fonctions de fenêtreAvez-vous même besoin de mettre vos données en mémoire comme ça? Ou pourriez-vous faire tout le travail dans la requête SQL en utilisant des fonctions de fenêtre? Si vous pouvez faire tout (ou peut-être même juste une partie importante) du travail dans la base de données, fantastique! Votre problème de code disparaît (ou devient beaucoup plus simple)!
Problème 4: tu fais quoi à ça
data
?En supposant que vous ne pouvez pas tout faire dans la base de données, permettez-moi de clarifier les choses. Vous prenez les données sous forme de carte (qui est basée sur des éléments que vous ne souhaitez pas trier), puis vous les itérez dans l' ordre d'insertion et modifiez la carte en place en remplaçant la valeur de certaines clés et en ajoutant les nouvelles?
Je suis désolé, mais qu'est-ce que c'est que ça?
Les appelants ne devraient pas avoir à se soucier de tout cela . Le système que vous avez créé est extrêmement fragile. Il suffit d'une seule erreur stupide (peut-être même faite par vous-même, comme nous l'avons tous fait) pour effectuer un petit changement erroné et le tout s'effondre comme un jeu de cartes.
Voici peut-être une meilleure idée:
List
.Une variante possible pourrait être de construire une représentation triée, puis de créer une carte de clé à indexer . Cela vous permettrait de modifier votre copie triée en place, sans créer accidentellement des doublons.
Ou peut-être que cela a plus de sens: se débarrasser du
data
paramètre et faireprocessData
réellement récupérer ses propres données. Vous pouvez alors documenter que vous faites cela car cela a des exigences très spécifiques sur la façon dont les données sont récupérées. En d'autres termes, faites en sorte que la fonction soit propriétaire de l'intégralité du processus, et pas seulement d'une partie de celui-ci; les interdépendances sont trop fortes pour diviser la logique en petits morceaux. (Modifiez le nom de la fonction dans le processus.)Peut-être que cela ne fonctionnera pas pour votre situation. Je ne sais pas sans tous les détails du problème. Mais je connais un design fragile et déroutant quand j'en entends un.
Sommaire
Je pense que le problème ici est finalement que le diable est dans les détails. Lorsque je commence à rencontrer des problèmes comme celui-ci, c'est généralement parce que j'ai une représentation inappropriée de mes données pour le problème que j'essaie de résoudre. La meilleure solution est de trouver une meilleure représentation , puis mon problème devient simple (peut-être pas facile, mais simple) à résoudre.
Trouvez quelqu'un qui obtient ce point: votre travail consiste à réduire votre problème à un ensemble de problèmes simples et directs. Ensuite, vous pouvez créer du code robuste et intuitif. Parlez-leur. Un bon code et une bonne conception vous font penser que n'importe quel idiot aurait pu les imaginer, car ils sont simples et directs. Peut-être y a-t-il un développeur senior qui a cet état d'esprit auquel vous pouvez parler.
la source
select key, value from table where ... order by othercolumn
et doit maintenir l'ordre dans leur traitement. L' ordre d'insertion auquel ils se réfèrent est l' ordre d'insertion dans leur carte , défini par l'ordre utilisé dans leur requête, et non l' ordre d'insertion dans la base de données . Cela est rendu clair par leur utilisation deLinkedHashMap
, qui est une structure de données qui a les caractéristiques à la fois de aMap
et de aList
de paires clé-valeur.order by
clause dans la requête, mais elle n'est pas triviale ( pas seulementorder by column
), donc je veux éviter de réimplémenter le tri en Java. Bien que SQL soit puissant (et nous parlons ici d'une base de données Oracle 11g), la nature de l'processData
algorithme facilite son expression en Java. Et oui, "ordre d'insertion" signifie " ordre d'insertion de carte ", c'est-à-dire l'ordre des résultats de la requête.