refactor

Par github · awesome-copilot

Refactorisation chirurgicale du code pour améliorer la maintenabilité sans modifier le comportement. Couvre l'extraction de fonctions, le renommage de variables, la décomposition de fonctions « god », l'amélioration de la sûreté des types, l'élimination des code smells et l'application de design patterns. Moins radical que repo-rebuilder ; à utiliser pour des améliorations progressives.

npx skills add https://github.com/github/awesome-copilot --skill refactor

Refactor

Overview

Améliorer la structure et la lisibilité du code sans modifier le comportement externe. La refactorisation est une évolution graduelle, non une révolution. À utiliser pour améliorer le code existant, non pour une réécriture complète.

When to Use

Utiliser cette skill quand :

  • Le code est difficile à comprendre ou à maintenir
  • Les fonctions/classes sont trop volumineuses
  • Des code smells doivent être corrigés
  • Ajouter des fonctionnalités est difficile en raison de la structure du code
  • L'utilisateur demande « nettoie ce code », « refactor ceci », « améliore cela »

Refactoring Principles

The Golden Rules

  1. Le comportement est préservé - La refactorisation ne change pas ce que fait le code, seulement comment
  2. Petites étapes - Faire de minuscules changements, tester après chacun
  3. Le contrôle de version est ton allié - Committer avant et après chaque état sûr
  4. Les tests sont essentiels - Sans tests, ce n'est pas de la refactorisation, c'est de l'édition
  5. Une seule chose à la fois - Ne pas mélanger refactorisation et changements de fonctionnalités

When NOT to Refactor

- Code qui fonctionne et ne changera plus (si c'est pas cassé...)
- Code critique en production sans tests (ajouter les tests d'abord)
- Quand tu es sous une deadline serrée
- « Juste parce que » - besoin d'un objectif clair

Common Code Smells & Fixes

1. Long Method/Function

# BAD: Fonction de 200 lignes qui fait tout
- async function processOrder(orderId) {
-   // 50 lignes: fetch order
-   // 30 lignes: validate order
-   // 40 lignes: calculate pricing
-   // 30 lignes: update inventory
-   // 20 lignes: create shipment
-   // 30 lignes: send notifications
- }

# GOOD: Divisée en fonctions ciblées
+ async function processOrder(orderId) {
+   const order = await fetchOrder(orderId);
+   validateOrder(order);
+   const pricing = calculatePricing(order);
+   await updateInventory(order);
+   const shipment = await createShipment(order);
+   await sendNotifications(order, pricing, shipment);
+   return { order, pricing, shipment };
+ }

2. Duplicated Code

# BAD: Même logique à plusieurs endroits
- function calculateUserDiscount(user) {
-   if (user.membership === 'gold') return user.total * 0.2;
-   if (user.membership === 'silver') return user.total * 0.1;
-   return 0;
- }
-
- function calculateOrderDiscount(order) {
-   if (order.user.membership === 'gold') return order.total * 0.2;
-   if (order.user.membership === 'silver') return order.total * 0.1;
-   return 0;
- }

# GOOD: Extraire la logique commune
+ function getMembershipDiscountRate(membership) {
+   const rates = { gold: 0.2, silver: 0.1 };
+   return rates[membership] || 0;
+ }
+
+ function calculateUserDiscount(user) {
+   return user.total * getMembershipDiscountRate(user.membership);
+ }
+
+ function calculateOrderDiscount(order) {
+   return order.total * getMembershipDiscountRate(order.user.membership);
+ }

3. Large Class/Module

# BAD: Objet-dieu qui en sait trop
- class UserManager {
-   createUser() { /* ... */ }
-   updateUser() { /* ... */ }
-   deleteUser() { /* ... */ }
-   sendEmail() { /* ... */ }
-   generateReport() { /* ... */ }
-   handlePayment() { /* ... */ }
-   validateAddress() { /* ... */ }
-   // 50 autres méthodes...
- }

# GOOD: Une responsabilité par classe
+ class UserService {
+   create(data) { /* ... */ }
+   update(id, data) { /* ... */ }
+   delete(id) { /* ... */ }
+ }
+
+ class EmailService {
+   send(to, subject, body) { /* ... */ }
+ }
+
+ class ReportService {
+   generate(type, params) { /* ... */ }
+ }
+
+ class PaymentService {
+   process(amount, method) { /* ... */ }
+ }

4. Long Parameter List

# BAD: Trop de paramètres
- function createUser(email, password, name, age, address, city, country, phone) {
-   /* ... */
- }

# GOOD: Regrouper les paramètres connexes
+ interface UserData {
+   email: string;
+   password: string;
+   name: string;
+   age?: number;
+   address?: Address;
+   phone?: string;
+ }
+
+ function createUser(data: UserData) {
+   /* ... */
+ }

# EVEN BETTER: Utiliser le pattern builder pour la construction complexe
+ const user = UserBuilder
+   .email('test@example.com')
+   .password('secure123')
+   .name('Test User')
+   .address(address)
+   .build();

5. Feature Envy

# BAD: Méthode qui utilise les données d'un autre objet plus que les siennes
- class Order {
-   calculateDiscount(user) {
-     if (user.membershipLevel === 'gold') {
+       return this.total * 0.2;
+     }
+     if (user.accountAge > 365) {
+       return this.total * 0.1;
+     }
+     return 0;
+   }
+ }

# GOOD: Déplacer la logique vers l'objet propriétaire des données
+ class User {
+   getDiscountRate(orderTotal) {
+     if (this.membershipLevel === 'gold') return 0.2;
+     if (this.accountAge > 365) return 0.1;
+     return 0;
+   }
+ }
+
+ class Order {
+   calculateDiscount(user) {
+     return this.total * user.getDiscountRate(this.total);
+   }
+ }

6. Primitive Obsession

# BAD: Utiliser les primitives pour les concepts du domaine
- function sendEmail(to, subject, body) { /* ... */ }
- sendEmail('user@example.com', 'Hello', '...');

- function createPhone(country, number) {
-   return `${country}-${number}`;
- }

# GOOD: Utiliser les types du domaine
+ class Email {
+   private constructor(public readonly value: string) {
+     if (!Email.isValid(value)) throw new Error('Invalid email');
+   }
+   static create(value: string) { return new Email(value); }
+   static isValid(email: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }
+ }
+
+ class PhoneNumber {
+   constructor(
+     public readonly country: string,
+     public readonly number: string
+   ) {
+     if (!PhoneNumber.isValid(country, number)) throw new Error('Invalid phone');
+   }
+   toString() { return `${this.country}-${this.number}`; }
+   static isValid(country: string, number: string) { /* ... */ }
+ }
+
+ // Utilisation
+ const email = Email.create('user@example.com');
+ const phone = new PhoneNumber('1', '555-1234');

7. Magic Numbers/Strings

# BAD: Valeurs inexpliquées
- if (user.status === 2) { /* ... */ }
- const discount = total * 0.15;
- setTimeout(callback, 86400000);

# GOOD: Constantes nommées
+ const UserStatus = {
+   ACTIVE: 1,
+   INACTIVE: 2,
+   SUSPENDED: 3
+ } as const;
+
+ const DISCOUNT_RATES = {
+   STANDARD: 0.1,
+   PREMIUM: 0.15,
+   VIP: 0.2
+ } as const;
+
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
+
+ if (user.status === UserStatus.INACTIVE) { /* ... */ }
+ const discount = total * DISCOUNT_RATES.PREMIUM;
+ setTimeout(callback, ONE_DAY_MS);

8. Nested Conditionals

# BAD: Code en flèche
- function process(order) {
-   if (order) {
-     if (order.user) {
-       if (order.user.isActive) {
-         if (order.total > 0) {
-           return processOrder(order);
+         } else {
+           return { error: 'Invalid total' };
+         }
+       } else {
+         return { error: 'User inactive' };
+       }
+     } else {
+       return { error: 'No user' };
+     }
+   } else {
+     return { error: 'No order' };
+   }
+ }

# GOOD: Clauses de garde / retours précoces
+ function process(order) {
+   if (!order) return { error: 'No order' };
+   if (!order.user) return { error: 'No user' };
+   if (!order.user.isActive) return { error: 'User inactive' };
+   if (order.total <= 0) return { error: 'Invalid total' };
+   return processOrder(order);
+ }

# EVEN BETTER: Utiliser le type Result
+ function process(order): Result<ProcessedOrder, Error> {
+   return Result.combine([
+     validateOrderExists(order),
+     validateUserExists(order),
+     validateUserActive(order.user),
+     validateOrderTotal(order)
+   ]).flatMap(() => processOrder(order));
+ }

9. Dead Code

# BAD: Code inutilisé qui persiste
- function oldImplementation() { /* ... */ }
- const DEPRECATED_VALUE = 5;
- import { unusedThing } from './somewhere';
- // Commented out code
- // function oldCode() { /* ... */ }

# GOOD: Le supprimer
+ // Supprimer les fonctions inutilisées, les imports et le code commenté
+ // Si tu en as besoin plus tard, l'historique git l'a

10. Inappropriate Intimacy

# BAD: Une classe accède profondément à une autre
- class OrderProcessor {
-   process(order) {
-     order.user.profile.address.street;  // Trop intime
-     order.repository.connection.config;  // Bris d'encapsulation
+   }
+ }

# GOOD: Demander, ne pas imposer
+ class OrderProcessor {
+   process(order) {
+     order.getShippingAddress();  // Order sait comment l'obtenir
+     order.save();  // Order sait comment se sauvegarder
+   }
+ }

Extract Method Refactoring

Before and After

# Avant : Une fonction longue
- function printReport(users) {
-   console.log('USER REPORT');
-   console.log('============');
-   console.log('');
-   console.log(`Total users: ${users.length}`);
-   console.log('');
-   console.log('ACTIVE USERS');
-   console.log('------------');
-   const active = users.filter(u => u.isActive);
-   active.forEach(u => {
-     console.log(`- ${u.name} (${u.email})`);
-   });
-   console.log('');
-   console.log(`Active: ${active.length}`);
-   console.log('');
-   console.log('INACTIVE USERS');
-   console.log('--------------');
-   const inactive = users.filter(u => !u.isActive);
-   inactive.forEach(u => {
-     console.log(`- ${u.name} (${u.email})`);
-   });
-   console.log('');
-   console.log(`Inactive: ${inactive.length}`);
- }

# Après : Méthodes extraites
+ function printReport(users) {
+   printHeader('USER REPORT');
+   console.log(`Total users: ${users.length}\n`);
+   printUserSection('ACTIVE USERS', users.filter(u => u.isActive));
+   printUserSection('INACTIVE USERS', users.filter(u => !u.isActive));
+ }
+
+ function printHeader(title) {
+   const line = '='.repeat(title.length);
+   console.log(title);
+   console.log(line);
+   console.log('');
+ }
+
+ function printUserSection(title, users) {
+   console.log(title);
+   console.log('-'.repeat(title.length));
+   users.forEach(u => console.log(`- ${u.name} (${u.email})`));
+   console.log('');
+   console.log(`${title.split(' ')[0]}: ${users.length}`);
+   console.log('');
+ }

Introducing Type Safety

From Untyped to Typed

# Avant : Pas de types
- function calculateDiscount(user, total, membership, date) {
-   if (membership === 'gold' && date.getDay() === 5) {
-     return total * 0.25;
-   }
-   if (membership === 'gold') return total * 0.2;
-   return total * 0.1;
- }

# Après : Sécurité complète des types
+ type Membership = 'bronze' | 'silver' | 'gold';
+
+ interface User {
+   id: string;
+   name: string;
+   membership: Membership;
+ }
+
+ interface DiscountResult {
+   original: number;
+   discount: number;
+   final: number;
+   rate: number;
+ }
+
+ function calculateDiscount(
+   user: User,
+   total: number,
+   date: Date = new Date()
+ ): DiscountResult {
+   if (total < 0) throw new Error('Total cannot be negative');
+
+   let rate = 0.1; // Default bronze
+
+   if (user.membership === 'gold' && date.getDay() === 5) {
+     rate = 0.25; // Friday bonus for gold
+   } else if (user.membership === 'gold') {
+     rate = 0.2;
+   } else if (user.membership === 'silver') {
+     rate = 0.15;
+   }
+
+   const discount = total * rate;
+
+   return {
+     original: total,
+     discount,
+     final: total - discount,
+     rate
+   };
+ }

Design Patterns for Refactoring

Strategy Pattern

# Avant : Logique conditionnelle
- function calculateShipping(order, method) {
-   if (method === 'standard') {
-     return order.total > 50 ? 0 : 5.99;
-   } else if (method === 'express') {
-     return order.total > 100 ? 9.99 : 14.99;
+   } else if (method === 'overnight') {
+     return 29.99;
+   }
+ }

# Après : Pattern Strategy
+ interface ShippingStrategy {
+   calculate(order: Order): number;
+ }
+
+ class StandardShipping implements ShippingStrategy {
+   calculate(order: Order) {
+     return order.total > 50 ? 0 : 5.99;
+   }
+ }
+
+ class ExpressShipping implements ShippingStrategy {
+   calculate(order: Order) {
+     return order.total > 100 ? 9.99 : 14.99;
+   }
+ }
+
+ class OvernightShipping implements ShippingStrategy {
+   calculate(order: Order) {
+     return 29.99;
+   }
+ }
+
+ function calculateShipping(order: Order, strategy: ShippingStrategy) {
+   return strategy.calculate(order);
+ }

Chain of Responsibility

# Avant : Validation imbriquée
- function validate(user) {
-   const errors = [];
-   if (!user.email) errors.push('Email required');
+   else if (!isValidEmail(user.email)) errors.push('Invalid email');
+   if (!user.name) errors.push('Name required');
+   if (user.age < 18) errors.push('Must be 18+');
+   if (user.country === 'blocked') errors.push('Country not supported');
+   return errors;
+ }

# Après : Chain of Responsibility
+ abstract class Validator {
+   abstract validate(user: User): string | null;
+   setNext(validator: Validator): Validator {
+     this.next = validator;
+     return validator;
+   }
+   validate(user: User): string | null {
+     const error = this.doValidate(user);
+     if (error) return error;
+     return this.next?.validate(user) ?? null;
+   }
+ }
+
+ class EmailRequiredValidator extends Validator {
+   doValidate(user: User) {
+     return !user.email ? 'Email required' : null;
+   }
+ }
+
+ class EmailFormatValidator extends Validator {
+   doValidate(user: User) {
+     return user.email && !isValidEmail(user.email) ? 'Invalid email' : null;
+   }
+ }
+
+ // Construire la chaîne
+ const validator = new EmailRequiredValidator()
+   .setNext(new EmailFormatValidator())
+   .setNext(new NameRequiredValidator())
+   .setNext(new AgeValidator())
+   .setNext(new CountryValidator());

Refactoring Steps

Safe Refactoring Process

1. PREPARE
   - S'assurer que les tests existent (les écrire s'ils manquent)
   - Committer l'état actuel
   - Créer une branche de fonctionnalité

2. IDENTIFY
   - Trouver le code smell à corriger
   - Comprendre ce que fait le code
   - Planifier la refactorisation

3. REFACTOR (petites étapes)
   - Faire un seul petit changement
   - Exécuter les tests
   - Committer si les tests passent
   - Répéter

4. VERIFY
   - Tous les tests passent
   - Test manuel si nécessaire
   - Performance inchangée ou améliorée

5. CLEAN UP
   - Mettre à jour les commentaires
   - Mettre à jour la documentation
   - Commit final

Refactoring Checklist

Code Quality

  • [ ] Les fonctions sont petites (< 50 lignes)
  • [ ] Les fonctions font une seule chose
  • [ ] Pas de code dupliqué
  • [ ] Noms explicites (variables, fonctions, classes)
  • [ ] Pas de nombres/chaînes magiques
  • [ ] Code mort supprimé

Structure

  • [ ] Le code connexe est ensemble
  • [ ] Frontières de module claires
  • [ ] Les dépendances s'écoulent dans une direction
  • [ ] Pas de dépendances circulaires

Type Safety

  • [ ] Types définis pour toutes les API publiques
  • [ ] Pas de types any sans justification
  • [ ] Types nullables explicitement marqués

Testing

  • [ ] Le code refactorisé est testé
  • [ ] Les tests couvrent les cas limites
  • [ ] Tous les tests passent

Common Refactoring Operations

Opération Description
Extract Method Transformer un fragment en méthode
Extract Class Déplacer le comportement à une nouvelle classe
Extract Interface Créer une interface à partir de l'implémentation
Inline Method Replacer le corps de la méthode chez l'appelant
Inline Class Déplacer le comportement de la classe à l'appelant
Pull Up Method Déplacer la méthode à la superclasse
Push Down Method Déplacer la méthode à une sous-classe
Rename Method/Variable Améliorer la clarté
Introduce Parameter Object Regrouper les paramètres connexes
Replace Conditional with Polymorphism Utiliser le polymorphisme au lieu de switch/if
Replace Magic Number with Constant Constantes nommées
Decompose Conditional Décomposer les conditions complexes
Consolidate Conditional Combiner les conditions dupliquées
Replace Nested Conditional with Guard Clauses Retours précoces
Introduce Null Object Éliminer les vérifications null
Replace Type Code with Class/Enum Typage fort
Replace Inheritance with Delegation Composition plutôt qu'héritage

Skills similaires