🔧 Мастерство Рефакторинга
Улучшай код, не меняя его поведения
"Рефакторинг — это контролируемая техника реструктуризации существующего кода, изменяющая его внутреннюю структуру без изменения внешнего поведения."
Когда Делать Рефакторинг
📖 Правило Бойскаута
"Оставь код чище, чем нашёл его." Внеси небольшое улучшение при каждом изменении.
🎯 Правило Трёх
Когда делаешь что-то в третий раз — пора рефакторить и выносить в отдельную функцию.
🐛 Перед Исправлением Бага
Сначала сделай код понятнее, затем исправь баг. Так будет проще найти причину.
✨ Перед Добавлением Фичи
Подготовь почву — рефактори код так, чтобы новую функциональность было легко добавить.
Основные Техники Рефакторинга
📦 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
Класс только делегирует работу другим