Voici une liste de 10 pratiques de codage Java qui sont plus subtiles que les règles Java efficaces de Josh Bloch. Par rapport à la liste de Josh Bloch qui est facile à apprendre et se concentre sur les situations quotidiennes, cette liste contiendra des situations impliquant des choses inhabituelles dans la conception API / SPI, qui peuvent avoir un grand impact.
Je les ai rencontrés en écrivant et en maintenant JOOQ (DSL interne modélisé par SQL en Java). En tant que DSL interne, JOOQ remet en question les compilateurs Java et les génériques dans la plus grande mesure, combinant des génériques, des paramètres et des surcharges mutables, que Josh Bloch peut ne pas recommander pour une API aussi large.
Permettez-moi de partager avec vous 10 meilleures pratiques subtiles pour le codage Java:
1. Rappelez-vous le destructeur de C ++
Rappelez-vous le destructeur de C ++? Vous ne vous souvenez pas? Ensuite, vous avez vraiment de la chance parce que vous n'avez pas à déboguer les fuites de mémoire en raison de la mémoire allouée après la libération de la suppression d'objet. Merci à Sun / Oracle pour le mécanisme de collecte des ordures mis en œuvre!
Néanmoins, le destructeur fournit une fonctionnalité intéressante. Il comprend l'ordre d'allocation inverse à la mémoire libre. N'oubliez pas que c'est le cas en Java, lorsque vous utilisez la syntaxe de destructrice de classe:
Il existe divers autres cas d'utilisation. Voici un exemple concret de la façon d'implémenter SPI des auditeurs d'événements:
@OverridePublic void avant-Event (eventContext e) {super.beforeEvent (e); // super code avant mon code} @OverRidePublic void AfterEvent (eventContext e) {// super code après mon code super.afterEvent (e);} Le tristement célèbre problème de restauration du philosophe est un autre bon exemple de la raison pour laquelle cela compte. Pour des questions sur les repas philosophes, veuillez vérifier le lien:
http://adit.io/posts/2013-05-11-the-ining-philosophers-problem-with-ron-swanson.html
Règle : Chaque fois que vous utilisez avant / après, allouer / libre, prendre / retourner la sémantique pour implémenter la logique, envisagez d'effectuer des opérations après / libre / de retour dans l'ordre inverse.
Fournir aux clients des méthodes SPI qui leur permettent d'injecter facilement un comportement personnalisé dans votre bibliothèque / code. Méfiez-vous que vos jugements SPI Evolution peuvent vous confondre, vous faisant penser que vous avez (pas) l'intention d'avoir besoin de paramètres supplémentaires. Bien sûr, les fonctions ne doivent pas être ajoutées trop tôt. Mais une fois que vous avez publié votre SPI, une fois que vous avez décidé de suivre les versioning sémantique, vous regretterez vraiment l'ajout d'un paramètre unique stupide à votre SPI lorsque vous vous rendez compte que dans certains cas, vous pourriez avoir besoin d'un autre paramètre:
Interface EventListener {// Message Bad void (message de chaîne);}Et si vous avez besoin d'un ID de message et d'une source de message? L'évolution de l'API vous empêchera d'ajouter des paramètres aux types ci-dessus. Bien sûr, avec Java 8, vous pouvez ajouter une méthode de défenseur qui "défend" vos mauvaises décisions de conception au début:
Interface EventListener {// Bad par défaut Void Message (String Message) {message (message, null, null); } // Mieux? Message void (Message de chaîne, ID entier, source MessageSource);} Notez que malheureusement, la méthode du défenseur ne peut pas utiliser le modificateur final.
Mais il serait préférable d'utiliser des objets de contexte (ou des objets de paramètre) que de polluer votre SPI en utilisant de nombreuses méthodes.
Interface MessageContext {String Message (); INTER ID (); MessageSource Source ();} Interface EventListener {// génial! Message void (contexte MessageContext);} Vous pouvez évoluer plus facilement l'API MessageContext que EventListner SPI, car peu d'utilisateurs l'implémenteront.
Règle : Chaque fois qu'un SPI est spécifié, envisagez d'utiliser un objet contextuel / paramètre au lieu d'écrire des méthodes avec des paramètres fixes.
Remarque : C'est également une bonne idée d'échanger des résultats via un type dédié MessageResult, qui peut être construit à l'aide de l'API Builder. Cela augmentera considérablement la flexibilité de l'évolution SPI.
Les programmeurs de swing n'appuient généralement que quelques touches de raccourci pour générer des centaines ou des milliers de classes anonymes. Dans la plupart des cas, cela ne fait pas de mal de le faire tant que l'interface est suivie et que le cycle de vie du sous-type SPI n'est pas violé. Mais n'utilisez pas fréquemment des classes anonymes, locales ou internes pour une raison simple - ils économiseront des références aux classes externes. Parce que peu importe où ils vont, les catégories externes doivent suivre. Par exemple, si le fonctionnement externe d'une classe locale est inapproprié, le graphique de l'objet entier subira des modifications subtiles, ce qui peut entraîner une fuite de mémoire.
Règle: Avant d'écrire des classes anonymes, locales ou internes, veuillez réfléchir à deux fois si vous pouvez la convertir en classes de haut niveau statiques ou ordinaires, évitant ainsi les moyens de retourner leurs objets dans un domaine de niveau extérieur.
Remarque: utilisez des accolades à double boucle pour initialiser les objets simples:
new hashmap <string, string> () {{put ("1", "a"); put ("2", "b");}}Cette méthode utilise l'initialisateur d'instance décrite dans la spécification JLS §8.6. Il a l'air bien en surface, mais il n'est pas recommandé en fait. Parce que si vous utilisez un objet HashMap complètement indépendant, l'instance ne conservera pas toujours de références aux objets externes. De plus, cela permettra au chargeur de classe de gérer plus de classes.
Le rythme de Java8 approche. Avec Java 8 apporte des expressions de lambda, que cela vous plaise ou non. Bien que vos utilisateurs d'API puissent l'aimer, vous feriez mieux de vous assurer qu'ils peuvent l'utiliser aussi souvent que possible. Donc, à moins que votre API ne reçoive de simples types de "scalaire", tels que INT, Long, String et Date, laissez votre API recevoir SAM aussi souvent que possible.
Qu'est-ce que Sam? SAM est une méthode abstraite unique [Type]. Également connu sous le nom d'interface de fonction, il sera bientôt annoté sous le nom de @FunctionalInterface. Cela correspond à la règle 2, EventListener est en fait un SAM. Le meilleur SAM n'a qu'un seul paramètre, car cela simplifiera encore l'écriture des expressions de lambda. Imaginez écrire
auditers.add (c -> System.out.println (C.Message ()));
Pour remplacer
auditers.add (new EventListener () {@Override public void Message (MessageContext c) {System.out.println (c.Message ())); }});Imaginez la manipulation de XML à Jooox. Joox contient beaucoup de Sam:
$ (document) // trouver des éléments avec un id .find (c -> $ (c) .id ()! = null) // trouver leurs enfants éléments .children (c -> $ (c) .tag ().
Règles: Soyez gentil avec vos utilisateurs de l'API, commencez à écrire les interfaces SAM / fonction à partir de maintenant .
Remarque: Il existe de nombreux blogs intéressants sur les expressions Java8 Lambda et API des collections améliorées:
J'ai écrit 1 ou 2 articles sur Java Nulls, et j'ai également expliqué l'introduction de nouvelles classes facultatives dans Java 8. D'un point de vue académique ou pratique, ces sujets sont assez intéressants.
Bien que Null et NullPointerException soient toujours un défaut en Java à ce stade, vous pouvez toujours concevoir une API qui n'aura aucun problème. Lorsque vous concevez des API, vous devez éviter de retourner autant que possible, car vos utilisateurs peuvent appeler la méthode dans une chaîne:
initialiser (someargument) .Calculate (data) .dispatch ();
Comme on peut le voir à partir du code ci-dessus, aucune méthode ne doit renvoyer null. En fait, l'utilisation de NULL est généralement considérée comme une hétérogénéité comparable. La bibliothèque comme JQuery ou Joox a complètement abandonné Null sur les objets itérables.
NULL est généralement utilisé dans l'initialisation retardée. Dans de nombreux cas, l'initialisation retardée doit également être évitée sans affecter gravement les performances. En fait, si la structure des données impliquée est trop grande, l'initialisation de retard doit être utilisée avec prudence.
Règle: Les méthodes doivent éviter de retourner nuls quand elles le sont. Null est uniquement utilisé pour représenter la sémantique de "non initialisé" ou "non existé".
Bien qu'il soit OK de renvoyer une méthode avec une valeur nul dans certains cas, ne renvoyez jamais un tableau vide ou une collection vide! Veuillez consulter la méthode java.io.file.list (), elle est conçue comme ceci:
Cette méthode renvoie un tableau de chaînes pour tous les fichiers ou répertoires dans le répertoire spécifié. Si le répertoire est vide, le tableau renvoyé est vide. Renvoie null si le chemin spécifié n'existe pas ou si une erreur d'E / S se produit.
Par conséquent, cette méthode est généralement utilisée comme ceci:
File Directory = // ... if (Directory.isDirectory ()) {String [] List = Directory.List (); if (list! = null) {for (file de chaîne: list) {// ...}}}Pensez-vous que un contrôle nul est nécessaire? La plupart des opérations d'E / S produiront IOExceptions, mais cette méthode ne renvoie que NULL. Null ne peut pas stocker des messages d'erreur d'E / S. Par conséquent, une telle conception a les trois lacunes suivantes:
Si vous regardez le problème du point de vue des collections, alors un tableau ou une collection vide est la meilleure implémentation de "non existé". Le retour d'un tableau ou d'une collection vide est d'une signification pratique à moins d'être utilisée pour une initialisation retardée.
Règle: le tableau ou la collection retourné ne doit pas être nul.
L'avantage de HTTP est apatride. Tous les états pertinents sont transférés dans chaque demande et réponse. C'est l'essence de la dénomination du repos: transfert d'état (transfert d'état de représentation). C'est aussi formidable de le faire à Java. Pensez-y du point de vue de la règle 2 lorsque la méthode reçoit l'objet de paramètre d'état. Si l'état est transmis par un tel objet, plutôt que d'exploiter l'état de l'extérieur, les choses seront plus faciles. Prenez JDBC comme exemple. L'exemple suivant lit un curseur d'un programme stocké.
CallableStatement s = connection.prepareCall ("{? = ...}"); // manipulation verbose de l'état de déclaration: S.RegisterOutParameter (1, curseur); S.SetString (2, "ABC"); S.Execute (); ResultTset rs = s.getObject (1); // Verbose Manipulation du résultat Set State: rs.next ();););Cela rend l'API JDBC si bizarre. Chaque objet est avec état et difficile à utiliser. Plus précisément, il y a deux principaux problèmes:
Règles: plus d'implémentations dans le style fonctionnel. Transférer l'état à travers les paramètres de la méthode. Très peu d'états d'objets opérationnels.
C'est un moyen relativement facile de fonctionner. Dans les systèmes d'objets relativement complexes, vous pouvez obtenir des améliorations significatives des performances, tant que vous faites d'abord des jugements égaux dans la méthode equals () de tous les objets:
@OverridePublic Boolean est égal (objet autre) {if (this == autre) return true; // autre logique de jugement d'égalité ...}Notez que d'autres vérifications de court-circuit peuvent impliquer des vérifications de valeur nulle, ils doivent donc également être ajoutés:
@OverridePublic Boolean est égal (objet autre) {if (this == autre) return true; if (autre == null) renvoie false; // Reste de la logique d'égalité ...}Règle: utilisez des courts-circuits dans toutes vos méthodes equals () pour améliorer les performances.
Certaines personnes peuvent être en désaccord avec cela, car faire finir par défaut la méthode est contraire à l'habitude des développeurs Java. Mais si vous avez un contrôle complet sur le code, il est certainement correct de rendre la méthode par défaut:
Cela convient particulièrement aux méthodes statiques, auquel cas "superposition" (en fait l'occlusion) ne fonctionne guère. J'ai récemment rencontré un exemple d'une très mauvaise méthode statique d'ombrage dans Apache Tika. Jetez un œil:
TikainputStream étend TaggedInputStream pour masquer sa méthode statique get () avec une implémentation relativement différente.
Contrairement aux méthodes conventionnelles, les méthodes statiques ne peuvent pas être écrasées les unes avec les autres car l'appel est lié à l'appel de méthode statique au moment de la compilation. Si vous n'avez pas de chance, vous pouvez accidentellement obtenir la mauvaise méthode.
Règle: Si vous avez un contrôle complet de votre API, faites final autant de méthodes que possible.
Dans des occasions spéciales, vous pouvez utiliser la méthode du paramètre variable "Accept-All" pour recevoir un objet ... paramètre:
void acceptall (objet ... all);
L'écriture d'une telle méthode apporte une petite sensation javascript à l'écosystème Java. Bien sûr, vous voudrez peut-être limiter le type réel en fonction de la situation réelle, comme la chaîne…. Puisque vous ne voulez pas trop limiter, vous pourriez penser que c'est une bonne idée de remplacer l'objet par un T générique:
vide acceptall (t ... all);
Mais pas. T sera toujours déduit comme un objet. En fait, vous pouvez simplement penser que les génériques ne peuvent pas être utilisés dans les méthodes ci-dessus. Plus important encore, vous pourriez penser que vous pouvez surcharger la méthode ci-dessus, mais vous ne pouvez pas:
void acceptall (t ... all); void acceptall (message de chaîne, t ... all);
Il semble que vous puissiez éventuellement transmettre un message de chaîne à la méthode. Mais qu'arrive-t-il à cet appel?
Acceptall ("Message", 123, "ABC");Le compilateur déduit t comme <? étend sérialisable et comparable <? >>, qui rendra l'appel peu clair!
Ainsi, chaque fois que vous avez une signature "accepte-tout" (même si c'est un générique), vous ne pourrez jamais le surcharger de type-saafely. Les consommateurs d'API ne peuvent laisser le compilateur que le compilateur choisir "accidentellement" la méthode "correcte" lorsqu'ils ont de la chance. Mais il est également possible d'utiliser la méthode d'acceptation ou de ne pas appeler une méthode.
Règle : Évitez les signatures "accepter tous" si possible. Sinon, ne surchargez pas une telle méthode.
Java est une bête. Contrairement à d'autres langues plus idéalistes, elle a lentement évolué vers ce qu'elle est aujourd'hui. Cela peut être une bonne chose, car à la vitesse du développement de Java, il y a déjà des centaines d'avertissements, et ces avertissements ne peuvent être saisis que par des années d'expérience.
Restez à l'écoute pour plus des dix premières listes sur ce sujet!
Lien original: JOOQ Traduction: importnew.com - compamer
Lien de traduction: http://www.importnew.com/10138.html
[Veuillez garder la source d'origine, le traducteur et le lien de traduction lors de la réimpression. ]]