Master MIAGE Paris Descartes, 2011 👴
Senior Software Engineer, Datadog 🐶
Qu’est-ce que le génie ?

La personne qui exauce vos voeux ?
¯\_(ツ)_/¯
« Démon qui préside à la conception »
Wiktionnaire
« Aptitude de quelqu’un à concevoir des choses d’une qualité exceptionnelle »
Larousse
« Pas d’intérêt, pas d’action »
Maîtriser les enjeux, les problématiques :

Écouter, comprendre et s’approprier le besoin.
Traduire le besoin en solution (i.e. ingénierie)
The Project Cartoon

N’ayez pas peur d’échouer

« La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer. »
Antoine de Saint-Exupéry
Maîtriser votre processus de développement.
Pour produire de la technologie, utiliser de la technologie.
Automatiser les tâches récurrentes
Déployer le plus souvent possible
« Vous voulez aller vite ? Alors prenez votre temps ! »
Daniel Guillaume

Le génie logiciel cherche à faire un « bon » produit.
Conforme
Le produit répond au besoin de l’utilisateur.
Maintenable
Le code est compréhensible par mes pairs.
Testable
Le produit est sûr et ne régresse pas.
Réutilisable
Le code est modulaire et limite ses adhérences.
Documenté
Le produit, contexte et/ou code sont expliqués.
Performant
Le produit s’exécute promptement sur la volumétrie cible.
Faire un produit n’est pas complexe en soi, c’est l’environnement dans lequel vous le faites qui joue en votre défaveur.
L’informatique est une science qui va très vite, ce qui est hype aujourd’hui sera obsolète demain.
L’utilisateur a soif de nouveautés, se trouve dans un environnement concurrentiel, et son besoin est sans cesse réajusté.
Livraisons très fréquentes, voire constantes.
Prendre en compte les retours utilisateurs et… leur faire un retour !
Rewarded feedback
L’imagination est plus importante que le savoir.
Albert Einstein

git init
git status

git add <nom_du_fichier>
git add -i
git add -p
git checkout -- <nom_du_fichier>
git restore <nom_du_fichier>
git reset HEAD -- <nom_du_fichier>
git restore --staged <nom_du_fichier>
git checkout -b <nom_de_la_branche>
git switch --create <nom_de_la_branche>
git checkout <nom_de_la_branche>
git switch <nom_de_la_branche>
git stash
git stash pop
git commit -m "commentaire de votre commit"
Écrire un bon message de commit
git log
git show <commit-sha>
ssh-keygen -t ed25519 -a 100 -C “(whoami)@(hostname)” -f “${HOME}/.ssh/id_ed25519”
git remote add origin git@github.com:ViBiOh/l3miage.git
git push

git push <nom_du_remote> <nom_de_la_branche>
git push origin main
git push <nom_du_remote> <commit_ref>:<nom_de_la_branche>
git push origin HEAD:main
Ne pas oublier le HEAD car
git push origin :main supprime la branche
git pull
git pull <nom_du_remote> <nom_de_la_branche>
git pull origin main
Par “en dessous”
git pull --rebase <nom_de_la_branche_source>
Par “au dessus”
git pull --rebase=false <nom_de_la_branche_source>
git clone <url_du_repo>
via HTTP, pour du public mais pas idéal pour travailler.
git clone https://github.com/ViBiOh/l3miage.git
via SSH, pour du public et privé, plus sûr.
git clone git@github.com:ViBiOh/l3miage.git
Références
# Titre de niveau 1
## Titre de niveau 2
### Vous avez compris... !
*texte* est en italique**texte** là est en gras***texte*** est en gras
italiqueLe marqueur _ peut se substituer au marqueur
*
En mode en ligne simplement en l’entourant d’une
apostrophe arrière `
Ou en mode bloc en indentant
l'ensemble avec un quadruple espace
ou une tabulation, ou balisé de 3 `
Le texte du lien doit se trouver entre crochets et l’URL de votre lien dans des parenthèses juxtaposées.
[Texte de votre lien](url_de_votre_lien)
Une image est un lien externe, c’est la même syntaxe que l’URL mais avec un point d’exclamation devant.

« Nine women can’t make a baby in one month. »
Frederic Brooks
L’informatique, et le développement de logiciels en particulier, sont bien souvent un laboratoire d’essais pour les méthodes de management et/ou de gestion de projet.
Très contractuel et procédurier : un cahier des charges initial, un cahier de recette final.
Souvent utilisé dans le cadre de “forfait” en ESN (ex SSII).

Crée un effet « tunnel » car chaque tâche dépend de la précédente, sans validation de l’utilisateur.
Processus itératif où l’on présente fréquemment l’avancée du produit à l’utilisateur.
Risque bien moins grand de dévier du besoin réel, possibilité de le réajuster en cours de développement.
Tout est timeboxé. Chaque cérémonie de la méthode Scrum a une durée qu’il faut respecter. On va à l’essentiel.
Si vous entendez parlez de Sprint, de burn-down chart, de daily standup, vous êtes dans une équipe agile Scrum.
… ou alors si vous voyez des montagnes de Post-it ® sur un des bureaux !
La qualité vous fait peur ?
Tant mieux : on ne négocie pas avec les terroristes la
qualité.
Ne pas “trop” prévoir, car, par définition, une prévision est incertaine.

“Flux tiré” par la demande, on lit le tableau de droite à gauche.
Limiter l’encours afin de favoriser la vélocité.
« Stop starting, start finishing. »
C’est un premier pas vers le lean manufacturing
Références :
Comme je suis connecté, quand je vais sur mon profil, je peux vérifier l’exactitude de mes informations.
En tant que client, je veux pouvoir consulter mon profil afin de vérifier l’exactitude de mes informations.
Phrases courtes permettant de définir une fonctionnalité, un besoin, un bug, etc.
Attention à correctement les organiser pour que cela reste maintenable et lisible.
La carte n’est pas le territoire.
Cadrage & périmètre
Pourquoi et dans quelle mesure faisons nous ce produit/projet ?
Expression fonctionnelle du besoin
Quelles sont toutes les règles que le produit/projet doit respecter ?
Méthodes & contraintes
Comment allons nous travailler au sein de ce produit/projet ?
Délais et parties prenantes
À qui et quand rendre compte ?
Continuous Integration / Continuous Delivery
L’objectif de l’entreprise est de réduire le Time To Market (T.T.M.) et donc pour la R&D le Time To Ship.
On livre toujours du code qu’on assume : exempt de bugs, performant…
De qualité !

A chaque erreur détectée lors du processus, l’intégration continue doit être en mesure d’identifier les nouveaux commits depuis le dernier succès et d’en avertir les parties prenantes.
Désactiver des fonctionnalités à la volée
Seul on va plus vite, ensemble on va plus loin.
Partager votre vision
Faire de la veille, assister à des conférences, des meetups, des salons, …
« Stay hungry, stay foolish. » - Steve Jobs
Discuter des implémentations, technologies, actualités.
En méso-économie, on ne peut ignorer un évènement. Même si un domaine ne vous intéresse pas, il vous impactera directement ou indirectement.
Être bon communicant passe par de bons outils
Travailler dans des organisations implique de connaître quelques lois empiriques qui les régissent.
Tout employé tend à s’élever à son niveau d’incompétence…
… Avec le temps, tous les postes d’une entreprise sont occupés par des incompétents.
Les gens les moins compétents sont systématiquement affectés aux postes où ils risquent de causer le moins de dégâts : ceux de managers.
Gratuit ne veut pas dire sans valeur
On n’a rien sans rien
“Free beer” vs “free speech”
Le temps est subjectif : ce qui est plaisant passe vite, ce qui est désagréable semble durer.
Le travail se dilate jusqu’à occuper tout le temps qui lui est dévolu.
Ajouter des personnes à un projet en retard, accroît son retard.
n(n-1)/2 channels de communication
Après un certain temps de travail, la productivité décroît. La pause devient nécessaire.
Si quelque chose peut arriver, alors ça arrivera.
Pourquoi ? Pourquoi ? Pourquoi ? Pourquoi ? Pourquoi ?
Combien ? Qui ? Quand ? Comment ? Où ? Quoi ? Pourquoi ?
La puissance des ordinateurs double tous les 18 mois.
Les programmes ralentissent plus vite que le matériel n’accélère.
Règle des 80 - 20
Leave the campground cleaner that you found it.
No finger pointing
No name, no blame, no shame
You Aren’t Gonna Need It
Analogie faite par Ward Cunningham qui applique le principe d’une dette financière au développement logiciel.
Le capital est votre base de code, les intérêts sont :
Chaque ajout de fonctionnalité vient modifier l’application, il faut donc veiller à refactorer au fil de l’eau pour ne pas empiler du code.
e.g. Un
if/elsepeut vite se transformer en enchaînement disgracieux : faire unswitch
Enfin, l’environnement évolue sans cesse, les modèles, les méthodes, les outils, etc. Il faut donc veiller à ne pas avoir des architectures trop vieilles, devenues immaintenables.
Utiliser ce qui existe quand cela répond à votre besoin
En combinant des outils, on peut en créer d’autres. Philosophie unix.
e.g. la stack ELK pour analyser vos logs : ElasticSearch Logstash Kibana
Eviter le “too busy to improve”

Pourquoi faire compliqué quand on peut faire simple ?
Il est parfois compliqué de faire simple en appliquant les patterns de programmation



Le plus grand virus informatique est l’interface clavier-chaise.
S’il y a vraiment un bug (i.e. après avoir lu la documentation), ouvrez un ticket !
Contribuez, corrigez, améliorez : appropriez-vous vos outils

Débugger son code avec des sysout, console.log ou des echo c’est bien quand on est un script kiddie.
Un professionnel met des points d’arrêt et observe la stack et le contenu des variables.
Un débuggeur comporte principalement trois fonctionnalités.
Positionnement de points d’arrêts & pilotage de l’exécution
watch ou espion de variables
Exploration de la stack d’appels
Afficher des informations sur l’activité du produit
Ecrire des enregistrements consistants, avec l’ensemble des informations nécessaires à la compréhension du comportement
Faire attention au multithreading, à l’asynchronisme, au volume de données, à la performance
Exploiter vos logs avec des systèmes de monitoring, d’alarmes et d’analyses
Peu importe votre religion, il faut l’assumer et la maîtriser
Tester c’est douter.
Oui c’est vrai. Mais l’avenir est incertain.
Ça prend du temps, ça ne sert à rien, il faut les maintenir.
Oui ça prend du temps, mais c’est de la capitalisation.
Ça sert énormément en cas de refactoring.
Les tests c’est pour ceux qui ne savent pas coder.
Bien au contraire, écrire un test est un gage de qualité.
Connaître le comportement attendu de l’application :
Vérifier qu’il n’y a pas de code inutile
Identifier les anomalies au plus tôt et ainsi, économiser !
Exemple d’un mauvais test
Il est exécuté fréquemment, à chaque modification du code de l’application
Il fait partie du code de l’application :
Il couvre un besoin ou un cas technique ou fonctionnel
Il est créé dès qu’un bug a été détecté afin d’éviter qu’il ne revienne
Objectif : faire des tests FIRST
F astI solateR epeatableS elf-validatingT imely ou T
horoughTester un seul composant et pas ses dépendances
Pouvoir rejouer chaque test unitairement et à tout moment
Aucune dépendance entre les tests :
Pouvoir se dire
« ce composant (ou cette fonction) est stable et répond à notre besoin, le problème n’est pas là »
Test du lecteur d’entiers
public class IntegerReaderTest {
@Test(expected = NullPointerException.class)
public void read_null_exception() throws Exception {
IntegerReader.read(null);
}
@Test
public void read_match_positive() throws Exception {
assertEquals(Integer.valueOf(123), IntegerReader.read("123"));
}
@Test
public void read_matchNegative_negative() throws Exception {
assertEquals(Integer.valueOf(-123), IntegerReader.read("-123"));
}
}Quels sont les problèmes ?
Comment tester le déroulement d’un algorithme ayant des dépendances mais sans en être dépendant ?
e.g. sauvegarde dans une base de données, lecture d’un fichier, service qui calcule une information complexe
Créer des classes ayant le même comportement que les dépendances
Fournir un jeu de données fixe pour les tests
Envisager les comportements probables sans les provoquer
Stub InputStream pour IntegerReader
public class InputStreamStub extends InputStream {
private String[] VALUES = { "0", "123", "-123" };
private int index;
private int seq;
public InputStreamStub(final int index) {
this.index = index;
}
@Override
public int read() throws IOException {
if (seq < VALUES[index].length()) {
return VALUES[index].charAt(seq++);
}
return -1;
}
}IntegerReaderTest
public class IntegerReaderTest {
private IntegerReader integerReader;
@Test
public void nextInt_123() {
integerReader = new IntegerReader(new InputStreamStub(1));
assertEquals(123, integerReader.readInt());
}
@Test
public void nextInt_negative_123() {
integerReader = new IntegerReader(new InputStreamStub(2));
assertEquals(-123, integerReader.readInt());
}
}Quels sont les problèmes ?
Fastidieux à écrire et cela requiert un effort de maintenance considérable
Fort couplage entre le jeu de données décrit dans le Stub et le cas de test
Simuler le comportement d’une dépendance sans l’appeler et sans l’écrire
Préciser l’entrée à laquelle on réagit et la sortie que l’on produit en conséquence
Préparation du contexte d’exécution
Test à proprement parler
public class ProcessImplTest {
@Test
public void nextInt_empty() throws IOException {
when(reader.read()).thenReturn(Optional.empty());
when(operation.compute(eq(Optional.empty())))
.thenReturn(Optional.empty());
doThrow(new IOException()).when(writer)
.write(eq(Optional.empty()));
assertEquals(1, process.execute());
}
}Quels sont les problèmes ?
Chaque composant peut fonctionner parfaitement individuellement…
…mais ne pas fonctionner en équipe !
S’assurer de la bonne intégration :

e.g. Tester la bonne intégration des composants
Intégration problématique entre composants
public class DateHelper {
public String now() {
return new SimpleDateFormat("dd/MM/yyyy").format(new Date());
}
}
public class MyService {
@Autowired
private DateHelper dateHelper;
boolean isBefore(final Date value) throws ParseException {
return new SimpleDateFormat("yyyy/MM/dd")
.parse(dateHelper.now()).before(value); // Mostly true
}
}e.g. Tester la validité des requêtes SQL
Intégration problématique avec une dépendance
@Repository
public class BadDAO {
private JdbcTemplate jdbcTemplate;
@Autowired
public void init(final DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public Collection<String> list() {
return jdbcTemplate
.queryForList(
"SELECT age FROM Person WHERE name = birthDate"
, String.class); // Seems annoying
}
}Quels sont les problèmes ?
Dans quel ordre tester les composants ?
Du plus bas niveau vers le haut ?
Du plus haut niveau vers le bas ?
Aucune solution n’est satisfaisante.
Cela requiert un ou plusieurs environnements d’intégration.
Plus lent à s’exécuter car nécessite de préparer l’environnement à chaque exécution de test.
e.g. chargement de base de données, copie de fichiers, etc.
On ne vérifie pas le fonctionnel de l’application mais seulement que les composants se comprennent
Simuler l’utilisation du logiciel par un utilisateur final
Vérifier que les règles de gestion de l’application sont respectées
Vérifier que le rendu final est conforme aux attentes
Outils
Cucumber, Fitnesse, Robot Framework, NightwatchJS, CyPress, etc.
Dans un monde idéal, on réalise les trois types de tests précédents. Dans un registre plus pragmatique, on réalise les tests unitaires, fonctionnels et une fraction choisie des tests d’intégrations.
Les tests d’intégration complets sont complexes à mettre en œuvre. Mise en œuvre qui peut se révéler (trop) coûteuse pour le projet.
Votre application doit être conforme aux règles métiers de l’utilisateur mais elle doit le faire dans un temps acceptable.
Effectuer des tests fonctionnels “unitaires” ne permet pas d’apprécier le temps de réponse sur une volumétrie réelle.
e.g. Générer la fiche de paye PDF d’un salarié prend 1 seconde. Si vous l’implantez chez Wal Mart (~2 M d’employés), il vous faudra plus de 23 jours complets pour tout générer.
e.g. Effectuer une recherche dans le référentiel “Produit” prend une demi-seconde. Ce temps est-il constant si vous importez le catalogue d’Amazon ?
Il existe des outils pour simuler la connexion simultanée de plusieurs utilisateurs : Gatling, Apache JMeter
Il ne faut pas chercher à bâtir une architecture qui réponde quoiqu’il advienne (c’est un problème de scalabilité ) mais connaître les limites et analyser la courbe de réponse avec des outils de profiling
Cela requiert, comme pour les tests d’intégration, des environnements capables de supporter la volumétrie et la charge.
Il existe d’autres tests à réaliser sur une application, plus marginaux, mais néanmoins possibles.
Les tests ou audit de sécurité pratiquent notamment du pen-testing ou s’assurent que les normes de sécurité sont respectés
La sécurité est un processus, c’est aussi bien :
L’erreur est toujours humaine.
Les tests d’assurance qualité (Quality Assurance - QA) sont aussi essentiels. On ne peut pas tout tester automatiquement, à un moment, il faut qu’un humain utilise vraiment l’application.
e.g. Vérifier que des éléments sont bien alignés à l’écran. Vérifier la présence judicieuse des scroll-bar
Cela conduit bien souvent à vérifier que la User eXpérience est satisfaisante au niveau de l’application.
Attention, l’UX n’est pas synonyme d’UI ni d’ergonomie. C’est bien de l’« expérience utilisateur » que l’on parle.
e.g. Uber ou BlaBlaCar vous proposent une application, mais la majeure partie de l’UX s’effectue dans la voiture.
Lorsqu’on écrit du code, on cherche à répondre à un besoin
Ce besoin peut se formuler sous la forme d’un test
On écrit d’abord le test qui vérifie notre besoin, et ensuite on écrit le code qui répond à ce test
Tout ceci s’inclut dans un processus itératif afin d’éviter d’écrire trop de choses non testées. On répond au test, puis on refactore.
e.g. FizzBuzz
Si le nombre est multiple de 3, afficher “fizz”.
Si le nombre est multiple de 5, afficher “buzz”.
Sinon afficher le nombre.
Si je donne le chiffre 1, renvoyer 1
Le code correspondant est donc le suivant
Si je donne le chiffre 2, renvoyer 2
Modification du code pour renvoyer 2
Refactoring possible ?
Si je donne le chiffre 3, renvoyer ‘fizz’
Adaptation du code pour tester 3
Si je donne le chiffre 6, renvoyer ‘fizz’
Adaptation du code pour 6
Refactoring possible ?
Et ainsi de suite.
Ecrire un test 🔴.
Corriger pour passer au ✅.
Refactorer en gardant le ✅.
Une autre solution possible
« La théorie, c’est quand on sait tout et que rien ne fonctionne.
La pratique, c’est quand tout fonctionne et que personne ne sait pourquoi.
Ici, nous avons réuni théorie et pratique : rien ne fonctionne… et personne ne sait pourquoi ! »
Albert Einstein
« There are only two hard things in Computer Science: cache invalidation and naming things »
Phil Karlton
Objectif : faire du code SOLID
S ingle Responsibility PrincipleO pen / CloseL iskov Substitution PrincipleI nterface segregationD ependency Injection« Design depends largely on constraints. »
Charles Eames
« Toujours prendre un exemple stupide pour que tout le monde comprenne. »
Daniel Guillaume
Afficher l’inverse de l’entier saisi par l’utilisateur
Quels sont les problèmes ?
Combien d’actions sont réalisées ?
La classe fait trois ? cinq ? trop de choses
:
Aucune réutilisabilité
Multiples raisons d’évolution
Eviter les god objects
Principe de « diviser pour mieux régner »
Lecture d’un entier
Juste un proxy ? Justement, ajoutons une validation par regex
class IntegerReader
Calcul de l’inverse
Affichage du résultat
Orchestration
class Program {
public static void main(String[] args) throws IOException {
IntegerReader integerReader = new IntegerReader(System.in);
InverseOperation inverse = new InverseOperation();
InverseWriter display = new InverseWriter(System.out);
display.write(inverse.compute(integerReader.readInt()));
}
}Quels sont les problèmes ?
Nombreuses instanciations, avec des arguments
Composants intimement liés
Testabilité complexe voire impossible
L’application a besoin de comportements, pas d’implémentations
Les comportements existent :
Lecture d’un entier
Calcul de l’inverse
Calcul du carré
Ecriture du résultat
Processus de traitement : lire - traiter - écrire
class ProcessImpl<I> implements Process<I> {
private Reader<I> reader;
private Operation<I, Object> operation;
private Writer<Object> writer;
@Override
public Process execute() {
try {
Integer input = reader.read().orElse(null);
writer.write(operation.compute(input).orElse(null));
} catch (IOException e) {
logger.log(Level.SEVERE, "Something went wrong", e);
}
return this;
}
}Processus de traitement : des méthodes à générer
@Override
public Process setReader(Reader<I> reader) {
this.reader = reader;
return this;
}
@Override
public Process setOperation(Operation<I, Object> operation) {
this.operation = operation;
return this;
}
@Override
public Process setWriter(Writer<Object> writer) {
this.writer = writer;
return this;
}Orchestration
class Program {
public static void main(String[] args) {
Reader<Integer> reader = new IntegerReader(System.in);
Operation<Integer, Double> inverse = new InverseOperation();
Writer<Object> inverseWriter = new InverseWriter(System.out);
Operation<Integer, Integer> square = new SquareOperation();
Writer<Object> squareWriter = new SquareWriter(System.out);
new ProcessImpl<Integer>().setReader(reader)
.setOperation(inverse).setWriter(inverseWriter)
.execute()
.setOperation(square).setWriter(squareWriter)
.execute();
}
}Quels sont les problèmes ?
Déclaration des getters/setters fastidieuse
Toujours des instanciations avec arguments, mais externalisées
Connaissance des dépendances entre classes
Service de calcul d’une inverse
Injection de dépendances dans le constructeur
Injection par constructeur (moderne)
@Service
class InverseWriter implements Writer<Object> {
private final OutputStream out;
public InverseWriter(OutputStream out) {
this.out = out;
}
public void write(Object value) throws IOException {
out.write(
("Inverse: " + Optional.ofNullable(value).orElse(""))
.getBytes(StandardCharsets.UTF_8)
);
}
}Injection des dépendances par constructeur
@Component
class ProcessImpl<I> implements Process {
private final Reader<I> reader;
private final Operation<I, Object> operation;
private final Writer<Object> writer;
public ProcessImpl(Reader<I> reader,
Operation<I, Object> operation,
Writer<Object> writer) {
this.reader = reader;
this.operation = operation;
this.writer = writer;
}
}Utilisation des comportements
Configuration de l’application
@Configuration
@ComponentScan("org.vibioh.spring")
class Program implements CommandLineRunner {
private final Process inverse;
public Program(Process inverse) {
this.inverse = inverse;
}
@Bean
InputStream input() { return System.in; }
@Bean
OutputStream output() { return System.out; }
@Override
public void run(String... args) {
inverse.execute();
}
}Démarrage de l’application
@SpringBootApplication
class Program implements CommandLineRunner {
// ... (voir au-dessus)
public static void main(String[] args) {
SpringApplication.run(Program.class, args);
}
}Alternative avec Lombok (encore plus concis)
Chaque sous-classe doit avoir le même comportement que la classe mère
En dehors de vos GIF & feu Vines, personne n’aime se répéter
Extraire toutes les constantes du code, aussi appelés magic number
Implémentation ne respectant pas le DRY
Extraction des constantes et mutualisation du code
class GoodDry {
private static String FIRST_VALUE = "8000";
private static String SECOND_VALUE = "Emile";
public static void main(String[] args) {
forceArgValue(FIRST_VALUE, args, 0);
forceArgValue(SECOND_VALUE, args, 1);
Logger.getAnonymousLogger().info(Arrays.toString(args));
}
private static void forceArgValue(String expectedValue,
String[] array, int i) {
if (array.length > i && !expectedValue.equals(array[i])) {
array[i] = expectedValue;
}
}
}Transformation de l’appel répété par une boucle
class BestDry {
private static String[] EXPECTED_VALUES = {"8000", "Emile"};
public static void main(String[] args) {
forceArgValues(args);
Logger.getAnonymousLogger().info(Arrays.toString(args));
}
private static void forceArgValues(String[] array) {
for (int i = 0, size = array.length; i < size; ++i) {
if (!EXPECTED_VALUES[i].equals(array[i])) {
array[i] = EXPECTED_VALUES[i];
}
}
}
}Ne pas se rendre dépendant d’une coutume
Affichage particuliers
Pas que de l’affichage :
Ne pas le prévoir, c’est s’attendre à beaucoup de refactoring
L’ajout d’une Locale doit rester simple
Mettre toutes les règles dans un fichier
Ne parlez qu’aux gens que vous connaissez
Eviter l’effet tunnel de l’appel de composants
Que faire en cas d’évolutions de la classe Etudiant
?
e.g. Ce n’est plus une liste mais une map clé/valeur
Comment gérer les null-check ? Les exceptions ?
Fournir des méthodes qui vont, de proche en proche, récupérer l’information souhaitée