JavaRush /Blog Java /Random-FR /Pause café #85. Trois leçons Java que j'ai apprises à mes...

Pause café #85. Trois leçons Java que j'ai apprises à mes dépens. Comment utiliser les principes SOLID dans le code

Publié dans le groupe Random-FR

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. Pause café #85.  Trois leçons Java que j'ai apprises à mes dépens.  Comment utiliser les principes SOLID dans le code - 1

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. Pause café #85.  Trois leçons Java que j'ai apprises à mes dépens.  Comment utiliser les principes SOLID dans le code - 2

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
Ne t'inquiète pas! Ces principes sont bien plus simples qu’il n’y paraît !

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
Cette classe a désormais plusieurs responsabilités. Si le processus d'inscription change, la classe d'utilisateurs changera. La même chose se produira si le processus de connexion ou le processus de notification change. Cela signifie que la classe est surchargée. Il a trop de responsabilités. Le moyen le plus simple de résoudre ce problème consiste à transférer la responsabilité vers vos classes afin que la classe User soit uniquement responsable de la combinaison des classes. Si le processus change ensuite, vous disposez d’une classe claire et distincte qui doit changer.

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
Cette classe a-t-elle plusieurs raisons de changer ? Non. Cette classe a une fonction claire : afficher une notification pour un nouvel utilisateur. Cela signifie que la classe a une raison de changer. A savoir, si cet objectif change. Cette classe ne viole donc pas le principe de responsabilité unique. Bien sûr, certaines choses peuvent changer : la façon dont les notifications sont marquées comme lues peut changer, ou la façon dont la notification apparaît. Cependant, puisque l’objectif du cours est clair et basique, c’est très bien.

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.
Ceci peut être réalisé en implémentant une abstraction qui sépare les modules de haut et de bas niveau. Le nom du principe suggère que la direction de la dépendance change, mais ce n’est pas le cas. Il ne sépare les dépendances qu'en introduisant une abstraction entre elles. En conséquence, vous obtiendrez deux dépendances :
  • Module de haut niveau, en fonction de l'abstraction
  • Module bas niveau dépendant de la même abstraction
Cela peut paraître difficile, mais en fait cela se produit automatiquement si vous appliquez correctement le principe ouvert/fermé et le principe de substitution de Liskov. C'est tout! Vous avez maintenant appris les cinq principes fondamentaux qui sous-tendent SOLID. Avec ces cinq principes, vous pouvez rendre votre code incroyable !
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION