Intégration: dépasser la peur de l'échec
La période d’intégration dure 5 semaines chez Marmelab. C’est une formation accélérée sur les méthodes de travail et les techniques utilisées au sein de l’entreprise. Elle est éprouvante mais enrichissante. Mais cette période m’a aussi inculqué une philosophie : l’échec fait partie de notre métier, et nous devons l’accepter pour avancer.
Cet article a pour but de mettre en lumière les différents échecs rencontrés, ce qu'ils nous ont appris, et comment nous les avons surmontés.
Nouveau poste : un saut dans l'inconnu ?
Pour placer le contexte de mon arrivée chez Marmelab, je suis un développeur junior en poste depuis plus d'un an dans une boite "tranquille". Cependant, je cherche à évoluer et apprendre de personnes plus expérimentées.
Suite à un tweet, je vois que Marmelab cherche à agrandir son équipe. Mais pour moi, c’était une boîte “inaccessible” vu mon niveau. En en parlant autour de moi, on me conseille quand même de postuler. Qui ne tente rien n’a rien.
Et là François me répond et me propose un entretien. Super !
Suite à cet entretien, il me donne une réponse favorable, chouette !
Ça veut dire démissionner de mon poste actuel et risquer de ne pas réussir la période d’intégration. Mais bon pour progresser je suis prêt à prendre ce risque.
Je me rends rapidement compte que ce ne sera pas ma dernière prise de risque chez Marmelab...
Au menu de mon intégration
Comme dit plus haut et expliqué dans l'article sur l'intégration agile, la période d'intégration Marmelab dure 5 semaines. Au travers d'un jeu fil rouge, l'intégration amène les développeurs à se placer dans une démarche d'innovation. Pour cela, les développeurs se retrouvent de façon répétée face à l'inconnu et sont forcés de prendre des risques - et donc, parfois, rencontrent l'échec.
Petite nouveauté, nous sommes deux à la passer en même temps. Je vais donc travailler en duo avec Alexandre pour programmer un jeu qui se nomme Kuba.
Kuba est un jeu de plateau où le principe est de déplacer des billes et de pousser les billes de son adversaire en dehors du plateau.
A chaque semaine d'intégration, nous devons atteindre un objectif différent :
- Un CLI (Command Line Interface) en Node
- Une API en NestJS
- Une interface d'administration via React-Admin
- Une application mobile en React-native
- Un travail sur l'optimisation de l'ergonomie de l'application mobile.
Nous n'avons qu'un vernis de connaissances sur les technologies à employer, et nous découvrons le jeu le premier jour. Voilà pour le saut dans l'inconnu !
L'exercice de l'estimation
Chaque semaine commence par un Poker Planning. C'est une réunion où nous devons estimer la difficulté et la durée des tâches que nous allons réaliser dans la semaine.
On pourrait se dire que l'équipe de Marmelab nous a prémâché le travail. Mais le Poker Planning est une étape cruciale dans notre processus d'intégration. Il sert à questionner le développeur sur les difficultés qu'il pourra rencontrer et à voir les points auquels le client n'a pas pensé.
A trop voir cette réunion comme une formalité, on passe sur des complexités cachées, et on sous-estime les cartes. Nous en faisons l'amère expérience.
Pour le premier sprint, c'est un échec. Nous avons accumulé plus de 2 jours de retard par rapport à nos estimations. Nous n'avons pas su analyser correctement les tickets pour y déceler des difficultés.
L'une de ces difficultés était la modélisation du jeu.
L'importance du modèle de données
Au début, nous avons décidé de modéliser le jeu Kuba avec un tableau en deux dimensions. Les exemples ci-dessous sont donnés sur la base d'un plateau de 3x3 pour simplifier (le jeu de Kuba se passe en réalité sur un plateau de 7x7).
// 0 = empty slot
// 1 = Player 1's marble
// 2 = neutral marble
// 3 = Player 2's marble
const INITIAL_BOARD: Board = [
[1, 0, 3],
[0, 2, 0],
[1, 0, 3],
];
Dès que nous proposons cette solution, on nous alerte sur le fait que cette représentation a des limites. Mais nous nous sentons assez à l'aise avec ce choix.
Très vite les difficultés s'accumulent. Pour détecter les cases adjacentes dans un tableau de tableaux, on manipule des i+1
et des j-1
et le code devient rapidement illisible. Quand le plateau de jeu va grandir, comment allons-nous calculer les coups possibles facilement ?
On nous aiguille donc sur la modélisation en forme de graphe. L'idée est de représenter chaque case par un nœud du graphe avec comme clef sa position. Chaque noeud ("node") est relié par des liens ("edges") qui nous permettent de savoir dans quel sens se déplacer.
const graph: Graph = {
nodes: {
"0,0": { x: 0, y: 0, value: 1 },
"1,0": { x: 1, y: 0, value: 0 },
"2,0": { x: 2, y: 0, value: 3 },
"0,1": { x: 0, y: 1, value: 0 },
"1,1": { x: 1, y: 1, value: 2 },
"2,1": { x: 2, y: 1, value: 0 },
"0,2": { x: 0, y: 2, value: 1 },
"1,2": { x: 1, y: 2, value: 0 },
"2,2": { x: 2, y: 2, value: 3 },
},
edges: [
{ from: "0,0", to: "0,1", direction: "S" },
{ from: "0,0", to: "1,0", direction: "E" },
{ from: "0,1", to: "0,0", direction: "N" },
{ from: "0,1", to: "0,2", direction: "S" },
{ from: "0,1", to: "1,1", direction: "E" },
{ from: "0,2", to: "0,1", direction: "N" },
{ from: "0,2", to: "1,2", direction: "E" },
{ from: "1,0", to: "0,0", direction: "W" },
{ from: "1,0", to: "1,1", direction: "S" },
{ from: "1,0", to: "2,0", direction: "E" },
{ from: "1,1", to: "0,1", direction: "W" },
{ from: "1,1", to: "1,0", direction: "N" },
{ from: "1,1", to: "1,2", direction: "S" },
{ from: "1,1", to: "2,1", direction: "E" },
{ from: "1,2", to: "0,2", direction: "W" },
{ from: "1,2", to: "1,1", direction: "N" },
{ from: "1,2", to: "2,2", direction: "E" },
{ from: "2,0", to: "1,0", direction: "W" },
{ from: "2,0", to: "2,1", direction: "S" },
{ from: "2,1", to: "1,1", direction: "W" },
{ from: "2,1", to: "2,0", direction: "N" },
{ from: "2,1", to: "2,2", direction: "S" },
{ from: "2,2", to: "1,2", direction: "W" },
{ from: "2,2", to: "2,1", direction: "N" },
],
};
Ca a l'air mais compliqué, mais d'un coup, le code devient bien plus simple. Déplacer un pion dans une direction revient à chercher le lien où il apparait en from
, et qui a la bonne direction
, et on trouve directement le noeud cible dans to
. Chaque coup ne nécessite donc qu'un parcours du tableau des liens.
Sortir un pion du plateau revient à le déplacer vers un noeud extérieur - qu'il faudra donc ajouter au modèle. La logique de jeu se reflète aussi plus facilement dans le code, qui devient plus testable, plus maintenable.
Après une semaine de travail, et après l'ajout de l'interactivité avec blessed (que je détaille dans un autre article), nous arrivons à présenter une démo à notre client.
Ca marche sur mon poste
Pour la deuxième semaine de notre intégration, nous réalisons une API REST. C'est une interface permettant l’interaction entre différentes applications. Dans notre cas, elle va permetre à deux joueurs de jouer sur un ordinateur différent et aussi à distance.
Coté technique, je découvre le framework NestJS. C'est assez verbeux, mais une fois pris en main, il est facile de comprendre ce que l'on fait.
Par exemple pour créer une partie, nous créons un GameController
qui va appeler une fonction de notre GameService
. Le tout va être sécurisé par @UseGuards
qui va nous permettre de vérifier si un token envoyé à l'API est autorisé à communiquer avec.
La philosophie de Nest ressemble beaucoup à celle d'Angular de par son architecture découpable en module :
@UseGuards(JwtAuthGuard)
@Controller('games')
export class GameController {
constructor(
private readonly gameService: GameService,
) {}
@Post('')
async createGame(): Promise<Game> {
return await this.gameService.createGame();
}
}
Vient maintenant la partie déploiement de notre API. Et là, les choix réalisés vont engendrer de nombreux problèmes qui vont nous suivre pendant les sprints suivants.
Le choix a été de déployer sur une instance EC2 chez AWS. Nous décidons aussi de mettre en place Cloudfront pour distribuer notre EC2 (ce point aura son importance).
Mis à part l'interface d'AWS dure à comprendre, pour l'instant pas de soucis et je décide d'automatiser la mise en production avec un Makefile
. Notre CLI arrive bien à communiquer avec notre API de production, c'est l'heure de la démonstration au client, elle se passe sans accroche. Mais c'est maintenant que les problèmes arrivent.
Pour la troisième semaine, nous implémentons une application d'administration de notre jeu. Nous utilisons React-admin pour cela. Je ne vais pas rentrer dans les détails. La mise en place et l'utilisation se font très simplement. Il suffit de suivre la documentation .
Sur notre environnement de développement la liaison entre notre administration et notre API fonctionne parfaitement. Nous sommes vers la fin du sprint, c'est le moment de déployer en production. L'application d'administration est hébergée sur un S3 d'AWS.
Vous connaissez le principe de CORS ? Moi pas du tout. Je suis donc surpris quand je me rends compte que la communication entre mon application et l'API en production ne fonctionne pas. Pourtant, elle fonctionne en local !!
Après avoir demandé un petit cours sur cette notion à François et regardé une vidéo sur le sujet, je repars à l'attaque. Avec NetsJS il est très facile de paramétrer et gérer le CORS :
const app = await NestFactory.create(AppModule, {
cors: {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
allowedHeaders: ['content-type', 'authorization']
},
});
De l'importance des démos
On arrive à la fin du sprint 3, nous sommes vendredi et la démonstration au client est dans 4h. Avec Alexandre nous mettons notre travail en commun. On effectue les derniers tests dans notre environnement de dev et une fois satisfaits de notre travail, nous publions en production.
Cependant, notre API de production ne fait que retourner des erreurs 401: unauthorized
à notre administration.
On pense que notre erreur provient du Guard
utilisé coté API. On enquête.
Rien....
L'heure arrive et notre production ne fonctionne pas. Nous décidons de faire la démonstration sur notre environnement de développement. Grave erreur.
Cette démo et les remarques qui en découlent sont durs pour moi à encaisser. Mon égo est touché, je n'ai pas réussi à atteindre les objectifs demandés. Mais grâce à cette expérience, on a pu changer notre façon de faire avec Alexandre.
Fini les mises en production au dernier moment. Sur les sprints qui suivront, dès qu'une fonctionnalité importante est développée et validée, un de nous deux s'occupe de la publier directement en production. Toutes les démos qui suivront seront bien réalisées en production et avec des fonctionnalités finies.
Pour revenir sur l'erreur 401, il s'agissait de notre CloudFront qui ne transmettait pas le header "Authorization" à notre EC2.
Make it work, first
Pour les deux derniers sprints, l'objectif est de réaliser une application mobile qui soit agréable à jouer pour nos utilisateurs.
Concernant les technologies, nous utilisons React-native ainsi que Expo. Cet écosystème nous permet de tester et de publier facilement une application multiplateforme.
Je ne vais pas m'étaler sur le côté technique, je peux cependant dire que durant ces deux semaines, je vais beaucoup apprendre notamment sur le concept des Hooks.
Au bout d'une semaine, nous arrivons à une version jouable. À chaque ajout de fonctionnalités, nous avons réalisé une application de production, pour être certain d'avoir quelque chose de montrable le jour de la démo.
Mais cette version n'est pas animée et les contrôles ne sont pas très ergonomiques. Nous avons pour objectif de pouvoir bouger les billes via un drag and drop. Avec Alexandre on se répartit le travail, je m'occupe de l'animation, pendant qu'il implémente le drag.
Cependant, des obstacles vont apparaitre. La veille de la démonstration, la fonctionnalité du drag est développée, mais celle-ci présente des bugs qui "cassent" l'expérience utilisateur. Notamment quand ce développement est couplé à celui des animations.
Après concertation avec Alex, on décide de développer rapidement une fonctionnalité qui sera plus simple à mettre en place. On place une croix directionnelle autour de la bille sélectionné par le joueur. Elle n'a pas l'effet "Waouh" du drag, mais nous permet au moins d'avoir une ergonomie agréable. C'est donc confiant que nous arrivons à la dernière démonstration de notre intégration. Notre application est en production, elle fonctionne et propose une ergonomie agréable.
Vous pouvez retrouver le code sur ce répertoire Github
Conclusion
Les 5 semaines de mon intégration ont été très intenses. La pression de l'exercice est réelle, mais l'expérience en vaut la peine. De mon point de vue de développeur junior, j'ai plus appris en 5 semaines qu'en 5 ans de vie professionnelle. Marmelab arrive à transmettre ses valeurs durant cette période.
Mais si je ne devais retenir qu'un point, c'est qu'en aucun cas l'échec n'est une mauvaise chose. C'est une manière d'apprendre !
Il ne faut pas en avoir peur.