Trois leçons Java que j'ai apprises à mes dépens
Source : Medium Apprendre Java est difficile. J'ai appris de mes erreurs. Maintenant, vous aussi pouvez apprendre de mes erreurs et de mes expériences amères, dont vous n’avez pas nécessairement besoin.1. Les Lambdas peuvent causer des problèmes.
Les lambdas dépassent souvent 4 lignes de code et sont plus grandes que prévu. Cela pèse sur la mémoire de travail. Avez-vous besoin de changer une variable de lambda ? Vous ne pouvez pas faire ça. Pourquoi? Si le lambda peut accéder aux variables du site d'appel, des problèmes de thread peuvent survenir. Par conséquent, vous ne pouvez pas modifier les variables de lambda. Mais Happy path dans lambda fonctionne bien. Après un échec d'exécution, vous recevrez cette réponse :at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
Il est difficile de suivre la trace de la pile lambda. Les noms prêtent à confusion et sont difficiles à retrouver et à déboguer. Plus de lambdas = plus de traces de pile. Quelle est la meilleure façon de déboguer les lambdas ? Utilisez des résultats intermédiaires.
map(elem -> {
int result = elem.getResult();
return result;
});
Un autre bon moyen consiste à utiliser des techniques avancées de débogage IntelliJ. Utilisez TAB pour sélectionner le code que vous souhaitez déboguer et combinez-le avec les résultats intermédiaires. «Lorsque nous nous arrêtons à une ligne contenant un lambda, si nous appuyons sur F7 (entrer), alors IntelliJ met en évidence le fragment qui doit être débogué. Nous pouvons faire passer le bloc en débogage à l’aide de Tab, et une fois que nous avons décidé cela, appuyez à nouveau sur F7. Comment accéder aux variables du site d'appel depuis lambda ? Vous ne pouvez accéder qu'aux variables finales ou réellement finales. Vous devez créer un wrapper autour des variables d'emplacement d'appel. Soit en utilisant AtomicType , soit en utilisant votre propre type. Vous pouvez modifier le wrapper de variable créé avec lambda. Comment résoudre les problèmes de trace de pile ? Utilisez des fonctions nommées. De cette façon, vous pouvez rapidement trouver le code responsable, vérifier la logique et résoudre les problèmes. Utilisez des fonctions nommées pour supprimer la trace cryptique de la pile. Le même lambda est-il répété ? Placez-le dans une fonction nommée. Vous aurez un seul point de référence. Chaque lambda obtient une fonction générée, ce qui rend difficile son suivi.
lambda$yourNamedFunction
lambda$0
Les fonctions nommées résolvent un problème différent. Des grosses lambdas. Les fonctions nommées décomposent les gros lambdas, créent des morceaux de code plus petits et créent des fonctions enfichables.
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)
2. Problèmes avec les listes
Vous devez travailler avec des listes ( Lists ). Vous avez besoin d'un HashMap pour les données. Pour les rôles, vous aurez besoin d'un TreeMap . La liste continue. Et il n’y a aucun moyen d’éviter de travailler avec des collections. Comment faire une liste ? De quel type de liste avez-vous besoin ? Doit-il être immuable ou mutable ? Toutes ces réponses affectent l’avenir de votre code. Choisissez la bonne liste à l’avance pour ne pas la regretter plus tard. Arrays::asList crée une liste « de bout en bout ». Que ne peux-tu pas faire avec cette liste ? Vous ne pouvez pas le redimensionner. Il est immuable. Que pouvez-vous faire ici? Spécifiez les éléments, le tri ou d'autres opérations qui n'affectent pas la taille. Utilisez Arrays::asList avec précaution car sa taille est immuable mais son contenu ne l'est pas. new ArrayList() crée une nouvelle liste « mutable ». Quelles opérations la liste créée prend-elle en charge ? C'est tout, et c'est une raison d'être prudent. Créez des listes mutables à partir de listes immuables en utilisant new ArrayList() . List::of crée une collection « immuable ». Sa taille et son contenu restent inchangés sous certaines conditions. Si le contenu est constitué de données primitives, telles que int , la liste est immuable. Jetez un œil à l’exemple suivant.@Test
public void testListOfBuilders() {
System.out.println("### TESTING listOF with mutable content ###");
StringBuilder one = new StringBuilder();
one.append("a");
StringBuilder two = new StringBuilder();
two.append("a");
List<StringBuilder> asList = List.of(one, two);
asList.get(0).append("123");
System.out.println(asList.get(0).toString());
}
### TESTING listOF avec contenu mutable ### a123
Vous devez créer des objets immuables et les insérer dans List::of . Cependant, List::of ne fournit aucune garantie d'immuabilité. List::of offre l'immuabilité, la fiabilité et la lisibilité. Sachez quand utiliser des structures mutables et quand utiliser des structures immuables. La liste des arguments qui ne doivent pas changer doit figurer dans la liste immuable. Une liste mutable peut être une liste mutable. Comprenez de quelle collection vous avez besoin pour créer un code fiable.
3. Les annotations vous ralentissent
Utilisez-vous des annotations ? Les comprenez-vous ? Savez-vous ce qu'ils font ? Si vous pensez que l'annotation Logged convient à toutes les méthodes, vous vous trompez. J'ai utilisé Logged pour enregistrer les arguments de la méthode. À ma grande surprise, cela n'a pas fonctionné.@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
Quel est le problème avec ce code ? Il y a beaucoup de résumé de configuration ici. Vous rencontrerez cela plusieurs fois. La configuration est mélangée avec du code normal. Pas mal en soi, mais ça attire l'attention. Des annotations sont nécessaires pour réduire le code passe-partout. Vous n'avez pas besoin d'écrire une logique de journalisation pour chaque point de terminaison. Pas besoin de configurer des transactions, utilisez @Transactional . Les annotations réduisent le modèle en extrayant le code. Il n’y a pas de gagnant clair ici puisque les deux sont dans le jeu. J'utilise toujours XML et les annotations. Lorsque vous trouvez un motif répétitif, il est préférable de déplacer la logique dans l'annotation. Par exemple, la journalisation est une bonne option d'annotation. Moralité : n'abusez pas des annotations et n'oubliez pas XML.
Bonus : vous pourriez avoir des problèmes avec Optionnel
Vous utiliserez orElse de Optionnel . Un comportement indésirable se produit lorsque vous ne transmettez pas la constante orElse . Vous devez en être conscient pour éviter des problèmes à l'avenir. Regardons quelques exemples. Lorsque getValue(x) renvoie une valeur, getValue(y) est exécuté . La méthode dans orElse est exécutée si getValue(x) renvoie une valeur facultative non vide .getValue(x).orElse(getValue(y)
.orElseThrow(() -> new NotFoundException("value not present")));
public Optional<Value> getValue(Source s)
{
System.out.println("Source: " + s.getName());
// returns value from s source
}
// when getValue(x) is present system will output
Source: x
Source: y
Utilisez orElseGet . Il n’exécutera pas de code pour lesOptions non vides .
getValue(x).orElseGet(() -> getValue(y)
.orElseThrow(() -> new NotFoundException("value not present")));
public Optional<Value> getValue(Source s)
{
System.out.println("Source: " + s.getName());
// returns value from s source
}
// when getValue(x) is present system will output
Source: x
Conclusion
Apprendre Java est difficile. Vous ne pouvez pas apprendre Java en 24 heures. Perfectionnez vos compétences. Prenez le temps, apprenez et excellez dans votre travail.Comment utiliser les principes SOLID dans le code
Source : Cleanthecode L'écriture de code fiable nécessite des principes SOLIDES. À un moment donné, nous avons tous dû apprendre à programmer. Et soyons honnêtes. Nous étions STUPIDES. Et notre code était le même. Dieu merci, nous avons SOLIDE.Des principes SOLIDES
Alors, comment écrire du code SOLIDE ? C'est en fait simple. Il vous suffit de suivre ces cinq règles :- Principe de responsabilité unique
- Principe ouvert-fermé
- Principe de remplacement de Liskov
- Principe de séparation des interfaces
- Principe d'inversion de dépendance
Principe de responsabilité unique
Dans son livre, Robert C. Martin décrit ce principe ainsi : « Une classe ne devrait avoir qu’une seule raison de changer. » Regardons deux exemples ensemble.1. Ce qu'il ne faut pas faire
Nous avons une classe appelée User qui permet à l'utilisateur de faire les choses suivantes :- Créer un compte
- Se connecter
- Recevez une notification lors de votre première connexion
2. Que faire
Imaginez une classe qui devrait afficher une notification à un nouvel utilisateur, FirstUseNotification . Il sera composé de trois fonctions :- Vérifier si une notification a déjà été affichée
- Montrer les notifications
- Marquer la notification comme déjà affichée
Principe ouvert-fermé
Le principe ouvert-fermé a été inventé par Bertrand Meyer : « Les objets logiciels (classes, modules, fonctions, etc.) doivent être ouverts pour extension, mais fermés pour modification. » Ce principe est en réalité très simple. Vous devez écrire votre code de manière à ce que de nouvelles fonctionnalités puissent y être ajoutées sans changer le code source. Cela permet d'éviter une situation dans laquelle vous devez modifier des classes qui dépendent de votre classe modifiée. Cependant, ce principe est beaucoup plus difficile à mettre en œuvre. Meyer a suggéré d'utiliser l'héritage. Mais cela conduit à une connexion forte. Nous en discuterons dans les principes de séparation d'interface et les principes d'inversion de dépendance. Martin a donc proposé une meilleure approche : utiliser le polymorphisme. Au lieu de l’héritage conventionnel, cette approche utilise des classes de base abstraites. De cette manière, les spécifications d’héritage peuvent être réutilisées alors que la mise en œuvre n’est pas requise. L'interface peut être écrite une fois puis fermée pour apporter des modifications. De nouvelles fonctions doivent alors implémenter cette interface et l'étendre.Principe de remplacement de Liskov
Ce principe a été inventé par Barbara Liskov, lauréate du Turing Award pour ses contributions aux langages de programmation et à la méthodologie logicielle. Dans son article, elle définit son principe comme suit : « Les objets d'un programme doivent pouvoir être remplacés par des instances de leurs sous-types sans affecter la bonne exécution du programme. » Examinons ce principe en tant que programmeur. Imaginez que nous avons un carré. Il pourrait s’agir d’un rectangle, ce qui semble logique puisqu’un carré est une forme particulière d’un rectangle. C’est ici que le principe de remplacement de Liskov vient à la rescousse. Partout où vous vous attendez à voir un rectangle dans votre code, il est également possible qu'un carré apparaisse. Imaginez maintenant que votre rectangle ait les méthodes SetWidth et SetHeight . Cela signifie que le carré a également besoin de ces méthodes. Malheureusement, cela n'a aucun sens. Cela signifie que le principe de remplacement de Liskov est ici violé.Principe de séparation des interfaces
Comme tous les principes, le principe de séparation des interfaces est beaucoup plus simple qu’il n’y paraît : « De nombreuses interfaces spécifiques au client valent mieux qu’une seule interface à usage général. » Comme pour le principe de responsabilité unique, l’objectif est de réduire les effets secondaires et le nombre de changements nécessaires. Bien entendu, personne n’écrit un tel code exprès. Mais c'est facile à rencontrer. Vous vous souvenez du carré du principe précédent ? Imaginez maintenant que nous décidions de mettre en œuvre notre plan : nous produisons un carré à partir d'un rectangle. Maintenant, nous forçons le carré à implémenter setWidth et setHeight , ce qui ne fait probablement rien. S'ils faisaient cela, nous casserions probablement quelque chose car la largeur et la hauteur ne correspondraient pas à nos attentes. Heureusement pour nous, cela signifie que nous ne violons plus le principe de substitution de Liskov, puisque nous autorisons désormais l'utilisation d'un carré partout où nous utilisons un rectangle. Cependant, cela crée un nouveau problème : nous violons désormais le principe de séparation des interfaces. Nous forçons la classe dérivée à implémenter les fonctionnalités qu’elle choisit de ne pas utiliser.Principe d'inversion de dépendance
Le dernier principe est simple : les modules de haut niveau doivent être réutilisables et ne doivent pas être affectés par les modifications apportées aux modules de bas niveau.- A. Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d’abstractions (telles que les interfaces).
- B. Les abstractions ne devraient pas dépendre des détails. Les détails (implémentations concrètes) doivent dépendre des abstractions.
- Module de haut niveau, en fonction de l'abstraction
- Module bas niveau dépendant de la même abstraction
GO TO FULL VERSION