Как при рефакторинге кода понимаешь что нужно остановиться?
Комментарии (3)
Ответ сгенерирован нейросетью и может содержать ошибки
Когда остановиться при рефакторинге: чек-лист профессионала
Рефакторинг - это как уборка дома: можно бесконечно совершенствовать, но надо знать, когда остановиться. Поделюсь своим подходом с 10+ летним опытом.
1. Правило YAGNI - You Arent Gonna Need It
Первый сигнал к остановке - когда я начинаю писать код "на будущее":
// STOP! Это переженжиниринг
class UserService {
async getUser(id) { /* ... */ }
async getUsers(ids) { /* ... */ }
async getUsersByRole(role) { /* ... */ }
async getUsersByDepartment(dept) { /* ... */ }
async getUsersByStatus(status) { /* ... */ }
// И ещё 10 вариантов для всех комбинаций...
}
// ПРАВИЛЬНО - пишу только то, что нужно сейчас
class UserService {
async getUser(id) { /* ... */ }
async getUsersByIds(ids) { /* ... */ }
}
Сигнал к остановке: если нет issue/task, которая требует эту функцию, её быть не должно.
2. Тесты как якорь
Если тесты зелёные и покрытие достаточное - это знак того, что рефакторинг завершён:
// До рефакторинга
function calculatePrice(items, discount, tax, shipping) {
let total = items.reduce((sum, item) => sum + item.price, 0);
total = total * (1 - discount);
total = total * (1 + tax);
total = total + shipping;
return Math.round(total * 100) / 100;
}
// После рефакторинга
function calculatePrice(items, discount, tax, shipping) {
const subtotal = calculateSubtotal(items);
const discounted = applyDiscount(subtotal, discount);
const withTax = applyTax(discounted, tax);
return addShipping(withTax, shipping);
}
// Когда остановиться?
// - Все исходные тесты проходят
// - Покрытие не упало
// - Новые функции поддерживаемы
// - Нет чувства "могу ещё улучшить"
Если все тесты проходят и покрытие хорошее - рефакторинг считаю завершённым.
3. Принцип "Достаточно хорошо"
Есть точка, после которой дополнительные улучшения дают минимальный ROI:
// Уровень 1: Работает (2 часа работы)
function filterAndMapUsers(users, role, needsActive) {
return users.filter(u => u.role === role && u.active === needsActive)
.map(u => ({ id: u.id, name: u.name }));
}
// Уровень 2: Более генеричное (4 часа работы)
function filterAndTransform(data, filters, mapper) {
return data
.filter(item => Object.entries(filters).every(
([key, value]) => item[key] === value
))
.map(mapper);
}
// Уровень 3: Ultimate абстракция (8+ часов)
class QueryBuilder {
// ...сложная логика...
}
// Где остановиться? На уровне 1-2
// Уровень 3 - это переженжиниринг для одной функции
Останавливаюсь, когда:
- Код читаемый и понятный
- Он решает текущую задачу
- Нет pain points при использовании
4. Метрика: Cognitive Complexity
Использую простой чек - если функция требует больше 1-2 секунд на понимание, пора её упростить:
// Сложность 10/10 - STOP!
function process(data) {
let result = [];
for (let i = 0; i < data.length; i++) {
if (data[i].type === "A") {
if (data[i].status === "active") {
if (data[i].priority > 5) {
result.push({
...data[i],
processed: true,
timestamp: Date.now()
});
}
}
}
}
return result;
}
// Сложность 2/10 - ХОРОШО
function processHighPriorityItems(data) {
return data
.filter(item => item.type === "A" && item.status === "active")
.filter(item => item.priority > 5)
.map(item => ({ ...item, processed: true, timestamp: Date.now() }));
}
Останавливаюсь, когда функция занимает меньше половины экрана и логика ясна с первого взгляда.
5. Правило Git Diff
Смотрю на размер изменений:
# Хороший рефакторинг - изменения понятны и локальны
git diff --stat
app/services/userService.ts | 45 ++--
# Красная лампочка - изменяю слишком много файлов
git diff --stat
app/services/userService.ts | 200 ++-
app/hooks/useUser.ts | 150 +--
app/components/UserCard.ts | 80 ++--
app/types/index.ts | 60 +++-
app/utils/helpers.ts | 120 +++
# STOP - это уже не рефакторинг, а переписывание
Если рефакторинг затрагивает 5+ файлов - пора остановиться или разбить на части.
6. Performance Baseline
Запускаю lighthouse/profiler до и после:
// Измеряю
console.time("render");
ReactDOM.render(<App />, root);
console.timeEnd("render");
// Если улучшение < 5% и код усложнился - STOP
// Если улучшение > 20% и код проще - продолжаем
7. Code Review Feedback Loop
Когда я сам себя начинаю критиковать:
// Мой внутренний монолог:
// "А может вместо этого паттерна использовать..."
// "Хм, это было бы лучше, если б я..."
// "Да, но может быть..."
// STOP! Это признак того, что я overthinking
Когда появляется рефлексия "может быть" - это точка для остановки и созыва code review.
Чек-лист остановки
- Тесты проходят и покрытие > 80%
- Код занимает <= 30 строк на функцию
- Cognitive complexity в норме (читаемо за 5 секунд)
- Изменил <= 3 файлов
- Performance не ухудшилась
- Ещё 2 идеи по улучшению, но они требуют новых tasks
- Code review заметил только minor issues
Практический пример: Когда я остановился
// Было: хардкод в компоненте
export function UserList() {
return (
<div className="space-y-4">
{users.map(u => (
<div key={u.id} className="p-4 bg-white rounded border">
<h3>{u.name}</h3>
<p>{u.email}</p>
</div>
))}
</div>
);
}
// Шаг 1: Извлечь компонент
export function UserItem({ user }) {
return (
<div className="p-4 bg-white rounded border">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
// Шаг 2: Добавить типизацию
interface UserItemProps {
user: User;
onSelect?: (user: User) => void;
}
// STOP! Здесь я остановился
// Дальше было бы:
// - Шаг 3: Кастомизируемые поля (YAGNI)
// - Шаг 4: Плагины для расширения (Оверенжиниринг)
Вывод
Останавливаюсь при рефакторинге, когда:
- Код работает правильно (тесты зелёные)
- Его легко читать (5-10 секунд на понимание)
- Следует SOLID, DRY, KISS
- ROI дальнейших улучшений минимален
Лучше иметь "хороший-достаточный" код, чем "идеальный-но-нечитаемый".