Catégorie « Développement logiciel »

Ce matin, je me suis dit que comme parmi mon groupe d'amis j'étais seul connecté Guild Wars 2, j'allais faire mes objectifs quotidiens JcJ. Là j'ai constaté que quelque chose avait changé sans figurer dans les notes de mises à jour (ou alors je l'ai raté) : on ne peut plus choisir quelle équipe on rejoint lorsqu'on rejoint une partie. Sur le principe je comprends assez bien qu'il s'agit d'empêcher le joueur qui rejoint la partie de se mettre dans l'équipe qui a le plus de points, la renforçant du coup et creusant l'écart, ce qui semblerait assez raisonnable. Sauf que du coup ça rend encore plus saillant un gros vice de conception sur lequel je pensais écrire un article depuis un moment, sans prendre le temps de le faire : un gros foirage dans l'utilisation des couleurs.

Comme dans beaucoup de jeux vidéos, des codes couleurs permettent de différencier les alliés des ennemis. En l'occurrence, tout ce qui est allié est indiqué dans une nuance de vert ou bleu et ce qui est ennemi en rouge (plus du blanc ou jaune pour les non-hostiles mais attaquables). Tout ça marche très bien lorsqu'on joue en JcE où tout est clair dans l'ensemble. Par contre ça se corse grandement quand on passe en JcJ. Dans ce dernier mode, deux équipes de joueurs s'affrontent avec divers objectifs, chaque équipe étant identifiée par une couleur. Et fort logiquement, ces couleurs sont... le bleu et le rouge !

Normal me direz vous, comme ça c'est cohérent avec le reste. Oui mais non. Parce que le bleu identifie l'une des équipes mais pas forcément la votre. Vous pouvez très bien vous retrouver dans l'équipe rouge. En principe, à ce stade vous devriez commencer à saisir le problème mais pour bien préciser les choses, voici ce que ça donne dans le cas de l'activité "Bagarre de barils" (où des barils de bière sont balancés périodiquement au milieu de la carte, l'équipe gagnante étant celle qui en aura rapporté le plus dans sa "base") :

Mélange des couleurs
Mélange des couleurs

Je ne sais pas pour vous mais quand je vois ça, j'ai toujours du mal à bien cerner ce qui représente mon équipe et ce qui représente l'équipe adverse...

J'ai beau tourner ça dans tous les sens, je n'arrive pas à comprendre comment ils en sont arrivés là. Parce qu'on ne parle pas d'un petit projet de fin d'étude, là, on parle d'un des MMORPG les plus joués du moment, vendu à plusieurs millions d'exemplaires, d'un jeu qui a pris des années à développer par une équipe conséquente, d'un jeu sorti depuis maintenant bientôt un an et demi et qui est régulièrement mis à jour.

Dans ces conditions il semble impensable qu'il n'y ait pas un jour un des membre de l'équipe qui ait lancé une phrase du style "Dites, je pense à un truc là, on aurait pas un problème avec les codes couleurs ?". Et franchement, j'aimerais vraiment savoir ce qu'ont bien pu répondre les autres pour qu'au final la décision soit prise de laisser les choses en l'état. Parce que là, je dis chapeau, justifier un truc aussi absurde, c'est une belle performance !

Et en attendant je vais arrêter de jouer en JcJ parce qu'autant avant je faisais en sorte de toujours être dans l'équipe bleue histoire d'avoir un truc cohérent, autant là vu qu'on peut plus choisir...

EDIT 21/12/2013 : Bon apparemment la suppression du choix de l'équipe ne devait être qu'un bug, parce qu'il a été rétabli depuis... Donc ça redevient jouable. Mais ça ne change rien au problème initial des codes couleur !


Plus on enlève de code, mieux ça marche

Encore récemment, dans le cadre du chiffrage d'une migration d'un projet vers la dernière version d'RBS Change (CMS / e-commece dont j'ai déjà parlé plusieurs fois ici), l'un des développeurs disait en parlant de certaines fonctionnalités développées sur le projet en spécifique et qui entre temps ont été implémentées dans le produit que maintenant que le code spécifique était écrit, ça ne coûtait pas cher de le garder tel quel plutôt que de prendre le temps de le remplacer par des appels au code du produit.

À part si un besoin spécifique n'est pas compatible avec le code du produit, je suis intimement convaincu que c'est faux. Et ce pour un certain nombre de raisons.

Le point le plus évident pour moi c'est le coût en maintenance. S'il y a bien une chose que j'ai appris en travaillant sur un logiciel qui fait plusieurs centaines de milliers de lignes de code, c'est que plus on a de code, plus c'est couteux à maintenir. D'une part parce que chaque ligne ajoutée peut comporter des bugs ou s'avérer incompatible avec d'autres parties du logiciel et d'autre part parce que plus il y a de code, plus il est difficile de retrouver la source d'un problème. C'est d'autant plus vrai si l'équipe chargée du projet change. Quand le développeur est le même pendant des années, il peut connaitre assez bien son code pour s'y retrouver parmi les implémentations parallèles (et encore... que celui qui ne s'est jamais senti perdu en se replongeant dans du code qu'il avait écrit rien qu'un an plus tôt lève la main) mais un nouvel arrivant sur le projet mettra beaucoup plus de temps à s'y retrouver s'il doit apprendre à connaitre les implémentations parallèles en plus du produit lui-même.

On en arrive du coup à un second point connexe au précédent : l'évolutivité. Naïvement on se dit que vu que la fonctionnalité est codée spécifiquement, on peut faire ce qu'on veut avec et donc on est bien plus libre qu'en utilisant une fonctionnalité native du produit sur laquelle on n'aura pas autant la main. Ce n'est pas faux. Mais cela implique de se couper en partie des évolutions du produit et de devoir tout faire soi-même de son côté. De plus, se pose le même problème qu'évoqué précédemment où tout nouvel arrivant devra oublier ce qu'il sait déjà du produit pour apprendre ce que fait le projet.

Ensuite on a les coûts d'interface utilisateur et d'apprentissage. De deux choses l'une : soit on laisse les deux implémentations parallèles accessibles dans l'interface et là c'est l'utilisateur qui se sentira perdu, ne sachant quoi choisir (ce qui implique du coût de formation et de réparation de ce que l'utilisateur aura mal fait), soit on doit masquer de l'interface les éléments relatifs à l'implémentation standard pour les remplacer par l'implémentation spécifique (ce qui implique un coût initial plus un coût à chaque mise à jour pour revalider les choses et les réadapter si besoin).

Après on peut avoir du mal à jeter le produit de nombreuses heures de développement. C'est normal mais pour un développeur c'est une chose à laquelle il faut s'habituer. Un logiciel qui n'évolue pas, à part s'il est extrêmement ciblé sur un besoin très pointu qui n'évolue pas du tout (chose très rare), c'est un logiciel mort. Un logiciel vivant évolue continuellement au gré des nouveaux besoins, des nouvelles possibilités et des nouvelles idées. Seulement on ne peut pas se contenter d'empiler de nouvelles choses dessus, sous peine de voir l'ensemble s'effondrer sous sa complexité, de devenir inmaintenable et incompréhensible au nouvel arrivant.

Des choses qui semblaient - et potentiellement étaient réellement - pertinentes à un moment donné ne le seront plus un an plus tard parce que le besoin aura évolué ou simplement parce qu'à force d'y greffer des verrues, on aboutit à un ensemble qui ne ressemble plus à rien. Il ne faut donc pas hésiter à remplacer des fois des pans entiers du logiciel pour repartir sur des bases plus saines. Qui elles-mêmes dégénèreront plus ou moins rapidement (selon la qualité de l’implémentation et la vitesse à laquelle les besoins liés évoluent) avant d'être à leur tour remplacées à nouveau.

Ce principe vaut aussi bien au niveau macroscopique (une API entière finit par devenir trop lourde et doit être remplacée) qu'au niveau microscopique (une méthode donnée peut souvent être réécrite en cinq fois moins de code parce qu'elle prévoyait des cas qui n'existent plus ou bien parce qu'à force de refactoring, le code se répète trop). Il faut toutefois prendre garde à ne pas sauter trop vite aux conclusions et tout réécrire continuellement, sans quoi d'une part on n'avance plus et d'autre part, à aller trop vite, on passe à côté de subtilités qui ne sautent pas aux yeux sans examen approfondi (pour ce dernier point, des tests unitaires ou autres peuvent aider à éviter les régressions, encore faut-il avoir le temps ou simplement prendre le temps de les mettre en place).

Comme souvent il s'agit de trouver un juste milieu entre tout réécrire et conserver à tout prix l'existant. Mais quand la réécriture consiste à utiliser quelque chose qui existe par ailleurs et qu'on n'aura donc pas à maintenir soi-même, je pense qu'il n'y a pas à hésiter : si fonctionnellement c'est compatible, ça vaut le coup de jeter le spécifique pour utiliser du natif.

Voilà voilà, félicitations si vous avez tout lu jusqu'ici et n'hésitez pas à réagir dans les commentaires si vous avez quelque chose à ajouter sur le sujet ou des objections à formuler :)