Dans le monde du développement logiciel, une citation bien
connue de Robert C. Martin, alias « Uncle Bob », nous rappelle une règle fondamentale :

« Laisse toujours le code plus propre que lorsque tu l’as trouvé. »

Cette philosophie nous incite à améliorer continuellement le code chaque fois que nous y touchons. Le refactoring n’est pas simplement une tâche technique, c’est un état d’esprit qui vise à maintenir un code de qualité, évolutif et facile à comprendre.

Pourquoi Refactorer le Code ?

Réduire la Dette Technique
La dette technique est comparable à une dette financière. Plus on attend pour la rembourser, plus les intérêts s’accumulent. En refactorant régulièrement, on évite l’accumulation de problèmes qui pourraient ralentir le développement futur.


Augmenter la Vélocité de Développement

Un code propre et bien structuré permet aux développeurs de travailler plus efficacement. Il est plus facile de comprendre, de maintenir et d’ajouter de nouvelles fonctionnalités à une base de code bien organisée.


Engager l’Équipe pour un Code Propre

Un code de qualité motive les développeurs. Personne n’aime travailler sur un code spaghetti difficile à comprendre. En maintenant une base de code propre, on favorise la satisfaction et la productivité de l’équipe.

Les Freins au Refactoring

Malgré ses avantages, le refactoring est parfois mis de côté pour diverses raisons :


• Pression des Délais : La nécessité de livrer rapidement des fonctionnalités peut pousser à négliger la qualité du code.


• Considéré comme un Coût par le Client : Les clients peuvent voir le refactoring comme une dépense inutile, ne comprenant pas les bénéfices à long terme.


• Peur des Régressions : Modifier le code existant peut introduire des bugs si les tests ne sont pas adéquats.

Les 8 Patterns de Refactoring

Voici des techniques courantes pour refactorer votre code de manière efficace.

1. Le Renommage

Renommer des variables, fonctions ou classes pour mieux refléter leur rôle.

Exemple en TypeScript :

// Avant
function calc(n: number): number {
return n * n;
}

// Après
function computeSquare(inputNumber: number): number {
return inputNumber * inputNumber;
}

2. L’Extraction

Extraire du code redondant ou complexe dans des fonctions ou classes dédiée

Exemple :


// Avant
const area = width * height;
const perimeter = 2 * (width + height);

// Après
function calculateArea(width: number, height: number): number {
return width * height;
}

function calculatePerimeter(width: number, height: number): number {
return 2 * (width + height);
}

3. Trier les Lignes

Réorganiser le code pour grouper les lignes liées logiquement.

Exemple :

// Avant
const user = getUser();
const product = getProduct();
processProduct(product);
processUser(user);

// Après
const user = getUser();
processUser(user);

const product = getProduct();
processProduct(product);

4. Réécrire les Conditions

Simplifier les structures conditionnelles pour une meilleure lisibilité.

Exemple :


// Avant
if (isActive === true) {
// …
}

// Après
if (isActive) {
// …
}

5. Réécrire les boucles

Utiliser des méthodes fonctionnelles comme map, filter, reduce.

Exemple :

// Avant
const numbers = [1, 2, 3, 4, 5];
const doubled: number[] = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}

// Après
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2);

6. Changement de Signature

Modifier les paramètres d’une fonction pour une utilisation plus intuitive.

Exemple



// Avant
function createUser(name: string, age: number, isAdmin: boolean) {
// …
}

// Après
interface UserOptions {
name: string;
age: number;
isAdmin: boolean;
}

function createUser(options: UserOptions) {
// …
}

7. Déplacement

Réorganiser le code en déplaçant des fonctions ou classes vers des modules plus appropriés.

Exemple :


// Avant (dans @/api/user.ts)
export function formatUserName(user: User): string {
return `${user.firstName} ${user.lastName}`;
}

// Après (dans @/utils/user.ts)
export function formatUserName(user: User): string {
return `${user.firstName} ${user.lastName}`;
}

// Importation dans @/api/user.ts
import { formatUserName } from « @/utils/user.ts »;

8. Inlining

Remplacer une fonction ou une variable par sa valeur directement dans le code. C’est souvent une technique qui n’est pas utilisée seule, mais qui permet de faire de l’extraction plus facilement une fois le code simplifié.

Exemple : Imaginons que nous souhaitons ajouter une fonctionnalité pour sélectionner une base de données et afficher les tables associées. Nous avons initialement extrait du code de DatabaseView et l’avons déplacé vers DatabaseSelectionBridge. Voici comment nous pouvons appliquer la combinaison de patterns Inline-Adjust-Extract.

Avant :

// Code appelant
class DatabaseView {
database: SqlDatabase;
bridge: DatabaseSelectionBridge;

constructor(currentDatabase: string) {
this.database = new SqlDatabase(currentDatabase);
this.bridge.databaseName = currentDatabase;
this.bridge.makeTableSelections(this.database);
}
}

// Code précedemment extrait
class DatabaseSelectionBridge {
selection: DatabaseSelection = new DatabaseSelection();

get databaseName(): string {
return this.selection.databaseName;
}

set databaseName(value: string) {
this.selection.databaseName = value;
}

makeTableSelections(database: SqlDatabase) {
this.selection.tableSelections = database.tableMetadata.map(
(metadata) => new TableSelection(metadata)
);
}
}

Inline :

class DatabaseView {
database: SqlDatabase;
bridge: DatabaseSelectionBridge;

constructor(currentDatabase: string) {
this.database = new SqlDatabase(currentDatabase);
this.bridge.selection.databaseName = currentDatabase;
this.bridge.selection.tableSelections = this.database.tableMetadata.map(
(metadata) => new TableSelection(metadata)
);
}
}


Adjust :

class DatabaseView {
database: SqlDatabase;
bridge: DatabaseSelectionBridge;

constructor(currentDatabase: string) {
this.database = new SqlDatabase(currentDatabase);
this.bridge.selection.databaseName = this.database.name; // adjusted here
this.bridge.selection.tableSelections = this.database.tableMetadata.map(
(metadata) => new TableSelection(metadata)
);
}
}

Extract :

class DatabaseView {
database: SqlDatabase;
bridge: DatabaseSelectionBridge;

constructor(currentDatabase: string) {
this.database = new SqlDatabase(currentDatabase);
this.bridge.select(this.database);
}
}

class DatabaseSelectionBridge {
selection: DatabaseSelection = new DatabaseSelection();

select(database: SqlDatabase) {
this.selection.databaseName = database.name;
this.selection.tableSelections = database.tableMetadata.map(
(metadata) => new TableSelection(metadata)
);
}
}


En suivant ce processus, nous avons rendu le site d’appel plus clair et le code appelé plus concis.

Cas d’Usage dans le Métier d’un Développeur

Maintenabilité à Long Terme : Lorsqu’une application évolue, le code doit être adaptable. Le refactoring facilite l’ajout de nouvelles fonctionnalités sans casser l’existant.

Onboarding de Nouveaux Développeurs : Un code propre et bien structuré facilite la compréhension pour les nouveaux membres de l’équipe, réduisant le temps d’onboarding.

Performance et Optimisation : Le refactoring permet également d’optimiser le code pour de meilleures performances, en identifiant et corrigeant les inefficacités.

Bonnes Pratiques pour un Refactoring Réussi

Tests Unitaires : Avant de refactorer, assurez-vous que votre code est bien couvert par des tests pour détecter rapidement les régressions.

Petites Etapes : Refactorez en petites portions pour faciliter le suivi des changements et la gestion des erreurs.

Documentations : Mettez à jour la documentation pour refléter les modifications apportées.

Conclusion

Le refactoring est essentiel pour maintenir une base de code saine et évolutive. Il s’agit d’un investissement à long terme qui facilite le travail des développeurs et contribue au succès global du projet. En adoptant une approche proactive et en surmontant les obstacles courants, les équipes de développement peuvent créer des logiciels de haute qualité qui répondent aux besoins des clients tout en étant faciles à maintenir.

À retenir :

  • Le refactoring réduit la dette technique.
  • Il augmente la vélocité de développement.
  • Il engage l’équipe pour un code propre.
  • Le refactoring régulier est une forme d’hygiène de code.

NB : N’oubliez pas que le refactoring s’applique aussi aux tests !

Vous souhaitez en savoir plus sur notre solution ?