Cours 13¶
Réusinage (Refactoring)¶
Le refactoring consiste à améliorer le code existant sans changer son comportement. C’est un processus essentiel pour maintenir la qualité du logiciel, réduire la dette technique et faciliter les futures évolutions 1. Ça vise à améliorer la lisibilité du code, sa maintenabilité et sa flexibilité.
- Lisibilité : Un code lisible est facile à comprendre autant par l'auteur que ceux qui ne le sont pas. Cela implique l’utilisation de noms de variables et de fonctions explicites, une indentation cohérente, des commentaires pertinents et une structure logique.
- Maintenabilité : La maintenabilité désigne la facilité avec laquelle un code peut être modifié pour corriger des bogues, ajouter de nouvelles fonctionnalités ou améliorer les performances. Un code maintenable est bien structuré, modulaire et suit des conventions de codage établies.
- Flexibilité : La flexibilité est la capacité d’une application à s’adapter aux changements, que ce soit par l’ajout, la modification ou la suppression de fonctionnalités, sans compromettre le système existant.
Pour guider le refactoring, plusieurs principes et méthodologies offrent des bonnes pratiques. Découvrons-les !
YAGNI¶
Le principe YAGNI (You Aren’t Gonna Need It2) encourage à ne pas coder ce qui n’est pas immédiatement nécessaire! Lorsqu’un développeur propose d’ajouter une fonctionnalité « au cas où », il est préférable de s’en tenir aux besoins actuels.
MoSCoW¶
MoSCoW (Must, Should, Could, Won’t3) est un méthode de priorisation des tâches en fonction de leur importance :
- Must : Indispensable
- Should : Important, mais pas critique
- Could : Optionnel
- Won’t : À exclure pour l’instant
DRY¶
DRY (Don't Repeat Yourself4) est une philosophie en programmation qui veut qu'on ne duplique aucune information ni logique. Identifiez des portions de code similaires et regrouper les dans une fonction.
KISS¶
Le principe KISS (Keep It Simple, Stupid5) favorise la simplicité dans le code. Un design complexe est plus difficile à comprendre et à maintenir.
Optimisation des médias¶
Toutes les images devraient être redimensionnées à la taille de leur utilisation. Vous pouvez utiliser votre logiciel d'édition graphique préféré ou ImageResizer.com.
Ensuite, on pour les optimiser en utilisant des outils comme TinyPNG.
Hygiène de code¶
Variables¶
Bien
Utilisez des noms de variables et de fonctions clairs et significatifs qui reflètent leur rôle ou leur contenu.
let nombreViesJoueur = 3;
À éviter
Évitez les noms vagues ou abrégés qui ne donnent pas d’indication sur leur utilité.
let nv = 3;
Fonctions¶
Bien
Écrivez des fonctions qui accomplissent une seule tâche spécifique, facilitant ainsi la réutilisation.
function calculerScore(niveau, temps) {
return niveau * 1000 - temps * 10;
}
À éviter
Évitez les fonctions longues qui réalisent plusieurs tâches distinctes.
function gererNiveau() {
// initialise le niveau
// met à jour le score
// gère les ennemis
}
Le saviez-vous ?
Dans vscode, vous pouvez afficher la structure de votre code en dépliant la section structure de la colonne de droite (juste en bas de votre structure de projet).
Consistance¶
Votre code doit être prévisible.
Bien
Maintenez une cohérence dans le style de codage, les conventions de nommage et la structure du code à travers tout le projet.
let joueur = {
positionX: 100,
positionY: 200,
vitesse: 5
};
let ennemi = {
positionX: 300,
positionY: 400,
vitesse: 3
};
À éviter
Évitez de mélanger différents styles ou conventions dans le même projet.
let joueur = {
posX: 100,
posY: 200,
speed: 5
};
let ennemi = { position_x: 300, position_x: 400, vitesse: 3 };
Commentaires¶
Grace aux commentaires, on devrait être en mesure de comprendre le code sans le lire, mais attention, n'en faites pas trop. Tout est dans l'équilibre.
Le code explique le « comment », les commentaires expliquent le « pourquoi ».6
Bien
Ajoutez des commentaires uniquement lorsque le code n’est pas suffisamment explicite, en évitant les commentaires redondants ou évidents.
// Utilisation d'un pool d'objets pour les projectiles afin d'améliorer les performances
function tirerProjectile() {
let projectile = poolObtenu();
if (!projectile) {
// Si aucun projectile n'est disponible dans le pool, en créer un nouveau
projectile = creerNouveauProjectile();
}
// Initialiser et activer le projectile
initialiserProjectile(projectile);
activerProjectile(projectile);
}
À éviter
Évitez les commentaires inutiles qui répètent ce que le code exprime déjà clairement.
// Décrémenter le nombre de vies du joueur de 1
joueur.vies -= 1;
// Si le nombre de vies du joueur est égal à zéro
if (joueur.vies === 0) {
// Appeler la fonction de fin de jeu
finDeJeu();
}
Nombres magiques ¶
Lorsqu'on utilise des nombres dans le code plutôt que dans des variables ou constantes, on dit que la valeur est « hardcoded ». Le problème est-ce que pour modifier des paramètres de l'application, il faut comprendre le code, ce qui ne devrait pas etre le cas.
Bien
Remplacez les valeurs numériques non explicites par des constantes nommées pour améliorer la lisibilité.
const VITESSE_JOUEUR = 5;
joueur.vitesse = VITESSE_JOUEUR;
À éviter
Évitez d’utiliser des valeurs numériques brutes sans explication dans le code.
joueur.vitesse = 5
Opérateur ternaire¶
Des fois c'est oui, des fois c'est non.
Bien
let isFlipped = false;
if(velocityX > 0) {
isFlipped = true;
}
let isFlipped = (velocityX > 0) ? true : false;
// Encore mieux
let isFlipped = velocityX > 0;
À éviter
let velocityX = 0;
if (cursors.left.isDown) {
velocityX = -160;
} else if (cursors.right.isDown) {
velocityX = 160;
}
const velocityX = cursors.left.isDown ? -160 : cursors.right.isDown ? 160 : 0;
Exemple de réusinage¶
Réusinons ensemble le code suivant
create() {
this.player.anims.create({
key: 'run',
frames: player.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.enemy.anims.create({
key: 'enemy_run',
frames: enemy.anims.generateFrameNumbers('enemy', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.enemy.anims.create({
key: 'enemy_dash',
frames: enemy.anims.generateFrameNumbers('enemy', { start: 12, end: 14 }),
frameRate: 10,
repeat: -1
});
}
update() {
if (cursors.left.isDown) {
player.setVelocityX(-160);
player.anims.play('left', true);
} else if (cursors.right.isDown) {
player.setVelocityX(160);
player.anims.play('right', true);
} else {
player.setVelocityX(0);
player.anims.play('turn');
}
if (cursors.up.isDown && player.body.blocked.down) {
player.setVelocityY(-330);
}
if (enemy.body.velocity.x < 0) {
enemy.anims.play('enemy-left', true);
} else if (enemy.body.velocity.x > 0) {
enemy.anims.play('enemy-right', true);
} else {
enemy.anims.play('enemy-idle', true);
}
}
create() {
this.PLAYER_NORMAL_SPEED = 160; // KISS
this.PLAYER_JUMP_SPEED = 333; // KISS
this.createAnimation('player_run', 'player_animations', 0, 3); // DRY
this.createAnimation('enemy_run', 'enemy_animations', 0, 3); // DRY
// YAGNI : Retrait du "enemy_dash", car pas utilisé
}
update() {
// KISS
handlePlayerMovement(this.player);
handlePlayerAnimation(this.player);
handleEnemyAnimation(this.enemy);
}
createAnimation(key, spritesheet, firstFrame, lastFrame, frameRate = 14, loop = -1) {
this.anims.create({
key: key,
frames: this.anims.generateFrameNumbers(spritesheet, {
start: firstFrame,
end: lastFrame
}),
frameRate: frameRate,
repeat: loop
});
}
handlePlayerMovement(player) {
// Gestion de la vélocité du joueur
let velocityX = 0;
if (cursors.left.isDown) {
velocityX = -this.PLAYER_NORMAL_SPEED;
} else if (cursors.right.isDown) {
velocityX = this.PLAYER_NORMAL_SPEED;
}
player.setVelocityX(velocityX);
// Gestion du saut
if (cursors.up.isDown && player.body.blocked.down) {
player.setVelocityY(-this.PLAYER_JUMP_SPEED);
}
}
// Activation des animations du player en fonction des controles
handlePlayerAnimation(player) {
if (cursors.left.isDown) {
player.anims.play('left', true);
} else if (cursors.right.isDown) {
player.anims.play('right', true);
} else {
player.anims.play('idle', true);
}
}
// Activation des animations de l'ennemi en fonction de sa vélocité
handleEnemyAnimation(enemy) {
if (enemy.body.velocity.x < 0) {
enemy.anims.play('enemy-left', true);
} else if (enemy.body.velocity.x > 0) {
enemy.anims.play('enemy-right', true);
} else {
enemy.anims.play('enemy-idle', true);
}
}
Programmation orientée objet¶
Lorsque les fonctions deviennent trop nombreuses et le code trop long, il est temps de penser à une stratégie de catégorisation plus structurée. C'est là que la programmation orientée objet (OOP) entre en jeu.
class Bird {
constructor(scene) {
this.scene = scene;
this.sprite = scene.add.sprite(0, 0, "bird").setDepth(-1).setTint(0x808080);
this.sprite.anims.play("bird_bg", true);
// Déplace l'oiseau de façon aléatoire
this.move();
}
// Fonction récursive pour l'animation du vol d'oiseau
move() {
const targetY = Phaser.Math.Between(0, config.height);
this.sprite.setPosition(0, targetY);
this.scene.tweens.add({
targets: this.sprite,
x: config.width,
duration: Phaser.Math.Between(3000, 6000),
ease: 'Linear',
onComplete: () => this.move(),
});
}
}
Dans la scène Jeu.js
createBirds() {
this.birds = [];
for (let i = 0; i < 3; i++) {
let bird = this.add.sprite(0, 0, "bird");
bird.setDepth(0).setTint(0x808080);
bird.anims.play("bird_bg", true);
this.birds.push(bird);
this.moveBird(bird); //...
}
}
createBirds() {
this.birds = [];
for (let i = 0; i < 3; i++) {
const bird = new Bird(this);
this.birds.push(bird);
}
}
Exemple de programmation orientée objet¶
Dans cet exemple, les soleils sont des objets et le joueur également. La scène sert alors de gestionnaire d'objets, ce qui est beaucoup moins lourd et plus segmenté comme code.
Exercice¶
Exercice - Phaser
Le grand ménage