Archives mensuelles : septembre 2019

Aparté – Vol au dessus d’un service informatique

Voilà quelques années j’étais en poste dans une entreprise qui avait commandé un audit à un cabinet d’expert afin de procéder à une réorganisation. Le but était fort louable : améliorer les flux d’information entre les services pour que les projets informatiques soient livrés à temps, conformes aux spécifications et sans bugs. A cette occasion, j’avais rédigé un petit mémo à l’intention des auditeurs…Il est finalement resté sur un disque dur et je ne suis retombé dessus que tout récemment.

Vous reconnaîtrez certainement des mauvaises habitudes qui ont cours dans votre entreprise car ces maux là sont hélas génériques !

Chronique d’un échec annoncé

Tout d’abord j’évoquais dans ce document le passé du service…j’aurais pu tout aussi bien parler de PASSIF !

  • L’équipe précédente, composée de 2 à 3 personnes, avait développé à la hâte un site truffé de bugs, elle avait été intégralement remerciée par la direction et nous avions été recrutés pour colmater les brèches et éteindre les incendies (XSS, injections SQL, mots de passes en clair et j’en passe) qui se déclaraient sur le site au fur et à mesure que nous découvrions le code écrit (rien n’était documenté).
  • Les délais de développement étaient sous-estimés pour faire plaisir aux services concernés. On appelle ça « vendre du rêve ». Le rêve est vite déçu.

La conséquence de tout ça a été la totale absence de crédibilité des développeurs qui passaient grosso modo pour un ramassis de fumistes incompétents. Parfois un « Pourquoi ça prend si longtemps ? La précédente équipe le faisait en 10 secondes ! » fusait, signifiant que le chemin était encore long vers la compréhension du fonctionnement normal d’un service informatique. Il fallait alors répondre calmement « Certes, mais vous avez vu ce que ça a donné ! » pour obtenir un silence gêné et contrit.

Les origines de la démotivation ou comment saborder un service en quelques leçons

  • Le pair programming était la règle, les binômes changeaient toutes les deux semaines…trop court !
  • Embauchés pour faire du Symfony 2, les développeurs se retrouvaient à corriger des bugs en PHP pur ou des requêtes SQL à longueur de journée. « Bientôt vous passerez sur des nouveaux projets ! ». Mais oui…
  • « Il est urgent d’attendre » était la règle: « Attendez l’arrivée du nouveau DSI ! », « Attendez la réorg’ ! »…Le feu ravageait le front, les projets naviguaient à vue et il fallait attendre sans cesse.
  • Les ambitions mais surtout les compétences des développeurs étaient pour le moins variées…certains refusaient de se remettre en question techniquement et livraient du junk code par bennes entières, ces trashers hermétiques à l’évolution nuisaient grandement à la qualité du code produit et les bons en avaient marre de payer pour les dilettantes…
  • Certains développeurs ont tenté d’introduire le TDD, SCRUM, les tests unitaires et fonctionnels. On leur a gentiment fait comprendre que c’était sympa tout ça mais qu’on avait pas le temps pour le moment. Mais promis plus tard on s’y intéressera…

Chacun sa place

  • Pourquoi le marketing continue de venir voir en douce les développeurs alors que des chefs de projets ont été embauchés ?
  • Pourquoi les specs sont-elles encore dictées à la machine à café, sur un coin de table ou entre deux portes alors que des process ont été mis en place, souvent après de longues négociations avec toutes les parties concernées ? L’impatience, le syndrome du gamin frustré qui n’a pas ce qu’il veut dans la seconde…
  • Les 320 allers-retours entre le marketing et le service info démotive les développeurs, qui sont bien incapables de dire quelle tâche ils auront à effectuer le lendemain, ni même si le projet sera poursuivi ou abandonné (combien de dizaines de milliers d’euros ont été foutus à la poubelle parce que finalement « ce projet n’est plus prioritaire » ?)
  • Les demandeurs pensent QUANTITÉ, les développeurs aimeraient faire de la QUALITÉ ! Ils se sentiraient bien mieux et auraient davantage envie de s’impliquer !

Vous reprendrez bien du GIGO (Garbage In, Garbage Out) ?

Traduction française : On ne récolte que ce que l’on sème ! Des spécifications floues donnent du code bancal et donc difficile à maintenir et encore plus à faire évoluer. On ne perd pas son temps en rédigeant une demande claire, on perd 10 fois plus de temps quand il faut réusiner un plat de spaghetti que même Chuck Norris ne saurait démêler.

Quelques portes ouvertes à enfoncer :

  • Coder c’est cool, tester c’est encore mieux ! Il nous est arrivé de tester en Prod ! Pourquoi ?
  • Mal spécifier c’est faire perdre du temps et donc de l’argent à sa boîte ! C’est provoquer de nombreux aller-retours qui font passer chaque service pour un ramassis de clowns
  • Le changement est bien mieux encaissé par les développeurs quand il n’a pas lieu 10 fois par jour !
  • Un interlocuteur et pas 25 ! Pourquoi Tic vient 5 minutes après Tac dire tout le contraire de ce sur quoi nous nous sommes mis d’accord après d’âpres négociations ?

MongoDB – lookup et l’agrégation

Cette étape de recherche (lookup) permet d’effectuer l’équivalent d’une jointure externe dans un système de gestion de bases de données relationnelles (SGBDR) et ne peut opérer que sur des collections non shardées situées dans la même base de données.
Look up

Syntaxe de lookup

{
    $lookup:
    {
        from: < collection à joindre >,
        localField: < champ dans les documents de la collection de départ >,
        foreignField: < champ dans les documents de la collection à joindre>,
        as: < nom du tableau qui sera ajouté aux documents du jeu de résultat >
    }
}

Dans chacun des documents reçus en entrée, l’étape $lookup va rajouter un tableau contenant des données en provenance de la collection sur laquelle la jointure a été requise avant de passer ces documents à la prochaine étape du pipeline le cas échéant. Le terme de jointure n’est pas toujours très approprié notamment parce qu’il est possible d’effectuer des requêtes décorrélées, c’est à dire sans clause d’égalité.

Afin de voir $lookup en situation, nous allons utiliser trois collections qui stockeront des élèves, leurs devoirs individuels et les projets sur lesquels ils travaillent à plusieurs.

Elèves, au travail !

Les travaux individuels

Commençons par les travaux individuels de nos élèves, voici les collections impliquées, aucun index n’a été mis pour garder les choses les plus simples possibles mais évidemment qu’il faudra y songer !

db.eleves.insertMany([
    {"nom": "Sébastien Ferrandez", "code": "NAT123"},
    {"nom": "Evelyne Durand", "code": "NAT125"},
    {"nom": "Christian Ton", "code": "NAT120"},
    {"nom": "Claire Annela", "code": "NAT127"}
])

db.devoirs.insertMany([
    {"code": "NAT123", "matiere": "SVT", "note": 12},
    {"code": "NAT123", "matiere": "Maths", "note": 10},
    {"code": "NAT125", "matiere": "Maths", "note": 11.75},
    {"code": "NAT120", "matiere": "Français", "note": 18},
    {"code": "NAT127", "matiere": "Latin", "note": 19}
])

Nous allons partir d’eleves pour aller vers devoirs; dans les deux cas notre nom de champ de « jointure » est code:

db.eleves.aggregate([
   {
     $lookup: {
         "from": "devoirs",
         "localField": "code",
         "foreignField": "code",
         "as": "detail_notes"
     }
   }
])

Voici le résultat produit par ce pipeline, il est assez difficilement lisible mais nous avons l’idée générale : les notes sont « raccrochées » à l’élève !

{
	"_id": ObjectId("5d6e6115d9a18feb0291b605"),
	"nom": "Sébastien Ferrandez",
	"code": "NAT123",
	"detail_notes": [{
		"_id": ObjectId("5d6e6174d9a18feb0291b60e"),
		"code": "NAT123",
		"matiere": "SVT",
		"note": 12
	}, {
		"_id": ObjectId("5d6e6174d9a18feb0291b60f"),
		"code": "NAT123",
		"matiere": "Maths",
		"note": 10
	}]
} {
	"_id": ObjectId("5d6e6115d9a18feb0291b606"),
	"nom": "Evelyne Durand",
	"code": "NAT125",
	"detail_notes": [{
		"_id": ObjectId("5d6e6174d9a18feb0291b610"),
		"code": "NAT125",
		"matiere": "Maths",
		"note": 11.75
	}]
} {
	"_id": ObjectId("5d6e6115d9a18feb0291b607"),
	"nom": "Christian Ton",
	"code": "NAT120",
	"detail_notes": [{
		"_id": ObjectId("5d6e6174d9a18feb0291b611"),
		"code": "NAT120",
		"matiere": "Français",
		"note": 18
	}]
} {
	"_id": ObjectId("5d6e6115d9a18feb0291b608"),
	"nom": "Claire Annela",
	"code": "NAT127",
	"detail_notes": [{
		"_id": ObjectId("5d6e6174d9a18feb0291b612"),
		"code": "NAT127",
		"matiere": "Latin",
		"note": 19
	}]
}

Ajoutons une étape project pour rendre cet affichage plus « digeste »…Nous éliminons les id et les champs redondants:

db.eleves.aggregate([
   {
     $lookup: {
         "from": "devoirs",
         "localField": "code",
         "foreignField": "code",
         "as": "detail_notes"
     }
   },
   {
     $project: {
         "_id": 0,
         "detail_notes._id": 0,
         "detail_notes.code": 0
     }
   }
])

Voilà qui est nettement mieux:

{
	"nom": "Sébastien Ferrandez",
	"code": "NAT123",
	"detail_notes": [{
		"matiere": "SVT",
		"note": 12
	}, {
		"matiere": "Maths",
		"note": 10
	}]
} {
	"nom": "Evelyne Durand",
	"code": "NAT125",
	"detail_notes": [{
		"matiere": "Maths",
		"note": 11.75
	}]
} {
	"nom": "Christian Ton",
	"code": "NAT120",
	"detail_notes": [{
		"matiere": "Français",
		"note": 18
	}]
} {
	"nom": "Claire Annela",
	"code": "NAT127",
	"detail_notes": [{
		"matiere": "Latin",
		"note": 19
	}]
}

Les projets

Voici notre collection projets, elle contient les codes des élèves impliqués ainsi que la note obtenue:

db.projets.insertMany([
    {"codes": ["NAT123", "NAT125"], "matiere": "Dessin", "note": 15}
])

Le champ codes est un tableau ici, pour que chaque élève puisse récupérer sa note, il va falloir éclater ce tableau à l’aide d’unwind avant d’utiliser lookup dessus, nous allons partir de projets cette fois:

db.projets.aggregate([
   {
      $unwind: "$codes"
   },
   {
     $lookup: {
         "from": "eleves",
         "localField": "codes",
         "foreignField": "code",
         "as": "eleve"
     }
   },
   {
     $project: {
         "_id": 0,
         "codes": 0,
         "eleve._id": 0
     }
   }
])

Voilà le jeu de résultat lié:

{
	"matiere": "Dessin",
	"note": 15,
	"eleve": [{
		"nom": "Sébastien Ferrandez",
		"code": "NAT123"
	}]
} {
	"matiere": "Dessin",
	"note": 15,
	"eleve": [{
		"nom": "Evelyne Durand",
		"code": "NAT125"
	}]
}

Nous allons rajouter une étape addFields pour que le tableau contenant un seul document (eleve) devienne un document tout simple, voici le pipeline final:

db.projets.aggregate([
   {
      $unwind: "$codes"
   },
   {
     $lookup: {
         "from": "eleves",
         "localField": "codes",
         "foreignField": "code",
         "as": "eleve"
     }
   },
   {
     $addFields: {
       "eleve": { $arrayElemAt: ["$eleve", 0] }
     }
   },
   {
     $project: {
         "_id": 0,
         "codes": 0,
         "eleve._id": 0
     }
   }
])

Et voilà l’travail !

{
	"matiere": "Dessin",
	"note": 15,
	"eleve": {
		"nom": "Sébastien Ferrandez",
		"code": "NAT123"
	}
} {
	"matiere": "Dessin",
	"note": 15,
	"eleve": {
		"nom": "Evelyne Durand",
		"code": "NAT125"
	}
}