🔧 Мастерство Рефакторинга

Улучшай код, не меняя его поведения

"Рефакторинг — это контролируемая техника реструктуризации существующего кода, изменяющая его внутреннюю структуру без изменения внешнего поведения." — Мартин Фаулер

Когда Делать Рефакторинг

📖 Правило Бойскаута

"Оставь код чище, чем нашёл его." Внеси небольшое улучшение при каждом изменении.

🎯 Правило Трёх

Когда делаешь что-то в третий раз — пора рефакторить и выносить в отдельную функцию.

🐛 Перед Исправлением Бага

Сначала сделай код понятнее, затем исправь баг. Так будет проще найти причину.

✨ Перед Добавлением Фичи

Подготовь почву — рефактори код так, чтобы новую функциональность было легко добавить.

Основные Техники Рефакторинга

📦 1. Extract Method (Извлечение Метода)

Выдели фрагмент кода в отдельную функцию с понятным именем.

❌ До рефакторинга
function displayJediInfo(jedi) {
    console.log('=================');
    console.log('Имя: ' + jedi.name);
    console.log('Ранг: ' + jedi.rank);
    console.log('Сила: ' + jedi.power);
    console.log('=================');
    
    // Вычисление боевого рейтинга
    let rating = jedi.power * 1.2;
    if (jedi.rank === 'Мастер') {
        rating *= 1.5;
    }
    if (jedi.lightsaberMastery > 80) {
        rating *= 1.3;
    }
    
    console.log('Боевой рейтинг: ' + rating);
}
✅ После рефакторинга
function displayJediInfo(jedi) {
    printHeader();
    printBasicInfo(jedi);
    printHeader();
    
    const rating = calculateCombatRating(jedi);
    console.log('Боевой рейтинг: ' + rating);
}

function printHeader() {
    console.log('=================');
}

function printBasicInfo(jedi) {
    console.log('Имя: ' + jedi.name);
    console.log('Ранг: ' + jedi.rank);
    console.log('Сила: ' + jedi.power);
}

function calculateCombatRating(jedi) {
    let rating = jedi.power * 1.2;
    
    if (isMaster(jedi)) {
        rating *= 1.5;
    }
    
    if (isLightsaberExpert(jedi)) {
        rating *= 1.3;
    }
    
    return rating;
}

function isMaster(jedi) {
    return jedi.rank === 'Мастер';
}

function isLightsaberExpert(jedi) {
    return jedi.lightsaberMastery > 80;
}

🔀 2. Replace Conditional with Polymorphism

Замени условные операторы полиморфизмом.

❌ До рефакторинга
function getJediAbility(jedi) {
    if (jedi.type === 'guardian') {
        return 'Мастер боя на световых мечах';
    } else if (jedi.type === 'consular') {
        return 'Мастер дипломатии и Силы';
    } else if (jedi.type === 'sentinel') {
        return 'Сбалансированный боец';
    } else {
        return 'Неизвестный тип';
    }
}

function getTrainingFocus(jedi) {
    if (jedi.type === 'guardian') {
        return 'Физическая подготовка';
    } else if (jedi.type === 'consular') {
        return 'Медитация и изучение Силы';
    } else if (jedi.type === 'sentinel') {
        return 'Разностороннее развитие';
    } else {
        return 'Базовая подготовка';
    }
}
✅ После рефакторинга
class Jedi {
    getAbility() {
        throw new Error('Метод должен быть переопределён');
    }
    
    getTrainingFocus() {
        throw new Error('Метод должен быть переопределён');
    }
}

class Guardian extends Jedi {
    getAbility() {
        return 'Мастер боя на световых мечах';
    }
    
    getTrainingFocus() {
        return 'Физическая подготовка';
    }
}

class Consular extends Jedi {
    getAbility() {
        return 'Мастер дипломатии и Силы';
    }
    
    getTrainingFocus() {
        return 'Медитация и изучение Силы';
    }
}

class Sentinel extends Jedi {
    getAbility() {
        return 'Сбалансированный боец';
    }
    
    getTrainingFocus() {
        return 'Разностороннее развитие';
    }
}

// Использование
const guardian = new Guardian();
console.log(guardian.getAbility());
console.log(guardian.getTrainingFocus());

🎯 3. Introduce Parameter Object

Сгруппируй связанные параметры в объект.

❌ До рефакторинга
function createJedi(
    name,
    age,
    rank,
    lightsaberColor,
    forceLevel,
    homeworld,
    master,
    specialization
) {
    return {
        name,
        age,
        rank,
        lightsaberColor,
        forceLevel,
        homeworld,
        master,
        specialization
    };
}

const jedi = createJedi(
    'Люк Скайуокер',
    23,
    'Падаван',
    'зелёный',
    7500,
    'Татуин',
    'Йода',
    'Guardian'
);
✅ После рефакторинга
class JediProfile {
    constructor(personal, training) {
        this.personal = personal;
        this.training = training;
    }
}

class PersonalInfo {
    constructor(name, age, homeworld) {
        this.name = name;
        this.age = age;
        this.homeworld = homeworld;
    }
}

class TrainingInfo {
    constructor(rank, forceLevel, master, specialization, lightsaberColor) {
        this.rank = rank;
        this.forceLevel = forceLevel;
        this.master = master;
        this.specialization = specialization;
        this.lightsaberColor = lightsaberColor;
    }
}

// Использование
const personal = new PersonalInfo('Люк Скайуокер', 23, 'Татуин');
const training = new TrainingInfo('Падаван', 7500, 'Йода', 'Guardian', 'зелёный');
const jedi = new JediProfile(personal, training);

🔁 4. Replace Magic Number with Constant

Замени магические числа именованными константами.

❌ До рефакторинга
function calculateJediPower(midichlorians, training) {
    let power = midichlorians * 0.01;
    
    if (training > 5) {
        power *= 2.5;
    } else if (training > 2) {
        power *= 1.5;
    }
    
    if (power > 100) {
        power = 100;
    }
    
    return power;
}
✅ После рефакторинга
const MIDICHLORIAN_MULTIPLIER = 0.01;
const MASTER_TRAINING_THRESHOLD = 5;
const KNIGHT_TRAINING_THRESHOLD = 2;
const MASTER_POWER_BOOST = 2.5;
const KNIGHT_POWER_BOOST = 1.5;
const MAX_POWER_LEVEL = 100;

function calculateJediPower(midichlorians, training) {
    let power = midichlorians * MIDICHLORIAN_MULTIPLIER;
    
    if (training > MASTER_TRAINING_THRESHOLD) {
        power *= MASTER_POWER_BOOST;
    } else if (training > KNIGHT_TRAINING_THRESHOLD) {
        power *= KNIGHT_POWER_BOOST;
    }
    
    return Math.min(power, MAX_POWER_LEVEL);
}

🧹 5. Remove Dead Code

Удали неиспользуемый код. Если понадобится — достанешь из git.

❌ До рефакторинга
class Jedi {
    constructor(name) {
        this.name = name;
        // Старый код - больше не используется
        // this.oldPowerLevel = 0;
    }
    
    train() {
        this.power += 10;
        // TODO: Добавить логирование
        // console.log('Training...');
    }
    
    // Этот метод никогда не вызывается
    calculateOldPower() {
        return this.power * 0.5;
    }
    
    // Закомментирован год назад
    // useForce() {
    //     console.log('Using force');
    // }
}
✅ После рефакторинга
class Jedi {
    constructor(name) {
        this.name = name;
    }
    
    train() {
        this.power += 10;
    }
}

⛓️ 6. Replace Nested Conditional with Guard Clauses

Используй ранний выход вместо глубокой вложенности.

❌ До рефакторинга
function promoteJedi(jedi) {
    if (jedi) {
        if (jedi.power > 50) {
            if (jedi.training > 100) {
                if (jedi.hasCompletedTrials) {
                    return { promoted: true, newRank: 'Рыцарь' };
                } else {
                    return { promoted: false, reason: 'Испытания не пройдены' };
                }
            } else {
                return { promoted: false, reason: 'Недостаточно тренировок' };
            }
        } else {
            return { promoted: false, reason: 'Недостаточно силы' };
        }
    } else {
        return { promoted: false, reason: 'Джедай не найден' };
    }
}
✅ После рефакторинга
function promoteJedi(jedi) {
    if (!jedi) {
        return { promoted: false, reason: 'Джедай не найден' };
    }
    
    if (jedi.power <= 50) {
        return { promoted: false, reason: 'Недостаточно силы' };
    }
    
    if (jedi.training <= 100) {
        return { promoted: false, reason: 'Недостаточно тренировок' };
    }
    
    if (!jedi.hasCompletedTrials) {
        return { promoted: false, reason: 'Испытания не пройдены' };
    }
    
    return { promoted: true, newRank: 'Рыцарь' };
}

💎 Правила Безопасного Рефакторинга

  • Всегда имей тесты перед рефакторингом
  • Делай маленькие шаги — рефактори по чуть-чуть
  • Запускай тесты после каждого изменения
  • Не смешивай рефакторинг с новой функциональностью
  • Коммить чаще — каждый успешный шаг отдельно
  • Используй IDE для автоматического рефакторинга
  • Не бойся откатиться если что-то пошло не так
  • Рефактори код, который ты понимаешь

👃 Признаки Плохого Кода (Code Smells)

🦹 Long Method

Функция длиннее 15-20 строк

🎯 Large Class

Класс делает слишком много

📋 Long Parameter List

Больше 3-4 параметров

🔀 Divergent Change

Класс часто меняется по разным причинам

💥 Shotgun Surgery

Одно изменение требует правки во многих местах

🔗 Feature Envy

Метод использует данные другого класса больше своих

📦 Data Clumps

Одни и те же группы данных повторяются

🎭 Switch Statements

Множественные switch по типу объекта

👻 Speculative Generality

Код "на будущее", который не используется

💬 Comments

Слишком много комментариев (код непонятен)

🔄 Duplicate Code

Одинаковый код в нескольких местах

🎲 Middle Man

Класс только делегирует работу другим