Un tutoriel simple permettant de coder un client lourd en JavaFX.
Je cherche à comprendre comment charger le navigateur embarqué de Java FX dans un client lourd. Je ne sais pas si ce projet fonctionne mais ce sera un premier pas vers l'obtention d'un PoC qui marche.
Je suis tombée sur cet autre repo qui a deux avantages :
- Il va droit au but en affichant un index.html
- Il s'appuie sur les modules de Java 9+
Parfait pour installer des applications du PlayStore dans une sandbox quand on s'est dégooglisé. Je pense à Teams, Microchiotte Authenticator et Plantes vs Zombies (faut bien que la grande joue 😁)
Merci à Kalvn pour le lien.
Avant ces vacances de Noël, on m'a confié la tâche de déterminer quelle techno utiliser pour coder plusieurs fronts.
J'avais regardé les frameworks SPA via Aurelia et Vue et les pages générées côté serveur via Twig que j'ai toujours adoré 😍.
Ce tuto de Kalvn tombe à point nommé puisqu'il complète très bien l'usage de la balise <template>
.
Comment créer et utiliser des sprites SVG (c'est-à-dire fusionner plusieurs SVG en un seul fichier). Pour résumer...
1) Créer un fichier SVG vide
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none;">
...
</svg>
2) Dans une balise <symbol>
y migrer le contenu du SVG à intégrer au sprite sans oublier d'y reporter la viewBox en attribut de la balise <symbol>
.
Par exemple le SVG suivant :
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95 95">
<defs>
<style>.cls-1{fill:#00dd7e;}</style>
</defs>
<title>8 Point Star</title>
<path class="cls-1" d="M83.59,63.91,97.5,50,83.59,36.09V16.41H63.91L50,2.5,36.09,16.41H16.41V36.09L2.5,50,16.41,63.91V83.59H36.09L50,97.5,63.91,83.59H83.59Z" transform="translate(-2.5 -2.5)"/>
</svg>
Sera intégré comme ceci :
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="icon-star" viewBox="0 0 95 95">
<path class="cls-1" d="M83.59,63.91,97.5,50,83.59,36.09V16.41H63.91L50,2.5,36.09,16.41H16.41V36.09L2.5,50,16.41,63.91V83.59H36.09L50,97.5,63.91,83.59H83.59Z" transform="translate(-2.5 -2.5)"/>
</symbol>
</svg>
3) Dans le même fichier, ajouter autant de balises <symbol>
qu'il y a de SVG à intégrer au sprite **en leur donnant des ID différents
4) Pour utiliser le SVG il suffit d'écrire ceci dans votre page HTML
<svg class="icon icon--red">
<use xlink:href="/path/to/sprite.svg#icon-star"></use>
</svg>
Le top du top c'est que le SVG sera mis en cache pas le navigateur, ce qui est une solution idéale pour un set d'icônes à la résolution accroissable (nouveau mot).
Apprendre à se laver les mains.
Je me mets sous le coude ce tuto coiffure pour quand ma petite dernière aura les cheveux suffisamment longs.
Aujourd'hui j'ai relu un cours de @Kysofer qu'il prépare sur la bonne utilisation des monades en programmation fonctionnelle et ça m'a donné envie de le résumer en un cas d'école clair et simple.
Pour la petite histoire et depuis Java 8, je vois apparaître des Optionals
partout mais 99% des développeurs n'ont pas compris comment s'en servir (c'est pour cette raison que je dis souvent que la programmation fonctionnelle est un cancer métastasé, car une fois ces mauvaises pratiques installées, le code devient alors incurable). #Sadness
Bref, c'est partie pour une explication claire et courte... Notre cas d'école sera le suivant :
Une
Personne
possède uneDate de Naissance
, cette date contient elle-même uneAnnée de Naissance
qui contient à son tour une valeur (logée dans un Integer).L'objectif est d'imprimer l'année de naissance si elle existe sans jamais écrire le moindre 'if' pour l'exécution ci-après déclarée dans la méthode
main()
.
La méthode main()
:
class Main {
public static void main(String[] args) {
optionalPrintln(Optional.ofNullable(new Person(new BirthDay(new YearOfBirth(2000)))));
optionalPrintln(Optional.ofNullable(new Person(new BirthDay(null))));
optionalPrintln(Optional.ofNullable(new Person(null)));
}
}
La sortie attendue dans la console doit être celle-ci
2000
Étape 1 - Les classes/structures de données Person, BirthDay et YearOfBirth
class Person {
private final BirthDay birthDay;
public Person(BirthDay birthDay) {
this.birthDay = birthDay;
}
public Optional<BirthDay> getBirthDay() {
return Optional.ofNullable(birthDay);
}
}
class BirthDay {
private final YearOfBirth year;
public BirthDay(YearOfBirth year) {
this.year = year;
}
public Optional<YearOfBirth> getYearOfBirth() {
return Optional.ofNullable(year);
}
}
class YearOfBirth {
private final int year;
public YearOfBirth(int year) {
this.year = year;
}
public Optional<Integer> getValue() {
return Optional.of(year);
}
}
Étape 2 - Ce qu'il ne faut pas faire
Les 9-10ième des développeurs Java que je côtoie écriront ce genre de code pour la optionalPrintln(Optional<Person> option)
:
class Main {
// ...
public static void optionalPrintln(Optional<Person> option) {
if (option.isPresent()) {
Person person = option.get();
Optional<BirthDay> birthDay = person.getBirthDay();
if (birthDay.isPresent()) {
Optional<YearOfBirth> yearOfBirth = birthDay.get().getYearOfBirth();
if (yearOfBirth.isPresent()) {
Optional<Integer> year = yearOfBirth.get().getValue();
if (year.isPresent()) {
System.out.println(year.get());
}
}
}
}
}
}
C'est très moche, c'est très compliqué et ça n'est pas fonctionnel du tout ! Pire encore, certains développeurs écriront ceci :
year.ifPresent(it -> System.out.println(it));
à la place de ce dernier 'if' :
if (year.isPresent()) {
System.out.println(year.get());
}
tout en croyant coder en fonctionnel, or ça n'est pas le cas du tout non plus ! Croire que l'on fait bien alors que l'on fait mal, c'est en ce sens que la chose est pire AMHA.
Étape 3 - Ce qu'il faut faire
La programmation fonctionnelle ne cherche pas à représenter des instructions, c'est-à-dire une succession d'actions techniques qui s'enchaînent mais a contrario, elle cherche à représenter un flux de conversion, c'est-à-dire le fait de passer d'un type A à un type B.
L'API permettant une telle prouesse réside dans le map
de la stream API, or cette fonctionnalité n'est pas applicable à un objet composé comme c'est le cas du Optional
puisqu'il s'agit d'un arbre à deux niveaux sur une seule branche.
Dans ce cas, il faut utiliser la méthode flatMap()
de la même API pour supprimer le niveau superflue de l'arbre et accéder directement à la donnée si elle n'est pas null
.
Ce qui donne le code suivant :
public static void optionalPrintln(Optional<Person> option) {
option.flatMap(it -> it.getBirthDay())
.flatMap(it -> it.getYearOfBirth())
.flatMap(it -> it.getValue())
.ifPresent(it -> System.out.println(it));
}
Explication
A chaque invocation de flatMap()
, cette méthode va jouer pour nous un optional.ifPresent(it -> ...)
afin de nous en décharger. Si rien n'est présent, alors l'évaluation du flatMap()
lui succédant ne se fera tout simplement pas car le flux d'instances à convertir d'un type à un autre type sera dépourvu de toute instance à convertir. #Malin
Et c'est comme cela que l'on code en fonctionnel avec les Optionals ! Le véritable but étant de ne jamais s'en servir explicitement.
Remarque :
La programmation fonctionnelle est extrêmement difficile car elle oblige les développeurs à passer du stade "je dis à la machine comment faire" au stade "je décris à la machine quoi transformer et vers quoi d'autre".
Depuis ces 20 dernières années, j'ai toujours constaté que moins de 5% des développeurs étaient capables de penser dans ce paradigme et parmi ce petit pourcentage, à peine la moitié s'en servira dans sa vie professionnelle.
Je découvre le mot-clef expect
de Kotlin. Son objectif est de dire que pour une même classe, il va y avoir différentes implémentations en fonction de la plateforme.
Par exemple pour la classe (vide) :
expect class KMPDate(formatString: String) {
fun asString(): String
}
Nous aurons cette première implémentation pour Android :
actual class KMPDate actual constructor(formatString: String) { // 1
private val dateFormat = SimpleDateFormat(formatString) // 2
actual fun asString(): String {
return dateFormat.format(Date()) // 3
}
}
et cette seconde pour iOS :
actual class KMPDate actual constructor(formatString: String) { // 1
private val dateFormatter = NSDateFormatter().apply { // 2
this.dateFormat = formatString
}
actual fun asString(): String {
return formatter.stringFromDate(NSDate()) // 3
}
}
KPMDate n'est pas une interface mais bien une classe concrète avec plusieurs implémentions dites "platform-dependent" dans le même fichier.
Mieux encore, c'est compatible avec GraalVM et Kotlin native pour produire des binaires natifs (AMD64 et ARM64 principalement) compilés et linkés statiquement (+ compilation AOT + tree shaking pour les optims) sans avoir besoin d'une JRE d'installée sur l'environnement cible, donc parfait dans des conteneurs très légers par exemple :D
Mais cet article est excellent !
Merci à @Sebsauvage pour sa doc et @Shikiryu pour le lien.
@Guigui : super je me le note. Merci !
J'avais déjà expliqué mon désamour pour Kafka ici mais comme toujours, le client est roi et des architectes poussent cet outil même si leurs besoins en terme de charge et de volume ne sont pas suffisamment élevés pour justifier Kafka.
Ici, un bon article expliquant les stratégies de partitionnement des topics Kafka. Merci à son auteur, ça m'aide.
P.S : au regard de ma critique vive de Kafka, cet outil possède un avantage qui est extrêmement intéressant : le découplage temporel, c'est-à-dire qu'en forçant l'asynchronisme des messages, Kafka retire du SI les problèmes d'indisponibilités des services appelés, et ça c'est une très bonne chose (que l'on peut toujours compenser par un cluster de micro-services + Kubernetes mais bon).
A lire pour plus tard.
Pour @Lenny et @Kysofer qui doivent se servir de Netty.
EDIT 5: un cinquière tuto ici.
EDIT 4 : un quatrième tuto ici.
EDIT 3 : un troisième tuto ici.
EDIT 2 : un deuxième tuto ici.
EDIT : je viens de tomber sur ce tuto et quand je lis ce genre de code :
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
public class WebSocketHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof WebSocketFrame) {
System.out.println("This is a WebSocket frame");
System.out.println("Client Channel : " + ctx.channel());
if (msg instanceof BinaryWebSocketFrame) {
System.out.println("BinaryWebSocketFrame Received : ");
System.out.println(((BinaryWebSocketFrame) msg).content());
} else if (msg instanceof TextWebSocketFrame) {
System.out.println("TextWebSocketFrame Received : ");
ctx.channel().writeAndFlush(
new TextWebSocketFrame("Message recieved : " + ((TextWebSocketFrame) msg).text()));
System.out.println(((TextWebSocketFrame) msg).text());
} else if (msg instanceof PingWebSocketFrame) {
System.out.println("PingWebSocketFrame Received : ");
System.out.println(((PingWebSocketFrame) msg).content());
} else if (msg instanceof PongWebSocketFrame) {
System.out.println("PongWebSocketFrame Received : ");
System.out.println(((PongWebSocketFrame) msg).content());
} else if (msg instanceof CloseWebSocketFrame) {
System.out.println("CloseWebSocketFrame Received : ");
System.out.println("ReasonText :" + ((CloseWebSocketFrame) msg).reasonText());
System.out.println("StatusCode : " + ((CloseWebSocketFrame) msg).statusCode());
} else {
System.out.println("Unsupported WebSocketFrame");
}
}
}
}
J'ai juste envie de pleurer (et je vais vous expliquer pourquoi). La programmation orientée objets reposent sur trois principes :
- L'encapsulation.
- Le polymorphisme.
- L'héritage (mais il faut l'oublier car c'est un anti-pattern).
Le polymorphisme, c'est le fait qu'une classe fille puisse se faire passer pour sa classe mère. L'idée étant que l'on doit organiser notre code de telle sorte que nous puissions TOUJOURS utiliser les super-types (classe mère abstraite ou interface). Or dès que l'on doit ajouter des conditions du type truc instanceof Class
c'est que l'on passe à côté du principale intérêt du polymorphisme : ne jamais avoir besoin de dépendre des implémentations.
Soyons clair, dans un code orienté objet je n'ai jamais eu besoin d'écrire des instanceof
et de toute ma vie, ce n'a dû m'arriver qu'une ou deux fois et parce que je dépendais d'un code aussi vieux que Java lui-même !
Bref, si vous codez en Java et que vous avez besoin des implémentations, pis encore des instanceof
et des casts, alors c'est que vous pensez en procédural et non en objet. Et pour tout ceux qui souhaitent apprendre à penser en objet, je les renvoie aux formations de Kysofer (car il m'a promis une com' si vous y aller de ma part :D) et il y a la pré-collection automne de Sezane qui me fait grave de l’œil si vous voulez...
Article mis sous le coude pour tout à l'heure.
J'avais entendu le nom il y a quelques années mais n'ayant pas plus creusé le concept je l'avais oublié. Le hasard du destin me fait retomber dessus aujourd'hui. Bref il s'agit de 9 règles de programmation à appliquer.
En résumé :
- N'utiliser qu'un seul niveau d'indentation
- Ne pas utiliser le mot-clef ELSE
- Encapsuler toutes les primitives et les String dans des objets
- Encapsuler les collections dans des objets
- N'enchaîner qu'un seul point (ou flèche) par ligne
- Ne pas utiliser d’abréviations
- Garder des classes petites (moins de 50 lignes)
- Aucune classe ne doit avoir plus de deux instances en guise de variables
- Ne jamais utiliser de Getter/Setter
Globalement, je le fais sans m'en rendre compte, mes classes sont minuscules (20 lignes en moyennes). Éventuellement le ELSE m'est encore utile mais c'est vrai que programmant en fail-first je lui préfère un throw direct et donc qu'il n'apparaît vraiment pas souvent.
Note personnelle : je n'aime vraiment pas du tout la syntaxe de Rust, mais vraiment :(. À chaque fois que j'en fais je meurs un petit peu. Après je maintiens qu'il s'agit d'un des meilleurs langages du marché à cette heure et une tendance de fond qui me pousse à prendre la vague.
Bon je vais reprendre le début du kata @Kysofer (qui va me faire intervenir dans ses formations) pour montrer comment il programme en OOP "pure" avec Rust en mettant en place une encapsulation stricte (à la Yegor Bugayenko). Le poste se fera en plusieurs parties, cette première étape consistant à montrer comment fonctionnent les trait
, les struct
et la notion de value borrowed
pour les débutants.
Objectifs du kata :
- Fournir deux structures différentes permettant l'addition.
- Décorer la première structure par la seconde.
- Introduction succincte au
value borrowed
.
Code :
#![allow(non_snake_case)] // Oui je viens du monde Java/Kotlin
// Structures
struct Couple {
opA: i32,
opB: i32,
}
struct Triplet {
opA: Couple,
opB: i32,
}
// Interface
trait Addition {
fn add(&self) -> i32;
}
// Implementations
impl Addition for Couple {
fn add(&self) -> i32 {
return self.opA + self.opB;
}
}
impl Addition for Triplet {
fn add(&self) -> i32 {
return self.opA.add() + self.opB;
}
}
Exécution :
Pour instancier et exécuter le code il faut écrire ceci. Tout marche, aucun problème.
fn main() {
let additionA = Couple { opA: 1, opB: 2 };
println!("Couple add : [{}]", additionA.add()); // Print 3
let additionB = Triplet {
opA: additionA,
opB: 3,
};
println!("Triplet add : [{}]", additionB.add()); // Print 6
}
La petite difficulté (el famoso "value borrowed") arrive dès que l'on initialise les deux structures l'une derrière l'autre :
fn main() {
let additionA = Couple { opA: 1, opB: 2 };
let additionB = Triplet {
opA: additionA,
opB: 3,
};
println!("Couple add : [{}]", additionA.add()); // NE COMPILE PAS
println!("Triplet add : [{}]", additionB.add());
}
En réalité, au moment du premier println!()
, la variable additionA
n'est plus dans l'espace courant mais a été empruntée par Triplet
, elle se retrouve donc utilisable mais uniquement par lui. Alors soit on réordonnance le code (ce que j'ai fait dans la première exécution qui fonctionne) soit on indique que Triplet
restitue la variable qu'il a emprunté (je montrerai plus tard comment faire).
Bref, jusque là rien de transcendant nous remarquerons quand même que Triplet
contient un Couple
(c'est-à-dire une structure) et non une Addition
(c'est-à-dire une interface) ce qui matérialise un couplage fort entre les types et donc l'impossibilité de programmer par contrat (c'est-à-dire en masquant les implémentations et en protégeant l'encapsulation des données contenues dans la structure Couple
).
C'est là qu'arrivera la second difficulté dont je parlerai aussi plus tard : comment calculer dynamiquement les tailles des implémentations des traits pour les utiliser en tant qu'attribut d'une structure.
Avis personnel : depuis quelques mois que je m'amuse avec Rust je peux dire que globalement j'aime ce langage mais cela ne m'empêche pas de le trouver pauvre sur pas mal de ses aspects (au sens paradigme de programmation).
Dans ce qui me déplaît, je mettrais la forte emprunte qu'il retire C et du C++, notamment leur style procédurale omniprésent (on ne code plus comme en Pascal en 2020, damned), l'encapsulation découragée totalement (en ce sens Rust n'est pas objet pour un sous, et la programmation en structure-first est un anti-concept, encore une fois cf. Elegant Objects de Yegor Bugayenko) et pour ma plus grande tragédie, une syntaxe que je trouve lourdingue car s'appuyant sur beaucoup de symboles ou trop peux expressive (je suis navrée pour ceux qui pensent que i32 et u32 sont de bonnes façons d'écrire des entiers signés ou non en 2020).
Encore une fois, la syntaxe de Kotlin (sauf le sucre syntaxique du préfixe get/set et les equals methods), l'API immutable de Kotlin, la gestion des Threads/KoRoutines de Kotlin, le null-check de Kotlin, le tout mêlé au Owernship de Rust, à la performance du binaire produit par son compilateur, à sa capacité à gérer du bas niveau, tout ceci en ferait sûrement l'un des langages les plus efficaces et fiable du siècle.
Je prie pour que Kotlin Native soit un "game changer" notamment pour qu'il parvienne à s'élever au niveau de Rust en terme de performances et de protection contre les race-conditions.
Je me le note, merci à JcFrog pour le lien.
Alors ce tutoriel est très bien pour ceux qui souhaitent apprendre la syntaxe du langage par contre en aucun cas il ne correspond au titre de l'e-book dont il est un chapitre : "Introduction au langage Kotlin, découvrir les notions avancées de la programmation orientée objet".
En rien l'article ne parle d'OOP et de ses concepts avancés, bien au contraire il met en avant les aspects du langage qui poussent à la programmation procédurale réalisée à partir de structures de données façon C/VB/PL-SQL et je m'explique.
Lorsque vous créez une classe avec des getters et des setters, vous ne faites ni plus ni moins que de créer une structure avec des attributs public déguisés en attributs privés. Fondamentalement, même si c'est au travers d'une méthode, vous permettez à n'importe quelle autre classe de violer l'aspect le plus fondamental de l'OOP : l'encapsulation (je vous invite à suivre les tutos de @Kysofer sur le sujet, c'est un grand gourou du domaine).
D'ailleurs, et pour reprendre l'un de ses cours, l'encapsulation c'est deux composantes :
- Personne d'autre que la classe ne peut connaître (et encore moins modifier) les données qu'elle contient.
- Personne d'autre que la classe ne peut connaître le type réel des données qu'elle contient.
Avec un exemple simple, si j'ai cette horreur (du point de vue de l'OOP) :
val name:String = person.getName()`
person.setName("my-name")`
Alors cela revient exactement au même que d'écrire :
val name:String = person.name`
person.name = "my-name"
Cela est tellement la même chose que Kotlin l'assume et génère à la compilation, à partir des getters/setters, le code du second exemple pour permettre l'accès en public aux attributs.
Mais là n'est pas le problème, en effet le vrai problème est que si je venais à changer le type interne de name
pour le passer d'une String
à un nouveau type, par exemple appelé Name
, alors il faudra que je modifie TOUS LES APPELS EFFECTUÉS à getName() car les codes appelant s'attendent à une String, rien n'est encapsulé, rien n'est masqué, a contrario toute la représentation interne fuite à travers les getters/setters et les gens appellent cela innocemment de l'OOP alors que ça n'en est pas du tout : ce sont des struct
déguisées en objets.
La solution consiste à n'utiliser que des interfaces car par essence, une interface ne peut pas avoir d'attributs. Alors certes les dérives depuis Java 8 et certains anti-patterns de la programmation fonctionnelle (du point de vue de l'OOP toujours) ont eu tendance à modifier le langage mais dans la pratique, c'est vraiment une très mauvaise idée de mettre des attributs/constantes dans une interface.
Je sais que @Kysofer et @Lenny réfléchissaient à faire des tutos sur l'OOP sur Youtube/PeerTube, j'espère que ce post va les motiver à passer à l'acte. D'ailleurs je partagerai toutes vos vidéos mes chéris :) #Waiting
Il y a plusieurs façons de faire, mais lorsqu'il s'agit de remplir un placeholder d'un input text, cette méthode marche plutôt bien :
<input type="text" i18n="[placeholder]my-i18n-key" />
L'attribut qui se fera injecter le i18n doit être mis entre crochet [ ]
et s'il y en a plusieurs, alors ils doivent être séparés par des virgules s'ils possèdent la même valeur ; ou par des points-virgules s'ils possèdent chacun une valeur différente.
Par exemple :
<!-- Les deux attributs posséderont la même valeur i18n -->
<input type="text" i18n="[placeholder,aria-placeholder]my-i18n-key" />
<!-- Chaque attribut possédera sa propre valeur i18n -->
<input type="text" i18n="[placeholder]my-i18n-key;[aria-placeholder]my-other-i18n-key" />
Aurelia est un framework à la fois simple et élégant.